我之前从未注意到这一点,但是当鼠标关闭但是尚未发布时,WPF ListBox似乎更改了其SelectedItem。作为一个简单的例子,只需创建一个包含多个ListBoxItem的简单ListBox,如下所示:
<ListBox>
<ListBoxItem>Hello</ListBoxItem>
<ListBoxItem>World</ListBoxItem>
<ListBoxItem>ListBox</ListBoxItem>
<ListBoxItem>Test</ListBoxItem>
</ListBox>
启动你的应用程序,按下鼠标按钮(不要释放它!)并移动鼠标。 SelectedItem将随着鼠标移动而改变。这说明了一个更大的问题(至少对我来说),当你鼠标按下时,ListBox的SelectedItem将很快设置为 ,而不是在鼠标启动时。通常这不是问题,但在我的情况下,我想启用拖放功能。放下我的ListBox中的项目,而不明确选择项目。
我想我唯一的办法就是构建一个自定义的ItemsControl或Selector,其选择风格类似于ListBox,所以我的问题更多,为什么ListBox以这种方式工作?有没有人对此有任何见解?
答案 0 :(得分:9)
我个人更喜欢使用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派生类),例如
<ListBox ItemsSource="{Binding ViewModelItems}"
SelectedItem="{Binding SelectedViewModelItem}"
ui:SelectorBehavior.ShouldSelectItemOnMouseUp="True" />
答案 1 :(得分:6)
这可能有点偏离主题,但我只是遇到了类似的问题。我不想拖放但我想在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;
}
}
我也想只选择鼠标左键。在拖放的情况下,必须在鼠标按下事件中保存所选项目,然后在鼠标按下事件中使用它。我希望这会对某人有所帮助。
答案 2 :(得分:1)
我假设您已经尝试创建一个新的鼠标按下事件,它可以执行您想要的操作,并以这种方式覆盖标准行为......这里有一些伪代码应该可以解决这个问题:
ListBoxItem selected;
on_any_event_that_should_change_whats_selected()
{
selected=whatever_you_want_selected;
}
on_selection_changed()
{
theListBox.selectedItem=selected;
}
我的wpf技巧有点生疏,但我认为你需要存储该项目然后生成一个容器,所以这个伪代码是一个非常简单的过程,但算法应该可以解决这个问题。
答案 3 :(得分:1)
似乎对我有用的替代方法:
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();
}
答案 4 :(得分:0)
我发现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<MouseButtonEventArgs>(this, "PreviewMouseDown");
var mouseUp = Observable.FromEventPattern<MouseEventArgs>(this, "MouseUp");
var mouseMove = Observable.FromEventPattern<MouseEventArgs>(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<object>()
.Select(item => ItemContainerGenerator.ContainerFromItem(item))
.OfType<ListViewItem>()
.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;
}
}
答案 5 :(得分:0)
使用反射从来都不是理想的选择,但是下面的一些代码对我有用。
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<SelectOnMouseUpListBox>(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(单,多,扩展)。
答案 6 :(得分:0)
Tomas' answer很好,但是可能需要对其进行调整才能起作用。我必须进行一些转换,不一定很漂亮,但是可以完成工作。
这应该很简单,可以更新您的需求。
private void MembersList_MouseUp(object sender, MouseButtonEventArgs e) {
if (e.ChangedButton == MouseButton.Left) {
DependencyObject obj = myListBox.ContainerFromElement((Visual)e.OriginalSource);
if (obj != null) {
FrameworkElement element = obj as FrameworkElement;
if (element != null) {
ListBoxItem item = element as ListBoxItem;
if (item != null && ((MyViewModel)myListBox.DataContext).myListObject.Contains((MyListItem)item.DataContext)) {
myListBox.SelectedItem = item.DataContext;
}
}
}
}
}