Your custom panel should derive from Panel
instead of Canvas
and override the MeasureOverride
and ArrangeOverride
methods. Moreover it should define its own attached properties for child element layout, like the four properties RelativeX
, RelativeY
, RelativeWidth
and RelativeHeight
shown below.
It would be used in XAML like this:
<local:RelativeLayoutPanel>
<Rectangle Fill="Red"
local:RelativeLayoutPanel.RelativeX="0.2"
local:RelativeLayoutPanel.RelativeY="0.1"
local:RelativeLayoutPanel.RelativeWidth="0.6"
local:RelativeLayoutPanel.RelativeHeight="0.8"/>
</local:RelativeLayoutPanel>
Here's the implementation:
public class RelativeLayoutPanel: Panel
{
public static readonly DependencyProperty RelativeXProperty = DependencyProperty.RegisterAttached(
"RelativeX", typeof(double), typeof(RelativeLayoutPanel),
new FrameworkPropertyMetadata(0d, FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsArrange));
public static readonly DependencyProperty RelativeYProperty = DependencyProperty.RegisterAttached(
"RelativeY", typeof(double), typeof(RelativeLayoutPanel),
new FrameworkPropertyMetadata(0d, FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsArrange));
public static readonly DependencyProperty RelativeWidthProperty = DependencyProperty.RegisterAttached(
"RelativeWidth", typeof(double), typeof(RelativeLayoutPanel),
new FrameworkPropertyMetadata(0d, FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsArrange));
public static readonly DependencyProperty RelativeHeightProperty = DependencyProperty.RegisterAttached(
"RelativeHeight", typeof(double), typeof(RelativeLayoutPanel),
new FrameworkPropertyMetadata(0d, FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsArrange));
public static double GetRelativeX(UIElement element)
{
return (double)element.GetValue(RelativeXProperty);
}
public static void SetRelativeX(UIElement element, double value)
{
element.SetValue(RelativeXProperty, value);
}
public static double GetRelativeY(UIElement element)
{
return (double)element.GetValue(RelativeYProperty);
}
public static void SetRelativeY(UIElement element, double value)
{
element.SetValue(RelativeYProperty, value);
}
public static double GetRelativeWidth(UIElement element)
{
return (double)element.GetValue(RelativeWidthProperty);
}
public static void SetRelativeWidth(UIElement element, double value)
{
element.SetValue(RelativeWidthProperty, value);
}
public static double GetRelativeHeight(UIElement element)
{
return (double)element.GetValue(RelativeHeightProperty);
}
public static void SetRelativeHeight(UIElement element, double value)
{
element.SetValue(RelativeHeightProperty, value);
}
protected override Size MeasureOverride(Size availableSize)
{
availableSize = new Size(double.PositiveInfinity, double.PositiveInfinity);
foreach (UIElement element in InternalChildren)
{
element.Measure(availableSize);
}
return new Size();
}
protected override Size ArrangeOverride(Size finalSize)
{
foreach (UIElement element in InternalChildren)
{
element.Arrange(new Rect(
GetRelativeX(element) * finalSize.Width,
GetRelativeY(element) * finalSize.Height,
GetRelativeWidth(element) * finalSize.Width,
GetRelativeHeight(element) * finalSize.Height));
}
return finalSize;
}
}
If you don't need the four layout properties to be independently bindable or settable by style setters etc. you could perhaps replace them by a single attached property of type Rect
:
<local:RelativeLayoutPanel>
<Rectangle Fill="Red" local:RelativeLayoutPanel.RelativeRect="0.2,0.1,0.6,0.8"/>
</local:RelativeLayoutPanel>
with this much shorter implementation:
public class RelativeLayoutPanel: Panel
{
public static readonly DependencyProperty RelativeRectProperty = DependencyProperty.RegisterAttached(
"RelativeRect", typeof(Rect), typeof(RelativeLayoutPanel),
new FrameworkPropertyMetadata(new Rect(), FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsArrange));
public static Rect GetRelativeRect(UIElement element)
{
return (Rect)element.GetValue(RelativeRectProperty);
}
public static void SetRelativeRect(UIElement element, Rect value)
{
element.SetValue(RelativeRectProperty, value);
}
protected override Size MeasureOverride(Size availableSize)
{
availableSize = new Size(double.PositiveInfinity, double.PositiveInfinity);
foreach (UIElement element in InternalChildren)
{
element.Measure(availableSize);
}
return new Size();
}
protected override Size ArrangeOverride(Size finalSize)
{
foreach (UIElement element in InternalChildren)
{
var rect = GetRelativeRect(element);
element.Arrange(new Rect(
rect.X * finalSize.Width,
rect.Y * finalSize.Height,
rect.Width * finalSize.Width,
rect.Height * finalSize.Height));
}
return finalSize;
}
}