Created
December 13, 2023 17:50
-
-
Save Youness-ESSalmy/2c30a87ad1ba431cf2f7d0fe234657b8 to your computer and use it in GitHub Desktop.
FrameworkElement Wrapper (Decorator Class) WPF
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.ComponentModel; | |
using System.Diagnostics; | |
using System.Runtime.CompilerServices; | |
using System.Windows; | |
using System.Windows.Controls; | |
using System.Windows.Media; | |
namespace CustomControlLibrary | |
{ | |
public class BorderWrapper : Decorator, INotifyPropertyChanged | |
{ | |
#region Private Members | |
private Thickness _borderThickness; | |
public Thickness BorderThickness | |
{ | |
get { return _borderThickness; } | |
private set | |
{ | |
if (_borderThickness != value) | |
{ | |
_borderThickness = value; | |
OnPropertyChanged(); | |
InvalidateMeasure(); | |
} | |
} | |
} | |
private enum Sides | |
{ | |
Left, | |
Top, | |
Right, | |
Bottom | |
} | |
#endregion | |
#region DependencyProperties | |
private FrameworkElement _left; | |
[DefaultValue(null)] | |
public FrameworkElement Left | |
{ | |
get { return _left; } | |
set | |
{ | |
if( _left != value) | |
{ | |
_left = SetSide(_left, value, Sides.Left); | |
} | |
} | |
} | |
private FrameworkElement _top; | |
[DefaultValue(null)] | |
public FrameworkElement Top | |
{ | |
get { return _top; } | |
set | |
{ | |
if(_top != value) | |
_top = SetSide(_top, value, Sides.Top); | |
} | |
} | |
private FrameworkElement _right; | |
[DefaultValue(null)] | |
public FrameworkElement Right | |
{ | |
get { return _right; } | |
set | |
{ | |
if(_right != value) | |
_right = SetSide(_right, value, Sides.Right); | |
} | |
} | |
private FrameworkElement _bottom; | |
[DefaultValue(null)] | |
public FrameworkElement Bottom | |
{ | |
get { return _bottom; } | |
set | |
{ | |
if (_bottom != value) | |
_bottom = SetSide(_bottom, value, Sides.Bottom); | |
} | |
} | |
public CornerRadius CornerRadius | |
{ | |
get { return (CornerRadius)GetValue(CornerRadiusProperty); } | |
set { SetValue(CornerRadiusProperty, value); } | |
} | |
public static readonly DependencyProperty CornerRadiusProperty = | |
DependencyProperty.Register("CornerRadius", typeof(CornerRadius), typeof(BorderWrapper), new FrameworkPropertyMetadata( | |
default(CornerRadius), FrameworkPropertyMetadataOptions.AffectsArrange | FrameworkPropertyMetadataOptions.AffectsMeasure)); | |
#endregion | |
// Constructers | |
public BorderWrapper() | |
{ | |
} | |
// Override members | |
protected override int VisualChildrenCount => 5; | |
protected override Visual GetVisualChild(int index) | |
{ | |
switch (index) | |
{ | |
case 0: return Child; | |
case 1: return Left; | |
case 2: return Top; | |
case 3: return Right; | |
case 4: return Bottom; | |
default: throw new ArgumentException(index + " index visual Is out of bounds"); | |
} | |
} | |
// Measure the DesiredSize of each Object | |
protected override Size MeasureOverride(Size constraint) | |
{ | |
// Do not set the border if the child is not set | |
if (!(Child is FrameworkElement child && child != null)) | |
return constraint; | |
// Initialize border sizes to the default value Size(0.0, 0.0) | |
Size leftSize; | |
Size topSize; | |
Size rightSize; | |
Size bottomSize = (leftSize = (topSize = (rightSize = default))); | |
// Measure the the thickness of each side considering the available space | |
if (Left != null) | |
{ | |
leftSize = new Size(Math.Min(constraint.Width, BorderThickness.Left), child.ActualHeight); | |
Left.Measure(leftSize); | |
} | |
if (Top != null) | |
{ | |
topSize = new Size(child.ActualWidth, Math.Min(constraint.Height, BorderThickness.Top)); | |
Top.Measure(topSize); | |
} | |
if (Right != null) | |
{ | |
rightSize = new Size(Math.Min(constraint.Width, BorderThickness.Right), child.ActualHeight); | |
Right.Measure(rightSize); | |
} | |
if (Bottom != null) | |
{ | |
bottomSize = new Size(child.ActualWidth, Math.Min(constraint.Height, BorderThickness.Bottom)); | |
Bottom.Measure(bottomSize); | |
} | |
// Measure child availableSize minus the sides thickness | |
Size childDesiredSize = new Size(Math.Max(0.0, constraint.Width - leftSize.Width - rightSize.Width) | |
, Math.Max(0.0, constraint.Height - topSize.Height - bottomSize.Height)); | |
// set child desired size based on the availability of its size | |
if (!double.IsNaN(child.Width)) | |
childDesiredSize.Width = Math.Min(child.Width, childDesiredSize.Width); | |
if(!double.IsNaN(child.Height)) | |
childDesiredSize.Height = Math.Min(child.Height, childDesiredSize.Height); | |
// Measure the child's DesiredSize | |
child.Measure(childDesiredSize); | |
// Total Measured Size Calculation | |
Size MeasurementSize = new Size(childDesiredSize.Width + Left.DesiredSize.Width + Right.DesiredSize.Width, | |
childDesiredSize.Height + Top.DesiredSize.Height + Bottom.DesiredSize.Height); | |
Trace.WriteLine("constraint: " + constraint + " | MeasurementSize: " + MeasurementSize); | |
return MeasurementSize; | |
} | |
// The default size & the bounderies of un object | |
protected override Size ArrangeOverride(Size arrangeSize) | |
{ | |
// Do not arrange the sides when the child component is not set | |
if (!(Child is FrameworkElement child && child != null)) | |
return default; | |
// Initialize the child's arrangeSize to childArrangeSize | |
Size childArrangeSize = new Size(Math.Max(0.0, arrangeSize.Width - Left.DesiredSize.Width - Right.DesiredSize.Width) | |
, Math.Max(0.0, arrangeSize.Height - Top.DesiredSize.Height - Bottom.DesiredSize.Height)); | |
// use childDesiredSize when the bounderies of the child are set | |
Size childDesiredSize = new Size(Math.Min(child.DesiredSize.Width, arrangeSize.Width), Math.Min(child.DesiredSize.Height, arrangeSize.Height)); | |
// Set childDesiredSize dimensions to childDArrangeSize incase of they're not set | |
if (double.IsNaN(child.Width)) | |
childDesiredSize.Width = childArrangeSize.Width; | |
if (double.IsNaN(child.Height)) | |
childDesiredSize.Height = childArrangeSize.Height; | |
// Arrange the Child | |
child.Arrange(new Rect(new Point(Left.DesiredSize.Width, Top.DesiredSize.Height), childDesiredSize)); | |
// Get the position of the child relative to this wrapper | |
Point childPos = child.TranslatePoint(default, this); | |
// Arrangement Size Calculation | |
Size arrangementSize = new Size(childDesiredSize.Width + Left.DesiredSize.Width + Right.DesiredSize.Width, | |
childDesiredSize.Height + Top.DesiredSize.Height + Bottom.DesiredSize.Height); | |
// Arrange each of the border sides | |
if (Left != null) | |
{ | |
Left.Arrange(new Rect(Math.Max(childPos.X - Left.DesiredSize.Width, 0), childPos.Y, | |
Left.DesiredSize.Width, Math.Min(childDesiredSize.Height, arrangementSize.Height - childPos.Y)));// Added | |
} | |
if (Top != null) | |
{ | |
Top.Arrange(new Rect(childPos.X, Math.Max(childPos.Y - Top.DesiredSize.Height, 0.0), | |
Math.Min(childDesiredSize.Width, arrangementSize.Width - childPos.X), Top.DesiredSize.Height)); | |
} | |
if (Right != null) | |
{ | |
Right.Arrange(new Rect(Math.Min(childPos.X + childDesiredSize.Width, arrangeSize.Width - Right.DesiredSize.Width), childPos.Y, | |
Right.DesiredSize.Width, Math.Min(childDesiredSize.Height, arrangementSize.Height - childPos.Y))); | |
} | |
if (Bottom != null) | |
{ | |
Bottom.Arrange(new Rect(childPos.X, Math.Min(childPos.Y + childDesiredSize.Height, arrangeSize.Height - Bottom.DesiredSize.Height), | |
Math.Min(childDesiredSize.Width, arrangementSize.Width - childPos.X), Bottom.DesiredSize.Height)); | |
} | |
return arrangementSize; | |
} | |
private static double ReplaceNaNWithZero(double value) | |
{ | |
return double.IsNaN(value) ? 0 : value; | |
} | |
private FrameworkElement SetSide(FrameworkElement oldValue, FrameworkElement newValue, Sides side) | |
{ | |
RemoveVisualChild(oldValue); | |
RemoveLogicalChild(oldValue); | |
switch (side) | |
{ | |
case Sides.Left: | |
newValue.SizeChanged += (o, e) => | |
{ | |
Thickness thickness = BorderThickness; | |
thickness.Left = ReplaceNaNWithZero(newValue.Width); | |
BorderThickness = thickness; | |
}; | |
break; | |
case Sides.Top: | |
newValue.SizeChanged += (o, e) => | |
{ | |
Thickness thickness = BorderThickness; | |
thickness.Top = ReplaceNaNWithZero(newValue.Height); | |
BorderThickness = thickness; | |
}; | |
break; | |
case Sides.Right: | |
newValue.SizeChanged += (o, e) => | |
{ | |
Thickness thickness = BorderThickness; | |
thickness.Right = ReplaceNaNWithZero(newValue.Width); | |
BorderThickness = thickness; | |
}; | |
break; | |
case Sides.Bottom: | |
newValue.SizeChanged += (o, e) => | |
{ | |
Thickness thickness = BorderThickness; | |
thickness.Bottom = ReplaceNaNWithZero(newValue.Height); | |
BorderThickness = thickness; | |
}; | |
break; | |
} | |
AddVisualChild(newValue); | |
AddLogicalChild(newValue); | |
InvalidateMeasure(); | |
return newValue; | |
} | |
public event PropertyChangedEventHandler PropertyChanged; | |
protected void OnPropertyChanged([CallerMemberName] string name = null) | |
{ | |
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name)); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
cc:BorderWrapper x:Name="wrapper" HorizontalAlignment="Left"> | |
<cc:BorderWrapper.Left> | |
<Rectangle x:Name="Left" Width="10"> | |
<Rectangle.Style> | |
<Style TargetType="Rectangle"> | |
<Setter Property="Fill" Value="Blue"/> | |
<Style.Triggers> | |
<Trigger Property="IsMouseOver" Value="True"> | |
<Setter Property="Fill" Value="Black"/> | |
</Trigger> | |
</Style.Triggers> | |
</Style> | |
</Rectangle.Style> | |
</Rectangle> | |
</cc:BorderWrapper.Left> | |
<cc:BorderWrapper.Top> | |
<Rectangle x:Name="Top" Fill="Green" Height="10" /> | |
</cc:BorderWrapper.Top> | |
<cc:BorderWrapper.Right> | |
<Rectangle x:Name="Right" Fill="DarkCyan" Width="10" /> | |
</cc:BorderWrapper.Right> | |
<cc:BorderWrapper.Bottom> | |
<Rectangle x:Name="Bottom" Fill="DarkMagenta" Height="10"/> | |
</cc:BorderWrapper.Bottom> | |
<Rectangle x:Name="child" Fill="Red" | |
HorizontalAlignment="Left" | |
VerticalAlignment="Top" | |
Width="100" | |
Height="100"/> | |
</cc:BorderWrapper> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment