简体   繁体   English

如何在 WPF 中使用命令和绑定?

[英]How do I use command and binding together in WPF?

I've been practicing MVVM pattern and come across the problem which I don't know how to solve.我一直在练习 MVVM 模式,遇到了我不知道如何解决的问题。 The problem is pretty simple and I hope the solution as well.问题很简单,我也希望解决方案。 The point is that I'm trying to use a command and binding for an element, when I'm setting up it's style, but I can't do it at the same time.关键是当我设置它的样式时,我正在尝试对元素使用命令和绑定,但我不能同时进行。

I have the following style for ListBoxItem:我对 ListBoxItem 有以下样式:

<Style x:Key="OptionDieStyle" TargetType="ListBoxItem">
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="ListBoxItem">
                        <Border Width="Auto"
                                BorderThickness="1.5"
                                CornerRadius="10"
                                Height="30"
                                Background="Transparent"
                                Margin="5">
                            <TextBlock Margin="5"
                                       Text="{Binding}"
                                       Foreground="White"
                                       VerticalAlignment="Center"/>
                            <Border.InputBindings>
                                <MouseBinding MouseAction="LeftClick" Command="#Omitted"
                            </Border.InputBindings>
                        </Border>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>

This ListBox is filled with strings which are displayed in particular way because of the style.此 ListBox 充满了字符串,这些字符串因样式而以特定方式显示。 That means that when I want to handle user's click on that element, using command, I need to set DataContext, which contains ViewModel, where command is located, for this item, but if I do it no content will be displayed in ListBox Items.这意味着当我想处理用户对该元素的单击时,使用命令,我需要为该项目设置 DataContext,其中包含命令所在的 ViewModel,但如果我这样做,ListBox 项目中将不会显示任何内容。 Certainly, I could set event for this Border like "MouseDown" but it would be the wrong way to use MVVM.当然,我可以为这个边框设置事件,如“MouseDown”,但使用 MVVM 的方式是错误的。

If you have some thoughts how to solve this using commands please share them.如果您对如何使用命令解决此问题有一些想法,请分享。

To make these scenarios easier, I've derived a class from CommandBindin.为了简化这些场景,我从 CommandBindin 派生了一个 class。 In which he added the ability to bind to ViewModel commands.他在其中添加了绑定到 ViewModel 命令的功能。 You can set the binding to both Execute and PreviewExecute.您可以将绑定设置为 Execute 和 PreviewExecute。

using System;
using System.Windows;
using System.Windows.Data;
using System.Windows.Input;

namespace CommonCore.AttachedProperties
{
    public class CommandBindingHelper : CommandBinding
    {
        // Using a DependencyProperty as the backing store for MyProperty.  This enables animation, styling, binding, etc...
        protected static readonly DependencyProperty CommandProperty =
            DependencyProperty.RegisterAttached(
                "Command",
                typeof(ICommand),
                typeof(CommandBindingHelper),
                new PropertyMetadata(null));

        // Using a DependencyProperty as the backing store for MyProperty.  This enables animation, styling, binding, etc...
        protected static readonly DependencyProperty PreviewCommandProperty =
            DependencyProperty.RegisterAttached(
                "PreviewCommand",
                typeof(ICommand),
                typeof(CommandBindingHelper),
                new PropertyMetadata(null));

        public BindingBase Binding { get; set; }
        public BindingBase PreviewBinding { get; set; }

        public CommandBindingHelper()
        {
            Executed += (s, e) => PrivateExecuted(CheckSender(s), e.Parameter, CommandProperty, Binding);
            CanExecute += (s, e) => e.CanExecute = PrivateCanExecute(CheckSender(s), e.Parameter, CommandProperty, Binding);
            PreviewExecuted += (s, e) => PrivateExecuted(CheckSender(s), e.Parameter, PreviewCommandProperty, PreviewBinding);
            PreviewCanExecute += (s, e) => e.CanExecute = PrivateCanExecute(CheckSender(s), e.Parameter, PreviewCommandProperty, PreviewBinding);
        }
        private static void PrivateExecuted(UIElement sender, object parameter, DependencyProperty commandProp, BindingBase commandBinding)
        {
            ICommand command = GetCommand(sender, commandProp, commandBinding);
            if (command is not null && command.CanExecute(parameter))
            {
                command.Execute(parameter);
            }
        }
        private static bool PrivateCanExecute(UIElement sender, object parameter, DependencyProperty commandProp, BindingBase commandBinding)
        {
            ICommand command = GetCommand(sender, commandProp, commandBinding);
            return command?.CanExecute(parameter) ?? true;
        }

        private static UIElement CheckSender(object sender)
        {
            if (sender is not UIElement element)
                throw new NotImplementedException("Implemented only for UIElement.");
            return element;
        }
        private static ICommand GetCommand(UIElement sender, DependencyProperty commandProp, BindingBase commandBinding)
        {
            BindingBase binding = BindingOperations.GetBindingBase(sender, commandProp);
            if (binding != commandBinding)
            {
                if (commandBinding is null)
                {
                    BindingOperations.ClearBinding(sender, commandProp);
                }
                else
                {
                    BindingOperations.SetBinding(sender, commandProp, commandBinding);
                }
            }
            return (ICommand)sender.GetValue(CommandProperty);
        }
    }
}

