如何设置 ItemsControl.ItemTemplate 的依赖属性并处理其事件 [英] How to set dependency property of ItemsControl.ItemTemplate and handle its events

查看:23
本文介绍了如何设置 ItemsControl.ItemTemplate 的依赖属性并处理其事件的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个主控件 (MainWindow.xaml) 和一个用户控件 (ItemView.xaml).MainWindow 包含一个用于所有 ItemView-s 的 ItemsControl 和一个用于添加项目的简单按钮.所有逻辑都(应该?)在两个对应的视图模型(MainWindowViewModel 和 ItemViewModel)中.下面是我的代码(尽可能短),但我有两个问题:

  1. 添加新项目后,它会正确显示,但会引发异常(无法创建默认转换器以在WpfApplication1.ItemView"和WpfApplication1.ItemViewModel"类型之间执行双向"转换.).
  2. MainWindowViewModel 中的 OnDelete 事件处理程序从未引发?实际上 BtnDeleteClick 中的 ViewModel 属性为 null 所以是的......当然.

顺便说一句 - 我使用 Fody PropertyChanged.

MainWindow.xaml:

<网格><Grid.RowDefinitions><RowDefinition Height="自动"></RowDefinition><RowDefinition></RowDefinition></Grid.RowDefinitions><Button Grid.Row="0" Width="100" Height="35" Content="Add" Horizo​​ntalAlignment="Left" Margin="10" Click="BtnAddClick"></Button><Border Grid.Row="1" MinHeight="50"><ItemsControl ItemsSource="{Binding ViewModel.Items}"><ItemsControl.ItemsPanel><ItemsPanelTemplate><堆栈面板/></ItemsPanelTemplate></ItemsControl.ItemsPanel><ItemsControl.ItemTemplate><数据模板><wpfApplication1:ItemView ViewModel="{Binding ., PresentationTraceSources.TraceLevel=High, Mode=TwoWay}"></wpfApplication1:ItemView></数据模板></ItemsControl.ItemTemplate></ItemsControl></边框></网格></窗口>

MainWindow.xaml.cs:

[ImplementPropertyChanged]公共部分类 MainWindow{公共 MainWindowViewModel ViewModel { 获取;放;}公共主窗口(){初始化组件();ViewModel = new MainWindowViewModel();}私有无效 BtnAddClick(对象发送者,RoutedEventArgs e){ViewModel.Add();}}

MainWindowViewModel.cs:

[ImplementPropertyChanged]公共类 MainWindowViewModel{公共 ObservableCollection项目{得到;放;}公共 MainWindowViewModel(){Items = new ObservableCollection();}公共无效添加(){var item = new ItemViewModel();item.OnDelete += (sender, args) =>{Debug.WriteLine("-- 等待这发生--");Items.Remove(item);};Items.Add(item);}}

ItemViewModel.xaml:

<网格><Button Width="100" Height="35" Content="Delete" Click="BtnDeleteClick"></Button></网格></用户控件>

ItemView.xaml.cs:

[ImplementPropertyChanged]公共部分类 ItemView{public static readonly DependencyProperty ViewModelProperty = DependencyProperty.Register("ViewModel", typeof(ItemViewModel), typeof(ItemView), new UIPropertyMetadata(null));公共 ItemViewModel 视图模型{获取{返回(ItemViewModel)GetValue(ViewModelProperty);}set { SetValue(ViewModelProperty, value);}}公共项目视图(){初始化组件();}私有无效 BtnDeleteClick(对象发送者,RoutedEventArgs e){ViewModel.Delete();}}

和 ItemViewModel.cs:

[ImplementPropertyChanged]公共类 ItemViewModel{公共事件 EventHandler OnDelete;公共无效删除(){var 处理程序 = OnDelete;如果(处理程序!= null){处理程序(这个,新的事件参数());}}}

解决方案

你不应该设置

DataContext="{Binding RelativeSource={RelativeSource Self}}"

在您的 ItemView 的 XAML 中.它有效地打破了 MainWindow.xaml 中的 ViewModel="{Binding .}" 绑定,因为 DataContext 不再是一个 ItemsViewModel,而是一个 ItemsView.

作为规则,您永远不应显式设置 UserControl 的 DataContext,因为所有外部"绑定都需要显式的 SourceRelativeSource 值.<小时>

也就是说,你做的这一切都太复杂了.您可以简单地使用带有删除命令的视图模型,并将按钮的 Command 属性绑定到此命令,而不是在 ItemsView 中使用按钮单击处理程序.

它可能看起来像这样:

公共类ItemViewModel{公共字符串名称{获取;放;}公共 ICommand 删除 { 获取;放;}}公共类 MainViewModel{公共主视图模型(){Items = new ObservableCollection();}公共 ObservableCollection项目{得到;私人订制;}public void AddItem(字符串名称){Items.Add(新的 ItemViewModel{姓名 = 姓名,Delete = new DelegateCommand(p => Items.Remove(p as ItemViewModel))});}}

并且会像这样使用:

<网格><按钮内容="删除"命令="{绑定删除}"命令参数="{绑定}"/></网格></用户控件>

I have a main control (MainWindow.xaml) and an user control (ItemView.xaml). MainWindow contains an ItemsControl for all the ItemView-s and a simple button to add an item. All logic is (should be?) inside two corresponding viewmodels (MainWindowViewModel and ItemViewModel). Below is my code (made it as short as possible), but I have two problems with it:

  1. When a new item is added it is correctly displayed but the exception is raised (Cannot create default converter to perform 'two-way' conversions between types 'WpfApplication1.ItemView' and 'WpfApplication1.ItemViewModel'.).
  2. The OnDelete event handler in MainWindowViewModel is never raised? Edit: actually the ViewModel property inside BtnDeleteClick is null so yeah... of course.

Btw - I use Fody PropertyChanged.

MainWindow.xaml:

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:wpfApplication1="clr-namespace:WpfApplication1"
        Title="MainWindow" Height="350" Width="525"
        DataContext="{Binding RelativeSource={RelativeSource Self}}">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"></RowDefinition>
            <RowDefinition></RowDefinition>
        </Grid.RowDefinitions>
        <Button Grid.Row="0" Width="100" Height="35" Content="Add" HorizontalAlignment="Left" Margin="10" Click="BtnAddClick"></Button>
        <Border Grid.Row="1" MinHeight="50">
            <ItemsControl ItemsSource="{Binding ViewModel.Items}">
                <ItemsControl.ItemsPanel>
                    <ItemsPanelTemplate>
                        <StackPanel/>
                    </ItemsPanelTemplate>
                </ItemsControl.ItemsPanel>
                <ItemsControl.ItemTemplate>
                    <DataTemplate>
                        <wpfApplication1:ItemView ViewModel="{Binding ., PresentationTraceSources.TraceLevel=High, Mode=TwoWay}"></wpfApplication1:ItemView>
                    </DataTemplate>
                </ItemsControl.ItemTemplate>
            </ItemsControl>
        </Border>
    </Grid>
</Window>

MainWindow.xaml.cs:

[ImplementPropertyChanged]
public partial class MainWindow
{
    public MainWindowViewModel ViewModel { get; set; }

    public MainWindow()
    {
        InitializeComponent();

        ViewModel = new MainWindowViewModel();
    }

    private void BtnAddClick(object sender, RoutedEventArgs e)
    {
        ViewModel.Add();
    }
}

MainWindowViewModel.cs:

[ImplementPropertyChanged]
public class MainWindowViewModel
{
    public ObservableCollection<ItemViewModel> Items { get; set; }

    public MainWindowViewModel()
    {
        Items = new ObservableCollection<ItemViewModel>();
    }

    public void Add()
    {
        var item = new ItemViewModel();
        item.OnDelete += (sender, args) =>
        {
            Debug.WriteLine("-- WAITING FOR THIS TO HAPPEN --");
            Items.Remove(item);
        };
        Items.Add(item);
    }
}

ItemViewModel.xaml:

<UserControl x:Class="WpfApplication1.ItemView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             DataContext="{Binding RelativeSource={RelativeSource Self}}">
    <Grid>
            <Button Width="100" Height="35" Content="Delete" Click="BtnDeleteClick"></Button>
    </Grid>
</UserControl>

ItemView.xaml.cs:

[ImplementPropertyChanged]
public partial class ItemView
{
    public static readonly DependencyProperty ViewModelProperty = DependencyProperty.Register
    (
        "ViewModel", typeof(ItemViewModel), typeof(ItemView), new UIPropertyMetadata(null)
    );

    public ItemViewModel ViewModel
    {
        get { return (ItemViewModel)GetValue(ViewModelProperty); }
        set { SetValue(ViewModelProperty, value); }
    }

    public ItemView()
    {
        InitializeComponent();
    }

    private void BtnDeleteClick(object sender, RoutedEventArgs e)
    {
        ViewModel.Delete();
    }
}

And ItemViewModel.cs:

[ImplementPropertyChanged]
public class ItemViewModel
{
    public event EventHandler OnDelete;

    public void Delete()
    {
        var handler = OnDelete;
        if (handler != null)
        {
            handler(this, new EventArgs());
        }
    }
}

解决方案

You should not set

DataContext="{Binding RelativeSource={RelativeSource Self}}"

in the XAML of your ItemView. It effectively breaks the ViewModel="{Binding .}" binding in MainWindow.xaml, because the DataContext is no longer an ItemsViewModel, but an ItemsView.

As a rule, you should never explicitly set the DataContext of a UserControl, because all "external" bindings would then require an explicit Source or RelativeSource value.


That said, you're doing all this way too complicated. Instead of having a button click handler in your ItemsView, you could simply have a view model with a delete command, and bind the Button's Command property to this command.

It may look like this:

public class ItemViewModel
{
    public string Name { get; set; }
    public ICommand Delete { get; set; }
}

public class MainViewModel
{
    public MainViewModel()
    {
        Items = new ObservableCollection<ItemViewModel>();
    }

    public ObservableCollection<ItemViewModel> Items { get; private set; }

    public void AddItem(string name)
    {
        Items.Add(new ItemViewModel
        {
            Name = name,
            Delete = new DelegateCommand(p => Items.Remove(p as ItemViewModel))
        });
    }
}

and would be used like this:

<UserControl x:Class="WpfApplication1.ItemView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Grid>
        <Button Content="Delete"
                Command="{Binding Delete}"
                CommandParameter="{Binding}"/>
    </Grid>
</UserControl>

这篇关于如何设置 ItemsControl.ItemTemplate 的依赖属性并处理其事件的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