I've found similar problem with ListView
. I wasn't able to start drag-drop of some item, without loosing selection of another.
I solved it by deriving from ListView
, and handling PreviewMouseDown
event. Instead of this, I selected item on MouseUp.
Rest of drag logic is implemented using Reactive Extensions.
ListBox
is similar to ListView
, so you might be able to just derive from ListBox
and it'll work.
Code:
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;
}
}