为什么WPF列表框会在鼠标按钮上改变选择而不是按下按钮?

我之前从未注意到这一点,但是当鼠标停止时,WPF ListBox似乎更改了其SelectedItem,但尚未发布。 作为一个简单的例子,只需创建一个包含多个ListBoxItem的简单ListBox,如下所示:

 Hello World ListBox Test  

启动你的应用程序,按下鼠标按钮(不要释放它!)并移动鼠标。 SelectedItem将随着鼠标移动而改变。 这说明了一个更大的问题(对我来说,至少), 一旦你按下鼠标就会设置ListBox的SelectedItem,而不是在鼠标出现时设置。 通常这不是问题,但在我的情况下,我想在我的ListBox中的项目上进行拖放操作,而不会明确选择项目。

我想我唯一的办法就是构建一个自定义的ItemsControl或Selector,其选择风格类似于ListBox,所以我的问题更多,为什么ListBox以这种方式工作? 有没有人对此有任何见解?

这可能有点偏离主题,但我只是遇到了类似的问题。 我不想拖放但我想在MouseUp上的ListBox上选择项目,而不是MouseDown。 虽然Sheena伪代码可能会给出一些暗示,但在我找到正确的解决方案之前,我还需要一段时间。 所以这是我解决问题的方法。

 public class ListBoxSelectionItemChangedOnMouseUp : ListBox { protected override void OnMouseUp(MouseButtonEventArgs e) { if (e.ChangedButton == MouseButton.Left) { DependencyObject obj = this.ContainerFromElement((Visual)e.OriginalSource); if (obj != null) { FrameworkElement element = obj as FrameworkElement; if (element != null) { ListBoxItem item = element as ListBoxItem; if (item != null && this.Items.Contains(item)) { this.SelectedItem = item; } } } } } protected override void OnPreviewMouseDown(MouseButtonEventArgs e) { e.Handled = true; } } 

我也想只选择鼠标左键。 在拖放的情况下,必须在鼠标按下事件中保存所选项目,然后在鼠标按下事件中使用它。 我希望这会对某人有所帮助。

我个人更喜欢MVVM和附加属性来调整元素的行为。

此外,当绑定ItemsSource属性时,Tomas Kosar提出的解决方案似乎不起作用。

这是我目前使用的(C#7语法)

 public static class SelectorBehavior { #region bool ShouldSelectItemOnMouseUp public static readonly DependencyProperty ShouldSelectItemOnMouseUpProperty = DependencyProperty.RegisterAttached( "ShouldSelectItemOnMouseUp", typeof(bool), typeof(SelectorBehavior), new PropertyMetadata(default(bool), HandleShouldSelectItemOnMouseUpChange)); public static void SetShouldSelectItemOnMouseUp(DependencyObject element, bool value) { element.SetValue(ShouldSelectItemOnMouseUpProperty, value); } public static bool GetShouldSelectItemOnMouseUp(DependencyObject element) { return (bool)element.GetValue(ShouldSelectItemOnMouseUpProperty); } private static void HandleShouldSelectItemOnMouseUpChange( DependencyObject d, DependencyPropertyChangedEventArgs e) { if (d is Selector selector) { selector.PreviewMouseDown -= HandleSelectPreviewMouseDown; selector.MouseUp -= HandleSelectMouseUp; if (Equals(e.NewValue, true)) { selector.PreviewMouseDown += HandleSelectPreviewMouseDown; selector.MouseUp += HandleSelectMouseUp; } } } private static void HandleSelectMouseUp(object sender, MouseButtonEventArgs e) { var selector = (Selector)sender; if (e.ChangedButton == MouseButton.Left && e.OriginalSource is Visual source) { var container = selector.ContainerFromElement(source); if (container != null) { var index = selector.ItemContainerGenerator.IndexFromContainer(container); if (index >= 0) { selector.SelectedIndex = index; } } } } private static void HandleSelectPreviewMouseDown(object sender, MouseButtonEventArgs e) { e.Handled = e.ChangedButton == MouseButton.Left; } #endregion } 

现在您可以将它应用于任何ListBox(或Selector派生类),例如

  

一种似乎对我有用的替代方法:

 private class SelectOnMouseUpListViewItem: ListViewItem { protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e) { if (IsSelected) e.Handled = true; base.OnMouseLeftButtonDown(e); } protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e) { if (!IsSelected) base.OnMouseLeftButtonDown(e); base.OnMouseLeftButtonUp(e); } } protected override DependencyObject GetContainerForItemOverride() // in ListView { return new SelectOnMouseUpListViewItem(); } 

