问题描述
我首先让图片做一些说话。
所以你看,我想创建一个WPF用户控件,支持绑定到父窗口的DataContext。用户控件只是一个带有自定义ItemTemplate的按钮和ListBox,可以使用Label和Remove Button来呈现东西。
添加按钮应该调用主要的ICommand查看模型以与用户交互选择新事物(IThing的实例)。用户控件中ListBoxItem中的Remove按钮应类似地调用主视图模型上的ICommand以请求相关事物的删除。为了工作,删除按钮将不得不发送一些识别信息到视图模型关于要求删除的事情。所以有两种类型的Command可以绑定到这个控件。有一些像AddThingCommand()和RemoveThingCommand(Ithing thing)的东西。
我使用了Click事件功能,但感觉很诡异,在XAML背后产生一堆代码,并与原始的MVVM实现的其余部分摩擦。我真的想要使用Commands和MVVM通常。
有足够的代码涉及到一个基本的演示工作,我停止发布整个事情减少混乱。什么是工作,让我觉得我很亲密的ListBox的DataTemplate绑定标签正确,当父窗口添加项目到集合,它们显示。
< 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就像它是一样的 - 恰好恰好是从其他控件组成)。这意味着什么?这意味着您应该将DependencyProperties放在您的ViewModel可以绑定的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 =MainWindowHeight =350 Width =525>
< Window.DataContext>
< t:ViewModel />
< /Window.DataContext>
< t:ItemsEditor Items ={Binding Items}
AddItem ={Binding AddItem}
RemoveItem ={Binding RemoveItem}/>
< / Window>注意,我们有我们的项目编辑器,它显示了所有需要的属性 - 项目列表正在编辑,添加新项目的命令和删除项目的命令。
接下来,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}>删除< / Button>
< TextBox Text ={Binding Name}Width =100/>
< / StackPanel>
< / DataTemplate>
< /UserControl.Resources>
< StackPanel>
< Button Command ={Binding AddItem,ElementName = root}>添加< / Button>
< ItemsControl ItemsSource ={Binding Items,ElementName = root}/>
< / StackPanel>
< / UserControl>
我们将控件绑定到UC表面定义的DP。请不要像 DataContext = this;
这样的废话,因为这种反模式打破了更复杂的UC实现。
以下是UC上这些属性的定义
public partial class ItemsEditor:UserControl
{
#region项目
public static readonly DependencyProperty ItemsProperty =
DependencyProperty.Register(
Items,
typeof(IEnumerable< Item>),
typeof(ItemsEditor),
new UIPropertyMetadata(null));
public IEnumerable< Item>项目
{
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。没有biggie。而且我们的ViewModel也是简单的
public class ViewModel
{
public ObservableCollection< Item>物品{get;私人集合}
public ICommand AddItem {get;私人集合}
public ICommand RemoveItem {get;私人集合}
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));
}
}
您正在编辑三个不同的集合,所以你可能想要公开更多的ICommands来清楚说明你正在添加/删除哪些。或者你可以很便宜,并使用CommandParameter来计算出来。
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.
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).
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.
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.
<Label Content="{Binding Path=DisplayName}" />
While that displays the IThing correctly, the Remove button right next to it does nothing when I click it.
<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,
解决方案 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.
Let's take a trivial (thrown together in under two minutes) example. I'll leave out the ICommand implementation.
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.
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.
Here's the definitions of these properties on the 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
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.
这篇关于如何在UserControl和父窗口之间绑定WPF命令的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!