【问题标题】:How do I Bind WPF Commands between a UserControl and a parent Window如何在 UserControl 和父窗口之间绑定 WPF 命令
【发布时间】:2015-05-21 11:35:50
【问题描述】:

我先让一张照片说话。

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

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

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

涉及的代码足以让基本演示工作,我暂缓发布整个内容以减少混乱。让我感觉如此接近的工作是 ListBox 的 DataTemplate 正确绑定了 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 }}}">

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

非常感谢您提供的任何见解,

【问题讨论】:

  • 在 UserControl 的表面添加两个 ICommand 属性。将控件中的按钮绑定到这些属性。将属性绑定到添加和删除 ICommands 到您的窗口的视图模型。大功告成。
  • 谢谢,威尔。我正在使用 Ganesh 的解决方案,但仍然遇到困难。我会尝试你的建议并告诉你。

标签: wpf xaml mvvm wpf-controls data-binding


【解决方案1】:

这是微不足道的,并且通过将您的 UserControl 视为它是一个控件(恰好由其他控件组成)来实现。那是什么意思?这意味着您应该将 DependencyProperties 放置在您的 ViewModel 可以绑定到的 UC 上,就像任何其他控件一样。按钮公开 Command 属性,TextBox 公开 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>

我们将控件绑定到 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 来解决这个问题。

【讨论】:

  • 虽然我不确定我是否将解决方案称为“微不足道”:) 您完美地解释了它并且您的解决方案“刚刚奏效”。我看到我让其中一些变得比必要的更复杂,而且我错过了一些小东西,这些东西加起来一无所获。非常感谢您的清晰回复。我又进入了 MVVM 梦境!
  • 可能值得注意的是,在自定义控件中使用x:Name="root' 可能是个坏主意,因为如果包含窗口使用相同的名称,事情可能会变得相当丑陋(我只是有一个绑定的用户控件一个属性,因为控件在内部与窗口具​​有相同的名称)。最好使用像 itemsEditorRoot 这样的唯一名称,或者甚至更好,使用像 {RelativeSource Mode=FindAncestor, AncestorType={x:Type t:ItemsEditor}} 这样的名称。
  • 我对 WPF 并不太熟悉,但这确实发生了。只需将root 重命名为mainWindow(在x:NameElementName 中)即可立即解决问题。如果理论范围界定和现实生活之间确实存在矛盾,我会进一步调查,也许会发布一个新问题。或者,如果没有真正的问题,除了我的误解,也许我还是会发布一个自我回答的问题。
  • 好吧,我放弃了。这对我来说没有任何意义。 Here it is the question.
  • 我认为这个答案很简单,涵盖了大多数人为了重用而试图“将一些控件组合在一个控件中”。谢谢
【解决方案2】:

参考下面的代码。 用户控件.XAML

<Grid>
    <ListBox ItemsSource="{Binding Things}" x:Name="lst">
        <ListBox.ItemTemplate>
            <DataTemplate>
                <StackPanel Orientation="Horizontal">
                    <TextBlock Text="{Binding ThingName}" Margin="3"/>
                    <Button Content="Remove" Margin="3" Command="{Binding ElementName=lst, Path=DataContext.RemoveCommand}" CommandParameter="{Binding}"/>
                </StackPanel>
            </DataTemplate>
        </ListBox.ItemTemplate>
    </ListBox>
</Grid>

Window.Xaml

<Window x:Class="MultiBind_Learning.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:MultiBind_Learning"
    Title="Window1" Height="300" Width="300">
<StackPanel Orientation="Horizontal">
    <Button Content="Add" Width="50" Height="25" Command="{Binding AddCommnd }"/>
    <local:UserControl2/>
</StackPanel>

Window.xaml.cs

public partial class Window1 : Window
{
    public Window1()
    {
        InitializeComponent();
        this.DataContext = new ThingViewModel();
    }
}

ThingViewModel.cs

  class ThingViewModel
{
    private ObservableCollection<Thing> things = new ObservableCollection<Thing>();

    public ObservableCollection<Thing> Things
    {
        get { return things; }
        set { things = value; }
    }

    public ICommand AddCommnd { get; set; }
    public ICommand RemoveCommand { get; set; }

    public ThingViewModel()
    {
        for (int i = 0; i < 10; i++)
        {
            things.Add(new Thing() { ThingName="Thing" +i});
        }

        AddCommnd = new BaseCommand(Add);
        RemoveCommand = new BaseCommand(Remove);
    }

    void Add(object obj)
    {
      things.Add(new Thing() {ThingName="Added New" });
    }

    void Remove(object obj)
    {
      things.Remove((Thing)obj);
    }
}

Thing.cs

class Thing :INotifyPropertyChanged
{
    private string thingName;

    public string ThingName
    {
        get { return thingName; }
        set { thingName = value; OnPropertyChanged("ThingName"); }
    }

    public event PropertyChangedEventHandler PropertyChanged;
    private void OnPropertyChanged(string propName)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propName));
        }
    }
}

BaseCommand.cs

public class BaseCommand : ICommand
{
    private Predicate<object> _canExecute;
    private Action<object> _method;
    public event EventHandler CanExecuteChanged;

    public BaseCommand(Action<object> method)
    {
        _method = method;            
    }

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

    public void Execute(object parameter)
    {
        _method.Invoke(parameter);
    }
}

您可以尝试使用 MVVMLight 中的 RelayCommand 或 PRISM 库中的 DelegateCommand,而不是 Base 命令。

【讨论】:

  • 感谢您的详细回复。在第一次阅读时,它看起来确实给了我解决这个问题所需的模式。让我试试看,我很快就会给出答案。谢谢!
【解决方案3】:

默认情况下,您的用户控件将继承其容器的 DataContext。因此,您的窗口使用的 ViewModel 类可以由用户控件直接绑定到,使用 XAML 中的绑定表示法。无需指定 DependentProperties 或 RoutedEvents,只需照常绑定到命令属性即可。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-11-21
    相关资源
    最近更新 更多