在运行时将 EventSetter 动态添加到现有的分层数据模板
Add EventSetter dynamically on runtime to existing Hierarchical Data Template
我有一个 WPF TreeView
控件,它通过绑定获取分层数据。为了控制控件中的视觉输出,我使用 Hierarchical Data Templates
。 TreeView
的 DataContext
是自定义 class 的 ObservableCollection
,可以容纳不同种类的 children 类型。
public class PaletteGroup
{
public string Name { get; set; }
public ObservableCollection<Palette> Palettes { get; set; }
public ObservableCollection<PaletteGroup> PaletteGroups { get; set; }
public IList Children
{
get
{
return new CompositeCollection()
{
new CollectionContainer() { Collection = Palettes },
new CollectionContainer() { Collection = PaletteGroups }
};
}
}
}
public class Palette
{
public string Name { get; set; }
}
由于 PaletteGroup
class 可以容纳 Palette
和 PaletteGroup
类型的 children,我使用 CompositeCollection
来组合两者ObservableCollection
s 在一个层次结构中用于 TreeView
中的视觉输出,因为我的 classes 可以有你想要的任意多个子节点级别。
视觉输出本身在我的 xaml 文件中定义,我在其中使用两个 classes 的 Name
属性 来显示 object:
<local:DragDropDecorator AllowDrop="True"
AllowPaletteItems="False"
AllowPaletteGroups="True"
AllowPalettes="True">
<TreeView Margin="10,10,10,40"
Name="PaletteStructureView"
VirtualizingStackPanel.IsVirtualizing="True"
VirtualizingStackPanel.VirtualizationMode="Recycling"
MouseRightButtonUp="PalettesListBoxMouseRightButtonUp"
ItemsSource="{Binding LoadedPaletteGroups}">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:PaletteGroup}"
ItemsSource="{Binding Children}">
<TextBlock Foreground="DarkGreen"
Text="{Binding Path=Name}" />
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:Palette}">
<TextBlock Foreground="DarkBlue"
Text="{Binding Path=Name}" />
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
</local:DragDropDecorator>
如您所见,我还将 TreeView
控件包装在一个名为 DragDropDecorator
的自定义 class 拖放操作中,我在其中添加了运行时控件的所有必要事件.当我使用很多不同的控件时,我厌倦了总是将事件绑定到 xaml 文件中的控件。此 class 的 Loaded
事件如下所示:
private void DragableItemsControl_Loaded( object sender, RoutedEventArgs e )
{
if (!(base.DecoratedUIElement is ItemsControl))
throw new InvalidCastException(string.Format("DragDragDecorator cannot have child of type {0}", Child.GetType()));
ItemsControl itemsControl = (ItemsControl)DecoratedUIElement;
itemsControl.AllowDrop = AllowDrop;
itemsControl.PreviewMouseLeftButtonDown += new MouseButtonEventHandler(ItemsControl_PreviewMouseLeftButtonDown);
itemsControl.PreviewMouseMove += new MouseEventHandler(ItemsControl_PreviewMouseMove);
itemsControl.PreviewMouseLeftButtonUp += new MouseButtonEventHandler(ItemsControl_PreviewMouseLeftButtonUp);
itemsControl.PreviewDrop += new DragEventHandler(ItemsControl_PreviewDrop);
itemsControl.PreviewQueryContinueDrag += new QueryContinueDragEventHandler(ItemsControl_PreviewQueryContinueDrag);
itemsControl.PreviewDragEnter += new DragEventHandler(ItemsControl_PreviewDragEnter);
itemsControl.PreviewDragOver += new DragEventHandler(ItemsControl_PreviewDragOver);
itemsControl.DragLeave += new DragEventHandler(ItemsControl_DragLeave);
}
这对于 ListBox
控件来说绝对可以正常工作,这也是我的项目所需要的。但是我在使用 TreeView
控件时遇到了很多困难,因为事件仅针对 TreeView
中最上面的节点引发,即使我在某些 children 上尝试拖放操作也是如此。
首先,我尝试将所有事件添加到 TreeView.ItemContainerStyle
。这适用于第一级子节点,但会忽略更深的节点结构和最上面的节点。
然后我尝试将所有事件添加到DragDropDecorator
class的Loaded
事件中的Hierarchical Data Template
:
if (itemsControl.GetType() == typeof(TreeView))
{
foreach (object item in itemsControl.Resources.Keys)
{
var hdt = itemsControl.FindResource(item);
if (hdt != null & hdt.GetType() == typeof(HierarchicalDataTemplate))
{
var newHdt = (HierarchicalDataTemplate)hdt;
var test = new Style();
test.Setters.Add(new EventSetter(PreviewMouseLeftButtonDownEvent, new MouseButtonEventHandler(ItemsControl_PreviewMouseLeftButtonDown)));
test.Setters.Add(new EventSetter(PreviewMouseMoveEvent, new MouseEventHandler(ItemsControl_PreviewMouseMove)));
test.Setters.Add(new EventSetter(PreviewMouseLeftButtonUpEvent, new MouseButtonEventHandler(ItemsControl_PreviewMouseLeftButtonUp)));
test.Setters.Add(new EventSetter(PreviewDropEvent, new DragEventHandler(ItemsControl_PreviewDrop)));
test.Setters.Add(new EventSetter(PreviewQueryContinueDragEvent, new QueryContinueDragEventHandler(ItemsControl_PreviewQueryContinueDrag)));
test.Setters.Add(new EventSetter(PreviewDragEnterEvent, new DragEventHandler(ItemsControl_PreviewDragEnter)));
test.Setters.Add(new EventSetter(PreviewDragOverEvent, new DragEventHandler(ItemsControl_PreviewDragOver)));
test.Setters.Add(new EventSetter(DragLeaveEvent, new DragEventHandler(ItemsControl_DragLeave)));
newHdt.ItemContainerStyle = test;
}
}
}
使用此代码,我得到一个 InvalidOperationException
,因为模板已经密封 object。
所以我的问题是:
- 如何在运行时将 EventSetter 添加到已经存在的
Hierarchical Data
Template
?
- 这是正确的方法吗,或者我有
其他选项可以使它更优雅?
经过几个小时尝试不同的方法并在 Internet 上搜索解决方案后,我现在被困住了。如果有人能给我指出正确的方向,或者甚至给我写一些代码片段来帮助我回到正轨,我将不胜感激。
我希望我发布的代码足够了。如果没有,请留下评论,我会添加其他部分。
在此先感谢您的宝贵时间!
我自己解决了这个问题,我想post这里的代码以备将来参考。也许这不是最好的解决方案,但它对我有用。我将以下行添加到 DragDropDecorator
class 的 Loaded
事件中:
if (itemsControl.GetType() == typeof(TreeView))
{
var originalStyle = itemsControl.Style;
var newStyle = new Style();
newStyle.BasedOn = originalStyle;
newStyle.Setters.Add(new EventSetter(PreviewMouseLeftButtonDownEvent, new MouseButtonEventHandler(ItemsControl_PreviewMouseLeftButtonDown)));
newStyle.Setters.Add(new EventSetter(PreviewMouseMoveEvent, new MouseEventHandler(ItemsControl_PreviewMouseMove)));
newStyle.Setters.Add(new EventSetter(PreviewMouseLeftButtonUpEvent, new MouseButtonEventHandler(ItemsControl_PreviewMouseLeftButtonUp)));
newStyle.Setters.Add(new EventSetter(PreviewDropEvent, new DragEventHandler(ItemsControl_PreviewDrop)));
newStyle.Setters.Add(new EventSetter(PreviewQueryContinueDragEvent, new QueryContinueDragEventHandler(ItemsControl_PreviewQueryContinueDrag)));
newStyle.Setters.Add(new EventSetter(PreviewDragEnterEvent, new DragEventHandler(ItemsControl_PreviewDragEnter)));
newStyle.Setters.Add(new EventSetter(PreviewDragOverEvent, new DragEventHandler(ItemsControl_PreviewDragOver)));
newStyle.Setters.Add(new EventSetter(DragLeaveEvent, new DragEventHandler(ItemsControl_DragLeave)));
itemsControl.ItemContainerStyle = newStyle;
}
我无法编辑样式,因为它一旦设置就被密封了。所以我在新样式对象上使用 BasedOn
属性 来获取已经设置的样式信息,添加我的 EventSetter
s 并将新样式应用到 Control
.
我有一个 WPF TreeView
控件,它通过绑定获取分层数据。为了控制控件中的视觉输出,我使用 Hierarchical Data Templates
。 TreeView
的 DataContext
是自定义 class 的 ObservableCollection
,可以容纳不同种类的 children 类型。
public class PaletteGroup
{
public string Name { get; set; }
public ObservableCollection<Palette> Palettes { get; set; }
public ObservableCollection<PaletteGroup> PaletteGroups { get; set; }
public IList Children
{
get
{
return new CompositeCollection()
{
new CollectionContainer() { Collection = Palettes },
new CollectionContainer() { Collection = PaletteGroups }
};
}
}
}
public class Palette
{
public string Name { get; set; }
}
由于 PaletteGroup
class 可以容纳 Palette
和 PaletteGroup
类型的 children,我使用 CompositeCollection
来组合两者ObservableCollection
s 在一个层次结构中用于 TreeView
中的视觉输出,因为我的 classes 可以有你想要的任意多个子节点级别。
视觉输出本身在我的 xaml 文件中定义,我在其中使用两个 classes 的 Name
属性 来显示 object:
<local:DragDropDecorator AllowDrop="True"
AllowPaletteItems="False"
AllowPaletteGroups="True"
AllowPalettes="True">
<TreeView Margin="10,10,10,40"
Name="PaletteStructureView"
VirtualizingStackPanel.IsVirtualizing="True"
VirtualizingStackPanel.VirtualizationMode="Recycling"
MouseRightButtonUp="PalettesListBoxMouseRightButtonUp"
ItemsSource="{Binding LoadedPaletteGroups}">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:PaletteGroup}"
ItemsSource="{Binding Children}">
<TextBlock Foreground="DarkGreen"
Text="{Binding Path=Name}" />
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:Palette}">
<TextBlock Foreground="DarkBlue"
Text="{Binding Path=Name}" />
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
</local:DragDropDecorator>
如您所见,我还将 TreeView
控件包装在一个名为 DragDropDecorator
的自定义 class 拖放操作中,我在其中添加了运行时控件的所有必要事件.当我使用很多不同的控件时,我厌倦了总是将事件绑定到 xaml 文件中的控件。此 class 的 Loaded
事件如下所示:
private void DragableItemsControl_Loaded( object sender, RoutedEventArgs e )
{
if (!(base.DecoratedUIElement is ItemsControl))
throw new InvalidCastException(string.Format("DragDragDecorator cannot have child of type {0}", Child.GetType()));
ItemsControl itemsControl = (ItemsControl)DecoratedUIElement;
itemsControl.AllowDrop = AllowDrop;
itemsControl.PreviewMouseLeftButtonDown += new MouseButtonEventHandler(ItemsControl_PreviewMouseLeftButtonDown);
itemsControl.PreviewMouseMove += new MouseEventHandler(ItemsControl_PreviewMouseMove);
itemsControl.PreviewMouseLeftButtonUp += new MouseButtonEventHandler(ItemsControl_PreviewMouseLeftButtonUp);
itemsControl.PreviewDrop += new DragEventHandler(ItemsControl_PreviewDrop);
itemsControl.PreviewQueryContinueDrag += new QueryContinueDragEventHandler(ItemsControl_PreviewQueryContinueDrag);
itemsControl.PreviewDragEnter += new DragEventHandler(ItemsControl_PreviewDragEnter);
itemsControl.PreviewDragOver += new DragEventHandler(ItemsControl_PreviewDragOver);
itemsControl.DragLeave += new DragEventHandler(ItemsControl_DragLeave);
}
这对于 ListBox
控件来说绝对可以正常工作,这也是我的项目所需要的。但是我在使用 TreeView
控件时遇到了很多困难,因为事件仅针对 TreeView
中最上面的节点引发,即使我在某些 children 上尝试拖放操作也是如此。
首先,我尝试将所有事件添加到 TreeView.ItemContainerStyle
。这适用于第一级子节点,但会忽略更深的节点结构和最上面的节点。
然后我尝试将所有事件添加到DragDropDecorator
class的Loaded
事件中的Hierarchical Data Template
:
if (itemsControl.GetType() == typeof(TreeView))
{
foreach (object item in itemsControl.Resources.Keys)
{
var hdt = itemsControl.FindResource(item);
if (hdt != null & hdt.GetType() == typeof(HierarchicalDataTemplate))
{
var newHdt = (HierarchicalDataTemplate)hdt;
var test = new Style();
test.Setters.Add(new EventSetter(PreviewMouseLeftButtonDownEvent, new MouseButtonEventHandler(ItemsControl_PreviewMouseLeftButtonDown)));
test.Setters.Add(new EventSetter(PreviewMouseMoveEvent, new MouseEventHandler(ItemsControl_PreviewMouseMove)));
test.Setters.Add(new EventSetter(PreviewMouseLeftButtonUpEvent, new MouseButtonEventHandler(ItemsControl_PreviewMouseLeftButtonUp)));
test.Setters.Add(new EventSetter(PreviewDropEvent, new DragEventHandler(ItemsControl_PreviewDrop)));
test.Setters.Add(new EventSetter(PreviewQueryContinueDragEvent, new QueryContinueDragEventHandler(ItemsControl_PreviewQueryContinueDrag)));
test.Setters.Add(new EventSetter(PreviewDragEnterEvent, new DragEventHandler(ItemsControl_PreviewDragEnter)));
test.Setters.Add(new EventSetter(PreviewDragOverEvent, new DragEventHandler(ItemsControl_PreviewDragOver)));
test.Setters.Add(new EventSetter(DragLeaveEvent, new DragEventHandler(ItemsControl_DragLeave)));
newHdt.ItemContainerStyle = test;
}
}
}
使用此代码,我得到一个 InvalidOperationException
,因为模板已经密封 object。
所以我的问题是:
- 如何在运行时将 EventSetter 添加到已经存在的
Hierarchical Data Template
? - 这是正确的方法吗,或者我有 其他选项可以使它更优雅?
经过几个小时尝试不同的方法并在 Internet 上搜索解决方案后,我现在被困住了。如果有人能给我指出正确的方向,或者甚至给我写一些代码片段来帮助我回到正轨,我将不胜感激。
我希望我发布的代码足够了。如果没有,请留下评论,我会添加其他部分。
在此先感谢您的宝贵时间!
我自己解决了这个问题,我想post这里的代码以备将来参考。也许这不是最好的解决方案,但它对我有用。我将以下行添加到 DragDropDecorator
class 的 Loaded
事件中:
if (itemsControl.GetType() == typeof(TreeView))
{
var originalStyle = itemsControl.Style;
var newStyle = new Style();
newStyle.BasedOn = originalStyle;
newStyle.Setters.Add(new EventSetter(PreviewMouseLeftButtonDownEvent, new MouseButtonEventHandler(ItemsControl_PreviewMouseLeftButtonDown)));
newStyle.Setters.Add(new EventSetter(PreviewMouseMoveEvent, new MouseEventHandler(ItemsControl_PreviewMouseMove)));
newStyle.Setters.Add(new EventSetter(PreviewMouseLeftButtonUpEvent, new MouseButtonEventHandler(ItemsControl_PreviewMouseLeftButtonUp)));
newStyle.Setters.Add(new EventSetter(PreviewDropEvent, new DragEventHandler(ItemsControl_PreviewDrop)));
newStyle.Setters.Add(new EventSetter(PreviewQueryContinueDragEvent, new QueryContinueDragEventHandler(ItemsControl_PreviewQueryContinueDrag)));
newStyle.Setters.Add(new EventSetter(PreviewDragEnterEvent, new DragEventHandler(ItemsControl_PreviewDragEnter)));
newStyle.Setters.Add(new EventSetter(PreviewDragOverEvent, new DragEventHandler(ItemsControl_PreviewDragOver)));
newStyle.Setters.Add(new EventSetter(DragLeaveEvent, new DragEventHandler(ItemsControl_DragLeave)));
itemsControl.ItemContainerStyle = newStyle;
}
我无法编辑样式,因为它一旦设置就被密封了。所以我在新样式对象上使用 BasedOn
属性 来获取已经设置的样式信息,添加我的 EventSetter
s 并将新样式应用到 Control
.