WPF Drag & drop depuis ListBox avec SelectionMode Plusieurs

j'ai presque obtenu ce travail à part d'une petite chose ennuyante...

parce que la sélection de la boîte de liste se produit sur la souris vers le bas, si vous commencez la traînée avec la souris vers le bas lors de la sélection du dernier élément pour la traînée il fonctionne bien, mais si vous sélectionnez tous les éléments pour la traînée d'abord et puis cliquez sur la sélection pour commencer à la traîner, celui que vous cliquez sur obtient non sélectionné et laissé derrière après la traînée.

des idées sur la meilleure façon de se déplacer cette?

<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;
        }
    }
}
21
demandé sur Michael Myers 2009-10-12 13:42:38

4 réponses

...étant devenu le fier propriétaire d'un badge tumbleweed, je me suis remis à cela pour essayer de trouver un moyen de l'contourner. ; -)

Je ne suis pas sûr d'aimer la solution donc je suis toujours très ouvert à toute meilleure approche.

fondamentalement, ce que j'ai fini par faire est de me rappeler quel ListBoxItem a été cliqué pour la dernière fois et ensuite s'assurer que cela est ajouté aux éléments sélectionnés avant une traînée. Cela signifie aussi regarder à quelle distance la souris se déplace avant de commencer une traînée-parce que cliquer sur un élément sélectionné pour désélectionner il pourrait parfois avoir pour résultat de se faire sélectionner à nouveau si rebond de la souris a commencé une petite opération de traînée.

finalement, j'ai ajouté un suivi à chaud aux éléments de la liste, donc, si vous cliquez sur un élément sélectionné, il ne sera pas sélectionné, mais vous obtiendrez quand même quelques commentaires indiquant qu'il sera inclus dans l'opération de glisser-déposer.

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;
}

changements XAML pour inclure le suivi à chaud...

<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>
13
répondu IanR 2012-09-21 19:51:18

j'ai trouvé un moyen très simple d'activer le comportement de drag/drop de Windows Explorer lorsque plusieurs éléments sont sélectionnés. La solution remplace le commun ListBox avec un petit shim dérivé qui remplace le ListBox. Voici la classe. Pour un exemple complet, consultez mon repo 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);
        }
    }
}
11
répondu David Schmitt 2014-04-18 09:26:46

une option serait de ne pas autoriser ListBox ou ListView à supprimer les éléments sélectionnés jusqu'à ce que MouseLeftButtonUp soit déclenché. Exemple de code:

    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();
        }
    }
2
répondu user2471271 2013-06-10 14:22:00

je suis surpris que la différence de comportement entre ListBox et L'Explorateur Windows n'ait pas été prise en compte après 4 ans à travers 3 Mises à jour majeures du framework .net.

j'ai rencontré ce problème à Silverlight 3. J'ai fini par substitution de la souris et de la souris jusqu'gestionnaire d'événement pour simuler intégralement l'Explorateur Windows comportement.

Je n'ai plus le code source mais la logique devrait être:

Quand la souris vers le bas

  • si l'élément cible n'est pas activée, désactivez-la sélection existante
    • si la touche Ctrl est désactivée, ajouter l'élément cible à la sélection
    • si la touche Shift est désactivée
      • s'il y a un élément déjà sélectionné, ajoutez tous les éléments entre l'élément cible et l'élément précédent à la sélection
      • sinon ajouter seulement l'élément cible à la sélection
  • si l'élément cible est sélectionné désélectionner seulement si Ctrl la clé est en baisse

Quand la souris (sur le même élément)

  • si l'élément cible est sélectionnée
    • si la touche Ctrl est enfoncée, supprimer l'élément de la sélection
    • si la touche Shift est désactivée
      • s'il y a un élément déjà sélectionné, supprimez tous les éléments entre l'élément cible et l'élément précédent de la sélection
      • sinon, supprimer l'élément cible sélection

Toutefois Cela devrait vraiment être la tâche de Microsoft de mettre à jour le comportement pour être cohérent avec le système d'exploitation et d'être plus intuitif. Je l'ai soumis comme un bug à Microsoft si n'importe quel corps veut voter pour lui: http://connect.microsoft.com/VisualStudio/feedback/details/809192/

2
répondu Leon Zhou 2013-11-21 05:17:43