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

Ema*_*iel 100 wpf mvvm

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

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

如何以干净的MVVM方式完成,即视图中没有代码?

jbe*_*jbe 77

请注意,代码背后并不是件坏事.不幸的是,WPF社区中有很多人弄错了.

MVVM不是消除背后代码的模式.它是将视图部分(外观,动画等)与逻辑部分(工作流)分开.此外,您还可以对逻辑部件进行单元测试.

我知道你必须编写代码的足够场景,因为数据绑定不是解决所有问题的方法.在您的方案中,我将在代码隐藏文件中处理DoubleClick事件,并将此调用委托给ViewModel.

可以在此处找到使用代码并仍然实现MVVM分离的示例应用程序:

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

  • @Nam Gi VU:当WPF控件支持时,我总是更喜欢命令绑定.命令绑定不仅仅是将"Click"事件中继到ViewModel(例如CanExecute).但命令仅适用于最常见的场景.对于其他场景,我们可以使用代码隐藏文件,并在那里将非UI相关问题委托给ViewModel或Model. (21认同)
  • 好吧,我拒绝使用所有代码和额外的DLL只是为了双击! (5认同)
  • 这只使用Binding的东西给了我一个真正的头痛.这就像被要求用一只手臂,一只眼睛贴在眼罩上,站在一条腿上进行编码.双击应该很简单,我不知道所有这些额外的代码是如何值得的. (4认同)
  • 恐怕我不完全同意你的观点。如果你说“代码隐藏还不错”,那么我有一个问题:为什么我们不委托按钮的单击事件,而是经常使用绑定(使用 Command 属性)呢? (3认同)
  • 现在我更了解你!和你讨论好! (2认同)

Rus*_*uan 71

我能够使用.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>
Run Code Online (Sandbox Code Playgroud)

  • 我正在试图弄清楚为什么它对你们所有人而不是为我而工作.我突然意识到,在项模板的上下文中,数据上下文是itemssource中的当前项,而不是主窗口的视图模型.所以我使用以下方法使其工作<MouseBinding MouseAction ="LeftDoubleClick"Command ="{Binding Path = DataContext.EditBandCommand,RelativeSource = {RelativeSource AncestorType = {x:Type Window}}}"/>在我的情况下,EditBandCommand是页面上的viewmodel上的命令不在绑定的实体上. (4认同)
  • 好的 - 这个旧的栗子再次......需要将背景设置为透明以接收鼠标事件,例如http://stackoverflow.com/questions/7991314/mouse-event-on-transparent-background (3认同)
  • 似乎不适用于整个区域,例如我在停靠面板上执行此操作,它仅适用于停靠面板内的某些内容(例如文本块,图像)但不适用于空白区域. (2认同)

rmo*_*ore 44

我喜欢使用附加命令行为和命令.Marlon Grech非常好地实现了附加命令行为.使用这些,我们可以为ListView的ItemContainerStyle属性分配一个样式,该属性将为每个ListViewItem设置命令.

这里我们设置要在MouseDoubleClick事件上触发的命令,CommandParameter将是我们单击的数据对象.在这里,我沿着可视树向上移动以获取我正在使用的命令,但您可以轻松地创建应用程序范围的命令.

<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>
Run Code Online (Sandbox Code Playgroud)

对于命令,您可以直接实现ICommand,也可以使用MVVM Toolkit中的一些帮助程序.


Gun*_*ter 13

我发现使用Blend SDK事件触发器执行此操作非常简单明了.清理MVVM,可重用,无代码隐藏.

你可能已经有这样的事情了:

<Style x:Key="MyListStyle" TargetType="{x:Type ListViewItem}">
Run Code Online (Sandbox Code Playgroud)

现在为ListViewItem包含一个ControlTemplate,如果你还没有使用它:

<Setter Property="Template">
  <Setter.Value>
    <ControlTemplate TargetType="{x:Type ListViewItem}">
      <GridViewRowPresenter Content="{TemplateBinding Content}"
                            Columns="{TemplateBinding GridView.ColumnCollection}" />
    </ControlTemplate>
  </Setter.Value>
 </Setter>
Run Code Online (Sandbox Code Playgroud)

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>
Run Code Online (Sandbox Code Playgroud)

如果你有GridRowPresenter"以上"的视觉元素(probalby从网格开始),你也可以将Trigger放在那里.

不幸的是,MouseDoubleClick事件不是从每个可视元素生成的(它们来自Controls,但不是来自FrameworkElements).解决方法是从EventTrigger派生一个类,并查找ClickCount为2的MouseButtonEventArgs.这有效地过滤掉所有非MouseButtonEvents和所有带有ClickCount!= 2的MoseButtonEvents.

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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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

<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>
Run Code Online (Sandbox Code Playgroud)


小智 6

我意识到这个讨论已经有一年了,但是对于.NET 4,对这个解决方案有什么想法吗?我绝对同意MVVM的观点并不是要消除文件背后的代码.我也非常强烈地感到,只是因为事情很复杂,并不意味着它更好.以下是我在后面的代码中添加的内容:

    private void ButtonClick(object sender, RoutedEventArgs e)
    {
        dynamic viewModel = DataContext;
        viewModel.ButtonClick(sender, e);
    }
Run Code Online (Sandbox Code Playgroud)

  • viewmodel应该具有代表您可以在域中执行的操作的名称.您域中的"ButtonClick"操作是什么?ViewModel在视图友好的上下文中表示域的逻辑,它不仅仅是视图的帮助器.所以:ButtonClick永远不应该在viewmodel上,使用viewModel.DeleteSelectedCustomer或者这个动作实际代表的任何东西. (12认同)