如何在UserControl和父窗口之间绑定WPF命令

Tod*_*ang 23 data-binding wpf xaml mvvm wpf-controls

我首先让照片做一些谈话.

MVVM用户控制到窗口线框

所以你看,我想创建一个支持绑定到父窗口的DataContext的WPF用户控件.用户控件只是一个Button和一个带有自定义ItemTemplate的ListBox,用于显示带有Label和Remove Button的东西.

"添加"按钮应调用主视图模型上的ICommand,以便在选择新事物(IThing实例)时与用户进行交互.用户控件中ListBoxItem中的Remove按钮应该类似地调用主视图模型上的ICommand以请求删除相关内容.要使其工作,"删除"按钮必须向视图模型发送有关请求删除的事物的一些标识信息.因此,有两种类型的Command应该可绑定到此控件.像AddThingCommand()和RemoveThingCommand(IThing的东西).

我使用Click事件获得了功能,但是感觉很麻烦,在XAML背后产生了一堆代码,并且与原始MVVM实现的其余部分相冲突.我真的想要正常使用命令和MVVM.

有足够的代码来完成一个基本的演示工作,我暂缓发布整个事情来减少混乱.什么工作使我觉得我离我很近是ListBox的DataTemplate正确地绑定了Label,当父窗口将项目添加到集合时,它们会显示出来.

<Label Content="{Binding Path=DisplayName}" />
Run Code Online (Sandbox Code Playgroud)

虽然它正确显示了IThing,但当我单击它时,它旁边的Remove按钮什么都不做.

<Button Command="{Binding Path=RemoveItemCommand, RelativeSource={RelativeSource AncestorType={x:Type userControlCommands:ItemManager }}}">
Run Code Online (Sandbox Code Playgroud)

这并不是非常意外,因为没有提供特定项,但Add按钮不必指定任何内容,也无法调用该命令.

<Button Command="{Binding Path=AddItemCommand, RelativeSource={RelativeSource AncestorType={x:Type userControlCommands:ItemManager }}}">
Run Code Online (Sandbox Code Playgroud)

所以我需要的是Add按钮的"基本"修复,以便它调用父窗口的命令来添加一个东西,以及更复杂的Remove按钮修复,以便它也调用父命令但也传递它的约束.

非常感谢任何见解,

小智 37

这是微不足道的,通过将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>
Run Code Online (Sandbox Code Playgroud)

请注意,我们有了Items编辑器,它公开了所需内容的属性 - 它正在编辑的项目列表,添加新项目的命令以及删除项目的命令.

接下来,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>
Run Code Online (Sandbox Code Playgroud)

我们将控件绑定到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();
    }
}
Run Code Online (Sandbox Code Playgroud)

只是DP在UC的表面上.没什么大不了的.我们的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));
    }
}
Run Code Online (Sandbox Code Playgroud)

您正在编辑三个不同的集合,因此您可能希望公开更多ICommands以清楚地说明要添加/删除的内容.或者你可以便宜并使用CommandParameter来解决它.

  • 虽然我不确定我会把这个解决方案称为"微不足道":)但你完全解释了它并且你的解决方案"刚刚起作用".我看到我正在制作一些比必要更复杂的东西,而且我错过了一些小东西,这些东西都加起来无处可去.非常感谢清醒的回应.我再次进入MVVM梦境! (7认同)
  • 值得注意的是,在自定义控件中使用`x:Name ="root"可能是一个坏主意,因为如果包含的窗口使用相同的名称,事情可能会变得相当丑陋(我只是有一个绑定属性的用户控件)因为控件的内部名称与窗口相同,所以最好使用`itemsEditorRoot`之类的唯一名称,或者甚至更好,使用类似`{RelativeSource Mode = FindAncestor,AncestorType = {x:Type t:ItemsEditor }}`. (2认同)

Ayy*_*ian 5

参考下面的代码。用户控件.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>
Run Code Online (Sandbox Code Playgroud)

窗口.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>
Run Code Online (Sandbox Code Playgroud)

窗口.xaml.cs

public partial class Window1 : Window
{
    public Window1()
    {
        InitializeComponent();
        this.DataContext = new ThingViewModel();
    }
}
Run Code Online (Sandbox Code Playgroud)

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

东西.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));
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

基本命令文件

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

除了 Base 命令,您还可以尝试 MVVMLight 中的 RelayCommand 或 PRISM 库中的 DelegateCommand。