Add EventSetter dynamically on runtime to existing Hierarchical Data Template

944 Views Asked by At

I have a WPF TreeView control, which gets hierarchical data by binding. To control the visual output in the control I use Hierarchical Data Templates. The DataContext of the TreeView is an ObservableCollection of a custom class which can hold different kind of children types.

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

As the PaletteGroup class can hold childrens of type Palette and PaletteGroup, I use a CompositeCollection to combine both ObservableCollections in one hierarchy for the visual output in the TreeView, because of my classes it is possible to have as many subnode levels you want.

The visual output itself is defined in my xaml file, where I use the Name property of the two classes to show the name of the 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>

As you can see, I also wrapped the TreeView control in a custom class for Drag&Drop operations called DragDropDecorator, where I add all the necessary events for the controls on runtime. As I use a lot of different controls, I got tired of always binding the events to the controls in the xaml file. The Loaded event of this class looks like this:

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

This is absolutely working fine for ListBox controls, which was also a need for my project. But I have quite a hard time with the TreeView control, as the events are only raised for the upper most node in the TreeView, even if I try the Drag&Drop operations on some children.

First I tried to add all the events to the TreeView.ItemContainerStyle. This works fine for the first level of subnodes, but ignores deeper node structures and also the upper most nodes.

Then I tried to add all the events to the Hierarchical Data Template in the Loaded event of the DragDropDecorator class:

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

With this code I get an InvalidOperationException because of an already sealed template object.

So my questions are:

  • How can I add EventSetters to an already existing Hierarchical Data Template on runtime?
  • Is this the right way to do it, or do I have other options to make this more elegant?

After hours of trying different methods and searching for a solution on the internet I am stuck now. I would appreciate it if someone can point me in the right direction or even write me a little code snippet which should help me to get back on track.

I hope the code I posted is sufficient. If not, just leave a comment and I will add additional parts of it.

Thanks in advance and for your time!

1

There are 1 best solutions below

0
On BEST ANSWER

I fixed the problem by myself and I would like to post the code here for future reference. Maybe this is not the best solution, but it works for me. I added the following lines to the Loaded event of the DragDropDecorator class:

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

I wasn't able to edit the style, as it gets sealed once it is set. So I used the BasedOn property on a new style object, to get the already set style information, add my EventSetters and apply the new style to the Control.