WPF Drag & drop из ListBox с множественным выбором - PullRequest
22 голосов
/ 12 октября 2009

У меня почти все работает, кроме одной маленькой раздражающей вещи ...

Поскольку выбор ListBox происходит при нажатии мышью вниз, если вы начинаете перетаскивание мышью вниз при выборе последнего элемента для перетаскивания, он работает нормально, но если вы выбрали все элементы, которые нужно перетащить в первую очередь, а затем нажмите на выделение, чтобы начать перетаскивая его, тот, на который вы щелкаете, становится невыбранным и остается после перетаскивания.

Есть мысли о том, как лучше обойти это?

<DockPanel LastChildFill="True">
    <ListBox ItemsSource="{Binding SourceItems}"
             SelectionMode="Multiple"
             PreviewMouseLeftButtonDown="HandleLeftButtonDown"
             PreviewMouseLeftButtonUp="HandleLeftButtonUp"
             PreviewMouseMove="HandleMouseMove"
             MultiSelectListboxDragDrop:ListBoxExtension.SelectedItemsSource="{Binding SelectedItems}"/>
    <ListBox ItemsSource="{Binding DestinationItems}"
             AllowDrop="True"
             Drop="DropOnToDestination"/>
<DockPanel>

...

public partial class Window1
{
    private bool clickedOnSourceItem;

    public Window1()
    {
        InitializeComponent();
        DataContext = new WindowViewModel();
    }

    private void DropOnToDestination(object sender, DragEventArgs e)
    {
        var viewModel = (WindowViewModel)
                            e.Data.GetData(typeof(WindowViewModel));
        viewModel.CopySelectedItems();
    }

    private void HandleLeftButtonDown(object sender, MouseButtonEventArgs e)
    {
        var sourceElement = (FrameworkElement)sender;
        var hitItem = sourceElement.InputHitTest(e.GetPosition(sourceElement))
                      as FrameworkElement;

        if(hitItem != null)
        {
            clickedOnSourceItem = true;
        }
    }

    private void HandleLeftButtonUp(object sender, MouseButtonEventArgs e)
    {
        clickedOnSourceItem = false;
    }

    private void HandleMouseMove(object sender, MouseEventArgs e)
    {
        if(clickedOnSourceItem)
        {
            var sourceItems = (FrameworkElement)sender;
            var viewModel = (WindowViewModel)DataContext;

            DragDrop.DoDragDrop(sourceItems, viewModel, DragDropEffects.Move);
            clickedOnSourceItem = false;
        }
    }
}

Ответы [ 4 ]

13 голосов
/ 23 октября 2009

Итак ... став счастливым обладателем значка с завитками, я вернулся к этому, чтобы попытаться найти способ обойти это. ; -)

Я не уверен, что мне нравится решение, поэтому я все еще очень открыт для любых более подходящих подходов.

По сути, в итоге я запомнил, на что последний раз нажимали ListBoxItem, и затем убедитесь, что он добавляется к выбранным элементам перед перетаскиванием. Это также означало анализ того, насколько далеко мышь перемещается перед началом перетаскивания, поскольку нажатие на выбранный элемент для отмены выбора может иногда привести к его повторному выделению, если отскок мыши запустил небольшую операцию перетаскивания.

Наконец, я добавил горячее отслеживание к элементам списка, поэтому, если вы щелкнете мышью по выбранному элементу, он не будет выбран, но вы все равно получите некоторую обратную связь, чтобы указать, что он будет включен в операцию перетаскивания.

private void HandleLeftButtonDown(object sender, MouseButtonEventArgs e)
{
    var source = (FrameworkElement)sender;
    var hitItem = source.InputHitTest(e.GetPosition(source)) as FrameworkElement;
    hitListBoxItem = hitItem.FindVisualParent<ListBoxItem>();
    origPos = e.GetPosition(null);
}
private void HandleLeftButtonUp(object sender, MouseButtonEventArgs e)
{
    hitListBoxItem = null;
}
private void HandleMouseMove(object sender, MouseEventArgs e)
{
    if (ShouldStartDrag(e))
    {
        hitListBoxItem.IsSelected = true;

        var sourceItems = (FrameworkElement)sender;
        var viewModel = (WindowViewModel)DataContext;
        DragDrop.DoDragDrop(sourceItems, viewModel, DragDropEffects.Move);
        hitListBoxItem = null;
    }
}
private bool ShouldStartDrag(MouseEventArgs e)
{
    if (hitListBoxItem == null)
        return false;

    var curPos = e.GetPosition(null);
    return
  Math.Abs(curPos.Y-origPos.Y) > SystemParameters.MinimumVerticalDragDistance ||
  Math.Abs(curPos.X-origPos.X) > SystemParameters.MinimumHorizontalDragDistance;
}

Изменения XAML включают горячее отслеживание ...