使用reflection永远不是理想的,但这里有一些适合我的代码。

 public class SelectOnMouseUpListBoxItem : ListBoxItem { protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e) { var received = _receivedMouseDown; _receivedMouseDown = null; // validate that the mouse left button down event was called on this list box item if (received != this) return; var parent = WpfUtility.FindVisualParent(this); parent.NotifyListItemClickedImp(this, e.ChangedButton); base.OnMouseLeftButtonUp(e); } private SelectOnMouseUpListBoxItem _receivedMouseDown; protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e) { _receivedMouseDown = this; e.Handled = true; base.OnMouseLeftButtonDown(e); } } public class SelectOnMouseUpListBox : ListBox { static SelectOnMouseUpListBox() { _notifyListItemClickedMethodInfo = typeof(ListBox).GetMethod("NotifyListItemClicked", BindingFlags.Instance | BindingFlags.NonPublic); if (_notifyListItemClickedMethodInfo == null) throw new NotSupportedException("Failed to get NotifyListItemClicked method info by reflection"); } private static readonly MethodInfo _notifyListItemClickedMethodInfo; protected override DependencyObject GetContainerForItemOverride() { return new SelectOnMouseUpListBoxItem(); } public void NotifyListItemClickedImp(ListBoxItem item, MouseButton button) { _notifyListItemClickedMethodInfo.Invoke(this, new object[] {item, button}); } } 

这保留了通常的选择行为,具体取决于ListBox的SelectionMode(Single,Multiple,Extended)。

我假设您已经尝试创建一个新的鼠标按下事件,它可以执行您想要的操作,并以这种方式覆盖标准行为…这里有一些伪代码应该可以解决这个问题:

 ListBoxItem selected; on_any_event_that_should_change_whats_selected() { selected=whatever_you_want_selected; } on_selection_changed() { theListBox.selectedItem=selected; } 

我的wpf技巧有点生疏,但我认为你需要存储项目然后生成一个容器,所以这个伪代码是一个巨大的过度简化,但算法应该做到这一点。

我发现ListView有类似的问题。 我无法开始拖放某个项目,而不会失去对另一个项目的选择。

我通过从ListView派生并处理PreviewMouseDown事件来解决它。 而不是这个,我在MouseUp上选择了项目。

其余的拖动逻辑使用Reactive Extensions实现。

ListBox类似于ListView ,因此您可能只能从ListBox派生它并且它将起作用。

码:

 public class DragDroppableListView : ListView { private IDisposable _subscription; public DragDroppableListView() { Loaded += OnControlLoaded; Unloaded += OnControlUnloaded; } protected override void OnMouseUp(MouseButtonEventArgs e) { if (e.ChangedButton != MouseButton.Left) return; var obj = ContainerFromElement((Visual)e.OriginalSource); if (obj == null) return; var element = obj as FrameworkElement; if (element == null) return; var item = element as ListBoxItem; if (item == null) return; // select item item.IsSelected = true; } private void OnControlUnloaded(object sender, RoutedEventArgs e) { if (_subscription != null) _subscription.Dispose(); } private void OnControlLoaded(object sender, RoutedEventArgs e) { var mouseDown = Observable.FromEventPattern(this, "PreviewMouseDown"); var mouseUp = Observable.FromEventPattern(this, "MouseUp"); var mouseMove = Observable.FromEventPattern(this, "MouseMove"); _subscription = mouseDown .Where(a => a.EventArgs.LeftButton == MouseButtonState.Pressed) .Where(o => !IsScrollBar(o.EventArgs)) .Do(o => o.EventArgs.Handled = true) // not allow listview select on mouse down .Select(ep => ep.EventArgs.GetPosition(this)) .SelectMany(md => mouseMove .TakeWhile(ep => ep.EventArgs.LeftButton == MouseButtonState.Pressed) .Where(ep => IsMinimumDragSeed(md, ep.EventArgs.GetPosition(this))) .TakeUntil(mouseUp)) .ObserveOnDispatcher() .Subscribe(_ => OnDrag()); } private void OnDrag() { var item = GetItemUnderMouse(); if (item == null) return; DragDrop.DoDragDrop( this, new DataObject(typeof(object), item), DragDropEffects.Copy | DragDropEffects.Move); } private ListViewItem GetItemUnderMouse() { return Items.Cast() .Select(item => ItemContainerGenerator.ContainerFromItem(item)) .OfType() .FirstOrDefault(lvi => lvi.IsMouseOver); } private static bool IsMinimumDragSeed(Point start, Point end) { return Math.Abs(end.X - start.X) >= SystemParameters.MinimumHorizontalDragDistance || Math.Abs(end.Y - start.Y) >= SystemParameters.MinimumVerticalDragDistance; } private bool IsScrollBar(MouseEventArgs args) { var res = VisualTreeHelper.HitTest(this, args.GetPosition(this)); if (res == null) return false; var depObj = res.VisualHit; while (depObj != null) { if (depObj is ScrollBar) return true; // VisualTreeHelper works with objects of type Visual or Visual3D. // If the current object is not derived from Visual or Visual3D, // then use the LogicalTreeHelper to find the parent element. if (depObj is Visual || depObj is System.Windows.Media.Media3D.Visual3D) depObj = VisualTreeHelper.GetParent(depObj); else depObj = LogicalTreeHelper.GetParent(depObj); } return false; } }