使用拖放重新排序后,按钮将丢失其命令绑定

本文关键字:按钮 绑定 命令 拖放 新排序 排序 | 更新日期: 2023-09-27 18:24:50

我创建了一个UserControl,它将绑定到视图模型上动态创建的按钮列表,并允许我对它们进行拖放重新排序。

在我将按钮拖动到列表中的新位置后,我刚刚拖动的按钮在单击时将不再触发其命令。如果我移动列表中的另一个按钮,第一个按钮现在可以单击,而刚才拖动的按钮则不能。

命令窗口中没有绑定错误。

可以使用下面的代码和以下操作重现问题:

  • 在HandleContentButton()上放置一个断点,当按钮被单击
  • 单击按钮1,您将看到它触发了断点
  • 将按钮1拖动到按钮2上,它们将切换位置
  • 单击按钮1,不会发生任何事情
  • 将按钮2拖到按钮3上
  • 现在,当您单击按钮1时,它工作,但按钮2不工作

我的控件的XAML:

<UserControl x:Class="SafetyApplication.Controls.SortableItemsControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:SafetyApplication.Controls"
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300">
    <Grid>
        <ItemsControl Grid.Row="2" Grid.Column="0" IsTabStop="False" Name="icsp"
                      ItemsSource="{Binding Path=ContentButtons}">
            <ItemsControl.ItemsPanel>
                <ItemsPanelTemplate>
                    <StackPanel x:Name="sp" AllowDrop="True" 
                                PreviewMouseLeftButtonDown="sp_PreviewMouseLeftButtonDown" 
                                PreviewMouseLeftButtonUp="sp_PreviewMouseLeftButtonUp" 
                                PreviewMouseMove="sp_PreviewMouseMove"
                                DragEnter="sp_DragEnter" Drop="sp_Drop"/>
                </ItemsPanelTemplate>
            </ItemsControl.ItemsPanel>
        </ItemsControl>
    </Grid>
</UserControl>

控件背后的代码:

 public partial class SortableItemsControl : UserControl
    {
        #region Fields
        private bool _isDown;
        private bool _isDragging;
        private Point _startPoint;
        private UIElement _realDragSource;
        private readonly UIElement _dummyDragSource = new UIElement();
        #endregion
        #region Constructor
        public SortableItemsControl()
        {
            InitializeComponent();
        }
        #endregion
        #region Dependency Property
        #region Item Source
        public static readonly DependencyProperty ItemSourceProperty =
            DependencyProperty.Register("ItemSource", typeof(IEnumerable<UIElement>), typeof(SortableItemsControl),
                new FrameworkPropertyMetadata(null, new PropertyChangedCallback(OnItemSourceChanged)));
        public IEnumerable<UIElement> ItemSource
        {
            get { return this.GetValue(ItemSourceProperty) as IEnumerable<UIElement>; }
            set { this.SetValue(ItemSourceProperty, value); }
        }
        private static void OnItemSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var sortableItemsControl = d as SortableItemsControl;
            if (sortableItemsControl?.ItemSource != null)
            {
                sortableItemsControl.icsp.ItemsSource = sortableItemsControl.ItemSource;
                sortableItemsControl.icsp.Items.Refresh();
            }
        }
        #endregion
        #endregion
        #region Drag Drop Logic
        private void sp_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            var sp = FindChild<StackPanel>(Application.Current.MainWindow, "sp");
            if (e.Source != sp)
            {
                _isDown = true;
                _startPoint = e.GetPosition(sp);
            }
        }
        private void sp_PreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
        {
            _isDown = false;
            _isDragging = false;
            if (_realDragSource != null) _realDragSource.ReleaseMouseCapture();
        }
        private void sp_PreviewMouseMove(object sender, MouseEventArgs e)
        {
            if (_isDown)
            {
                var sp = FindChild<StackPanel>(Application.Current.MainWindow, "sp");
                if ((_isDragging == false) &&
                    ((Math.Abs(e.GetPosition(sp).X - _startPoint.X) > SystemParameters.MinimumHorizontalDragDistance) ||
                     (Math.Abs(e.GetPosition(sp).Y - _startPoint.Y) > SystemParameters.MinimumVerticalDragDistance)))
                {
                    _isDragging = true;
                    _realDragSource = e.Source as UIElement;
                    if (_realDragSource != null) _realDragSource.CaptureMouse();
                    DragDrop.DoDragDrop(_dummyDragSource, new DataObject("UIElement", e.Source, true),
                        DragDropEffects.Move);
                }
            }
        }
        private void sp_DragEnter(object sender, DragEventArgs e)
        {
            if (e.Data.GetDataPresent("UIElement"))
            {
                e.Effects = DragDropEffects.Move;
            }
        }
        private void sp_Drop(object sender, DragEventArgs e)
        {
            if (e.Data.GetDataPresent("UIElement"))
            {
                var sp = FindChild<StackPanel>(this, "sp");
                UIElement droptarget = e.Source as UIElement;
                int droptargetIndex = -1, i = 0;
                foreach (UIElement element in sp.Children)
                {
                    if (element.Equals(droptarget))
                    {
                        droptargetIndex = i;
                        break;
                    }
                    i++;
                }
                if (droptargetIndex != -1)
                {
                    var elements = icsp.ItemsSource as IEnumerable<UIElement>;
                    if (elements != null)
                    {
                        var items = elements.ToList();
                        UIElement button = _realDragSource;
                        items.Remove(button);
                        items.Insert(droptargetIndex, button);
                        icsp.ItemsSource = items;
                    }
                    icsp.Items.Refresh();
                }
                _isDown = false;
                _isDragging = false;
                _realDragSource.ReleaseMouseCapture();
            }
        }
        #endregion
        #region Find Child
        /// <summary>
        /// Finds a Child of a given item in the visual tree. 
        /// </summary>
        /// <param name="parent">A direct parent of the queried item.</param>
        /// <typeparam name="T">The type of the queried item.</typeparam>
        /// <param name="childName">x:Name or Name of child. </param>
        /// <returns>The first parent item that matches the submitted type parameter. 
        /// If not matching item can be found, 
        /// a null parent is being returned.</returns>
        public static T FindChild<T>(DependencyObject parent, string childName)
           where T : DependencyObject
        {
            // Confirm parent and childName are valid. 
            if (parent == null) return null;
            T foundChild = null;
            int childrenCount = VisualTreeHelper.GetChildrenCount(parent);
            for (int i = 0; i < childrenCount; i++)
            {
                var child = VisualTreeHelper.GetChild(parent, i);
                // If the child is not of the request child type child
                T childType = child as T;
                if (childType == null)
                {
                    // recursively drill down the tree
                    foundChild = FindChild<T>(child, childName);
                    // If the child is found, break so we do not overwrite the found child. 
                    if (foundChild != null) break;
                }
                else if (!string.IsNullOrEmpty(childName))
                {
                    var frameworkElement = child as FrameworkElement;
                    // If the child's name is set for search
                    if (frameworkElement != null && frameworkElement.Name == childName)
                    {
                        // if the child's name is of the request name
                        foundChild = (T)child;
                        break;
                    }
                }
                else
                {
                    // child element found.
                    foundChild = (T)child;
                    break;
                }
            }
            return foundChild;
        }
        #endregion
    }

