我创建了一个UserControl
,它将绑定到我的视图模型上的动态创建按钮列表,并允许我拖放重新排序。
将按钮拖动到列表中的新位置后,我刚拖动的按钮在单击时将不再触发其命令。如果我在列表中移动另一个按钮,则现在可以单击第一个按钮,而刚刚拖动的按钮则不能。
命令窗口中没有绑定错误。
可以使用以下代码重现此问题,并执行以下操作:
我的对手的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}"/>
答案 0 :(得分:0)
首先,非常感谢良好的代码示例。我只需要添加RelayCommand<T>
(参见上面的编辑),这是可以原谅的,考虑到它是这种常用模式的实现,很容易忘记它实际上并不在WPF中。 (遗憾的是)很难找到如此精心呈现的问题,并且非常感激。
至于你的实际问题,有一个非常简单的解决方案。在实际执行放置操作后,将_realDragSource
设置为null
。例如。作为sp_Drop()
方法中的最后一件事,在您致电ReleaseMouseCapture()
之后,请包含以下声明:
_realDragSource = null;
这解决了代码中的问题,如果用户点击了刚刚拖动的按钮,则sp_PreviewMouseLeftButtonUp()
方法会找到非空的字段,并在处理过程中调用ReleaseMouseCapture()
鼠标按钮事件。这导致Button
对象本身在它处理之前就退出观察鼠标事件。
换句话说,您的Button
对象没有丢失对命令的绑定(实际上您可以在调试器中检查它们并看到它们的配置永远不会更改)。他们只是忽略了执行该命令的用户输入。