Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

WPF: Bind to command from ControlTemplate

I am trying to add a button to a custom ListView (MyListView) which triggers a command (MyCustomCommand) defined in MyListView. I have added the button (and a title text) by applying a ControlTemplate. The problem is that I have not found a way to trigger MyCustomCommand when clicking the button. What I eventually want to achieve is to open a Popup or ContextMenu where I can select which columns should be visible in the ListView.

Here's my template source:

<Style TargetType="local:MyListView">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="local:MyListView">
                <Border Name="Border" BorderThickness="1" BorderBrush="Black">
                    <Grid>
                        <Grid.RowDefinitions>
                            <RowDefinition Height="30" />
                            <RowDefinition />
                        </Grid.RowDefinitions>

                        <Grid Background="LightSteelBlue">
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition />
                                <ColumnDefinition Width="Auto" />
                            </Grid.ColumnDefinitions>
                            <TextBlock Margin="3,3,3,3" Text="{TemplateBinding HeaderTitle}" Grid.Column="0" VerticalAlignment="Center" HorizontalAlignment="Stretch" FontSize="16" />
                            <Button Margin="3,3,3,3" Grid.Column="1" 
                                    VerticalAlignment="Center" HorizontalAlignment="Right" Height="20"
                                    Command="{TemplateBinding MyCustomCommand}">A button</Button>
                        </Grid>

                        <ScrollViewer Grid.Row="1" Style="{DynamicResource {x:Static GridView.GridViewScrollViewerStyleKey}}">
                            <ItemsPresenter />
                        </ScrollViewer>
                    </Grid>
                </Border>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

Here is the definition for MyListView:

public class MyListView : ListView
{
    public static readonly DependencyProperty MyCustomCommandProperty = 
        DependencyProperty.Register("MyCustomCommand", typeof(ICommand), typeof(MyListView));

    private static RoutedCommand myCustomCommand;

    public ICommand MyCustomCommand
    {
        get
        {
            if (myCustomCommand == null)
            {
                myCustomCommand = new RoutedCommand("MyCustomCommand", typeof(MyListView));

                var binding = new CommandBinding();
                binding.Command = myCustomCommand;
                binding.Executed += binding_Executed;

                CommandManager.RegisterClassCommandBinding(typeof(MyListView), binding);
            }
            return myCustomCommand;
        }
    }

    private static void binding_Executed(object sender, ExecutedRoutedEventArgs e)
    {
        MessageBox.Show("Command Handled!");
    }


    public static readonly DependencyProperty HeaderTitleProperty =
        DependencyProperty.Register("HeaderTitle", typeof(string), typeof(MyListView));

    public string HeaderTitle { get; set; }
}

And here is the XAML that creates a simple instance of MyListView:

<local:MyListView VerticalAlignment="Top" HeaderTitle="ListView title">
    <ListView.View>
        <GridView>
            <GridViewColumn Width="70" Header="Column 1" />
            <GridViewColumn Width="70" Header="Column 2" />
            <GridViewColumn Width="70" Header="Column 3" />
        </GridView>
    </ListView.View>

    <ListViewItem>1</ListViewItem>
    <ListViewItem>2</ListViewItem>
    <ListViewItem>1</ListViewItem>
    <ListViewItem>2</ListViewItem>
</local:MyListView>

Notice HeaderTitle which is bound to the DependencyProperty in MyListView. This works as expected. Why doesn't it work the same way with commands? Any clues of how to make this work?

like image 990
Christian Myksvoll Avatar asked Oct 06 '10 09:10

Christian Myksvoll


2 Answers

You should start by making the wrapper property for the command static and use

Command={x:Static local:MyListView.MyCustomCommand}

Generally you only want an ICommand property if the command is being set to a different value on each instance (like Button) or if it's something like a DelegateCommand/RelayCommand on a ViewModel. You should also remove all of the extra code in the getter and instead initialize the command either inline or in the static constructor and connect the CommandBinding in the control's instance constructor.

CommandBindings.Add(new CommandBinding(MyCustomCommand, binding_Executed));

**UPDATE

The RoutedCommand itself should be declared as static. ICommand instance properties are good for when an external consumer of your control is passing in a command to execute, which is not what you want here. There is also no need for a DP here and the one you're using is declared incorrectly - to be usable they need to have instance wrapper properties with GetValue/SetValue.

public static RoutedCommand ShowColumnPickerCommand
{
    get; private set;
}

static MyListView()
{        
    ShowColumnPickerCommand = new RoutedCommand("ShowColumnPickerCommand", typeof(MyListView));
}
like image 119
John Bowen Avatar answered Nov 13 '22 17:11

John Bowen


I am not sure if this the correct way to do this. It's a bit difficult to read source code in comments, so I write this reply as an answer...

Here is the constructor of MyListView + the command binding methods:

public MyListView()
{        
    showColumnPickerCommand = new RoutedCommand("ShowColumnPickerCommand", typeof(MyListView));

    var binding = new CommandBinding();
    binding.Command = showColumnPickerCommand;
    binding.Executed += ShowColumnPicker;
    binding.CanExecute += ShowColumnPickerCanExecute;

    CommandBindings.Add(binding);
}

private void ShowColumnPicker(object sender, ExecutedRoutedEventArgs e)
{
    MessageBox.Show("Show column picker");          
}

private void ShowColumnPickerCanExecute(object sender, CanExecuteRoutedEventArgs e)
{
    e.CanExecute = true;
}

The bindings are not set up in a static context. The only things that are static are the DependencyProperty for the command, and the command itself:

public static readonly DependencyProperty ShowColumnPickerCommandProperty =
    DependencyProperty.Register("ShowColumnPickerCommand", typeof(RoutedCommand), typeof(MyListView));

private static RoutedCommand showColumnPickerCommand;

public static RoutedCommand ShowColumnPickerCommand
{
    get
    {
        return showColumnPickerCommand;
    }
}

The command needs to be static to able to bind to it from the XAML like this:

<Button Command="{x:Static local:MyListView.ShowColumnPickerCommand}" />
like image 36
Christian Myksvoll Avatar answered Nov 13 '22 17:11

Christian Myksvoll