为什么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