使用MVVM从WPF ListView项目中触发双击事件

在使用MVVM的WPF应用程序中,我有一个带有listview项目的usercontrol。 在运行时,它将使用数据绑定来填充对象集合的列表视图。

将双击事件附加到列表视图中的项目的正确方法是什么,以便当列表视图中的项目被双击时,视图模型中的相应事件被触发并且对被点击的项目有引用?

怎样才能以干净的MVVM方式完成,即View中没有代码?

请代码背后并不是一件坏事。 不幸的是,很多WPF社区的人都搞错了。

MVVM不是消除背后代码的模式。 从逻辑部分(工作stream程)中分离视图部分(外观,animation等)。 此外,您可以对逻辑部分进行unit testing。

我知道有足够的场景需要在后面编写代码,因为数据绑定不是一切的解决scheme。 在您的scheme中,我将在代码隐藏文件中处理DoubleClick事件,并将此调用委托给ViewModel。

示例应用程序使用后面的代码,仍然履行MVVM分离可以在这里find:

WPF应用程序框架(WAF) – http://waf.codeplex.com

我能够得到这个与.NET 4.5一起工作。 看起来很简单,没有第三方或代码需要。

<ListView ItemsSource="{Binding Data}"> <ListView.ItemsPanel> <ItemsPanelTemplate> <StackPanel Orientation="Horizontal"/> </ItemsPanelTemplate> </ListView.ItemsPanel> <ListView.ItemTemplate> <DataTemplate> <Grid Margin="2"> <Grid.InputBindings> <MouseBinding Gesture="LeftDoubleClick" Command="{Binding ShowDetailCommand}"/> </Grid.InputBindings> <Grid.RowDefinitions> <RowDefinition/> <RowDefinition/> </Grid.RowDefinitions> <Image Source="..\images\48.png" Width="48" Height="48"/> <TextBlock Grid.Row="1" Text="{Binding Name}" /> </Grid> </DataTemplate> </ListView.ItemTemplate> </ListView> 

我喜欢使用附加的命令行为和命令。 Marlon Grech有很好的附加命令行为的实现。 使用这些,我们可以将样式分配给ListView的ItemContainerStyle属性,该属性将为每个ListViewItem设置命令。

在这里,我们设置要在MouseDoubleClick事件上触发的命令,并且CommandParameter将成为我们点击的数据对象。 在这里,我正在旅行了可视化树,以获得我正在使用的命令,但您可以轻松地创build应用程序范围的命令。

 <Style x:Key="Local_OpenEntityStyle" TargetType="{x:Type ListViewItem}"> <Setter Property="acb:CommandBehavior.Event" Value="MouseDoubleClick" /> <Setter Property="acb:CommandBehavior.Command" Value="{Binding ElementName=uiEntityListDisplay, Path=DataContext.OpenEntityCommand}" /> <Setter Property="acb:CommandBehavior.CommandParameter" Value="{Binding}" /> </Style> 

对于这些命令,您既可以直接实现ICommand ,也可以使用MVVM Toolkit中的一些助手。

我发现一个非常简单和干净的方法来完成Blend SDK事件触发器。 清洁MVVM,可重用,没有代码隐藏。

你可能已经有这样的东西了:

 <Style x:Key="MyListStyle" TargetType="{x:Type ListViewItem}"> 

现在,如果您还没有使用ListViewItem,请为此添加一个ControlTemplate:

 <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type ListViewItem}"> <GridViewRowPresenter Content="{TemplateBinding Content}" Columns="{TemplateBinding GridView.ColumnCollection}" /> </ControlTemplate> </Setter.Value> </Setter> 

GridViewRowPresenter将成为组成列表行元素的“内部”所有元素的可视化根。 现在我们可以在那里插入一个触发器来查找MouseDoubleClick路由事件并通过InvokeCommandAction调用一个命令,如下所示:

 <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type ListViewItem}"> <GridViewRowPresenter Content="{TemplateBinding Content}" Columns="{TemplateBinding GridView.ColumnCollection}"> <i:Interaction.Triggers> <i:EventTrigger EventName="MouseDoubleClick"> <i:InvokeCommandAction Command="{Binding DoubleClickCommand}" /> </i:EventTrigger> </i:Interaction.Triggers> </GridViewRowPresenter> </ControlTemplate> </Setter.Value> </Setter> 

如果GridRowPresenter(“以网格开始的probalby”)的上方有可视元素,则也可以将Trigger放在那里。