<Style TargetType="ListBoxItem">
    <Setter Property="Margin" Value="1"/>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type ListBoxItem}">
                <Grid>
                  <Border Background="{TemplateBinding Background}" />
                  <Border Background="#BEFFFFFF" Margin="1">
                    <Grid>
                      <Grid.RowDefinitions>
                        <RowDefinition /><RowDefinition />
                      </Grid.RowDefinitions>
                      <Border Margin="1" Grid.Row="0" Background="#57FFFFFF" />
                    </Grid>
                  </Border>
                  <ContentPresenter Margin="8,5" />
                </Grid>
                <ControlTemplate.Triggers>
                  <Trigger Property="IsSelected" Value="True">
                    <Setter Property="Background" Value="PowderBlue" />
                  </Trigger>
                  <MultiTrigger>
                    <MultiTrigger.Conditions>
                      <Condition Property="IsMouseOver" Value="True" />
                      <Condition Property="IsSelected" Value="False"/>
                    </MultiTrigger.Conditions>
                    <Setter Property="Background" Value="#5FB0E0E6" />
                  </MultiTrigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>
12 голосов
/ 18 апреля 2014

Я нашел очень простой способ включить проводник Windows, такой как поведение перетаскивания, когда выбрано несколько элементов. Решение заменяет обычный ListBox на небольшую производную прокладку, которая заменяет ListBoxItem на более интеллектуальную версию. Таким образом, мы можем инкапсулировать состояние щелчка на нужном уровне и вызывать механизм защищенного выбора ListBox. Вот соответствующий класс. Полный пример см. В моем репозитории на github .

.
public class ListBoxEx : ListBox
{
    protected override DependencyObject GetContainerForItemOverride()
    {
        return new ListBoxItemEx();
    }

    class ListBoxItemEx : ListBoxItem
    {
        private bool _deferSelection = false;

        protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
        {
            if (e.ClickCount == 1 && IsSelected)
            {
                // the user may start a drag by clicking into selected items
                // delay destroying the selection to the Up event
                _deferSelection = true;
            }
            else
            {
                base.OnMouseLeftButtonDown(e);
            }
        }

        protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e)
        {
            if (_deferSelection)
            {
                try
                {
                    base.OnMouseLeftButtonDown(e);
                }
                finally
                {
                    _deferSelection = false;
                }
            }
            base.OnMouseLeftButtonUp(e);
        }

        protected override void OnMouseLeave(MouseEventArgs e)
        {
            // abort deferred Down
            _deferSelection = false;
            base.OnMouseLeave(e);
        }
    }
}
2 голосов
/ 21 ноября 2013

Я удивлен, что разница в поведении между ListBox и Windows Explorer не была устранена через 4 года в трех основных обновлениях .NET Framework.

Я столкнулся с этой проблемой еще в Silverlight 3. В итоге я переопределил обработчик событий мыши и мыши, чтобы полностью имитировать поведение Windows Explorer.

У меня больше нет исходного кода, но логика должна быть:

Когда мышь нажата

  • если целевой элемент не выбран, очистить существующий выбор
    • если клавиша Ctrl не нажата, добавить целевой элемент в выделение
    • если клавиша Shift не нажата
      • если есть ранее выбранный элемент, добавьте все элементы между целевым и предыдущим элементом к выбору
      • еще только добавить целевой объект к выбору
  • если выбран целевой элемент, отмените выбор, только если клавиша Ctrl нажата

При наведении мыши (на тот же предмет)

  • если выбран целевой элемент
    • если клавиша Ctrl не нажата, убрать элемент из выделения
    • если клавиша Shift не нажата
      • если есть ранее выбранный элемент, удалить все элементы между целевым элементом и предыдущим элементом из выбора
      • еще только удалить целевой объект из выбора

Однако Это действительно должно быть задачей Microsoft по обновлению поведения, чтобы оно соответствовало операционной системе и было более интуитивным. Я отправил сообщение об ошибке в Microsoft, если кто-то хочет проголосовать за него: http://connect.microsoft.com/VisualStudio/feedback/details/809192/

2 голосов
/ 10 июня 2013

Один из вариантов - запретить ListBox или ListView удалять выбранные элементы, пока не будет запущен MouseLeftButtonUp. Пример кода:

    List<object> removedItems = new List<object>();

    private void ListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        if (e.RemovedItems.Count > 0)
        {
            ListBox box = sender as ListBox;
            if (removedItems.Contains(e.RemovedItems[0]) == false)
            {
                foreach (object item in e.RemovedItems)
                {
                    box.SelectedItems.Add(item);
                    removedItems.Add(item);
                }
            }
        }
    }

    private void ListBox_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
    {
        if (removedItems.Count > 0)
        {
            ListBox box = sender as ListBox;
            foreach (object item in removedItems)
            {
                box.SelectedItems.Remove(item);
            }
            removedItems.Clear();
        }
    }
...