Skip to content

Instantly share code, notes, and snippets.

@Youness-ESSalmy
Created December 13, 2023 17:50
Show Gist options
  • Save Youness-ESSalmy/2c30a87ad1ba431cf2f7d0fe234657b8 to your computer and use it in GitHub Desktop.
Save Youness-ESSalmy/2c30a87ad1ba431cf2f7d0fe234657b8 to your computer and use it in GitHub Desktop.
FrameworkElement Wrapper (Decorator Class) WPF
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));
}
}
}
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