1

I have a question regarding custom attached properties/events. In my scenario I want to attach a property/event to any control. The value of this property/event should be an event handler. In short, it should look like:

<TextBox local:Dragging.OnDrag="OnDrag" />

First I tried to implement OnDrag as an attached property. This works for the case above, but then the following case fails:

<Style TargetType="TextBox">
    <Setter Property="local:Dragging.OnDrag" Value="OnDrag" />
</Style>

Because the "OnDrag" string can apparently not be made into a RoutedEventHandler (the attached property's type) by the XAML system.

The next thing I tried then was to try and use an attached event, very much like the builtin Mouse.MouseEnter for example.

The complete code for this is shown at the bottom. There are curious things happening with this version:

  1. If you run the code as shown (with the RegisterRoutedEvent line commented) it will show the "Add handler" function is called. Then the xaml system has an internal exception when applying the style (due to missing registered event I guess).

  2. If you run the code with the RegisterRoutedEvent line in effect everything runs, but the "Add handler" function is never called. I want it to be called though, so that I can register at the drag and drop manager.

  3. Curiously, if I change the event in the EventSetter from my own to Mouse.MouseEnter the code that's automatically generated by the xaml designer (in MainWindow.g[.i].cs) is different.

I am not sure why 2) does not call the AddXYZHandler. MSDN seems to indicate this should work.

Finally my questions:

  1. How can I make this work? Is it possible at all?

  2. Do I better use an attached event or an attached property for my scenario?

  3. in case of properties: How do I fix the Style Setter so it converts the OnDrag string to a proper RoutedEventHandler?

  4. in case of events: What's going wrong here? Any way to fix this? I want AddXYZHandler to be called, but apparently that does not work with the style.

MainWindow.xaml:

<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        x:Class="GridTest.MainWindow"
        xmlns:local="clr-namespace:GridTest"
        Title="MainWindow" Height="350" Width="525"
        local:XYZTest.XYZ="OnXYZAttached">
    <Window.Style>
        <Style TargetType="Window">
            <EventSetter Event="local:XYZTest.XYZ" Handler="OnXYZStyle" />
        </Style>
    </Window.Style>
</Window>

MainWindow.xaml.cs:

using System.Windows;

namespace GridTest
{
    public class XYZTest
    {
        //public static readonly RoutedEvent XYZEvent = EventManager.RegisterRoutedEvent("XYZ", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(XYZTest));

        public static void AddXYZHandler(DependencyObject element, RoutedEventHandler handler)
        {
            MessageBox.Show("add handler");
        }

        public static void RemoveXYZHandler(DependencyObject element, RoutedEventHandler handler)
        {
            MessageBox.Show("remove handler");
        }
    }

    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        public void OnXYZAttached(object sender, RoutedEventArgs e)
        {
            MessageBox.Show("attached");
        }

        public void OnXYZStyle(object sender, RoutedEventArgs e)
        {
            MessageBox.Show("style");
        }
    }
}

}


New code:

<Window
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        x:Class="GridTest.MainWindow"
        x:Name="root"
        xmlns:local="clr-namespace:GridTest"
        local:XYZTest.ABC="OnXYZTopLevel"
        Title="MainWindow" Height="350" Width="525">
    <ListBox ItemsSource="{Binding}">
        <ListBox.ItemContainerStyle>
            <Style TargetType="ListBoxItem">
                <Setter Property="Background" Value="Red" />
                <Setter Property="local:XYZTest.ABC" Value="OnXYZStyle" /> 
                <!-- <Setter Property="local:XYZTest.ABC" Value="{Binding OnXYZStyleProperty, ElementName=root}" /> -->
            </Style>
        </ListBox.ItemContainerStyle>
    </ListBox>
</Window>
using System.Windows;

namespace GridTest
{
    public class XYZTest
    {
        public static readonly DependencyProperty ABCProperty = DependencyProperty.RegisterAttached("ABC", typeof(RoutedEventHandler), typeof(XYZTest), new UIPropertyMetadata(null, OnABCChanged));

        public static void SetABC(UIElement element, RoutedEventHandler value)
        {
            System.Diagnostics.Debug.WriteLine("ABC set to " + value.Method.Name);
        }

        static void OnABCChanged(DependencyObject depObj, DependencyPropertyChangedEventArgs e)
        {
            System.Diagnostics.Debug.WriteLine("ABC changed to " + ((RoutedEventHandler)e.NewValue).Method.Name);
        }
    }

    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            DataContext = new[] { "A", "B", "C" };
        }

        public void OnXYZTopLevel(object sender, RoutedEventArgs e)
        {
            MessageBox.Show("handler top level");
        }

        public void OnXYZStyle(object sender, RoutedEventArgs e)
        {
            MessageBox.Show("handler style");
        }

        public RoutedEventHandler OnXYZStyleProperty
        {
            get { return OnXYZStyle; }
        }
    }
}
4

1 回答 1

2