我的视图模型:

 public class AdminViewModel
    {
        private readonly DataTransferManager _dataManager;
        public AdminViewModel(DataTransferManager dataManager)
        {
            _dataManager = dataManager;
            ContentButtonCommand = new RelayCommand<string>(HandleContentButton);
            BuildContentTable();
        }
        public ICommand ContentButtonCommand { get; set; }
        public IEnumerable<Button> ContentButtons { get; set; }
        private void HandleContentButton(object buttonDefinition)
        {
            //do stuff
        }
        private void BuildContentTable()
        {
            List<Button> buttons = new List<Button>();
            Button button1 = new Button();
            button1.Content = "Test 1";
            button1.Command = ContentButtonCommand;
            button1.CommandParameter = "Test 1";
            button1.Margin = new Thickness(5);
            buttons.Add(button1);
            Button button2 = new Button();
            button2.Content = "Test 2";
            button2.Command = ContentButtonCommand;
            button2.CommandParameter = "Test 2";
            button2.Margin = new Thickness(5);
            buttons.Add(button2);
            Button button3 = new Button();
            button3.Content = "Test 3";
            button3.Command = ContentButtonCommand;
            button3.CommandParameter = "Test 3";
            button3.Margin = new Thickness(5);
            buttons.Add(button3);

            ContentButtons = buttons;
        }
    }

一个退化的RelayCommand<T>实现足以支持上述视图模型:

class RelayCommand<T> : ICommand
{
    private readonly Action<T> _handler;
    public RelayCommand(Action<T> handler)
    {
        _handler = handler;
    }
    public bool CanExecute(object parameter)
    {
        return true;
    }
#pragma warning disable 0067
    public event EventHandler CanExecuteChanged;
#pragma warning restore 0067
    public void Execute(object parameter)
    {
        _handler((T)parameter);
    }
}

并使用控制:

<controls:SortableItemsControl ItemSource="{Binding Path=ContentButtons}"/>

使用拖放重新排序后,按钮将丢失其命令绑定

首先,非常感谢您提供的优秀代码示例。我只需要添加RelayCommand<T>(见上面的编辑),考虑到它是一个常用模式的实现,很容易忘记它实际上不在WPF中,这是可以原谅的。(遗憾的是)很少有人能如此仔细地提出一个问题,我们对此深表感谢。


至于你的实际问题,有一个很容易解决的办法。在实际执行删除操作之后,将_realDragSource设置为null。例如,作为sp_Drop()方法中的最后一件事,在调用ReleaseMouseCapture()之后,包括以下语句:

_realDragSource = null;

这修复了代码中的问题,在该问题中,如果用户单击了您刚刚拖动的按钮,则sp_PreviewMouseLeftButtonUp()方法会发现该字段为非null,并在处理鼠标向上按钮事件期间调用ReleaseMouseCapture()。这导致Button对象本身在处理鼠标事件之前停止观察鼠标事件

换句话说,Button对象并没有失去与命令的绑定(事实上,您可以在调试器中检查它们,并看到它们的配置从未更改)。他们只是忽略了本来会执行命令的用户输入。