如何设置 ItemsControl.ItemTemplate 的依赖属性并处理其事件 [英] How to set dependency property of ItemsControl.ItemTemplate and handle its events
问题描述
我有一个主控件 (MainWindow.xaml) 和一个用户控件 (ItemView.xaml).MainWindow 包含一个用于所有 ItemView-s 的 ItemsControl 和一个用于添加项目的简单按钮.所有逻辑都(应该?)在两个对应的视图模型(MainWindowViewModel 和 ItemViewModel)中.下面是我的代码(尽可能短),但我有两个问题:
- 添加新项目后,它会正确显示,但会引发异常(无法创建默认转换器以在WpfApplication1.ItemView"和WpfApplication1.ItemViewModel"类型之间执行双向"转换.).
- 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" HorizontalAlignment="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,因为所有外部"绑定都需要显式的 Source
或 RelativeSource
值.><小时>
也就是说,你做的这一切都太复杂了.您可以简单地使用带有删除命令的视图模型,并将按钮的 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:
- 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'.).
- 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屋!