I have a UserControl
in WinUI 3 where I have defined a RelativePanel
containing 2 TextBlock
.
One of these Textblocks
serves as a Label, and I would like to position it relative to the other Textblock
.
To achieve this, I’ve created a DependencyProperty
called LabelPosition
, that I can set to Above
, Below
, Right
or Left
.
I also have a callback where I set all the parameters of the RelativePanel so that the Label is positioned according to what is specified in the LabelPosition
property.
This is the XAML code of the UserControl:
<?xml version="1.0" encoding="utf-8"?>
<UserControl
x:Class="ClientMan.Controls.FormFieldUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:ClientMan.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
x:Name="FormField"
>
<RelativePanel>
<TextBlock x:Name="FieldLabel"
Text="{x:Bind Label}" />
<TextBlock x:Name="FieldTextBlock"
Text="{x:Bind Text}"
IsTextSelectionEnabled="True" />
</RelativePanel>
</UserControl>
In code-behind I have this:
//other code
public static readonly DependencyProperty LabelPositionProperty = DependencyProperty.Register(nameof(LabelPosition), typeof(Position), typeof(FormFieldUserControl), new PropertyMetadata(Position.Above, OnPositionLabelChanged));
public Position LabelPosition
{
get => (Position)GetValue(LabelPositionProperty);
set => SetValue(LabelPositionProperty, value);
}
private static void OnPositionLabelChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs eventArgs)
{
var control = dependencyObject as FormFieldUserControl;
var newPosition = (Position)eventArgs.NewValue;
switch (newPosition)
{
case Position.Left:
ResetPositionRelativePanel([control.FieldLabel, control.FieldTextBlock]);
RelativePanel.SetAlignRightWithPanel(control.FieldTextBlock, true);
RelativePanel.SetAlignTopWithPanel(control.FieldTextBlock, true);
RelativePanel.SetAlignLeftWithPanel(control.FieldLabel, true);
RelativePanel.SetAlignVerticalCenterWith(control.FieldLabel, control.FieldTextBlock);
RelativePanel.SetRightOf(control.FieldTextBlock, control.FieldLabel);
break;
case Position.Right:
//other code
default:
break;
}
}
The problem is with RelativePanel.SetAlignVerticalCenterWith
and RelativePanel.SetRightOf
, with this I put the Label
vertically centered relative to the other TextBlock
and then I position the FieldTextBlock
to the right of the Label
, but this create a circular dependency and the app crash.
I’ve considered a potential solution where I take the width of the Label
, remove the call to the RelativePanel.SetRightOf
method, add a call to RelativePanel.SetAlignLeftWithPanel(control.FieldTextBlockBorder, true)
and finally add a margin, equal to the width of the Label
, to the left of FieldTextBlock
.
However, I’m not sure if this is the best approach or if there are better ways to achieve the desired layout.
Edit:
This is what I try to achive
When I try to use Left
or Right
the app crash at startup (I don't get any error or warning in the error list).
Taking the code posted above as example, if I comment out one between RelativePanel.SetRightOf(control.FieldTextBlock, control.FieldLabel);
or RelativePanel.SetAlignVerticalCenterWith(control.FieldLabel, control.FieldTextBlock);
the app runs as expected.
I also tried to set the same properties in XAML and I get the following error
IMHO, it's easier to implement this feature by just using a Grid
and its ColumnDefinitions
:
FormFieldUserControl.xaml
<UserControl
x:Class="RelativePanelDemo.FormFieldUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:RelativePanelDemo"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Grid
x:Name="RootGrid"
RowDefinitions="Auto,Auto"
ColumnDefinitions="Auto,Auto">
<TextBlock
x:Name="FieldLabel"
Text="{x:Bind Label}" />
<TextBlock
x:Name="FieldTextBlock"
IsTextSelectionEnabled="True"
Text="{x:Bind Text}" />
</Grid>
</UserControl>
FormFieldUserControl.xaml.cs
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
namespace RelativePanelDemo;
public enum LabelPosition
{
Top,
Bottom,
Left,
Right,
}
public sealed partial class FormFieldUserControl : UserControl
{
public static readonly DependencyProperty LabelProperty =
DependencyProperty.Register(
nameof(Label),
typeof(string),
typeof(FormFieldUserControl),
new PropertyMetadata(default));
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register(
nameof(Text),
typeof(string),
typeof(FormFieldUserControl),
new PropertyMetadata(default));
public static readonly DependencyProperty LabelPositionProperty =
DependencyProperty.Register(
nameof(LabelPosition),
typeof(LabelPosition),
typeof(FormFieldUserControl),
new PropertyMetadata(LabelPosition.Left, OnLabelPositionPropertyChanged));
public FormFieldUserControl()
{
InitializeComponent();
RefreshPosition();
}
public string Label
{
get => (string)GetValue(LabelProperty);
set => SetValue(LabelProperty, value);
}
public string Text
{
get => (string)GetValue(TextProperty);
set => SetValue(TextProperty, value);
}
public LabelPosition LabelPosition
{
get => (LabelPosition)GetValue(LabelPositionProperty);
set => SetValue(LabelPositionProperty, value);
}
private static void OnLabelPositionPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is FormFieldUserControl control)
{
control.RefreshPosition();
}
}
private void RefreshPosition()
{
switch (LabelPosition)
{
case LabelPosition.Top:
Grid.SetRow(FieldLabel, 0);
Grid.SetColumn(FieldLabel, 0);
Grid.SetRow(FieldTextBlock, 1);
Grid.SetColumn(FieldTextBlock, 0);
break;
case LabelPosition.Bottom:
Grid.SetRow(FieldLabel, 1);
Grid.SetColumn(FieldLabel, 0);
Grid.SetRow(FieldTextBlock, 0);
Grid.SetColumn(FieldTextBlock, 0);
break;
case LabelPosition.Left:
Grid.SetRow(FieldLabel, 0);
Grid.SetColumn(FieldLabel, 0);
Grid.SetRow(FieldTextBlock, 0);
Grid.SetColumn(FieldTextBlock, 1);
break;
case LabelPosition.Right:
Grid.SetRow(FieldLabel, 0);
Grid.SetColumn(FieldLabel, 1);
Grid.SetRow(FieldTextBlock, 0);
Grid.SetColumn(FieldTextBlock, 0);
break;
default:
break;
}
}
}