WPF:如何使用 MVVM 将命令绑定到 ListBoxItem?

发布于 2024-10-30 08:39:15 字数 2094 浏览 1 评论 0原文

我刚刚开始学习MVVM。我按照这个 MVVM 教程(我强烈向所有 MVVM 初学者推荐它)。基本上,到目前为止我创建的是几个文本框,用户在其中添加他或她的数据,一个用于保存该数据的按钮,该按钮随后将使用所做的所有条目填充列表框。

这就是我陷入困境的地方:我希望能够双击 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");
    }
}

请帮忙,我将不胜感激。

I have just started learning MVVM. I've made the application from scratch by following this MVVM tutorial (I highly recommend it to all MVVM beginners out there). Basically, what I have created so far is a couple of text boxes where user adds his or her data, a button to save that data which subsequently populates the ListBox with all entries made.

Here's where I got stuck: I want to be able to double-click on a ListBoxItem and to trigger a command that I have created and added to my ViewModel. I don't know how to finish the XAML side, i.e. I don't know how to bind that command to the ListBox(Item).

Here's XAML:

...
<ListBox 
    Name="EntriesListBox" 
    Width="228" 
    Height="208" 
    Margin="138,12,0,0" 
    HorizontalAlignment="Left" 
    VerticalAlignment="Top" 
    ItemsSource="{Binding Entries}" />
...

Here's 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);
    }
    ...
}

And finally, here's the OpenEntryCommand that I want to be executed once the user double-clicks the item in the EntriesListBox:

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

Please help, I'd appreciate it.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

深海夜未眠 2024-11-06 08:39:15

不幸的是,只有 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

更新:我找到了一些可以帮助您的内容。检查 此链接MVVM Light Toolkit 包含有关如何执行此操作的演练,以及 链接到所需的库。 MVVM Light Toolkit 是一个非常有趣的框架,用于将 MVVM 与 Silverlight、WPF 和 WP7 一起应用。

希望这有帮助:)

Unfortunately, only ButtonBase derived controls have the possibility for binding ICommand objects to their Command properties (for the Click event).

However, you can use an API provided by Blend to map an event (like in your case MouseDoubleClick on the ListBox) to an ICommand object.

<ListBox>
    <i:Interaction.Triggers>
        <i:EventTrigger EventName="MouseDoubleClick">
            <i:InvokeCommandAction Command="{Binding YourCommand}"/>
        </i:EventTrigger>
    </i:Interaction.Triggers>
</ListBox>

You'll have to define: xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" and have a reference to System.Windows.Interactivity.dll.

-- EDIT --
This is part of WPF4, but u can use Microsoft.Windows.Interactivity if you're not using WPF4. This dll is from Blend SDK, which doesn't require Blend, from here:
http://www.microsoft.com/downloads/en/details.aspx?FamilyID=f1ae9a30-4928-411d-970b-e682ab179e17&displaylang=en

Update: I found something that should help you. check this link on MVVM Light Toolkit which contains a walkthrough on how to do this, along with a link to the needed libraries. MVVM Light Toolkit is a very interesting framework for applying MVVM with Silverlight, WPF, and WP7.

Hope this helps :)

凉世弥音 2024-11-06 08:39:15

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

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

2 和 3 可能更纯粹,但坦率地说,1 更容易,更简单,而且不是世界上最糟糕的事情。对于一次性情况,我可能会使用方法#1。

现在,如果您更改要求以在每个项目上使用超链接等,那就更容易了。首先在 XAML 中命名根元素 - 例如,对于窗口:

<Window .... Name="This">

现在,在 ListBox 项的 DataTemplate 中,使用如下内容:

<ListBox ...>
  <ListBox.ItemTemplate>
    <DataTemplate>
      <Hyperlink 
        Command="{Binding ElementName=This, Path=DataContext.OpenEntryCmd}"
        Text="{Binding Path=Name}" 
        />

ElementName 绑定允许您从 ViewModel 的上下文中解析 OpenEntryCmd,而不是从具体数据项。

This is made tricky because of the DoubleClick event. There are a few ways to do this:

  1. Handle the double-click event in code behind, and then manually invoke a command/method on your ViewModel
  2. Use an attached behavior to route the DoubleClick event to your Command
  3. Use a Blend Behavior to map the DoubleClick event to your command