An example of its use:其使用示例:

using Simplified; // This is the space of my ViewModelBase implementation
using System.Collections.ObjectModel;

namespace Core2023.SO.ASTERY.CommandInListItem
{
    public class ListItemsViewModel : ViewModelBase
    {
        public ObservableCollection<string> Items { get; } = new("first second third fourth fifth".Split());
        public RelayCommand RemoveCommand => GetCommand<string>(item => Items.Remove(item));
    }
}
<Window x:Class="Core2023.SO.ASTERY.CommandInListItem.ListItemsWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:Core2023.SO.ASTERY.CommandInListItem"
        xmlns:ap="clr-namespace:CommonCore.AttachedProperties;assembly=CommonCore"
        mc:Ignorable="d"
        Title="ListItemsWindow" Height="450" Width="800"
        FontSize="20">
    <Window.DataContext>
        <local:ListItemsViewModel/>
    </Window.DataContext>
    <Window.CommandBindings>
        <ap:CommandBindingHelper Command="Delete" Binding="{Binding RemoveCommand}"/>
    </Window.CommandBindings>
    <Grid>
        <ItemsControl ItemsSource="{Binding Items}">
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <UniformGrid Rows="1" Margin="5">
                        <TextBlock Text="{Binding}"/>
                        <Button Content="Remove"
                                Command="Delete"
                                CommandParameter="{Binding}"/>
                    </UniformGrid>
                </DataTemplate>
            </ItemsControl.ItemTemplate>
        </ItemsControl>
    </Grid>
</Window>

What do i do if I want to set different name?如果我想设置不同的名称怎么办?

  1. The easiest way is to create a command in Window or (better) App resources.最简单的方法是在 Window 或(更好的)App 资源中创建命令。
    <Application.Resources>
        <RoutedUICommand x:Key="commands.Remove" Text="Delete Item" />
    </Application.Resources>
   <Button Content="Remove"
           Command="{StaticResource commands.Remove}"
           CommandParameter="{Binding}"/>
  1. Create a static property containing the command.创建包含该命令的 static 属性。 But it should be created at the View level, not the ViewModel.但它应该在 View 级别创建,而不是 ViewModel。
    public static class MyCommands
    {
        public static RoutedUICommand Remove { get; }
            = new RoutedUICommand("Delete Item", "Remove", typeof(MyCommands));
        public static RoutedUICommand Add { get; }
            = new RoutedUICommand("Add Item", "Add", typeof(MyCommands));
    }
   <Button Content="Remove"
           Command="{x:Static local:MyCommands.Remove}"
           CommandParameter="{Binding}"/>
  1. Adding a markup extension to the previous version to make it easier to use in XAML. XAML 对之前的版本添加标记扩展,方便使用。
    public class MyCommandsExtension : MarkupExtension
    {
        public string? CommandName { get; set; }

        public MyCommandsExtension() { }

        public MyCommandsExtension(string commandName) => CommandName = commandName;

        public override object ProvideValue(IServiceProvider serviceProvider)
            => CommandName switch
            {
                nameof(MyCommands.Remove) => MyCommands.Remove,
                nameof(MyCommands.Add) => MyCommands.Add,
                _ => throw new NotImplementedException()
            };
    }
   <Button Content="Remove"
           Command="{local:MyCommands Remove}"
           CommandParameter="{Binding}"/>

The approach above is working fine, but only if we're going to use commands with default ApplicationCommands' names and won't give them individual names.上面的方法工作正常,但前提是我们要使用具有默认 ApplicationCommands 名称的命令并且不会给它们单独的名称。 I was racking my brains and eventually found the proper approach.我绞尽脑汁,终于找到了合适的方法。 All I had to do is just make my command static in ViewModel and change definition for my command in XAML like this:我所要做的就是在 ViewModel 中执行命令 static 并在 XAML 中更改命令的定义,如下所示:

Command="{x:Static viewModels:MyViewModel.MyCommand}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 我如何强制WPF ValidationRules绑定? - How do i force wpf ValidationRules binding? 在WPF绑定中,如何处理绑定的数据,例如将两个字符串连在一起? - In a WPF binding, how can I manipulate the data I'm binding, like, say concat two strings together? 如何正确使用显式实现的接口属性和wpf可见性? - How do I use an explicitly implemented interface property and wpf visiblity together properly? 如何将WPF与XML绑定用于具有从兄弟姐妹到父级的相关数据的指定路径 - How do I use WPF binding with XML for a specified path with related data from siblings to the parent WPF将3个集合绑定在一起 - WPF Binding 3 collections together 如何使用一个绑定多个文本框来分隔 WPF XAML 中的三位数字? - How do I use one binding for multiple text boxes to separate three-digit numbers in WPF XAML? 如何编写WPF命令绑定的测试? - How can I write a test for a WPF Command binding? 如何同时使用 SqlConnection 和 DataContext? - How do I use SqlConnection and DataContext together? 单击WPF按钮需要刷新绑定; 我该怎么做呢? - WPF button click needs to refresh a binding; how do I do this? 如何在启动时将backgroundworker与MVVM一起使用,而无需将命令绑定到按钮? - How do I use the backgroundworker with MVVM on startup without binding the command to a button?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM