wpf - 如何在 UserControl 和父窗口之间绑定(bind) WPF 命令

标签 wpf xaml mvvm wpf-controls data-binding

我先让一张照片来说话。

MVVM User Control to Window wireframe

所以你看,我想创建一个支持绑定(bind)到父窗口的 DataContext 的 WPF 用户控件。用户控件只是一个 Button 和一个带有自定义 ItemTemplate 的 ListBox,以使用 Label 和 Remove Button 呈现事物。

Add 按钮应该调用主视图模型上的 ICommand 以与用户交互以选择新事物(IThing 的实例)。用户控件中 ListBoxItem 中的 Remove 按钮应该类似地调用主视图模型上的 ICommand 来请求删除相关事物。为此,Remove 按钮必须向 View 模型发送一些关于请求删除的事物的识别信息。所以有 2 种类型的 Command 应该可以绑定(bind)到这个控件。像 AddThingCommand() 和 RemoveThingCommand(ITing thing) 这样的东西。

我使用 Click 事件使该功能正常工作,但这感觉很麻烦,在 XAML 后面产生了一堆代码,并且与原始 MVVM 实现的其余部分发生冲突。我真的很想正常使用命令和 MVVM。

有足够的代码可以让一个基本的演示工作,我推迟发布整个事情以减少困惑。使我感觉如此接近的工作是 ListBox 的 DataTemplate 正确绑定(bind)了 Label,并且当父窗口将项目添加到集合中时,它们会显示出来。

<Label Content="{Binding Path=DisplayName}" />

虽然它正确显示了 IThing,但当我单击它时,它旁边的“删除”按钮什么也不做。
<Button Command="{Binding Path=RemoveItemCommand, RelativeSource={RelativeSource AncestorType={x:Type userControlCommands:ItemManager }}}">

这并不是非常意外,因为没有提供特定的项目,但是“添加”按钮不必指定任何内容,并且它也无法调用该命令。
<Button Command="{Binding Path=AddItemCommand, RelativeSource={RelativeSource AncestorType={x:Type userControlCommands:ItemManager }}}">

所以我需要的是添加按钮的“基本”修复,以便它调用父窗口的命令来添加一个东西,以及更复杂的删除按钮修复,以便它也调用父命令但也传递它绑定(bind)的东西。

非常感谢您的任何见解,

最佳答案

这是微不足道的,并且通过将您的 UserControl 视为它是一个控件(恰好由其他控件组成)来实现。这意味着什么?这意味着您应该将 DependencyProperties 放置在您的 ViewModel 可以绑定(bind)到的 UC 上,就像任何其他控件一样。按钮公开 Command 属性,TextBoxes 公开 Text 属性等。您需要在 UserControl 的表面上公开完成工作所需的一切。

让我们举一个简单的(不到两分钟的时间放在一起)的例子。我将省略 ICommand 实现。

首先,我们的窗口

<Window x:Class="UCsAndICommands.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:t="clr-namespace:UCsAndICommands"
        Title="MainWindow" Height="350" Width="525">
    <Window.DataContext>
        <t:ViewModel />
    </Window.DataContext>
    <t:ItemsEditor Items="{Binding Items}"
                   AddItem="{Binding AddItem}"
                   RemoveItem="{Binding RemoveItem}" />
</Window>

请注意,我们有我们的项目编辑器,它公开了它需要的所有内容的属性——它正在编辑的项目列表、添加新项目的命令和删除项目的命令。

接下来,用户控件

<UserControl x:Class="UCsAndICommands.ItemsEditor"
             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"
             xmlns:t="clr-namespace:UCsAndICommands"
             x:Name="root">
    <UserControl.Resources>
        <DataTemplate DataType="{x:Type t:Item}">
            <StackPanel Orientation="Horizontal">
                <Button Command="{Binding RemoveItem, ElementName=root}"
                        CommandParameter="{Binding}">Remove</Button>
                <TextBox Text="{Binding Name}" Width="100"/>
            </StackPanel>
        </DataTemplate>
    </UserControl.Resources>
    <StackPanel>
        <Button Command="{Binding AddItem, ElementName=root}">Add</Button>
        <ItemsControl ItemsSource="{Binding Items, ElementName=root}" />
    </StackPanel>
</UserControl>

我们将控件绑定(bind)到 UC 表面上定义的 DP。请不要做任何像 DataContext=this; 这样的废话。因为这种反模式破坏了更复杂的 UC 实现。

这是 UC 上这些属性的定义

public partial class ItemsEditor : UserControl
{
    #region Items
    public static readonly DependencyProperty ItemsProperty =
        DependencyProperty.Register(
            "Items",
            typeof(IEnumerable<Item>),
            typeof(ItemsEditor),
            new UIPropertyMetadata(null));
    public IEnumerable<Item> Items
    {
        get { return (IEnumerable<Item>)GetValue(ItemsProperty); }
        set { SetValue(ItemsProperty, value); }
    }
    #endregion  
    #region AddItem
    public static readonly DependencyProperty AddItemProperty =
        DependencyProperty.Register(
            "AddItem",
            typeof(ICommand),
            typeof(ItemsEditor),
            new UIPropertyMetadata(null));
    public ICommand AddItem
    {
        get { return (ICommand)GetValue(AddItemProperty); }
        set { SetValue(AddItemProperty, value); }
    }
    #endregion          
    #region RemoveItem
    public static readonly DependencyProperty RemoveItemProperty =
        DependencyProperty.Register(
            "RemoveItem",
            typeof(ICommand),
            typeof(ItemsEditor),
            new UIPropertyMetadata(null));
    public ICommand RemoveItem
    {
        get { return (ICommand)GetValue(RemoveItemProperty); }
        set { SetValue(RemoveItemProperty, value); }
    }        
    #endregion  
    public ItemsEditor()
    {
        InitializeComponent();
    }
}

只是 UC 表面上的 DP。没什么大不了的。我们的 ViewModel 同样简单

public class ViewModel
{
    public ObservableCollection<Item> Items { get; private set; }
    public ICommand AddItem { get; private set; }
    public ICommand RemoveItem { get; private set; }
    public ViewModel()
    {
        Items = new ObservableCollection<Item>();
        AddItem = new DelegatedCommand<object>(
            o => true, o => Items.Add(new Item()));
        RemoveItem = new DelegatedCommand<Item>(
            i => true, i => Items.Remove(i));
    }
}

您正在编辑三个不同的集合,因此您可能希望公开更多 ICommand 以明确您正在添加/删除哪些。或者您可以便宜并使用 CommandParameter 来解决这个问题。

关于wpf - 如何在 UserControl 和父窗口之间绑定(bind) WPF 命令,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29126224/

相关文章:

c# - 设置 WPF 用户控件的样式

c# - 如何在 Prism 中设置 ServiceLocationProvider 进行单元测试导航?

c# - 在 C# WPF 应用程序中显示数据表

c# - WPF 通知不适用于多个 View 模型

wpf - 有没有更好的方法来测试此MVVM Light消息,该消息不会引起偶尔的线程错误?

wpf - 如何在WPF MVVM中调用窗口的Loaded事件?

wpf - 如何将 MenuItem.Header 绑定(bind)到 Window/UserControl 依赖属性?

c# - 如何使用项目和一个文本框填充组合框

c# - 双向绑定(bind)到数据集 - 更新数据库?

c# - 在 WPF 应用程序 C# 中设置所有矩形的背景