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

时间:2015-11-28 22:43:30

标签: c# .net wpf xaml

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

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

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

可以使用以下代码重现此问题,并执行以下操作:

  • 在HandleContentButton()上设置一个断点,当按钮时会触发 点击
  • 点击按钮1,您将看到它触发断点
  • 将按钮1拖到按钮2上,它们将切换位置
  • 点击按钮1,没有任何反应。
  • 在按钮3上拖动按钮2
  • 现在,当您单击按钮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}"/>

1 个答案:

答案 0 :(得分:0)

首先,非常感谢良好的代码示例。我只需要添加RelayCommand<T>(参见上面的编辑),这是可以原谅的,考虑到它是这种常用模式的实现,很容易忘记它实际上并不在WPF中。 (遗憾的是)很难找到如此精心呈现的问题,并且非常感激。


至于你的实际问题,有一个非常简单的解决方案。在实际执行放置操作后,将_realDragSource设置为null。例如。作为sp_Drop()方法中的最后一件事,在您致电ReleaseMouseCapture()之后,请包含以下声明:

_realDragSource = null;

这解决了代码中的问题,如果用户点击了刚刚拖动的按钮,则sp_PreviewMouseLeftButtonUp()方法会找到非空的字段,并在处理过程中调用ReleaseMouseCapture()鼠标按钮事件。这导致Button对象本身在它处理之前就退出观察鼠标事件。

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