如何在 UserControl 和父窗口之间绑定 WPF 命令
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/29126224/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How do I Bind WPF Commands between a UserControl and a parent Window
提问by Todd Sprang
I'l start by letting a picture do some talking.
我先让一张照片说话。
So you see, I want to create a WPF user control that supports binding to a parent window's DataContext. The user control is simply a Button and a ListBox with a custom ItemTemplate to present things with a Label and a Remove Button.
所以你看,我想创建一个支持绑定到父窗口的 DataContext 的 WPF 用户控件。用户控件只是一个按钮和一个带有自定义 ItemTemplate 的列表框,用于显示带有标签和删除按钮的内容。
The Add button should call an ICommand on the main view model to interact with the user in selecting a new thing (instance of IThing). The Remove buttons in the ListBoxItem in the user control should similarly call an ICommand on the main view model to request the related thing's removal. For that to work, the Remove button would have to send some identifying information to the view model about the thing requesting to be removed. So there are 2 types of Command that should be bindable to this control. Something like AddThingCommand() and RemoveThingCommand(IThing thing).
添加按钮应该调用主视图模型上的 ICommand 以与用户交互以选择新事物(IThing 的实例)。用户控件中 ListBoxItem 中的 Remove 按钮应该类似地调用主视图模型上的 ICommand 以请求删除相关事物。为此,Remove 按钮必须向视图模型发送一些有关请求删除的事物的标识信息。所以有两种类型的命令应该可以绑定到这个控件。像 AddThingCommand() 和 RemoveThingCommand(IThing thing) 之类的东西。
I got the functionality working using Click events, but that feels hacky, producing a bunch of code behind the XAML, and rubs against the rest of the pristine MVVM implementation. I really want to use Commands and MVVM normally.
我使用 Click 事件获得了该功能,但这感觉很糟糕,在 XAML 后面生成了一堆代码,并与原始 MVVM 实现的其余部分发生冲突。我真的很想正常使用Commands和MVVM。
There's enough code involved to get a basic demo working, I am holding off on posting the whole thing to reduce confusion. What is working that makes me feel like I'm so close is the DataTemplate for the ListBox binds the Label correctly, and when the parent window adds items to the collection, they show up.
有足够的代码来让基本的演示工作,我推迟发布整个事情以减少混淆。让我觉得自己如此接近的工作是 ListBox 的 DataTemplate 正确绑定了 Label,并且当父窗口向集合添加项目时,它们会显示出来。
<Label Content="{Binding Path=DisplayName}" />
While that displays the IThing correctly, the Remove button right next to it does nothing when I click it.
虽然它正确显示了 IThing,但当我单击它时,它旁边的 Remove 按钮没有任何作用。
<Button Command="{Binding Path=RemoveItemCommand, RelativeSource={RelativeSource AncestorType={x:Type userControlCommands:ItemManager }}}">
This isn't terribly unexpected since the specific item isn't provided, but the Add button doesn't have to specify anything, and it also fails to call the command.
这并不出人意料,因为未提供特定项目,但添加按钮不必指定任何内容,并且它也无法调用命令。
<Button Command="{Binding Path=AddItemCommand, RelativeSource={RelativeSource AncestorType={x:Type userControlCommands:ItemManager }}}">
So what I need is the "basic" fix for the Add button, so that it calls the parent window's command to add a thing, and the more complex fix for the Remove button, so that it also calls the parent command but also passes along its bound thing.
所以我需要的是添加按钮的“基本”修复,以便它调用父窗口的命令来添加一个东西,以及删除按钮的更复杂的修复,以便它也调用父命令但也传递它的绑定的东西。
Many thanks for any insights,
非常感谢您的任何见解,
回答by
This is trivial, and made so by treating your UserControl like what it is--a control (that just happens to be made up from other controls). What does that mean? It means you should place DependencyProperties on your UC to which your ViewModel can bind, like any other control. Buttons expose a Command property, TextBoxes expose a Text property, etc. You need to expose, on the surface of your UserControl, everything you need for it to do its job.
这是微不足道的,通过像对待它一样对待您的 UserControl 来实现的 - 一个控件(恰好由其他控件组成)。这意味着什么?这意味着您应该将 DependencyProperties 放在 ViewModel 可以绑定到的 UC 上,就像任何其他控件一样。按钮公开一个 Command 属性,TextBox 公开一个 Text 属性,等等。您需要在 UserControl 的表面公开它完成工作所需的一切。
Let's take a trivial (thrown together in under two minutes) example. I'll leave out the ICommand implementation.
让我们举一个微不足道的(在两分钟内拼凑起来的)示例。我将省略 ICommand 实现。
First, our Window
首先,我们的窗口
<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>
Notice we have our Items editor, which exposes properties for everything it needs--the list of items it is editing, a command to add a new item, and a command to remove an item.
请注意,我们有我们的 Items 编辑器,它公开了它需要的所有属性——它正在编辑的项目列表、添加新项目的命令和删除项目的命令。
Next, the UserControl
接下来,用户控件
<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>
We bind our controls to the DPs defined on the surface of the UC. Please, don't do any nonsense like DataContext=this;
as this anti-pattern breaks more complex UC implementations.
我们将控件绑定到 UC 表面上定义的 DP。请不要胡说八道,DataContext=this;
因为这种反模式会破坏更复杂的 UC 实现。
Here's the definitions of these properties on the 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();
}
}
Just DPs on the surface of the UC. No biggie. And our ViewModel is similarly simple
只是 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));
}
}
You are editing three different collections, so you may want to expose more ICommands to make it clear which you are adding/removing. Or you could cheap out and use the CommandParameter to figure it out.
您正在编辑三个不同的集合,因此您可能希望公开更多 ICommand 以明确您要添加/删除的集合。或者你可以便宜点并使用 CommandParameter 来解决这个问题。
回答by Ayyappan Subramanian
Refer the below code. UserControl.XAML
参考下面的代码。用户控件.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
窗口.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
窗口.xaml.cs
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
this.DataContext = new ThingViewModel();
}
}
ThingViewModel.cs
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
东西.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);
}
}
Instead of Base command you can try RelayCommand from MVVMLight or DelegateCommand from PRISM libraries.
除了 Base 命令,您还可以尝试 MVVMLight 中的 RelayCommand 或 PRISM 库中的 DelegateCommand。
回答by Rye bread
By default, your user control will inherit the DataContext of its container. So the ViewModel class that your window uses can be bound to directly by the user control, using the Binding notation in XAML. There's no need to specify DependentProperties or RoutedEvents, just bind to the command properties as normal.
默认情况下,您的用户控件将继承其容器的 DataContext。因此,您的窗口使用的 ViewModel 类可以使用 XAML 中的 Binding 表示法直接由用户控件绑定到。无需指定 DependentProperties 或 RoutedEvents,只需像往常一样绑定到命令属性即可。