WPF:如何使用 MVVM 将命令绑定到 ListBoxItem?
Posted
技术标签:
【中文标题】WPF:如何使用 MVVM 将命令绑定到 ListBoxItem?【英文标题】:WPF: How to bind a command to the ListBoxItem using MVVM? 【发布时间】:2011-07-29 13:07:42 【问题描述】:我刚刚开始学习 MVVM。我已经按照MVVM tutorial 从头开始制作应用程序(我强烈推荐给所有 MVVM 初学者)。基本上,到目前为止,我创建的是几个文本框,用户可以在其中添加他或她的数据,一个用于保存该数据的按钮,该按钮随后会在 ListBox 中填充所有条目。
这就是我卡住的地方:我希望能够双击 ListBoxItem 并触发我创建并添加到 ViewModel 中的命令。我不知道如何完成 XAML 方面,即我不知道如何将该命令绑定到 ListBox(Item)。
这里是 XAML:
...
<ListBox
Name="EntriesListBox"
Width="228"
Height="208"
Margin="138,12,0,0"
HorizontalAlignment="Left"
VerticalAlignment="Top"
ItemsSource="Binding Entries" />
...
这里是 ViewModel:
public class MainWindowViewModel : DependencyObject
...
public IEntriesProvider Entries
get return entries;
private IEntriesProvider entries;
public OpenEntryCommand OpenEntryCmd get; set;
public MainWindowViewModel(IEntriesProvider source)
this.entries = source;
...
this.OpenEntryCmd = new OpenEntryCommand(this);
...
最后,这是我希望在用户双击 EntriesListBox 中的项目后执行的 OpenEntryCommand:
public class OpenEntryCommand : ICommand
private MainWindowViewModel viewModel;
public OpenEntryCommand(MainWindowViewModel viewModel)
this.viewModel = viewModel;
public event EventHandler CanExecuteChanged
add CommandManager.RequerySuggested += value;
remove CommandManager.RequerySuggested -= value;
public bool CanExecute(object parameter)
return parameter is Entry;
public void Execute(object parameter)
string messageFormat = "Subject: 0\nStart: 1\nEnd: 2";
Entry entry = parameter as Entry;
string message = string.Format(messageFormat,
entry.Subject,
entry.StartDate.ToShortDateString(),
entry.EndDate.ToShortDateString());
MessageBox.Show(message, "Appointment");
请帮忙,我将不胜感激。
【问题讨论】:
【参考方案1】:不幸的是,只有ButtonBase
派生控件可以将ICommand
对象绑定到它们的Command
属性(对于Click
事件)。
但是,您可以使用 Blend 提供的 API 将事件(例如在您的情况下为 ListBox
上的 MouseDoubleClick
)映射到 ICommand
对象。
<ListBox>
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseDoubleClick">
<i:InvokeCommandAction Command="Binding YourCommand"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</ListBox>
您必须定义:xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
并引用 System.Windows.Interactivity.dll
。
-- 编辑-- 这是 WPF4 的一部分,但如果您不使用 WPF4,您可以使用 Microsoft.Windows.Interactivity。这个 dll 来自 Blend SDK,它不需要 Blend,来自这里: http://www.microsoft.com/downloads/en/details.aspx?FamilyID=f1ae9a30-4928-411d-970b-e682ab179e17&displaylang=en
更新:我发现了一些对你有帮助的东西。检查this link on MVVM Light Toolkit,其中包含有关如何执行此操作的演练,以及所需库的link。 MVVM Light Toolkit 是一个非常有趣的框架,用于将 MVVM 与 Silverlight、WPF 和 WP7 一起应用。
希望这会有所帮助:)
【讨论】:
我找不到 System.Windows.Interactivity.dll。我的猜测是它需要有 Blend 的 API。你能指点我到合适的位置吗?谢谢。 这是 WPF4 的一部分,但如果您不使用 WPF4,您可以使用 Microsoft.Windows.Interactivity。这个 dll 来自 Blend SDK,不需要 Blend,来自这里:microsoft.com/downloads/en/… 我下载并安装了 Microsoft Expression Blend 4 SDK。当我之后运行 VS2010 时,在为 Window 命名新命名空间时,智能感知下拉列表中没有提供“schemas.microsoft.com/expression/2010/interactivity”命名空间。我应该怎么做才能把它放在那里? @Boris:您必须添加对库的引用。单击项目上的“添加引用”并查找“System.Windows.Interactivity” -1:@AbdouMoumen:虽然这个答案大部分都可以,但它有一个棘手的问题:它将 mouse2click 绑定到 ListBox,而不是 ListBoxItem。这意味着如果 ListBox 显示其内部 滚动条,则双击此类滚动条将触发此命令。通常,这是非常不受欢迎的【参考方案2】:由于 DoubleClick 事件,这变得很棘手。有几种方法可以做到这一点:
-
在后面的代码中处理双击事件,然后在 ViewModel 上手动调用命令/方法
使用附加行为路由DoubleClick event to your Command
对map the DoubleClick event to your command 使用混合行为
2 和 3 可能更纯粹,但坦率地说,1 更容易,更简单,而且不是世界上最糟糕的事情。对于一次性案例,我可能会使用方法#1。
现在,如果您将要求更改为在每个项目上使用例如超链接,那会更容易。首先命名 XAML 中的根元素 - 例如,对于 Window:
<Window .... Name="This">
现在,在 ListBox 项的 DataTemplate 中,使用如下内容:
<ListBox ...>
<ListBox.ItemTemplate>
<DataTemplate>
<Hyperlink
Command="Binding ElementName=This, Path=DataContext.OpenEntryCmd"
Text="Binding Path=Name"
/>
ElementName 绑定允许您从 ViewModel 的上下文而不是特定的数据项解析 OpenEntryCmd。
【讨论】:
保罗,我同意没有 1 是最简单的解决方案,而不是世界上最糟糕的事情,正如你所说的那样。但是,由于我的问题的目的是教育,我将坚持您提供的其他两个解决方案。当谈到第二种选择时,我一无所知 - 我根本没有得到提供的答案。第三个对我来说最有意义,但我会等待 AbdouMoumen 的回复,因为他的解决方案避免创建额外的类 (ExecuteCommandAction)。感谢大家的帮助! Abdou 得分较高的答案有一个不平凡的问题。从那里复制 cmets 没有意义,我只想说将命令绑定到 LIST 与将其绑定到每个 ITEM 完全不同。这种变化具有重要的后果,从“仅处理点击”的角度来看是不可见的。数据上下文变化和“双击滚动条”可能是最容易发现的。另一方面,保罗描述的所有三种方法都没有这个问题,因为它们将命令绑定到适当的目标。简而言之,他们是正确的,Abdou 的则不然。【参考方案3】:我发现最好的方法是为我的内容创建一个简单的用户控件包装器,其中包含命令和参数的依赖属性。
我这样做的原因是按钮没有将点击事件冒泡到我的 ListBox,这阻止了它选择 ListBoxItem。
CommandControl.xaml.cs:
public partial class CommandControl : UserControl
public CommandControl()
MouseLeftButtonDown += OnMouseLeftButtonDown;
InitializeComponent();
private void OnMouseLeftButtonDown(object sender, MouseButtonEventArgs mouseButtonEventArgs)
if (Command != null)
if (Command.CanExecute(CommandParameter))
Command.Execute(CommandParameter);
public static readonly DependencyProperty CommandProperty =
DependencyProperty.Register("Command", typeof(ICommand),
typeof(CommandControl),
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.None));
public ICommand Command
get return (ICommand)GetValue(CommandProperty);
set SetValue(CommandProperty, value);
public static readonly DependencyProperty CommandParameterProperty =
DependencyProperty.Register("CommandParameter", typeof(object),
typeof(CommandControl),
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.None));
public object CommandParameter
get return (object)GetValue(CommandParameterProperty);
set SetValue(CommandParameterProperty, value);
CommandControl.xaml:
<UserControl x:Class="WpfApp.UserControls.CommandControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
Background="Transparent">
</UserControl>
用法:
<ListBoxItem>
<uc:CommandControl Command="Binding LoadPageCommand"
CommandParameter="Binding HomePageViewModel">
<TextBlock Text="Home" Margin="0,0,0,5" VerticalAlignment="Center"
Foreground="White" FontSize="24" />
</uc:CommandControl>
</ListBoxItem>
Content可以是任意的,当控件被点击时会执行命令。
编辑:将Background="Transparent"
添加到 UserControl 以启用控件整个区域的点击事件。
【讨论】:
谢谢!运行完美,似乎是 View Viewmodel 分层最干净的解决方案。 我必须将自定义事件从我的 UserControl 绑定到主视图中的命令。交互性不适用于自定义事件,我正在努力处理它。创建自定义命令而不是事件对我来说是最聪明的,实际上也是最优雅的解决方案。至少目前是这样。谢谢! 我的荣幸!我推荐使用 MVVM 框架,那么你就不必做任何这些了。例如,在 Caliburn.Micro 中,您可以使用 Actions 来处理 ViewModel 中的 XAML 事件,例如:<Button cal:Message.Attach="[Event Click] = [Action OnClick]" />
其中OnClick
是 ViewModel 中的一个方法。比这更优雅:-)【参考方案4】:
这有点小技巧,但它运行良好,允许您使用命令并避免代码落后。这还有一个额外的好处,即当您在空的 ScrollView
区域中双击(或任何您的触发器)时不会触发命令,假设您的 ListBoxItems
没有填满整个容器。
基本上,只需为您的ListBox
创建一个由TextBlock
组成的DataTemplate
,并将TextBlock
的宽度绑定到ListBox
的宽度,将边距和填充设置为0,并禁用水平滚动(因为TextBlock
会超出ScrollView
的可见边界,否则会触发水平滚动条)。我发现的唯一错误是,如果用户精确单击ListBoxItem
的边框,该命令将不会触发,我可以忍受。
这是一个例子:
<ListBox
x:Name="listBox"
Width="400"
Height="150"
ScrollViewer.HorizontalScrollBarVisibility="Hidden"
ItemsSource="Binding ItemsSourceProperty"
SelectedItem="Binding SelectedItemProperty">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Padding="0"
Margin="0"
Text="Binding DisplayTextProperty"
Width="Binding ElementName=listBox, Path=Width">
<TextBlock.InputBindings>
<MouseBinding
Command="Binding RelativeSource=RelativeSource FindAncestor, AncestorType=x:Type ListBox, Path=DataContext.SelectProjectCommand"
Gesture="LeftDoubleClick" />
</TextBlock.InputBindings>
</TextBlock>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
【讨论】:
【参考方案5】:如果您正在寻找一个不错的简单解决方案,该解决方案使用交互而不是使用用户控件、代码隐藏、输入绑定、自定义附加属性等。
并且您想要在 ListBoxItem 级别工作的东西,即不是按照(错误)接受的解决方案的 ListBox 级别。
然后这里是一个简单的“按钮之类”点击操作的 sn-p..
<ListBox>
<ListBox.ItemTemplate>
<DataTemplate>
<Grid Background="Transparent">
<!-- insert your visuals here -->
<b:Interaction.Triggers>
<b:EventTrigger EventName="MouseUp">
<b:InvokeCommandAction Command="Binding YourCommand" />
</b:EventTrigger>
</b:Interaction.Triggers>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
注意,需要 background="Transparent" 以确保整个 Grid 是可点击的,而不仅仅是里面的内容。
【讨论】:
【参考方案6】:我最近还需要在双击 ListBoxItem
时触发 ICommand
。
就我个人而言,我不喜欢DataTemplate
方法,因为它绑定到ListBoxItem
容器内的内容,而不是容器本身。我选择使用附加属性在容器上分配InputBinding
。它需要更多的肘部油脂,但效果很好。
首先,我们需要创建一个附加的属性类。我已经为派生自FrameworkElement
的任何类创建了一个更通用的类,以防万一我以不同的视觉效果再次遇到这个问题。
public class FrameworkElementAttachedProperties : DependencyObject
public static readonly DependencyProperty DoubleClickProperty = DependencyProperty.RegisterAttached("DoubleClick", typeof(InputBinding),
typeof(FrameworkElementAttachedProperties), new PropertyMetadata(null, OnDoubleClickChanged));
public static void SetDoubleClick(FrameworkElement element, InputBinding value)
element.SetValue(DoubleClickProperty, value);
public static InputBinding GetDoubleClick(FrameworkElement element)
return (InputBinding)element.GetValue(DoubleClickProperty);
private static void OnDoubleClickChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
FrameworkElement element = obj as FrameworkElement;
/// Potentially throw an exception if an object is not a FrameworkElement (is null).
if(e.NewValue != null)
element.InputBindings.Add(e.NewValue as InputBinding);
if(e.OldValue != null)
element.InputBindings.Remove(e.OldValue as InputBinding);
那么最后一步是覆盖ListBoxItem
的基本容器样式。
<ListBox.ItemContainerStyle>
<Style TargetType="x:Type ListBoxItem"
BasedOn="StaticResource ListBoxItem">
<Setter Property="local:FrameworkElementAttachedProperties.DoubleClick">
<Setter.Value>
<MouseBinding Command="Binding OnListBoxItemDoubleClickCommand"
MouseAction="LeftDoubleClick"/>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
现在,只要双击ListBoxItem
,它就会触发我们的OnListBoxItemDoubleClickCommand
。
【讨论】:
以上是关于WPF:如何使用 MVVM 将命令绑定到 ListBoxItem?的主要内容,如果未能解决你的问题,请参考以下文章