简体   繁体   中英

Binding to Dependency Property which is, in turn, bound to another binding source

It's hard to explain but i'll do my best. I wanted to have a reusable control that had 3 buttons, one meant for creation of entities, another for edition and another for deletion, here's the abbreviated XAML of the relevant part.

--

<!-- ActionButtons.xaml -->

<StackPanel Orientation="Horizontal" HorizontalAlignment="Left" VerticalAlignment="Top">
        <Button Name="btnNew" Content="New" Command="{Binding Path=NewCommand}" />
        <Button Name="btnEdit" Content="Edit" Command="{Binding Path=EditCommand, Mode=OneWay}" />
        <Button Name="btnDelete" Content="Delete" Command="{Binding Path=DeleteCommand, Mode=OneWay}" />
    </StackPanel>

--

Then, in the code behind I have the dpprops declarations:

// ActionButtons.xaml.cs

public uscActionButtons()
{
            InitializeComponent();
            this.DataContext = this;
        }

        public ICommand NewCommand
        {
            get { return (ICommand)GetValue(NewCommandProperty); }
            set { SetValue(NewCommandProperty, value); }
        }

        // Using a DependencyProperty as the backing store for NewCommand.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty NewCommandProperty =
            DependencyProperty.Register("NewCommand", typeof(ICommand), typeof(uscActionButtons), new UIPropertyMetadata(null, new PropertyChangedCallback(OnCommandChanged)));

I wanted to bind the NewCommand property to a specific implementation of it in another control. Sample intended usage:

<!-- SomeControl.xaml -->
<common:uscActionButtons Grid.Row="0" HorizontalAlignment="Left" 
                                 NewCommand="{Binding NewItemCommand}"
                                 />

And

// SomeControlViewModel.cs
// Note: SomeControlViewModel IS in the DataContext of SomeControl.
public ICommand NewItemCommand
        {
            get
            {
                if (mNewItemCommand == null)
                {
                    mNewItemCommand = new RelayCommand(x => this.CreateItem());
                }

                return mNewItemGroupCommand;
            }
        }

The problem is that the reusable control (ActionButtons) is not seeing the NewItemCommand. If I use a simple button, it sees it fine. It seems the problem is this "chained" binding. But I know it's possible, a WPF button has a Command dependency property to which you bind your commands, so it must not be that hard to create my own reusable control that exposes a ICommand dependency property.

Any ideas?

Thank you


edit: here's the solution, all I had to do was use RelativeSource with FindAncestor.

<StackPanel Orientation="Horizontal" HorizontalAlignment="Left" VerticalAlignment="Top">
        <Button Name="btnNew" Content="New" Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=my:uscActionButtons}, Path=NewCommand, Mode=OneWay}" />
        <Button Name="btnEdit" Content="Edit" Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=my:uscActionButtons}, Path=EditCommand, Mode=OneWay}" />
        <Button Name="btnDelete" Content="Delete" Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=my:uscActionButtons}, Path=DeleteCommand, Mode=OneWay}" />
    </StackPanel>

The problem that you are seeing is that you are altering the DataContext of the ActionButtons control. When you set its DataContext in the constructor, all Bindings that you specify on it (even from external XAML that instantiate it) will point to the new DataContext . So, when you are applying a Binding in SomeControl , that Binding is trying to bind on the DataContext , which happens to be the ActionButtons instance.

I don't think a Control should ever set its own DataContext , as it causes errors like you are seeing. If you want to use a UserControl (and I would probably use a Control myself so that I could do TemplateBindings ), then you can use a RelativeSource binding (as you mentioned in your comment to Snowbear), and the Bindings should work.

Here's what was missing from my code:

<StackPanel Orientation="Horizontal" HorizontalAlignment="Left" VerticalAlignment="Top">
        <Button Name="btnNew" Content="New" Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=my:uscActionButtons}, Path=NewCommand, Mode=OneWay}" />
        <Button Name="btnEdit" Content="Edit" Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=my:uscActionButtons}, Path=EditCommand, Mode=OneWay}" />
        <Button Name="btnDelete" Content="Delete" Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=my:uscActionButtons}, Path=DeleteCommand, Mode=OneWay}" />
    </StackPanel>

I had to use RelativeSource with FindAncestor.

Thank you for all your answers!

this.DataContext = this;

this seems to be a problem, because in this XAML:

 NewCommand="{Binding NewItemCommand}"

you're binding to the NewItemCommand of ActionButtons itself. This is why setting self DataContext inside control is bad pattern on my opinion. If you really need this DataContext (do you) then set it to you top-level inner element (StackPanel in your case)

Also Visual Studio debugger should help you here to debug binding. If you will run your application with debugger attached then in Visual Studio's output window you will see binding errors and they are usually easy to understand, the error will let you know that for this particular binding it looks for NewItemCommand property in instance of ActionButtons instead of class which you were expecting.

UPDATE Tested your code in VS. Error in Output window:

System.Windows.Data Error: 40 : BindingExpression path error: 'NewItemCommand' property not found on 'object' ''uscActionButtons' (Name='')'. BindingExpression:Path=NewItemCommand; DataItem='uscActionButtons' (Name=''); target element is 'uscActionButtons' (Name=''); target property is 'NewCommand' (type 'ICommand')
System.Windows.Data Error: 40 : BindingExpression path error: 'EditCommand' property not found on 'object' ''uscActionButtons' (Name='')'. BindingExpression:Path=EditCommand; DataItem='uscActionButtons' (Name=''); target element is 'Button' (Name='btnEdit'); target property is 'Command' (type 'ICommand')
System.Windows.Data Error: 40 : BindingExpression path error: 'DeleteCommand' property not found on 'object' ''uscActionButtons' (Name='')'. BindingExpression:Path=DeleteCommand; DataItem='uscActionButtons' (Name=''); target element is 'Button' (Name='btnDelete'); target property is 'Command' (type 'ICommand')

Could you miss these errors for example because of opening Output window too late?

Update 2:

Fixed with replacing yours:

this.DataContext = this;

with my:

root.DataContext = this; //root - name for stackpanel

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM