0

We've made a custom control with a clear button, which should delete the form where the control is used. We also use ReactiveProperty to implement Commands in the ViewModel.

MyControl ViewModel

internal sealed class MyControlViewModel : IDisposable
{
    public ReactiveCommand Clear { get; set; } = new ReactiveCommand();

    #region IDisposable
    // (dispose commands)
    #endregion
}

MyControl View

<UserControl (namespaces)>
    <UserControl.DataContext>
        <local:MyControlViewModel/>
    </UserControl.DataContext>

    <Button Command="{Binding Clear, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">Clear</Button>
</UserControl>

code behind:

public sealed partial class MyControl : UserControl, IDisposable
{
    public MyControl()
    {
        InitializeComponent();
        _subscriptions.Add(ViewModel?.Clear.Subscribe(o => Clear?.Execute(o)));
    }

    internal MyControlViewModel ViewModel => (MyControlViewModel)DataContext;


    #region DependencyProperty
    public static readonly DependencyProperty ClearProperty = 
            DependencyProperty.Register(nameof(Clear), typeof(ICommand), typeof(MyControl), new PropertyMetadata(null));

        public ICommand Clear
        {
            get { return (ICommand) GetValue(ClearProperty); }
            set { SetValue(ClearProperty, value); }
        }
    #endregion

    #region IDisposable
    // (dispose subscriptions, properties, commands)
    #endregion
}

Main ViewModel

internal sealed class MainViewModel : IDisposable
{
    public MainViewModel()
    {
        Clear = InputText.Select(t => !string.IsNullOrEmpty(t)).ToReactiveCommand();
        _subscriptions.Add(Clear.Subscribe(_ => InputText.Value = string.Empty));
    }

    public ReactiveCommand Clear { get; set; }

    public ReactiveProperty<string> InputText { get; set; } 
        = new ReactiveProperty<string>(string.Empty, ReactivePropertyMode.DistinctUntilChanged);

    #region IDisposable
    // (dispose subscriptions, properties, commands)
    #endregion
}

Main View

<Page (namespaces)>
    <Page.DataContext>
        <main:MainViewModel />
    </Page.DataContext>

    <local:MyControl x:Name="myControl" Clear="{Binding Clear}" />
</Page>

Unfortunately, the binding in the Main View does not work and the Clear property of MyControl's View is always null.

Remarks

It works by setting the Property manually, but it isn't what I want:

public sealed partial class MainPage
{
    public MainPage()
    {
        InitializeComponent();
        myControl.Clear = ViewModel.Clear;
    }

    private MainViewModel ViewModel => (MainViewModel) DataContext;
}

The question is how to do it properly using only data binding in XAML.

Edit

The reason why the binding is failing is obvious when trying this:

myControl.SetBinding(ViewModel.ClearAll, new Binding("ClearAll"));;

This code can't compile because ViewModel.ClearAll is a ReactiveCommand and therefore not assignable to a DependencyProperty.

Solution

For binding to ICommand, it is required to use {x:Bind Path} instead of {Binding Path}.

See also: MSDN: Event Binding and ICommand

MovGP0
  • 7,267
  • 3
  • 49
  • 42
  • You actually are binding the Clear property from user control to Clear ReactiveCommand from MainViewModel. Why you need this property repeated in both View Models? – E-Bat Dec 19 '15 at 16:05
  • 4
    Don't use ViewModels with Controls. ViewModels are for the Views, not for Controls. When you try to separate controls into control and ViewModel, you will run against a wall. Controls are made to be reusable across applications, so you can't tie them to a ViewModel, as ViewModel is application specific – Tseng Dec 19 '15 at 19:49
  • @E-Bat the code above is a simplification of the actual application. in the code there are multiple buttons invoking a single command with different parameters. the clear button for the form is a separate command. – MovGP0 Dec 20 '15 at 15:11
  • @Tseng the control is also reusable with the separate ViewModel. the advantage of the separation into a View and a ViewModel is that it is possible to unit test the behavior of the control. Also, the VM of the control does not appear to be the issue here, since the DependencyProperty is defined in the code behind of the View. – MovGP0 Dec 20 '15 at 15:14
  • 1
    You don't do that with Controls, just with Views. Controls have to be agnostic of how they are to be used. There is a reason NO usercontrols ever had a ViewModel... **never**. When you create a "LoginControl", you don't make a ViewModel for it. You expose Dependency Properties (`Username`, `Password`, `RememberMe`). When this control is used in your view, you bind your applications ViewModel to it, **not** a user controls viewmodel. – Tseng Dec 20 '15 at 15:25
  • @Tseng please explain: what exactly is the reason why a ViewModel does not work? can you provide a working code example? – MovGP0 Dec 20 '15 at 15:35
  • Cause you end up in troubles :P If you have a ViewModel for a UserControl, you have to bind it to `DataContext`, but then your actual application context becomes in accessible. And when you want to reuse this control (in different application), your may need to reimplement your controls ViewModel. Check out http://stackoverflow.com/a/33209250/455493, http://stackoverflow.com/a/28810652/455493, http://stackoverflow.com/a/3334780/455493 A Pager is a good example of a usercontrol that's not a View – Tseng Dec 20 '15 at 16:08
  • @Tseng for some reason you seem to miss, that the problem is not with the control at all, but with the binding of the ViewModel of the page. – MovGP0 Dec 20 '15 at 16:34
  • found the solution: for command binding one has to use x:Bind instead of Binding: [MSDN: Event Binding and ICommand](https://msdn.microsoft.com/en-us/library/windows/apps/mt210946.aspx#event_binding_and_icommand) – MovGP0 Dec 20 '15 at 23:40

0 Answers0