gpt4 book ai didi

c# - wpf绑定(bind)命令到窗口的快捷方式

转载 作者:行者123 更新时间:2023-12-03 10:27:35 25 4
gpt4 key购买 nike

对于基于MVVM填充的选项卡,我下面有一个非常简单的解决方案。如何设置以下两个命令,以及“添加”和“删除”。从网上阅读的内容来看,我似乎需要设置ICommand或类似的设置。在演示中,对于我来说还不够清楚,我无法使其正常工作。

  • Add 命令将调用ViewModel类中已经存在的函数。 Bu将通过键盘命令“Ctrl + N”
  • 调用
  • 当用户单击“X”按钮时,将调用 Remove 命令,这将删除该特定选项卡。否则,可以通过“Ctrl + W”将其调用,这将关闭当前选中的任何选项卡。

  • 命令的东西对我来说是新手,所以如果有人可以帮助我,将不胜感激。我希望对此进行扩展,并继续为该工具添加更多功能。

    链接到visual studio dropbox files。您会看到我已经将事情分解为类,并以一种使事情变得清晰的方式组织起来。

    enter image description here

    下面的工具片段...

    查看模型
    using System;
    using System.Collections.ObjectModel;
    using System.Windows;

    namespace WpfApplication1
    {
    public class ViewModel : ObservableObject
    {
    private ObservableCollection<TabItem> tabItems;

    public ObservableCollection<TabItem> TabItems
    {
    get { return tabItems ?? (tabItems = new ObservableCollection<TabItem>()); }
    }

    public ViewModel()
    {
    TabItems.Add(new TabItem { Header = "One", Content = DateTime.Now.ToLongDateString() });
    TabItems.Add(new TabItem { Header = "Two", Content = DateTime.Now.ToLongDateString() });
    TabItems.Add(new TabItem { Header = "Three", Content = DateTime.Now.ToLongDateString() });
    }

    public void AddContentItem()
    {
    TabItems.Add(new TabItem { Header = "Three", Content = DateTime.Now.ToLongDateString() });
    }
    }



    public class TabItem
    {
    public string Header { get; set; }
    public string Content { get; set; }
    }
    }

    MainWindow XAML
    <Window x:Class="WpfApplication1.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:data="clr-namespace:WpfApplication1"
    Title="MainWindow" Height="350" Width="250">

    <Window.DataContext>
    <data:ViewModel/>
    </Window.DataContext>

    <Grid>
    <Grid.RowDefinitions>
    <RowDefinition Height="Auto"/>
    <RowDefinition Height="*"/>
    </Grid.RowDefinitions>

    <!--<Button Content="Add" Command="{Binding AddCommand}" Grid.Row="0"></Button>-->
    <TabControl ItemsSource="{Binding TabItems}" Grid.Row="1" Background="LightBlue">
    <TabControl.ItemTemplate>
    <DataTemplate>
    <StackPanel Orientation="Horizontal" VerticalAlignment="Center">
    <TextBlock Text="{Binding Header}" VerticalAlignment="Center"/>
    <Button Content="x" Width="20" Height="20" Margin="5 0 0 0"/>
    </StackPanel>
    </DataTemplate>
    </TabControl.ItemTemplate>

    <TabControl.ContentTemplate>
    <DataTemplate>
    <TextBlock
    Text="{Binding Content}" />
    </DataTemplate>
    </TabControl.ContentTemplate>
    </TabControl>

    </Grid>
    </Window>

    最佳答案

    您已经收到另外两个答案。不幸的是,它们都不能同时精确地解决添加删除命令。此外,一个人更喜欢主要关注代码背后的实现,而不是XAML声明,并且无论如何都对细节相当稀疏,而另一个人更适当地关注于XAML的实现,但不包括正确的有效代码,并且(略)通过引入RelayCommand类型的额外抽象来混淆答案。

    因此,我将就这个问题提出自己的看法,希望这对您会更有用。

    尽管我同意将ICommand实现抽象到诸如RelayCommand之类的帮助器类中是有用的,甚至是理想的选择,但不幸的是,这倾向于隐藏正在发生的事情的基 native 制,并且需要在其他答案中提供更详细的实现。因此,现在让我们忽略它。

    相反,仅关注需要实现的内容:ICommand接口(interface)的两种不同实现。您的 View 模型会将它们公开为两个可绑定(bind)属性的值,这些属性表示要执行的命令。

    这是ViewModel类的新版本(已删除了无关的和未提供的ObservableObject类型):

    class ViewModel
    {
    private class AddCommandObject : ICommand
    {
    private readonly ViewModel _target;

    public AddCommandObject(ViewModel target)
    {
    _target = target;
    }

    public bool CanExecute(object parameter)
    {
    return true;
    }

    public event EventHandler CanExecuteChanged;

    public void Execute(object parameter)
    {
    _target.AddContentItem();
    }
    }

    private class RemoveCommandObject : ICommand
    {
    private readonly ViewModel _target;

    public RemoveCommandObject(ViewModel target)
    {
    _target = target;
    }

    public bool CanExecute(object parameter)
    {
    return true;
    }

    public event EventHandler CanExecuteChanged;

    public void Execute(object parameter)
    {
    _target.RemoveContentItem((TabItem)parameter);
    }
    }

    private ObservableCollection<TabItem> tabItems;

    public ObservableCollection<TabItem> TabItems
    {
    get { return tabItems ?? (tabItems = new ObservableCollection<TabItem>()); }
    }

    public ICommand AddCommand { get { return _addCommand; } }
    public ICommand RemoveCommand { get { return _removeCommand; } }

    private readonly ICommand _addCommand;
    private readonly ICommand _removeCommand;

    public ViewModel()
    {
    TabItems.Add(new TabItem { Header = "One", Content = DateTime.Now.ToLongDateString() });
    TabItems.Add(new TabItem { Header = "Two", Content = DateTime.Now.ToLongDateString() });
    TabItems.Add(new TabItem { Header = "Three", Content = DateTime.Now.ToLongDateString() });
    _addCommand = new AddCommandObject(this);
    _removeCommand = new RemoveCommandObject(this);
    }

    public void AddContentItem()
    {
    TabItems.Add(new TabItem { Header = "Three", Content = DateTime.Now.ToLongDateString() });
    }

    public void RemoveContentItem(TabItem item)
    {
    TabItems.Remove(item);
    }
    }

    请注意添加的两个嵌套类 AddCommandObjectRemoveCommandObject。这些都是几乎最简单的 ICommand实现的示例。它们总是可以执行的,因此 CanExecute()的返回值永远不会改变(因此无需引发 CanExecuteChanged事件)。他们确实需要对 ViewModel对象的引用,以便每个人都可以调用适当的方法。

    还添加了两个公共(public)属性以允许绑定(bind)这些命令。当然, RemoveContentItem()方法需要知道要删除的项目。这需要在XAML中进行设置,以便可以将值作为参数传递给命令处理程序,然后从那里传递给实际的 RemoveContentItem()方法。

    为了支持使用键盘输入命令,一种方法是将输入绑定(bind)添加到窗口。这就是我在这里选择的。 RemoveCommand绑定(bind)还需要删除要作为命令参数传递的项目,因此它绑定(bind)到 CommandParameter对象的 KeyBinding(就像该项目中 CommandParameterButton一样)。

    产生的XAML如下所示:
    <Window.DataContext>
    <data:ViewModel/>
    </Window.DataContext>

    <Window.InputBindings>
    <KeyBinding Command="{Binding AddCommand}">
    <KeyBinding.Gesture>
    <KeyGesture>Ctrl+N</KeyGesture>
    </KeyBinding.Gesture>
    </KeyBinding>
    <KeyBinding Command="{Binding RemoveCommand}"
    CommandParameter="{Binding SelectedItem, ElementName=tabControl1}">
    <KeyBinding.Gesture>
    <KeyGesture>Ctrl+W</KeyGesture>
    </KeyBinding.Gesture>
    </KeyBinding>
    </Window.InputBindings>

    <Grid>
    <Grid.RowDefinitions>
    <RowDefinition Height="Auto"/>
    <RowDefinition Height="*"/>
    </Grid.RowDefinitions>

    <TabControl x:Name="tabControl1" ItemsSource="{Binding TabItems}" Grid.Row="1" Background="LightBlue">
    <TabControl.ItemTemplate>
    <DataTemplate>
    <StackPanel Orientation="Horizontal" VerticalAlignment="Center">
    <TextBlock Text="{Binding Header}" VerticalAlignment="Center"/>
    <Button Content="x" Width="20" Height="20" Margin="5 0 0 0"
    Command="{Binding DataContext.RemoveCommand, RelativeSource={RelativeSource AncestorType=TabControl}}"
    CommandParameter="{Binding DataContext, RelativeSource={RelativeSource Self}}">
    </Button>
    </StackPanel>
    </DataTemplate>
    </TabControl.ItemTemplate>

    <TabControl.ContentTemplate>
    <DataTemplate>
    <TextBlock Text="{Binding Content}" />
    </DataTemplate>
    </TabControl.ContentTemplate>
    </TabControl>

    </Grid>

    编辑:

    如前所述,实际上,使用助手类来抽象 ICommand实现是有好处的,而不是为要实现的每个命令声明一个新类。 Why RelayCommand上引用的答案将松散耦合和单元测试作为动机。尽管我同意这些目标是好的,但我不能说这些目标实际上是通过 ICommand实现的抽象来实现的。

    相反,我看到的好处与进行此类抽象时的主要好处相同:它允许代码重用,并且这样做可以提高开发人员的工作效率以及代码的可维护性和质量。

    在我上面的示例中,每次需要一个新命令时,都必须编写一个实现 ICommand的新类。一方面,这意味着您编写的每个类都可以针对特定目的进行量身定制。根据需要,是否处理 CanExecuteChanged,是否传递参数,等等。

    另一方面,每次您编写此类时,都可以编写一个新的错误。更糟糕的是,如果引入一个错误,然后将其复制/粘贴,那么当您最终找到该错误时,您可能会或可能不会在存在该错误的位置进行修复。

    当然,一遍又一遍地编写这样的类变得乏味且耗时。

    同样,这些只是抽象可重用逻辑的“最佳实践”的一般常规智慧的特定示例。

    因此,如果我们已经接受抽象在这里有用(我当然有:)),那么问题就变成了,抽象看起来像什么?有多种解决问题的方法。引用的答案是一个示例。这是我编写的略有不同的方法:
    class DelegateCommand<T> : ICommand
    {
    private readonly Func<T, bool> _canExecuteHandler;
    private readonly Action<T> _executeHandler;

    public DelegateCommand(Action<T> executeHandler)
    : this(executeHandler, null) { }

    public DelegateCommand(Action<T> executeHandler, Func<T, bool> canExecuteHandler)
    {
    _canExecuteHandler = canExecuteHandler;
    _executeHandler = executeHandler;
    }

    public bool CanExecute(object parameter)
    {
    return _canExecuteHandler != null ? _canExecuteHandler((T)parameter) : true;
    }

    public event EventHandler CanExecuteChanged;

    public void Execute(object parameter)
    {
    _executeHandler((T)parameter);
    }

    public void RaiseCanExecuteChanged()
    {
    EventHandler handler = CanExecuteChanged;

    if (handler != null)
    {
    handler(this, EventArgs.Empty);
    }
    }
    }

    ViewModel类中,上面的代码将这样使用:
    class ViewModel
    {
    private ObservableCollection<TabItem> tabItems;

    public ObservableCollection<TabItem> TabItems
    {
    get { return tabItems ?? (tabItems = new ObservableCollection<TabItem>()); }
    }

    public ICommand AddCommand { get { return _addCommand; } }
    public ICommand RemoveCommand { get { return _removeCommand; } }

    private readonly ICommand _addCommand;
    private readonly ICommand _removeCommand;

    public ViewModel()
    {
    TabItems.Add(new TabItem { Header = "One", Content = DateTime.Now.ToLongDateString() });
    TabItems.Add(new TabItem { Header = "Two", Content = DateTime.Now.ToLongDateString() });
    TabItems.Add(new TabItem { Header = "Three", Content = DateTime.Now.ToLongDateString() });

    // Use a lambda delegate to map the required Action<T> delegate
    // to the parameterless method call for AddContentItem()
    _addCommand = new DelegateCommand<object>(o => this.AddContentItem());

    // In this case, the target method takes a parameter, so we can just
    // use the method directly.
    _removeCommand = new DelegateCommand<TabItem>(RemoveContentItem);
    }

    笔记:
  • 当然,现在不再需要特定的ICommand实现。 AddCommandObjectRemoveCommandObject类已从ViewModel类中删除。
  • 该代码使用DelegateCommand<T>类代替它们。
  • 注意,在某些情况下,命令处理程序不需要将参数传递给ICommand.Execute(object)方法。在上面的方法中,通过在lambda(匿名)委托(delegate)中接受参数,然后在调用无参数处理程序方法时将其忽略,来解决此问题。解决此问题的其他方法是使处理程序方法接受参数,然后将其忽略,或者使用非通用类,其中处理程序委托(delegate)本身可以是无参数的。恕我直言,本质上没有“正确的方法”……只是各种选择,根据个人喜好,这些选择或多或少被认为是可取的。
  • 另请注意,此实现在处理CanExecuteChanged事件方面与引用的答案的实现不同。在我的实现中,对客户端代码进行了对该事件的细粒度控制,但以要求客户端代码保留对所涉及的DelegateCommand<T>对象的引用并在适当的时间调用其RaiseCanExecuteChanged()方法为代价。在另一个实现中,它依赖于CommandManager.RequerySuggested事件。这是权宜之计与效率之间的权衡,在某些情况下还涉及正确性。也就是说,客户端代码必须保留对可能更改可执行状态的命令的引用,这不太方便,但是如果一条命令走了另一条路线,至少CanExecuteChanged事件的发生频率可能​​会比所需的发生频率高得多,在某些情况下,甚至有可能不应该提高它(这比可能的低效率更糟)。

  • 关于最后一点,另一种方法是使 ICommand实现成为依赖项对象,并提供一个依赖项属性,该属性用于控制命令的可执行状态。这要复杂得多,但是总体上可以认为是高级解决方案,因为它允许对 CanExecuteChanged事件的引发进行细粒度的控制,同时提供了一种很好的惯用方式来绑定(bind)命令的可执行状态,例如:在XAML中,任何一个或多个属性实际上决定了所说的可执行性。

    这样的实现可能看起来像这样:
    class DelegateDependencyCommand<T> : DependencyObject, ICommand
    {
    public static readonly DependencyProperty IsExecutableProperty = DependencyProperty.Register(
    "IsExecutable", typeof(bool), typeof(DelegateCommand<T>), new PropertyMetadata(true, OnIsExecutableChanged));

    public bool IsExecutable
    {
    get { return (bool)GetValue(IsExecutableProperty); }
    set { SetValue(IsExecutableProperty, value); }
    }

    private static void OnIsExecutableChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
    DelegateDependencyCommand<T> command = (DelegateDependencyCommand<T>)d;
    EventHandler handler = command.CanExecuteChanged;

    if (handler != null)
    {
    handler(command, EventArgs.Empty);
    }
    }

    private readonly Action<T> _executeHandler;

    public DelegateDependencyCommand(Action<T> executeHandler)
    {
    _executeHandler = executeHandler;
    }

    public bool CanExecute(object parameter)
    {
    return IsExecutable;
    }

    public event EventHandler CanExecuteChanged;

    public void Execute(object parameter)
    {
    _executeHandler((T)parameter);
    }
    }

    在上面,消除了该类的 canExecuteHandler参数,而不是 IsExecutable属性。当该属性更改时,将引发 CanExecuteChanged事件。恕我直言,很遗憾, ICommand接口(interface)在其设计方式和WPF正常工作方式(即具有可绑定(bind)属性)之间存在差异。我们本质上有一个属性,但是它通过名为 CanExecute()的显式getter方法公开,这有点奇怪。

    另一方面,这种差异确实有一些有用的目的,包括明确和方便地使用 CommandParameter来执行命令和检查可执行性。这些都是值得的目标。我个人不确定我是否会做出相同的选择,以平衡它们与WPF中连接状态的一致性(即通过绑定(bind))。幸运的是,如果确实需要,以一种可绑定(bind)的方式(即如上所述)实现 ICommand接口(interface)非常简单。

    关于c# - wpf绑定(bind)命令到窗口的快捷方式,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33087216/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com