diff --git a/src/Controls/samples/Controls.Sample/Pages/Core/BordersPage.xaml b/src/Controls/samples/Controls.Sample/Pages/Core/BordersPage.xaml
index 3a39b93bc08f..c9b0f3d3bf5a 100644
--- a/src/Controls/samples/Controls.Sample/Pages/Core/BordersPage.xaml
+++ b/src/Controls/samples/Controls.Sample/Pages/Core/BordersPage.xaml
@@ -22,7 +22,7 @@
-
+
+
+
+
+
+
diff --git a/src/Controls/samples/Controls.Sample/Pages/Core/BordersPage.xaml.cs b/src/Controls/samples/Controls.Sample/Pages/Core/BordersPage.xaml.cs
index df484685f457..c18bb7fd8fc2 100644
--- a/src/Controls/samples/Controls.Sample/Pages/Core/BordersPage.xaml.cs
+++ b/src/Controls/samples/Controls.Sample/Pages/Core/BordersPage.xaml.cs
@@ -17,6 +17,7 @@ public BordersPage()
BorderLineCapPicker.SelectedIndex = 0;
UpdateBackground();
+ UpdateContentBackground();
UpdateBorder();
UpdateCornerRadius();
}
@@ -66,6 +67,11 @@ void OnCornerRadiusChanged(object sender, ValueChangedEventArgs e)
UpdateCornerRadius();
}
+ void OnContentBackgroundCheckBoxChanged(object sender, CheckedChangedEventArgs e)
+ {
+ UpdateContentBackground();
+ }
+
void UpdateBorderShape()
{
CornerRadiusLayout.IsVisible = BorderShapePicker.SelectedIndex == 1;
@@ -93,6 +99,11 @@ void UpdateBackground()
};
}
+ void UpdateContentBackground()
+ {
+ BorderContent.BackgroundColor = ContentBackgroundCheckBox.IsChecked ? Color.FromArgb("#99FF0000") : Colors.Transparent;
+ }
+
void UpdateBorder()
{
var startColor = GetColorFromString(BorderStartColor.Text);
diff --git a/src/Controls/samples/Controls.Sample/ViewModels/ControlsViewModel.cs b/src/Controls/samples/Controls.Sample/ViewModels/ControlsViewModel.cs
index 3af978e01b6d..93d82246cbb0 100644
--- a/src/Controls/samples/Controls.Sample/ViewModels/ControlsViewModel.cs
+++ b/src/Controls/samples/Controls.Sample/ViewModels/ControlsViewModel.cs
@@ -11,6 +11,9 @@ protected override IEnumerable CreateItems() => new[]
{
new SectionModel(typeof(ActivityIndicatorPage), "ActivityIndicator",
"Displays an animation to show that the application is engaged in a lengthy activity."),
+
+ new SectionModel(typeof(BordersPage), "Border",
+ "Draws a border around a View."),
new SectionModel(typeof(BoxViewPage), "BoxView",
"BoxView renders a simple rectangle of a specified width, height, and color. You can use BoxView for decoration, rudimentary graphics, and for interaction with the user through touch."),
diff --git a/src/Controls/samples/Controls.Sample/ViewModels/CoreViewModel.cs b/src/Controls/samples/Controls.Sample/ViewModels/CoreViewModel.cs
index c6526121fcb8..630c57ee32de 100644
--- a/src/Controls/samples/Controls.Sample/ViewModels/CoreViewModel.cs
+++ b/src/Controls/samples/Controls.Sample/ViewModels/CoreViewModel.cs
@@ -18,9 +18,6 @@ protected override IEnumerable CreateItems() => new[]
new SectionModel(typeof(AppThemeBindingPage), "AppThemeBindings",
"Devices typically include light and dark themes, which each refer to a broad set of appearance preferences that can be set at the operating system level. Applications should respect these system themes, and respond immediately when the system theme changes."),
- new SectionModel(typeof(BordersPage), "Borders",
- "Draws a border around a View, Layout, or Control."),
-
new SectionModel(typeof(BrushesPage), "Brushes",
"A brush enables you to paint an area, such as the background of a control, using different approaches."),
diff --git a/src/Controls/samples/Controls.Sample/ViewModels/MainViewModel.cs b/src/Controls/samples/Controls.Sample/ViewModels/MainViewModel.cs
index 04b153ed9d72..e2be5287d878 100644
--- a/src/Controls/samples/Controls.Sample/ViewModels/MainViewModel.cs
+++ b/src/Controls/samples/Controls.Sample/ViewModels/MainViewModel.cs
@@ -60,9 +60,6 @@ protected override IEnumerable CreateItems() => new[]
new SectionModel(typeof(OthersPage), "Others Concepts",
"Other options like Graphics."),
-
- new SectionModel(typeof(HitTestingPage), "Hit Testing",
- "Demonstrates VisualTreeElementExtensions hit testing methods"),
};
}
}
\ No newline at end of file
diff --git a/src/Controls/samples/Controls.Sample/ViewModels/OthersViewModel.cs b/src/Controls/samples/Controls.Sample/ViewModels/OthersViewModel.cs
index 162ecbc40d67..c041b690ed01 100644
--- a/src/Controls/samples/Controls.Sample/ViewModels/OthersViewModel.cs
+++ b/src/Controls/samples/Controls.Sample/ViewModels/OthersViewModel.cs
@@ -11,14 +11,21 @@ protected override IEnumerable CreateItems() => new[]
{
new SectionModel(typeof(GraphicsViewPage), "GraphicsView",
"Allow to draw directly in a Canvas. You can combine a canvas and native Views on the same page."),
+
new SectionModel(typeof(LargeTitlesPageiOS), "Large Titles - iOS",
"This iOS platform-specific is used to display the page title as a large title on the navigation bar of a NavigationPage, for devices that use iOS 11 or greater."),
+
new SectionModel(typeof(StyleSheetsPage), "StyleSheets",
"Demonstrates the usage of CSS in XAML."),
+
new SectionModel(typeof(TwoPaneViewPage), "Foldable",
"Demonstrates the usage of TwoPaneView and hinge sensor."),
+
new SectionModel(typeof(RenderViewPage), "Render Views",
- "Demonstrates rendering views as images.")
+ "Demonstrates rendering views as images."),
+
+ new SectionModel(typeof(HitTestingPage), "Hit Testing",
+ "Demonstrates VisualTreeElementExtensions hit testing methods"),
};
}
}
\ No newline at end of file
diff --git a/src/Controls/src/Core/Border.cs b/src/Controls/src/Core/Border.cs
index 0da3958354c6..f468bad8bd76 100644
--- a/src/Controls/src/Core/Border.cs
+++ b/src/Controls/src/Core/Border.cs
@@ -1,6 +1,7 @@
#nullable enable
using System.Collections.Generic;
using System.Collections.ObjectModel;
+using System.Runtime.CompilerServices;
using Microsoft.Maui.Controls.Shapes;
using Microsoft.Maui.Graphics;
using Microsoft.Maui.Layouts;
@@ -191,5 +192,15 @@ public Thickness PaddingDefaultValueCreator()
{
return Thickness.Zero;
}
+
+ protected override void OnPropertyChanged([CallerMemberName] string? propertyName = null)
+ {
+ base.OnPropertyChanged(propertyName);
+
+ if (propertyName == HeightProperty.PropertyName ||
+ propertyName == WidthProperty.PropertyName ||
+ propertyName == StrokeShapeProperty.PropertyName)
+ Handler?.UpdateValue(nameof(IBorderStroke.Shape));
+ }
}
}
\ No newline at end of file
diff --git a/src/Controls/src/Core/HandlerImpl/RoundRectangle.Impl.cs b/src/Controls/src/Core/HandlerImpl/RoundRectangle.Impl.cs
index 70b5fb0f11a8..1c4e6c38dfed 100644
--- a/src/Controls/src/Core/HandlerImpl/RoundRectangle.Impl.cs
+++ b/src/Controls/src/Core/HandlerImpl/RoundRectangle.Impl.cs
@@ -1,9 +1,18 @@
-using Microsoft.Maui.Graphics;
+using System.Runtime.CompilerServices;
+using Microsoft.Maui.Graphics;
namespace Microsoft.Maui.Controls.Shapes
{
public partial class RoundRectangle : IShape
{
+ protected override void OnPropertyChanged([CallerMemberName] string propertyName = null)
+ {
+ base.OnPropertyChanged(propertyName);
+
+ if (propertyName == CornerRadiusProperty.PropertyName)
+ Handler?.UpdateValue(nameof(IShapeView.Shape));
+ }
+
public override PathF GetPath()
{
var path = new PathF();
diff --git a/src/Core/src/Handlers/Border/BorderHandler.Windows.cs b/src/Core/src/Handlers/Border/BorderHandler.Windows.cs
index 7789198cd845..0dead03ee7ec 100644
--- a/src/Core/src/Handlers/Border/BorderHandler.Windows.cs
+++ b/src/Core/src/Handlers/Border/BorderHandler.Windows.cs
@@ -3,46 +3,46 @@
namespace Microsoft.Maui.Handlers
{
public partial class BorderHandler : ViewHandler
- {
- public override void SetVirtualView(IView view)
- {
- base.SetVirtualView(view);
+ {
+ public override void SetVirtualView(IView view)
+ {
+ base.SetVirtualView(view);
- _ = PlatformView ?? throw new InvalidOperationException($"{nameof(PlatformView)} should have been set by base class.");
- _ = VirtualView ?? throw new InvalidOperationException($"{nameof(VirtualView)} should have been set by base class.");
+ _ = PlatformView ?? throw new InvalidOperationException($"{nameof(PlatformView)} should have been set by base class.");
+ _ = VirtualView ?? throw new InvalidOperationException($"{nameof(VirtualView)} should have been set by base class.");
- PlatformView.CrossPlatformMeasure = VirtualView.CrossPlatformMeasure;
- PlatformView.CrossPlatformArrange = VirtualView.CrossPlatformArrange;
- }
+ PlatformView.CrossPlatformMeasure = VirtualView.CrossPlatformMeasure;
+ PlatformView.CrossPlatformArrange = VirtualView.CrossPlatformArrange;
+ }
- static void UpdateContent(IBorderHandler handler)
- {
- _ = handler.PlatformView ?? throw new InvalidOperationException($"{nameof(PlatformView)} should have been set by base class.");
- _ = handler.VirtualView ?? throw new InvalidOperationException($"{nameof(VirtualView)} should have been set by base class.");
- _ = handler.MauiContext ?? throw new InvalidOperationException($"{nameof(MauiContext)} should have been set by base class.");
+ static void UpdateContent(IBorderHandler handler)
+ {
+ _ = handler.PlatformView ?? throw new InvalidOperationException($"{nameof(PlatformView)} should have been set by base class.");
+ _ = handler.VirtualView ?? throw new InvalidOperationException($"{nameof(VirtualView)} should have been set by base class.");
+ _ = handler.MauiContext ?? throw new InvalidOperationException($"{nameof(MauiContext)} should have been set by base class.");
handler.PlatformView.Children.Clear();
handler.PlatformView.EnsureBorderPath();
- if (handler.VirtualView.PresentedContent is IView view)
- handler.PlatformView.Children.Add(view.ToPlatform(handler.MauiContext));
- }
+ if (handler.VirtualView.PresentedContent is IView view)
+ handler.PlatformView.Content = view.ToPlatform(handler.MauiContext);
+ }
- protected override ContentPanel CreatePlatformView()
- {
- if (VirtualView == null)
- {
- throw new InvalidOperationException($"{nameof(VirtualView)} must be set to create a LayoutView");
- }
+ protected override ContentPanel CreatePlatformView()
+ {
+ if (VirtualView == null)
+ {
+ throw new InvalidOperationException($"{nameof(VirtualView)} must be set to create a LayoutView");
+ }
- var view = new ContentPanel
+ var view = new ContentPanel
{
- CrossPlatformMeasure = VirtualView.CrossPlatformMeasure,
- CrossPlatformArrange = VirtualView.CrossPlatformArrange
- };
+ CrossPlatformMeasure = VirtualView.CrossPlatformMeasure,
+ CrossPlatformArrange = VirtualView.CrossPlatformArrange
+ };
- return view;
- }
+ return view;
+ }
public static void MapContent(IBorderHandler handler, IBorderView border)
{
diff --git a/src/Core/src/Platform/Windows/ContentPanel.cs b/src/Core/src/Platform/Windows/ContentPanel.cs
index e615bd310328..46936bf41353 100644
--- a/src/Core/src/Platform/Windows/ContentPanel.cs
+++ b/src/Core/src/Platform/Windows/ContentPanel.cs
@@ -1,7 +1,13 @@
#nullable enable
using System;
+using Microsoft.Graphics.Canvas;
using Microsoft.Maui.Graphics;
+using Microsoft.Maui.Graphics.Win2D;
+using Microsoft.UI.Composition;
+using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
+using Microsoft.UI.Xaml.Hosting;
+using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Shapes;
namespace Microsoft.Maui.Platform
@@ -10,8 +16,20 @@ public class ContentPanel : Panel
{
readonly Path? _borderPath;
IShape? _borderShape;
+ FrameworkElement? _content;
internal Path? BorderPath => _borderPath;
+
+ internal FrameworkElement? Content
+ {
+ get => _content;
+ set
+ {
+ _content = value;
+ AddContent(_content);
+ }
+ }
+
internal Func? CrossPlatformMeasure { get; set; }
internal Func? CrossPlatformArrange { get; set; }
@@ -52,12 +70,14 @@ public ContentPanel()
SizeChanged += ContentPanelSizeChanged;
}
- private void ContentPanelSizeChanged(object sender, UI.Xaml.SizeChangedEventArgs e)
+ void ContentPanelSizeChanged(object sender, UI.Xaml.SizeChangedEventArgs e)
{
if (_borderPath == null)
return;
_borderPath.UpdatePath(_borderShape, ActualWidth, ActualHeight);
+ UpdateContent();
+ UpdateClip(_borderShape);
}
internal void EnsureBorderPath()
@@ -84,6 +104,58 @@ public void UpdateBorderShape(IShape borderShape)
return;
_borderPath.UpdateBorderShape(_borderShape, ActualWidth, ActualHeight);
+ UpdateContent();
+ UpdateClip(_borderShape);
+ }
+
+ void AddContent(FrameworkElement? content)
+ {
+ if (content == null)
+ return;
+
+ if (!Children.Contains(_content))
+ Children.Add(_content);
+ }
+
+ void UpdateContent()
+ {
+ if (Content == null || _borderPath == null)
+ return;
+
+ var strokeThickness = _borderPath.StrokeThickness;
+
+ Content.RenderTransform = new TranslateTransform() { X = -strokeThickness, Y = -strokeThickness };
+ }
+
+ void UpdateClip(IShape? borderShape)
+ {
+ if (Content == null)
+ return;
+
+ var clipGeometry = borderShape;
+
+ if (clipGeometry == null)
+ return;
+
+ double width = Content.ActualWidth;
+ double height = Content.ActualHeight;
+
+ if (height <= 0 && width <= 0)
+ return;
+
+ var visual = ElementCompositionPreview.GetElementVisual(Content);
+ var compositor = visual.Compositor;
+
+ var pathSize = new Graphics.Rect(0, 0, width, height);
+ var clipPath = clipGeometry.PathForBounds(pathSize);
+ var device = CanvasDevice.GetSharedDevice();
+ var geometry = clipPath.AsPath(device);
+
+ var path = new CompositionPath(geometry);
+ var pathGeometry = compositor.CreatePathGeometry(path);
+ var geometricClip = compositor.CreateGeometricClip(pathGeometry);
+
+ visual.Clip = geometricClip;
}
}
}
\ No newline at end of file