如何设置ItemsControl.ItemTemplate的依赖项属性并处理其事件

时间:2015-12-13 11:03:43

标签: c# .net wpf xaml mvvm

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

  1. 添加新项目时,它会正确显示但引发异常(无法创建默认转换器以在类型'WpfApplication1.ItemView'和'WpfApplication1.ItemViewModel'之间执行'双向'转换。)。
  2. MainWindowViewModel中的OnDelete事件处理程序永远不会被引发?编辑:实际上BtnDeleteClick中的ViewModel属性为null所以是的......当然。
  3. 顺便说一句 - 我使用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();
        }
    }
    

    和ItemViewModel.cs:

    [ImplementPropertyChanged]
    public class ItemViewModel
    {
        public event EventHandler OnDelete;
    
        public void Delete()
        {
            var handler = OnDelete;
            if (handler != null)
            {
                handler(this, new EventArgs());
            }
        }
    }
    

1 个答案:

答案 0 :(得分:2)

你不应该设置

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

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

作为一项规则,您永远不应该显式设置UserControl的DataContext,因为所有&#34;外部&#34;然后,绑定需要明确的SourceRelativeSource值。

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

看起来像这样:

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))
        });
    }
}

并将像这样使用:

<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>