2 and 3 might be more pure, but frankly, 1 is easier, less complex, and not the worst thing in the world. For a one-off case, I'd probably use approach #1.

Now, if you changed your requirements to use, say, a hyperlink on each item, it would be easier. Start out by naming the root element in your XAML - e.g., for a Window:

<Window .... Name="This">

Now, in the DataTemplate for your ListBox items, use something like this:

<ListBox ...>
  <ListBox.ItemTemplate>
    <DataTemplate>
      <Hyperlink 
        Command="{Binding ElementName=This, Path=DataContext.OpenEntryCmd}"
        Text="{Binding Path=Name}" 
        />

The ElementName binding lets you resolve the OpenEntryCmd from the context of your ViewModel, rather than the specific data item.

看透却不说透 2024-11-06 08:39:15

编辑:我作为一个没有经验的 WPF 开发人员写了这篇文章,现在我要么使用提供事件到命令绑定的框架,要么简单地使用按钮并重新设计它的样式。当然,为了获得最大的灵活性,这可能更好。

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

我这样做的原因是因为按钮没有将单击事件冒泡到我的列表框,这阻止了它选择列表框项。

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>

内容可以是任何内容,当单击该控件时,它将执行该命令。

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

EDIT: I wrote this post as an inexperienced WPF developer, nowadays I'd either use a framework that provides event to command binding, or simply use a button and restyle it. Of course for maximum flexibility this is maybe better.

I find the best way to do this is to create a simple user control wrapper for my content, with dependency properties for the command and parameter.

The reason I did this was due to the Button not bubbling the click event to my ListBox which prevented it from selecting the 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>

Usage:

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

The Content can be whatever, and when the control is clicked, it will execute the command.

EDIT: Added Background="Transparent" to UserControl to enable click events on the entire area of the control.

你怎么敢 2024-11-06 08:39:15

这有点破解,但效果很好,允许您使用命令并避免代码隐藏。这还有一个额外的好处,即当您在空的 ScrollView 区域中双击(或无论您的触发器是什么)时,不会触发命令,假设您的 ListBoxItems 没有填充整个容器。

基本上,只需为由 TextBlock 组成的 ListBox 创建一个 DataTemplate 并绑定 TextBlock 的宽度即可为 ListBox 的宽度,将边距和填充设置为 0,并禁用水平滚动(因为 TextBlock 会超出 ScrollView< 的可见边界) /code> 否则触发水平滚动条)。我发现的唯一错误是,如果用户精确单击 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>

This is a bit of a hack, but it works well and allows you to use commands and avoid code behind. This also has the added benefit of not triggering the command when you double-click (or whatever your trigger is) in the empty ScrollView area assuming your ListBoxItems don't fill the entire container.

Basically, just create a DataTemplate for your ListBox that is composed of a TextBlock and bind the width of the TextBlock to the width of the ListBox, set the margins and padding to 0, and disable horizontal scrolling (because the TextBlock will bleed beyond the visible bounds of the ScrollView triggering the horizontal scroll bar otherwise). The only bug I've found is that the command won't fire if the user clicks precisely on the border of the ListBoxItem, which I can live with.

Here is an example:

<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>
墨落成白 2024-11-06 08:39:15

我最近还需要在双击 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

I recently needed to trigger an ICommand upon double clicking a ListBoxItem as well.

Personally, I don't like the DataTemplate method as it is binding to the content inside the ListBoxItem container, and not the container itself. I've opted to use an Attached Property to assign an InputBinding on the container. It takes a little more elbow grease, but it works well.

First, we need to create an attached property class. I've created mine a little more generically towards any class that derives from FrameworkElement, just in case I run into this again with a different visual.

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

Then the final step is to override the base container style for the 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>

Now, anytime a ListBoxItem is double clicked, it will fire our OnListBoxItemDoubleClickCommand.

少跟Wǒ拽 2024-11-06 08:39:15

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

并且您想要在 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”以确保整个网格可单击,而不仅仅是其中的内容。

If you're looking for a nice simple solution that uses interactions instead of mucking about with user controls, code behind, input bindings, custom attached properties, etc.

And you want something that works at the ListBoxItem level, i.e. not ListBox level as per the (incorrectly) accepted solution.

Then here's a snippet for a simple 'button like' click action..

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

Note, background="Transparent" is required to ensure the entire Grid is clickable and not just the contents inside.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文