3

I've made a user control which contains a command, to be called in response to a certain event. This command is a dependency property. I want to use it in the main window like this:

<local:myUserControl Command="{Binding someCommand}"/>

The "myCommand" is the dependency property I created for this user control. And I bind it to a command of the view model of the main window ("someCommand").

The problem is that I am setting the datacontext of my usercontrol (I have a view model for it), and it seems to reset the "Command" to null… Here is the code-behind of my view model:

public partial class myUserControl : UserControl, ICommandSource
{
    public myUserControl()
    {
        this.DataContext = new myViewModel();

        InitializeComponent();
    }

    public ICommand Command
    {
        get { return (ICommand)GetValue(CommandProperty); }
        set { SetValue(CommandProperty, value); }
    }
    public static readonly DependencyProperty CommandProperty =
        DependencyProperty.Register("Command", typeof(ICommand), typeof(myUserControl), new PropertyMetadata(null));



    public object CommandParameter
    {
        get { return (object)GetValue(CommandParameterProperty); }
        set { SetValue(CommandParameterProperty, value); }
    }
    public static readonly DependencyProperty CommandParameterProperty =
        DependencyProperty.Register("CommandParameter", typeof(object), typeof(myUserControl), new PropertyMetadata(0));



    public IInputElement CommandTarget
    {
        get { return (IInputElement)GetValue(CommandTargetProperty); }
        set { SetValue(CommandTargetProperty, value); }
    }
    public static readonly DependencyProperty CommandTargetProperty =
        DependencyProperty.Register("CommandTarget", typeof(IInputElement), typeof(myUserControl), new PropertyMetadata(null));



    private void TextBlock_MouseUp(object sender, MouseButtonEventArgs e)
    {
        Command.Execute(this.CommandParameter);
    }
}

The code of my user control could be the Following:

<UserControl x:Class="myApp.myUserControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:myApp"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800">
    <Grid>
        <TextBlock MouseUp="TextBlock_MouseUp">
        </TextBlock>
    </Grid>
</UserControl>

(I know that this element seems a bit silly (or useless), but I have simplified it to test what didn't worked and also in order to ask a rather simple question).

I have discovered that, if I comment the "this.DataContext = new myViewModel();" line, the binding to the command works perfectly. And when I uncomment this line and put a breakpoint in the "TextBlock_MouseUp", the "Command" property is equal to null...

Would there be a way to resolve this problem? I have some complicated code in my view model (so I'm quite forced to keep this line "this.DataContext = new myViewModel();"), and I am not sure I could find another solution than having a "Command" dependency property in my user control…

To be sure I give a maximum of informations, I have the following code in the view model of my main window:


public ICommand someCommand { get; set; }

//Constructor
public MainWindowViewModel()
{
    this.someCommand = new RelayCommand((obj) => { return true; },
                                        (obj) =>
                                        {
                                            //I put a breakpoint here
                                            int dummy = 0;
                                        });
}

(The RelayCommand class is a standard RelayCommand class, with a "Predicate" CanExecute and an "Action Execute).

I hope this question is not a duplicate… I have found several similar question, but they did not seem to answer mine...

Valerian
  • 41
  • 7
  • 1
    A binding not explicitly specifying the source object for the binding will look at the object provided by the DataContext property. Note that DataContext property is an _inheriting_ property. If you don't set the DataContext in your user control, then the command binding there will use whatever object DataContext "inherits" from its parent container DataContext. So, basically setting vs. not setting DataContext inside your user control changes the object from which the binding will try to access the "someCommand" property. If your myViewModel instance has no "someCommand" property: null –  Jul 06 '19 at 11:34
  • aha, so @elgonzo it means that even if I set the `Command="{Binding someCommand}"` binding in the **MainWindow**, it will look up in the **UserControl**'s DataContext? And therefore, if I do not set the UserControl's DataContext, it inherits the one of the MainWindow, which _does contains a someCommand_, but if I set the UserControl's DataContext, it doesn't inherits the MainWindow datacontext, and so doesn't contain the someCommand anymore? Is that right? – Valerian Jul 06 '19 at 11:48
  • 1
    Yep, unless you explicitly specify some source for the binding, when trying to resolve the given path/property ("someCommand") the binding will lookup the DataContext of the binding target object. In your case the binding target is the `Command` DependencyProperty of your user control, hence the binding will try to find the "someCommand" property in the object provided by the DataContext of your user control. All you said there in your last comment is correct. :-) –  Jul 06 '19 at 11:58
  • @elgonzo ooook! Many thanks! And please apologize for the lack of knowledge: it seems I had not understand very well how bindings were made… thanks again! Do you want to make an answer for this (to earn some points), or shall I write it with the code that eventually worked? – Valerian Jul 06 '19 at 12:09
  • Feel free to self-answer with your code example (Self-answering questions is totally fine. It doesn't really matter who writes an answer.) Btw, I was just looking around a little in the official documentation for Binding.Source and the Microsofts own WPF binding guide/overview, and i was surprised not finding anything spelling out this "default source" behavior. I was just skimming the doc, though, so perhaps i just overlooked something. (If you don't answer and when i am able to find some reference documentation about it, i will sometime during the weekend write up an answer) –  Jul 06 '19 at 12:14

1 Answers1

1

I'm really sorry for this question which was in fact a bit silly. I hadn't understand very well what happens during a binding. I thought that this code line in the MainWindow…

<local:myUserControl Command="{Binding someCommand}"/>

…would have made an attempt to bind the UserControl's "Command" property to the "someCommand" of the datacontext of the MainWindow. In fact, as @elgonzo pointed out, the binding looks up in the UserControl's datacontext for the "someCommand" property (and not in the MainWindow's datacontext!!). Therefore, setting the UserControl's datacontext with this line…

this.DataContext = new myViewModel();

...was preventing the binding to be correctly done (since it looks for the "someCommand" property of the UserControl's datacontext, which is now "myViewModel", which does not contain "someCommand"...).

To fix this, I had to change the binding like this:

<local:myUserControl Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, 
                                       Path=DataContext.someCommand}"/>

I've found this solution here: https://stackoverflow.com/a/1127964/11609068.

Maybe it is not the best way to do it (the "Path= DataContext. someCommand" make me think this, it doesn't seem very elegant), but it works. Another way to do it is to name the MainWindow (x:Name="someName"), so that the binding is a bit simpler:

<local:myUserControl Command="{Binding ElementName=someName, Path=DataContext.someCommand}"/>

Again, sorry and many thanks to @elgonzo.

Valerian
  • 41
  • 7