31

我刚刚开始学习 MVVM。我按照这个MVVM 教程从头开始制作了应用程序(我强烈推荐给所有 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}" />
...

这是视图模型:

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

请帮忙,我将不胜感激。

4

6 回答 6

69

不幸的是,只有ButtonBase派生控件才有可能将对象绑定ICommand到它们的Command属性(对于Click事件)。

但是,您可以使用 Blend 提供的 API 将事件(如您的情况)映射到MouseDoubleClick对象。ListBoxICommand

<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

更新:我发现了一些可以帮助你的东西。检查MVVM Light Toolkit 上的此链接,其中包含有关如何执行此操作的演练,以及指向所需库的链接。MVVM Light Toolkit 是一个非常有趣的框架,用于将 MVVM 与 Silverlight、WPF 和 WP7 一起应用。

希望这可以帮助 :)

于 2011-04-04T23:09:53.067 回答
9

由于 DoubleClick 事件,这变得很棘手。有几种方法可以做到这一点:

  1. 在后面的代码中处理双击事件,然后在 ViewModel 上手动调用命令/方法
  2. 使用附加行为将DoubleClick 事件路由到您的命令
  3. 使用混合行为将 DoubleClick 事件映射到您的命令

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。

于 2011-04-04T23:02:30.650 回答
4

我发现最好的方法是为我的内容创建一个简单的用户控件包装器,其中包含命令和参数的依赖属性。

我这样做的原因是由于 Button 没有将单击事件冒泡到我的 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); }
    }
}

命令控制.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>

内容可以是任何东西,当控件被点击时,它会执行命令。

编辑:添加Background="Transparent"到 UserControl 以在控件的整个区域上启用单击事件。

于 2017-03-07T14:30:58.250 回答
4

这有点小技巧,但它运行良好,允许您使用命令并避免代码落后。ScrollView假设您ListBoxItems没有填满整个容器,这还有一个额外的好处,即当您在空白区域中双击(或任何您的触发器)时不会触发命令。

DataTemplate基本上,只需为您创建一个ListBox由 a 组成的TextBlock并将 的宽度绑定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>
于 2019-01-30T03:44:07.867 回答
0

如果您正在寻找一个很好的简单解决方案,它使用交互而不是使用用户控件、代码隐藏、输入绑定、自定义附加属性等。

并且您想要在 ListBoxItem 级别工作的东西,即不是按照(错误地)接受的解决方案的 ListBox 级别。

然后这是一个简单的“按钮之类”点击操作的片段。

<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 是可点击的,而不仅仅是里面的内容。

于 2021-05-14T13:31:01.923 回答
0

我最近也需要ICommand在双击 a 时触发 a ListBoxItem

就个人而言,我不喜欢该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只要双击 a ,它就会触发我们的OnListBoxItemDoubleClickCommand.

于 2021-05-28T04:16:55.647 回答