WPF - Handling custom attached events on custom controls
Asked Answered
A

1

7

I have a routed event declared as such (names have been changed to protect the innocent):

public class DragHelper : DependencyObject {
    public static readonly RoutedEvent DragCompleteEvent = EventManager.RegisterRoutedEvent(
        "DragComplete",
        RoutingStrategy.Bubble,
        typeof(DragRoutedEventHandler),
        typeof(DragHelper)
    );

    public static void AddDragCompleteHandler( DependencyObject dependencyObject, DragRoutedEventHandler handler ) {
        UIElement element = dependencyObject as UIElement;
        if (element != null) {
            element.AddHandler(DragCompleteEvent, handler);
        }
    }

    public static void RemoveDragCompleteHandler( DependencyObject dependencyObject, DragRoutedEventHandler handler ) {
        UIElement element = dependencyObject as UIElement;
        if (element != null) {
            element.RemoveHandler(DragCompleteEvent, handler);
        }
    }

Pretty standard stuff. In XAML, I have a DataTemplate that contains a single custom control. I am attempting to attach this event (as well as some other attached properties) to the control:

<DataTemplate ...>
    <My:CustomControl
        My:DragHelper.IsDragSource="True"
        My:DragHelper.DragComplete="DragCompleteHandler" />
</DataTemplate>

This fails to produce the desired results. Specifically, while the code that calls RaiseEvent() for the DragComplete event is called, the handler is never invoked. Nor, in fact, are the handlers for any other custom routed events that are hooked up elsewhere in this XAML file.

I tried changing the name of the routed event, and I tried switching the data template from one with a DataType to one with an x:Key. This produced no visible change in the behavior.

However, if I change My:CustomControl to any built-in WPF control, such as a TextBlock, the events are fired exactly as I would exect. Similarly, if I replace my custom control with any other custom UserControl subclass from my project, the behavior reverts to the broken, no-events-ever-seem-to-get-handled state.

This isn't make a whole lot of sense to me. Is there something specific I need to do to get this scenario to work? It seems like it should not matter. I suppose it's possible there is a specific thing I've done in all my custom controls that causes the event handling to break, but I haven't seen anything common in the three or four custom controls I've tried so far.

Astragalus answered 27/8, 2009 at 18:34 Comment(0)
P
1

You haven't posted all your code, so I had to infer and put together my own version. It works OK for me. Perhaps compare and contrast to your code:

Window1.xaml.cs:

using System.Windows;
using System.Windows.Controls;

namespace WpfApplication1
{
    public partial class Window1 : Window
    {
        public Window1()
        {
            InitializeComponent();
        }

        private void DragCompleteHandler(object sender, RoutedEventArgs e)
        {
            MessageBox.Show("YEP");
        }
    }

    public class CustomControl : TextBox
    {
    }

    public class DragHelper : DependencyObject
    {
        public static readonly DependencyProperty IsDragSourceProperty = DependencyProperty.RegisterAttached("IsDragSource",
            typeof(bool),
            typeof(DragHelper),
            new FrameworkPropertyMetadata(OnIsDragSourceChanged));

        public static bool GetIsDragSource(DependencyObject depO)
        {
            return (bool)depO.GetValue(IsDragSourceProperty);
        }

        public static void SetIsDragSource(DependencyObject depO, bool ids)
        {
            depO.SetValue(IsDragSourceProperty, ids);
        }

        public static readonly RoutedEvent DragCompleteEvent = EventManager.RegisterRoutedEvent(
            "DragComplete",
            RoutingStrategy.Bubble,
            typeof(RoutedEventHandler),
            typeof(DragHelper)
        );

        public static void AddDragCompleteHandler(DependencyObject dependencyObject, RoutedEventHandler handler)
        {
            UIElement element = dependencyObject as UIElement;
            if (element != null)
            {
                element.AddHandler(DragCompleteEvent, handler);
            }
        }

        public static void RemoveDragCompleteHandler(DependencyObject dependencyObject, RoutedEventHandler handler)
        {
            UIElement element = dependencyObject as UIElement;
            if (element != null)
            {
                element.RemoveHandler(DragCompleteEvent, handler);
            }
        }

        private static void OnIsDragSourceChanged(DependencyObject depO, DependencyPropertyChangedEventArgs e)
        {
            (depO as TextBox).TextChanged += delegate
            {
                (depO as TextBox).RaiseEvent(new RoutedEventArgs(DragCompleteEvent, null));
            };
        }
    }
}

Window1.xaml:

<Window x:Class="WpfApplication1.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:WpfApplication1"
    Title="Window1" Height="300" Width="300">
    <Window.Resources>
        <DataTemplate x:Key="Test">
            <local:CustomControl
                local:DragHelper.IsDragSource="True"
                local:DragHelper.DragComplete="DragCompleteHandler" />
        </DataTemplate>
    </Window.Resources>

    <ContentControl ContentTemplate="{StaticResource Test}"/>
</Window>
Platonism answered 27/8, 2009 at 18:58 Comment(2)
Thanks. My custom control was derived from UserControl, but even switching it to TextBox like yours still exhibits the problem for me. I omitted the rest of the code because it's all basically the same as yours, except for some namespace changes. However, it is part of a much larger project, so there might be some kind of interaction with some global style or resource or something I'm not aware of that could be impacting the problem. I'll see what I can do.Astragalus
Alright, so I've fooled with a little bit. Starting from your complete example, which did work for me, I tried various things to transform it into the nonfunctional case that I'm seeing in my larger project. After a while, I stumbled on a very simple change to your base code that reproduces the problem. If I take your DragHelper implementation and move it to a different assembly which is referenced by the main application and modify Window1.xaml accordingly (add an xmlns:other and user other: instead of local:) then I no longer see the message box fire when I type in the textbox. :|Astragalus

© 2022 - 2024 — McMap. All rights reserved.