3

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>
SoManyGoblins
  • 5,605
  • 8
  • 45
  • 65

3 Answers3

4

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.

Abe Heidebrecht
  • 30,090
  • 7
  • 62
  • 66
  • I changed to RelativeSource (I was sure I had already tried that) and at least now it invokes the GET on NewItemCommand property on SomeControlViewModel.cs, unfortunately it does not invoke the delegate injected into the RelayCommand's constructor (RelayCommand is already extensively used, so I'm sure it's good). Any ideas? Already removed the "this.DataContext = this", you convinced me the disadvantages of using it :) – SoManyGoblins Jan 14 '11 at 12:52
3

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!

SoManyGoblins
  • 5,605
  • 8
  • 45
  • 65
1
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
Community
  • 1
  • 1
Snowbear
  • 16,924
  • 3
  • 43
  • 67
  • You're mixing two different controls. NewCommand property is in ActionButtons, NewItemCommand is in SomeControl. I did the "this.DataContext = this" to bind the Command property of the "new" button to the "NewCommand" property. Alternatively I could have use RelativeSource, or ElementName and given the ActionButtons a name, but it's more or less the same to me. VS Debugger indicates no binding errors. Note: Everything's datacontext is where it needs to be. – SoManyGoblins Jan 13 '11 at 20:04
  • Updated my answer, replying in comments is hell :( – Snowbear Jan 13 '11 at 20:17
  • I removed the "this.DataContext = this" according to Abe Heidebrecht's comment, now it invokes the GET on the NewItemCommand's property on SomeControlViewModel.cs, but does not invoke the execute delegate when I click the button :( – SoManyGoblins Jan 14 '11 at 12:55
  • In your first block of code you have this: "Binding Path=NewCommand,". Maybe there is a some problem caused by this comma? – Snowbear Jan 14 '11 at 13:20