WPF C#: Rearrange items in listbox via drag and drop

前端 未结 7 1051
你的背包
你的背包 2020-11-27 12:08

I am trying to figure out how to move the items in a pre-populated listbox up and down via mouse drags.

I have looked at the Control.DoDragDrop method from microsof

相关标签:
7条回答
  • Repair code :

    private void listbox1_Drop(object sender, DragEventArgs e)
    {
        if (sender is ListBoxItem)
        {
            Emp droppedData = e.Data.GetData(typeof(Emp)) as Emp;
            Emp target = ((ListBoxItem)(sender)).DataContext as Emp;
    
            int removedIdx = listbox1.Items.IndexOf(droppedData);
            int targetIdx = listbox1.Items.IndexOf(target);
    
            if (removedIdx < targetIdx)
            {
                _empList.Insert(targetIdx + 1, droppedData);
                _empList.RemoveAt(removedIdx);
            }
            else
            {
                int remIdx = removedIdx + 1;
                if (_empList.Count + 1 > remIdx)
                {
                    _empList.Insert(targetIdx, droppedData);
                    _empList.RemoveAt(remIdx);
                }
            }
        }
    }
    
    0 讨论(0)
  • 2020-11-27 12:57

    Improving upon Wiesław Šoltés's modification of dnr3's answer, I abstracted this out into an easily-reusable class, so that you can set up multiple of these lists with only a few lines of code. I also added a feature whereby each item alternates between 2 background colours, for easier viewing (though this feature can easily be removed, if you don't want it).

    F.Y.I: Sorry if you're a lover of 'var', but I am absolutely not and those got removed by my IDE, and I don't intend to put them back. Of course this changes nothing about the actual program behaviour and should improve compile-time anyway, so.. winning! :p

    Here is the class:

    /// <typeparam name="IT">The item type to be stored in this list</typeparam>
    internal class ReorderableList<IT> where IT : class
    {
        private readonly SolidColorBrush m_alternator1, m_alternator2; // Background colours for the list items to alternate between
        private readonly ListBox m_ListBox; // The target ListBox we're modifying
        private readonly string m_displayMemberPath; // The name of the member in to display 
        private readonly IList<IT> m_items = new ObservableCollection<IT>();
        private Point m_cursorStartPos;
    
        /// <summary>
        /// Initializes the list (this must be done after components are initialized and loaded!).
        /// </summary>
        /// <param name="resourceProvider">Pass 'this' for this parameter</param>
        /// <param name="listBox">The target ListBox control to modify</param>
        /// <param name="displayMemberPath">The name of the member in the generic type contained in this list, to be displayed</param>
        public ReorderableList(ListBox listBox, string displayMemberPath, SolidColorBrush alternator1, SolidColorBrush alternator2)
        {
            m_ListBox = listBox;
            m_displayMemberPath = displayMemberPath;
            m_alternator1 = alternator1;
            m_alternator2 = alternator2;
    
            Initialize();
        }
    
        private void Initialize()
        {
            // Set the list box's items source and tell it what member in the IT class to use for the display name
            // Add an event handler for preview mouse move
    
            m_ListBox.DisplayMemberPath = m_displayMemberPath;
            m_ListBox.ItemsSource = m_items;
            m_ListBox.PreviewMouseMove += OnListPreviewMouseMove;
    
            // Create the item container style to be used by the listbox
            // Add mouse event handlers to the style
    
            Style style = new Style(typeof(ListBoxItem));
            style.Setters.Add(new Setter(UIElement.AllowDropProperty, true));
            style.Setters.Add(new EventSetter(UIElement.PreviewMouseLeftButtonDownEvent, new MouseButtonEventHandler(OnListPreviewMouseLeftButtonDown)));
            style.Setters.Add(new EventSetter(UIElement.DropEvent, new DragEventHandler(OnListDrop)));
    
            // Add triggers to alternate the background colour of each element based on its alternation index
            // (Remove this, as well as the two SolidColorBrush resources if you don't want this feature)
    
            Trigger trigger1 = new Trigger()
            {
                Property = ItemsControl.AlternationIndexProperty,
                Value = 0
            };
    
            Setter setter1 = new Setter()
            {
                Property = Control.BackgroundProperty,
                Value = m_alternator1
            };
    
            trigger1.Setters.Add(setter1);
            style.Triggers.Add(trigger1);
    
            Trigger trigger2 = new Trigger()
            {
                Property = ItemsControl.AlternationIndexProperty,
                Value = 1
            };
    
            Setter setter2 = new Setter()
            {
                Property = Control.BackgroundProperty,
                Value = m_alternator2
            };
    
            trigger2.Setters.Add(setter2);
            style.Triggers.Add(trigger2);
    
            // Set the item container style
    
            m_ListBox.ItemContainerStyle = style;
        }
    
        /// <summary>
        /// Adds an item to the list. If [ignoreDuplicates] is false and the item is already in the list,
        /// the item won't be added.
        /// </summary>
        /// <param name="item">The item to add</param>
        /// <param name="ignoreDuplicates">Whether or not to add the item regardless of whether it's already in the list</param>
        /// <returns>Whether or not the item was added</returns>
        public bool Add(IT item, bool ignoreDuplicates = true)
        {
            if (!ignoreDuplicates && Contains(item)) return false;
    
            m_items.Add(item);
            return true;
        }
    
        /// <summary>
        /// Removes an item from the list.
        /// </summary>
        /// <param name="item">The item to remove</param>
        /// <returns>Whether or not the item was removed from the list. This will be false if the item was not in the list to begin with.</returns>
        public bool Remove(IT item)
        {
            if (Contains(item)) return false;
    
            m_items.Remove(item);
            return true;
        }
    
        /// <summary>
        /// Returns whether or not the list contains the given item.
        /// </summary>
        /// <param name="item">The item to check for</param>
        /// <returns>Whether or not the list contains the given item.</returns>
        public bool Contains(IT item)
        {
            return m_items.Contains(item);
        }
    
        private void OnListPreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            m_cursorStartPos = e.GetPosition(null);
        }
    
        private void OnListPreviewMouseMove(object sender, MouseEventArgs e)
        {
            Point currentCursorPos = e.GetPosition(null);
            Vector cursorVector = m_cursorStartPos - currentCursorPos;
    
            if (e.LeftButton == MouseButtonState.Pressed
                &&(Math.Abs(cursorVector.X) > SystemParameters.MinimumHorizontalDragDistance
                || Math.Abs(cursorVector.Y) > SystemParameters.MinimumVerticalDragDistance))
            {
                ListBoxItem targetItem = FindVisualParent<ListBoxItem>(((DependencyObject)e.OriginalSource));
                if (targetItem != null)
                {
                    DragDrop.DoDragDrop(targetItem, targetItem.DataContext, DragDropEffects.Move);
                }
            }
        }
    
        private void OnListDrop(object sender, DragEventArgs e)
        {
            if (sender is ListBoxItem item)
            {
                IT source = e.Data.GetData(typeof(IT)) as IT;
                IT target = item.DataContext as IT;
    
                int sourceIndex = m_ListBox.Items.IndexOf(source);
                int targetIndex = m_ListBox.Items.IndexOf(target);
    
                Move(source, sourceIndex, targetIndex);
            }
        }
    
        private void Move(IT source, int sourceIndex, int targetIndex)
        {
            if (sourceIndex < targetIndex)
            {
                m_items.Insert(targetIndex + 1, source);
                m_items.RemoveAt(sourceIndex);
            }
            else
            {
                int removeIndex = sourceIndex + 1;
                if (m_items.Count + 1 > removeIndex)
                {
                    m_items.Insert(targetIndex, source);
                    m_items.RemoveAt(removeIndex);
                }
            }
        }
    
        private T FindVisualParent<T>(DependencyObject child) where T : DependencyObject
        {
            DependencyObject parentObject = VisualTreeHelper.GetParent(child);
            if (parentObject == null) return null;
            if (parentObject is T parent) return parent;
    
            return FindVisualParent<T>(parentObject);
        }
    }
    

    And here is an example IT (item type) class (same as in the original answer):

    public class ExampleItem
    {
        public string Name { get; set; }
    
        public ExampleItem(string name)
        {
            Name = name;
        }
    }
    

    And finally, the usage:

    public partial class MainWindow : Window
    {
        private readonly ReorderableList<ExampleItem> ExampleList;
    
        public MainWindow()
        {
            InitializeComponent();
    
            ExampleList = new ReorderableList<ExampleItem>(myXamlListBoxControl, "Name",
                FindResource("AlternatingBG1") as SolidColorBrush,
                FindResource("AlternatingBG2") as SolidColorBrush);
    
            ExampleList.Add(new ExampleItem("Test Item 1"));
            ExampleList.Add(new ExampleItem("Test Item 2"));
            ExampleList.Add(new ExampleItem("Test Item 3"));
            ExampleList.Add(new ExampleItem("Test Item 4"));
            ExampleList.Add(new ExampleItem("Test Item 5"));
            ExampleList.Add(new ExampleItem("Test Item 6"));
        }
    }
    
    0 讨论(0)
  • 2020-11-27 12:59

    This has helped me greatly thank you. Especially the generics version.

    I made the following amendments:

    Because I don't set the DataContext of the ListBox (just the ItemsSource), I use

    var items = this.ItemsSource as IList<T>;
    

    in the Move method.

    And at the end of "Move" I added:

    this.SelectedItem = source;
    

    as I want the user to have the moved item as the current selection.

    0 讨论(0)
  • 2020-11-27 13:00

    I took dnr3's answer and altered it for implementation in XAML. Same result, just prefer doing what I can in XAML rather than in the code-behind.

    In place of the code-behind:

    Style itemContainerStyle = new Style(typeof(ListBoxItem));
    itemContainerStyle.Setters.Add(new Setter(AllowDropProperty, true));
    itemContainerStyle.Setters.Add(new EventSetter(PreviewMouseLeftButtonDownEvent, new MouseButtonEventHandler(s_PreviewMouseLeftButtonDown)));
    itemContainerStyle.Setters.Add(new EventSetter(DropEvent, new DragEventHandler(listbox1_Drop)));
    listbox1.ItemContainerStyle = itemContainerStyle;
    

    Put this in the XAML:

    <Window.Resources>
        <Style x:Key="ListBoxDragDrop" TargetType="{x:Type ListBoxItem}">
            <Setter Property="AllowDrop" Value="true"/>
            <EventSetter Event="PreviewMouseMove" Handler="s_PreviewMouseMoveEvent"/>
            <EventSetter Event="Drop" Handler="listbox1_Drop"/>
        </Style>
    </Window.Resources>
    <Grid>
        <ListBox x:Name="listbox1" ItemContainerStyle="{StaticResource ListBoxDragDrop}" HorizontalAlignment="Left" Height="299" Margin="10,10,0,0" VerticalAlignment="Top" Width="224"/>
    </Grid>
    

    This is mouse-handler placed in the code-behind of the XAML.

    void s_PreviewMouseMoveEvent(object sender, MouseEventArgs e)
    {
        if (sender is ListBoxItem && e.LeftButton == MouseButtonState.Pressed)
        {
            ListBoxItem draggedItem = sender as ListBoxItem;
            DragDrop.DoDragDrop(draggedItem, draggedItem.DataContext, DragDropEffects.Move);
            draggedItem.IsSelected = true;
        }
    }
    
    0 讨论(0)
  • 2020-11-27 13:01

    Using dnr3's answers I have created version with fixed selection issues.

    Window1.xaml

    <Window x:Class="ListBoxReorderDemo.Window1"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            Title="ListBoxReorderDemo" Height="300" Width="300"
            WindowStartupLocation="CenterScreen">
        <Grid>
            <ListBox x:Name="listBox"/>
        </Grid>
    </Window>
    

    Window1.xaml.cs

    using System;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.Text;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Data;
    using System.Windows.Documents;
    using System.Windows.Input;
    using System.Windows.Media;
    
    namespace ListBoxReorderDemo
    {
        public class Item
        {
            public string Name { get; set; }
            public Item(string name)
            {
                this.Name = name;
            }
        }
    
        public partial class Window1 : Window
        {
            private Point _dragStartPoint;
    
            private T FindVisualParent<T>(DependencyObject child)
                where T : DependencyObject
            {
                var parentObject = VisualTreeHelper.GetParent(child);
                if (parentObject == null)
                    return null;
                T parent = parentObject as T;
                if (parent != null)
                    return parent;
                return FindVisualParent<T>(parentObject);
            }
    
            private IList<Item> _items = new ObservableCollection<Item>();
    
            public Window1()
            {
                InitializeComponent();
    
                _items.Add(new Item("1"));
                _items.Add(new Item("2"));
                _items.Add(new Item("3"));
                _items.Add(new Item("4"));
                _items.Add(new Item("5"));
                _items.Add(new Item("6"));
    
                listBox.DisplayMemberPath = "Name";
                listBox.ItemsSource = _items;
    
                listBox.PreviewMouseMove += ListBox_PreviewMouseMove;
    
                var style = new Style(typeof(ListBoxItem));
                style.Setters.Add(new Setter(ListBoxItem.AllowDropProperty, true));
                style.Setters.Add(
                    new EventSetter(
                        ListBoxItem.PreviewMouseLeftButtonDownEvent,
                        new MouseButtonEventHandler(ListBoxItem_PreviewMouseLeftButtonDown)));
                style.Setters.Add(
                        new EventSetter(
                            ListBoxItem.DropEvent, 
                            new DragEventHandler(ListBoxItem_Drop)));
                listBox.ItemContainerStyle = style;
            }
    
            private void ListBox_PreviewMouseMove(object sender, MouseEventArgs e)
            {
                Point point = e.GetPosition(null);
                Vector diff = _dragStartPoint - point;
                if (e.LeftButton == MouseButtonState.Pressed &&
                    (Math.Abs(diff.X) > SystemParameters.MinimumHorizontalDragDistance ||
                        Math.Abs(diff.Y) > SystemParameters.MinimumVerticalDragDistance))
                {
                    var lb = sender as ListBox;
                    var lbi = FindVisualParent<ListBoxItem>(((DependencyObject)e.OriginalSource));
                    if (lbi != null)
                    {
                        DragDrop.DoDragDrop(lbi, lbi.DataContext, DragDropEffects.Move);
                    }
                }
            }
            private void ListBoxItem_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
            {
                _dragStartPoint = e.GetPosition(null);
            }
    
            private void ListBoxItem_Drop(object sender, DragEventArgs e)
            {
                if (sender is ListBoxItem)
                {
                    var source = e.Data.GetData(typeof(Item)) as Item;
                    var target = ((ListBoxItem)(sender)).DataContext as Item;
    
                    int sourceIndex = listBox.Items.IndexOf(source);
                    int targetIndex = listBox.Items.IndexOf(target);
    
                    Move(source, sourceIndex, targetIndex);
                }
            }
    
            private void Move(Item source, int sourceIndex, int targetIndex)
            {
                if (sourceIndex < targetIndex)
                {
                    _items.Insert(targetIndex + 1, source);
                    _items.RemoveAt(sourceIndex);
                }
                else
                {
                    int removeIndex = sourceIndex + 1;
                    if (_items.Count + 1 > removeIndex)
                    {
                        _items.Insert(targetIndex, source);
                        _items.RemoveAt(removeIndex);
                    }
                }
            }
        }
    }
    

    Version with support for generics and data binding.

    Window1.xaml

    <Window x:Class="ListBoxReorderDemo.Window1"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:local="clr-namespace:ListBoxReorderDemo"
            Title="ListBoxReorderDemo" Height="300" Width="300"
            WindowStartupLocation="CenterScreen">
        <Grid>
            <local:ItemDragAndDropListBox x:Name="listBox" ItemsSource="{Binding}">
                <ListBox.ItemTemplate>
                    <DataTemplate>
                        <TextBlock Text="{Binding Name}"/>
                    </DataTemplate>
                </ListBox.ItemTemplate>
            </local:ItemDragAndDropListBox>
        </Grid>
    </Window>
    

    Window1.xaml.cs

    using System;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.Text;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Data;
    using System.Windows.Documents;
    using System.Windows.Input;
    using System.Windows.Media;
    
    namespace ListBoxReorderDemo
    {
        public class DragAndDropListBox<T> : ListBox 
            where T : class
        {
            private Point _dragStartPoint;
    
            private P FindVisualParent<P>(DependencyObject child) 
                where P : DependencyObject
            {
                var parentObject = VisualTreeHelper.GetParent(child);
                if (parentObject == null)
                    return null;
    
                P parent = parentObject as P;
                if (parent != null)
                    return parent;
    
                return FindVisualParent<P>(parentObject);
            }
    
            public DragAndDropListBox()
            {
                this.PreviewMouseMove += ListBox_PreviewMouseMove;
    
                var style = new Style(typeof(ListBoxItem));
    
                style.Setters.Add(new Setter(ListBoxItem.AllowDropProperty, true));
    
                style.Setters.Add(
                    new EventSetter(
                        ListBoxItem.PreviewMouseLeftButtonDownEvent,
                        new MouseButtonEventHandler(ListBoxItem_PreviewMouseLeftButtonDown)));
    
                style.Setters.Add(
                        new EventSetter(
                            ListBoxItem.DropEvent, 
                            new DragEventHandler(ListBoxItem_Drop)));
    
                this.ItemContainerStyle = style;
            }
    
            private void ListBox_PreviewMouseMove(object sender, MouseEventArgs e)
            {
                Point point = e.GetPosition(null);
                Vector diff = _dragStartPoint - point;
                if (e.LeftButton == MouseButtonState.Pressed &&
                    (Math.Abs(diff.X) > SystemParameters.MinimumHorizontalDragDistance ||
                        Math.Abs(diff.Y) > SystemParameters.MinimumVerticalDragDistance))
                {
                    var lb = sender as ListBox;
                    var lbi = FindVisualParent<ListBoxItem>(((DependencyObject)e.OriginalSource));
                    if (lbi != null)
                    {
                        DragDrop.DoDragDrop(lbi, lbi.DataContext, DragDropEffects.Move);
                    }
                }
            }
    
            private void ListBoxItem_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
            {
                _dragStartPoint = e.GetPosition(null);
            }
    
            private void ListBoxItem_Drop(object sender, DragEventArgs e)
            {
                if (sender is ListBoxItem)
                {
                    var source = e.Data.GetData(typeof(T)) as T;
                    var target = ((ListBoxItem)(sender)).DataContext as T;
    
                    int sourceIndex = this.Items.IndexOf(source);
                    int targetIndex = this.Items.IndexOf(target);
    
                    Move(source, sourceIndex, targetIndex);
                }
            }
    
            private void Move(T source, int sourceIndex, int targetIndex)
            {
                if (sourceIndex < targetIndex)
                {
                    var items = this.DataContext as IList<T>;
                    if (items != null)
                    {
                        items.Insert(targetIndex + 1, source);
                        items.RemoveAt(sourceIndex);
                    }
                }
                else
                {
                    var items = this.DataContext as IList<T>;
                    if (items != null)
                    {
                        int removeIndex = sourceIndex + 1;
                        if (items.Count + 1 > removeIndex)
                        {
                            items.Insert(targetIndex, source);
                            items.RemoveAt(removeIndex);
                        }
                    }
                }
            }
        }
    
        public class Item
        {
            public string Name { get; set; }
            public Item(string name)
            {
                this.Name = name;
            }
        }
    
        public class ItemDragAndDropListBox : DragAndDropListBox<Item> { }
    
        public partial class Window1 : Window
        {
            private IList<Item> _items = new ObservableCollection<Item>();
    
            public Window1()
            {
                InitializeComponent();
    
                _items.Add(new Item("1"));
                _items.Add(new Item("2"));
                _items.Add(new Item("3"));
                _items.Add(new Item("4"));
                _items.Add(new Item("5"));
                _items.Add(new Item("6"));
    
                listBox.DataContext = _items;
            }
        }
    }
    
    0 讨论(0)
  • 2020-11-27 13:02

    i've tried create one using observablecollection, have a look

        ObservableCollection<Emp> _empList = new ObservableCollection<Emp>();
    
        public Window1()
        {
            InitializeComponent();
    
            _empList .Add(new Emp("1", 22));
            _empList .Add(new Emp("2", 18));
            _empList .Add(new Emp("3", 29));
            _empList .Add(new Emp("4", 9));
            _empList .Add(new Emp("5", 29));
            _empList .Add(new Emp("6", 9));
            listbox1.DisplayMemberPath = "Name";
            listbox1.ItemsSource = _empList;
    
            Style itemContainerStyle = new Style(typeof(ListBoxItem));
            itemContainerStyle.Setters.Add(new Setter(ListBoxItem.AllowDropProperty, true));
            itemContainerStyle.Setters.Add(new EventSetter(ListBoxItem.PreviewMouseLeftButtonDownEvent, new MouseButtonEventHandler(s_PreviewMouseLeftButtonDown)));
            itemContainerStyle.Setters.Add(new EventSetter(ListBoxItem.DropEvent, new DragEventHandler(listbox1_Drop)));
            listbox1.ItemContainerStyle = itemContainerStyle;
        }
    

    Drag and drop process

        void s_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
    
            if (sender is ListBoxItem)
            {
                ListBoxItem draggedItem = sender as ListBoxItem;
                DragDrop.DoDragDrop(draggedItem, draggedItem.DataContext, DragDropEffects.Move);
                draggedItem.IsSelected = true;
            }
        }
    
        void listbox1_Drop(object sender, DragEventArgs e)
        {
            Emp droppedData = e.Data.GetData(typeof(Emp)) as Emp;
            Emp target = ((ListBoxItem)(sender)).DataContext as Emp;
    
            int removedIdx = listbox1.Items.IndexOf(droppedData);
            int targetIdx = listbox1.Items.IndexOf(target);
    
            if (removedIdx < targetIdx)
            {
                _empList.Insert(targetIdx + 1, droppedData);
                _empList.RemoveAt(removedIdx);
            }
            else
            {
                int remIdx = removedIdx+1;
                if (_empList.Count + 1 > remIdx)
                {
                    _empList.Insert(targetIdx, droppedData);
                    _empList.RemoveAt(remIdx);
                }
            }
        }
    

    Note:

    • one thing sucks in the implementation is since it use the PreviewMouseLeftButtonDown event, the dragged item looks like not selected
    • and also for an easier implementation the drop target is the list box items and not the listbox itself - might need a better solution for this
    0 讨论(0)
提交回复
热议问题