0

I commonly find myself wanting to wrap one or multiple UserControls into a single one for reusability reasons. In other frameworks/libraries this seems to be far more common/trivial to achieve. I take that in most frameworks utilizing Xaml, defining custom controls is not as common due to the MVVM approach, which might be the reason there is no useful documentation for this around There are a lot of vaguely similar questions on this topic, but I failed to find one that concisely elaborates on the ideal way to achieve this.

Background

This is how I would go about in in Blazor. Defining a property with the Parameter property results in a one-way binding and updates the component when that passed parameter changes.

[Parameter]
public bool Value { get; set; }

If Two way binding is desired, one has to define a corresponding callback explicitly and configure two-way binding inside the parent with the @bind- syntax.

 [Parameter]
    public EventCallback<bool> ValueChanged { get; set; }
    
    private async Task SetValue(bool value)
    {
        if (Value != value)
        {
            Value = value;
            await ValueChanged.InvokeAsync(value);
        }
    }

In React this is quite similar though the parent has to explicitly pass an event callback as there is no true two-way binding.

Issue

I can't get my head around how to do this in Xaml. I take that the usual way to define a property on a custom control is a registered DependencyProperty. My approach so far was:

  • Define a DependencyProperty;
  • Bind that the property's value to the wrapped control's value;
    • if it is readonly, use one-way binding;
    • if it is editable from within the custom control, two-way bind to the inner control's value to the property.

Here is a sample of a slider that display's its discrete index next to it.

<Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition />
            <ColumnDefinition Width="Auto"/>
        </Grid.ColumnDefinitions>
        <Slider x:Name="IndexSlider"
            Grid.Column="0"
            TickFrequency="1"
            Minimum="0"
            Value="{x:Bind Value, Mode=TwoWay}"
            Maximum="{x:Bind Maximum, Mode=OneWay}"
            Foreground="Blue"/>
        <TextBlock VerticalAlignment="Center"
            HorizontalAlignment="Right"
            Grid.Column="1"
            Margin="6,0,0,0"
            Text="{x:Bind local:WrappedSlider.DiscretePaginate(IndexSlider.Value, Maximum), Mode=OneWay}"/>
    </Grid>

public sealed partial class WrappedSlider : UserControl
    {
        public static readonly DependencyProperty ValueProperty = (/*...*/);

        public double Value
        {
            get { return (double)GetValue(ValueProperty); }
            set
            {
                SetValue(ValueProperty, value);
            }
        }

        public static readonly DependencyProperty MaximumProperty = (/*...*/);

        public double Maximum
        {
            get { return (double)GetValue(MaximumProperty); }
            set
            {
                SetValue(MaximumProperty, value);
            }
        }

        public WrappedSlider()
        {
            InitializeComponent();
        }

        public static string DiscretePaginate(double currentIndex, double upperBound) => $"{currentIndex}/{upperBound}";
    }

While this approach works for simple controls (in most cases so far) there is a plenty of things disturbing me.

My questions

  1. Is (one-way or two-way) binding a control's value to its wrappers passed DependencyProperty an issue at all or does it yield any pitfalls? It appears to me as there is no single source of truth anymore, but the inner control's value as well as the DependencyProperty's backing field. Can someone who has no idea about my control's implementation be ensured that he can reliably two-way to my control (or one-way bind for read-only controls/when reverse binding is not needed).
  2. Is the x:Bind syntax the right approach here or should I give the inner control a x:Name and set/get its value through the outer property's getter/setter? I noticed children occasionally not updating when using {x:Bind Variable, Mode=OneWay} and invoking the setter inside the code-behind, while doing MyControlsName.Value = did. Or should I even use a entirely different approach.
  3. Might INotifyPropertyChanged be feasible here? This post lists various advantages, but it appears to me as something that's exclusively used for the ViewModel in a MVVM architecture.

I really could use some input here. This seems like something that should be self-explanatory, but the countless ways to implement bindings in Xaml on top of the widely used MVVM approach makes this very obstructive for someone with a background in Blazor.

Beltway
  • 508
  • 4
  • 17

1 Answers1

1

Can someone who has no idea about my control's implementation be ensured that he can reliably two-way to my control (or one-way bind for read-only controls/when reverse binding is not needed).

Yes, when you create data binding between property and control. This is the bridge where they could connect with each other and receive changes from the other side. It's reliable unless you manually change the value in code.

Is the x:Bind syntax the right approach here or should I give the inner control a x:Name and set/get its value through the outer property's getter/setter?

Using x:Bind or Binding syntax is the correct way to implement data binding.

Might INotifyPropertyChanged be feasible here?

INotifyPropertyChanged interface is used for data-binding. When you want to use two-way binding, you will need to implement this interface. This is a data-binding implementation. Generally, MVVM mode uses data-binding, as a result,INotifyPropertyChanged interface is often used in MVVM mode.

Roy Li - MSFT
  • 8,043
  • 1
  • 7
  • 13
  • Thanks, this covers most of my questions; is there any recommendation about the first half of my first question? I noticed asynchronous calls invoked through certain events called by the runtime (e.g. data fetching after navigation to page) fail to initiate a state update when invoked through a regular getter. While explicitly naming and calling a control works it's not always ideal. On the other hand, neither is defining a private `DependencyProperty`. – Beltway Jan 18 '22 at 14:48
  • @Beltway Please post another question with the code snippet that you are using so that we could focus on that one. – Roy Li - MSFT Jan 19 '22 at 06:38