WPF:从ControlTemplate绑定到命令

9
我正在尝试向自定义的ListView(MyListView)添加一个按钮,该按钮触发在MyListView中定义的命令(MyCustomCommand)。我通过应用ControlTemplate添加了按钮(以及标题文本)。问题在于我还没有找到一种方法来在单击按钮时触发MyCustomCommand。最终我想要实现的是打开一个Popup或ContextMenu,在其中可以选择ListView中应该可见的列。
以下是我的模板源代码:
<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>

这里是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; }
}

以下是创建MyListView简单实例的XAML代码:

<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>

注意HeaderTitle是绑定到MyListView中的DependencyProperty的。这个可以正常工作。为什么命令不以同样的方式工作呢?有什么方法可以让它工作吗?

2个回答

6
你应该从将命令的包装属性设置为静态并使用开始。
Command={x:Static local:MyListView.MyCustomCommand}

一般来说,只有在命令属性每个实例中被设置为不同的值(比如Button)或者像ViewModel上的DelegateCommand/RelayCommand这样的情况下才需要使用ICommand属性。此外,应该移除getter中的所有额外代码,并且将命令初始化为内联或在静态构造函数中,并在控件的实例构造函数中连接CommandBinding。

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

**更新

路由命令本身应该声明为静态的。当外部控件的消费者传递要执行的命令时,ICommand实例属性是很好的选择,但这不是你在这里想要的。此处也不需要DP,并且你正在使用的一个被声明不正确 - 要可用,它们需要具有带有GetValue/SetValue的实例封装器属性。

public static RoutedCommand ShowColumnPickerCommand
{
    get; private set;
}

static MyListView()
{        
    ShowColumnPickerCommand = new RoutedCommand("ShowColumnPickerCommand", typeof(MyListView));
}

非常感谢,这解决了我的问题 :) 现在当命令执行时,我可以打开一个弹出窗口。 - Christian Myksvoll
我遇到了一个新问题...在窗口中,触发命令的按钮仅在MyListView的第一个实例中可用(启用)。这是否与以下代码中的Static关键字有关:Command={x:Static local:MyListView.MyCustomCommand}? - Christian Myksvoll
当命令的CanExecute为false或命令未附加执行处理程序时,带有命令的按钮将被禁用。确保CanExecute没有发生任何奇怪的事情,并且CommandBinding是在每个ListView实例中设置而不是在静态上下文中设置,这只会影响第一个实例。 - John Bowen
再次感谢。我已经写了一个回答作为对你最新评论的跟进。 - Christian Myksvoll
阅读您的更新后,我将此行从公共构造函数移动到静态构造函数: ShowColumnPickerCommand = new RoutedCommand("ShowColumnPickerCommand", typeof(MyListView));现在它完美地工作了。再次感谢 :) - Christian Myksvoll

3

我不确定这是否是正确的方法。在注释中阅读源代码有些困难,因此我将此回复编写为答案...

这是MyListView的构造函数以及命令绑定方法:

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;
}

绑定并未在静态上下文中设置。唯一的静态内容是命令的DependencyProperty和命令本身:
public static readonly DependencyProperty ShowColumnPickerCommandProperty =
    DependencyProperty.Register("ShowColumnPickerCommand", typeof(RoutedCommand), typeof(MyListView));

private static RoutedCommand showColumnPickerCommand;

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

命令需要是静态的,才能像这样从XAML中绑定到它:

<Button Command="{x:Static local:MyListView.ShowColumnPickerCommand}" />

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接