不幸的是,MouseDoubleClick事件不是从每个可视元素生成的(它们来自Controls,但不是来自FrameworkElements)。 解决方法是从EventTrigger中派生类,然后查找ClickCount为2的MouseButtonEventArgs。这有效地滤除了所有非MouseButtonEvents和所有MoseButtonEvent(使用ClickCount!= 2)。

 class DoubleClickEventTrigger : EventTrigger { protected override void OnEvent(EventArgs eventArgs) { var e = eventArgs as MouseButtonEventArgs; if (e == null) { return; } if (e.ClickCount == 2) { base.OnEvent(eventArgs); } } } 

现在我们可以写这个('h'是上面的helper类的名字空间):

 <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type ListViewItem}"> <GridViewRowPresenter Content="{TemplateBinding Content}" Columns="{TemplateBinding GridView.ColumnCollection}"> <i:Interaction.Triggers> <h:DoubleClickEventTrigger EventName="MouseDown"> <i:InvokeCommandAction Command="{Binding DoubleClickCommand}" /> </h:DoubleClickEventTrigger> </i:Interaction.Triggers> </GridViewRowPresenter> </ControlTemplate> </Setter.Value> </Setter> 

我意识到这个讨论已经有一年了,但是在.NET 4中,这个解决scheme有什么想法吗? 我绝对同意,MVVM的重点不是消除文件后面的代码。 我也非常强烈地认为,只是因为有些事情是复杂的,并不意味着更好。 下面是我在后面的代码:

  private void ButtonClick(object sender, RoutedEventArgs e) { dynamic viewModel = DataContext; viewModel.ButtonClick(sender, e); } 

您可以使用Caliburn的 “动作”function将事件映射到ViewModel上的方法。 假设您的ViewModel上有一个ItemActivated方法,那么相应的XAML将如下所示:

 <ListView x:Name="list" Message.Attach="[Event MouseDoubleClick] = [Action ItemActivated(list.SelectedItem)]" > 

有关更多详细信息,可以查看Caliburn的文档和示例。

在创build视图时,我发现链接命令更简单:

 var r = new MyView(); r.MouseDoubleClick += (s, ev) => ViewModel.MyCommand.Execute(null); BindAndShow(r, ViewModel); 

在我的情况BindAndShow看起来像这样(updatecontrols + avalondock):

 private void BindAndShow(DockableContent view, object viewModel) { view.DataContext = ForView.Wrap(viewModel); view.ShowAsDocument(dockManager); view.Focus(); } 

虽然这种方法应该以任何你打开新视图的方法来工作。

我看到了使用InuptBindings的rushui的解决scheme,但我仍然无法击中没有文本的ListViewItem区域 – 即使将背景设置为透明后,我也通过使用不同的模板来解决这个问题。

此模板适用于ListViewItem被选中并处于活动状态的情况:

 <ControlTemplate x:Key="SelectedActiveTemplate" TargetType="{x:Type ListViewItem}"> <Border Background="LightBlue" HorizontalAlignment="Stretch"> <!-- Bind the double click to a command in the parent view model --> <Border.InputBindings> <MouseBinding Gesture="LeftDoubleClick" Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.ItemSelectedCommand}" CommandParameter="{Binding}" /> </Border.InputBindings> <TextBlock Text="{Binding TextToShow}" /> </Border> </ControlTemplate> 

该模板适用于ListViewItem被选中且处于非活动状态的情况:

 <ControlTemplate x:Key="SelectedInactiveTemplate" TargetType="{x:Type ListViewItem}"> <Border Background="Lavender" HorizontalAlignment="Stretch"> <TextBlock Text="{Binding TextToShow}" /> </Border> </ControlTemplate> 

这是用于ListViewItem的默认样式:

 <Style TargetType="{x:Type ListViewItem}"> <Setter Property="Template"> <Setter.Value> <ControlTemplate> <Border HorizontalAlignment="Stretch"> <TextBlock Text="{Binding TextToShow}" /> </Border> </ControlTemplate> </Setter.Value> </Setter> <Style.Triggers> <MultiTrigger> <MultiTrigger.Conditions> <Condition Property="IsSelected" Value="True" /> <Condition Property="Selector.IsSelectionActive" Value="True" /> </MultiTrigger.Conditions> <Setter Property="Template" Value="{StaticResource SelectedActiveTemplate}" /> </MultiTrigger> <MultiTrigger> <MultiTrigger.Conditions> <Condition Property="IsSelected" Value="True" /> <Condition Property="Selector.IsSelectionActive" Value="False" /> </MultiTrigger.Conditions> <Setter Property="Template" Value="{StaticResource SelectedInactiveTemplate}" /> </MultiTrigger> </Style.Triggers> </Style> 

我不喜欢的是TextBlock和它的文本绑定的重复,我不知道我可以绕过只在一个位置声明。

我希望这可以帮助别人!