JohnnyHerms
JohnnyHerms

Reputation: 96

Complex Command DataBinding between nested UserControls in WPF

I have a UserControl called PAInstructionsControl inside my TestAdmin Class Library.

<UserControl x:Class="TestAdmin.Controls.PAInstructionsControl">
        <Button Command="{Binding ???}"
                CommandParameter="{Binding ???}"/>
</UserControl>

In the consumers of this UserControl: I need a ViewModel used as the Button's Command Binding and a different ViewModel for its CommandParameter but I'm not sure how to do this.

In my WMT Class Library I have a WmtPAInstructionsView UserControl that consumes the PAInstructionsControl

<UserControl x:Class="WMT.View.WmtPAInstructionsView"
             xmlns:controls="clr-namespace:TestAdmin.Controls;assembly=TestAdmin">
    <StackPanel>
        <TextBlock Text="WMT PA Instructions View"
                   Margin="10"/>

        <controls:PAInstructionsControl />

    </StackPanel>
</UserControl>

The WmtPAInstructionsView is nested inside a WMT.WmtMainWindow UserControl WMT

If the Button was simply inside the WmtPAInstructionsView UserControl, then I would need these ViewModel bindings :

<Button 
    Command="{Binding DataContext.DisplayNextIndexedViewCommand, RelativeSource=
                {RelativeSource AncestorType={x:Type local:WmtMainWindow}}, Mode=OneWay}"
    CommandParameter="{Binding}"/>

So the command would bind to a command within the WmtMainWindow's DataContext. The CommandParameter would be the WmtPAInstructionView's DataContext which is the WmtPAInstructionsViewModel

But I need the button in the PAInstructionsControl so it can be re-used elsewhere in my solution. What would I use for the bindings of the Command and CommandParameter? Do I need Dependency Properties?


Hopefully the above information is all you need to help answer my question. Here is more information if you need it:


I also have a MSVT Class Library that has a MsvtPAInstructionsView UserControl that consumes the PAInstructionsControl in a similar way.

If the Button was simply inside the MsvtPAInstructionsView UserControl, then I would need these ViewModel bindings :

<Button 
    Command="{Binding DataContext.DisplayNextIndexedViewCommand, RelativeSource=
                {RelativeSource AncestorType={x:Type local:MsvtMainWindow}}, Mode=OneWay}"
    CommandParameter="{Binding}"/>

Both the WMT and MSVT Class Libraries reference the TestAdmin library. WMT and MVST do NOT reference each other.

Here is the Main Application Window (WpfTestBase.MainWindow):

Main Application Window

Clicking on "New WMT Test" opens a new WpfTestBase.TestView Window. This TestView Window has several nested user controls : WMT

Here's the Visual Tree:

Visual Tree

Upvotes: 0

Views: 224

Answers (1)

Keith Stein
Keith Stein

Reputation: 6724

The "easy" (and sort of bad) solution would be to do this:

<Button 
    Command="{Binding DataContext.DisplayNextIndexedViewCommand, RelativeSource=
                {RelativeSource AncestorType={x:Type Window}}, Mode=OneWay}"
    CommandParameter="{Binding}"/>

This would work for any subclass of Window. While it does "solve" your problem, it makes a number of assumptions. The biggest is that you'd be assuming you'll always want your Button to bind to the parent Window, and that that Window's DataContext will always have a property exactly named DisplayNextIndexedViewCommand.


A better solution would be to define a chain of DependencyPropertys in each of your UserControls, where you would bind each child to its parent. This would give you total flexibility when consuming the controls.

Start with PAInstructionsControl and define a DisplayNextIndexedViewCommand DependencyProperty. You would then bind Buton.Command to that property like so:

<Button 
    Command="{Binding DisplayNextIndexedViewCommand, RelativeSource=
                {RelativeSource AncestorType={x:Type local:PAInstructionsControl}}, Mode=OneWay}"
    CommandParameter="{Binding}"/>

This brings you one level of nesting closer to the top. Next, also declare a DisplayNextIndexedViewCommand DependencyProperty in WmtPAInstructionsView. This lets you bind PAInstructionsControl.DisplayNextIndexedViewCommand to WmtPAInstructionsView.DisplayNextIndexedViewCommand like so:

<UserControl x:Class="WMT.View.WmtPAInstructionsView"
             xmlns:controls="clr-namespace:TestAdmin.Controls;assembly=TestAdmin">
    <StackPanel>
        <TextBlock Text="WMT PA Instructions View"
                   Margin="10"/>

        <controls:PAInstructionsControl DisplayNextIndexedViewCommand="{Binding DisplayNextIndexedViewCommand, RelativeSource={RelativeSource AncestorType={x:Type local:WmtPAInstructionsView}}}"/>

    </StackPanel>
</UserControl>

Now, finally, you can bind WmtPAInstructionsView.DisplayNextIndexedViewCommand to the actual command, which you have living in your Window's DataContext:

<local:WmtPAInstructionsView DisplayNextIndexedViewCommand="{Binding DataContext.DisplayNextIndexedViewCommand, RelativeSource={RelativeSource AncestorType={x:Type local:WmtMainWindow}}, Mode=OneWay}"/>

(Also, since DataContext is inherited, you might be able to leave out "DataContext." and the whole RelativeSource from the Binding)

Upvotes: 1

Related Questions