我完全使用Attached Properties. 如果我是你,我会避免为此使用自定义事件,因为你会被它们的参数所困扰。就个人而言,我选择了ICommand,但你也可以使用delegates.

请在下面查看Command我在拖放基类实现中使用的属性和 s 列表:

/// <summary>
/// Gets or sets the type of the drag and drop object required by the Control that the property is set on.
/// </summary>
public Type DragDropType { get; set; }

/// <summary>
/// Gets or sets the allowable types of objects that can be used in drag and drop operations.
/// </summary>
public List<Type> DragDropTypes { get; set; }

/// <summary>
/// Gets or sets the ICommand instance that will be executed when the user attempts to drop a dragged item onto a valid drop target Control.
/// </summary>
public ICommand DropCommand { get; set; }

/// <summary>
/// Gets or sets the DragDropEffects object that specifies the type of the drag and drop operations allowable on the Control that the property is set on.
/// </summary>
public DragDropEffects DragDropEffects { get; set; }

/// <summary>
/// The Point struct that represents the position on screen that the user initiated the drag and drop procedure.
/// </summary>
protected Point DragStartPosition
{
    get { return dragStartPosition; }
    set { if (dragStartPosition != value) { dragStartPosition = value; } }
}

/// <summary>
/// The UIElement object that represents the UI element that has the attached Adorner control... usually the top level view.
/// </summary>
protected UIElement AdornedUIElement
{
    get { return adornedUIElement; }
    set { if (adornedUIElement != value) { adornedUIElement = value; } }
}

AdornedUIElement属性包含一个Adorner在拖动项目时显示拖动项目的 ,但您可以选择实现。protected abstract在这个基类中,我实现了派生类必须实现的大部分拖放功能和公开的方法。例如,此方法调用该OnAdornedUIElementPreviewDragOver方法为派生类提供更改基类行为的机会:

private void AdornedUIElementPreviewDragOver(object sender, DragEventArgs e)
{
    PositionAdorner(e.GetPosition(adornedUIElement));
    OnAdornedUIElementPreviewDragOver(sender, e); // Call derived classes here <<<
    if (e.Handled) return; // to bypass base class behaviour
    HitTestResult hitTestResult = VisualTreeHelper.HitTest(adornedUIElement, e.GetPosition(adornedUIElement));
    Control controlUnderMouse = hitTestResult.VisualHit.GetParentOfType<Control>();
    UpdateDragDropEffects(controlUnderMouse, e);
    e.Handled = true;
}

/// <summary>
/// Must be overidden in derived classes to call both the UpdateDropProperties and UpdateDragDropEffects methods to provide feedback for the current drag and drop operation.
/// </summary>
/// <param name="sender">The Control that the user dragged the mouse pointer over.</param>
/// <param name="e">The DragEventArgs object that contains arguments relevant to all drag and drop events.</param>
protected abstract void OnAdornedUIElementPreviewDragOver(object sender, DragEventArgs e);

然后在我的扩展ListBoxDragDropManager课程中:

protected override void OnAdornedUIElementPreviewDragOver(object sender, DragEventArgs e)
{
    HitTestResult hitTestResult = VisualTreeHelper.HitTest(AdornedUIElement, e.GetPosition(AdornedUIElement));
    ListBox listBoxUnderMouse = hitTestResult.VisualHit.GetParentOfType<ListBox>();
    if (listBoxUnderMouse != null && listBoxUnderMouse.AllowDrop)
    {
        UpdateDropProperties(ListBoxProperties.GetDragDropType(listBoxUnderMouse), ListBoxProperties.GetDropCommand(listBoxUnderMouse));
    }
    UpdateDragDropEffects(listBoxUnderMouse, e);
    e.Handled = true;  // This bypasses base class behaviour
}

最后,它像这样简单地在 UI 中使用(RelativeSource这里的声明和狭窄的宽度使它看起来比实际更糟):

<ListBox ItemsSource="{Binding Disc.Tracks, IsAsync=True}" SelectedItem="{Binding 
    Disc.Tracks.CurrentItem}" AllowDrop="True" Attached:ListBoxProperties.
    IsDragTarget="True" Attached:ListBoxProperties.DropCommand="{Binding 
    DataContext.DropTracks, RelativeSource={RelativeSource AncestorType={x:Type 
    Views:ReleaseTracksView}}}" Attached:ListBoxProperties.DragDropTypes="{Binding 
    DataContext.DragDropTypes, RelativeSource={RelativeSource AncestorType={x:Type 
    Views:ReleaseTracksView}}}" Attached:ListBoxProperties.DragEffects="{Binding 
    DataContext.DragEffects, RelativeSource={RelativeSource AncestorType={x:Type 
    Views:ReleaseTracksView}}}">

不过,我必须说实话……这是很多工作。然而,现在我可以通过设置一些属性来实现带有视觉反馈的拖放操作,这似乎完全值得。

于 2013-09-10T09:11:35.277 回答