2

I need something that arranges/sorts items like a wrappanel but allows you to rearrange the items via drag & drop. I'm trying to prototype this out as quick as possible, so I'm just looking for something stupidly simple or hacky for now. I've been looking all over the place, but the only answers I could find required a relatively large amount of work to implement.

I would prefer not using an existing library if it's possible.

EDIT

To clarify: I need drag & drop as well as auto-rearrange mechanics within a control that arranges items like a wrappanel.

Since posting this question, I've found two posts/articles that seemed to be a good fit but only if combined.

Wrappanel: http://www.codeproject.com/Articles/18561/Custom-ListBox-Layout-in-WPF Drag-drop & arrangement: WPF C#: Rearrange items in listbox via drag and drop

I'll post my code in an answer when I get it all working.

Community
  • 1
  • 1
CheeseMo
  • 167
  • 2
  • 3
  • 12
  • Possible duplicate of [WPF C#: Rearrange items in listbox via drag and drop](http://stackoverflow.com/questions/3350187/wpf-c-rearrange-items-in-listbox-via-drag-and-drop) –  Oct 01 '15 at 00:35

1 Answers1

1

It took some time, but I was able to figure it out after Micky's suggestion. I'm posting an answer here for future reference and if anyone else is looking for this mechanic. The following code snippets should work by just pasting them into their appropriate files.

Here's what worked:

Make the ListBox arrange items like a WrapPanel via a custom/default style (mine is called Default.xaml).

<Style TargetType="{x:Type ListBox}">
    <Setter Property="ItemsPanel">
        <Setter.Value>
            <ItemsPanelTemplate>
                <!--'WrapPanel' can be replaced with other controls if you want it to display differently-->
                <WrapPanel/>
            </ItemsPanelTemplate>
        </Setter.Value>
    </Setter>
    <Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Disabled"/>
    <Setter Property="ItemTemplate">
        <Setter.Value>
            <DataTemplate>
                <!--Controls in each item-->
                <local:DocPage />
            </DataTemplate>
        </Setter.Value>
    </Setter>
</Style>


Set up the ListBox control (i.e. MainWindow.xaml):
<ListBox x:Name="lbx_Pages" AllowDrop="True" DragEnter="lbx_Pages_DragEnter" PreviewMouseLeftButtonDown="lbx_Pages_PreviewMouseLeftButtonDown" PreviewMouseMove="lbx_Pages_PreviewMouseMove" Drop="lbx_Pages_PagesDrop"/>


Impliment the controls in the .cs file (i.e. MainWindow.xaml.cs):
private Point dragStartPoint;
// Bindable list of pages (binding logic omitted-out of scope of this post)
private static ObservableCollection<DocPage> pages = new ObservableCollection<DocPage>();

// Find parent of 'child' of type 'T'
public static T FindParent<T>(DependencyObject child) where T : DependencyObject
{
    do
    {
        if (child is T)
            return (T)child;
        child = VisualTreeHelper.GetParent(child);
    } while (child != null);
    return null;
}

private void lbx_Pages_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
    dragStartPoint = e.GetPosition(null);
}

private void lbx_Pages_PreviewMouseMove(object sender, MouseEventArgs e)
{

    ListBoxItem item = null;
    DataObject dragData;
    ListBox listBox;
    DocPage page;

    // Is LMB down and did the mouse move far enough to register a drag?
    if (e.LeftButton == MouseButtonState.Pressed &&
        (Math.Abs(dragStartPoint.X - e.GetPosition(null).X) > SystemParameters.MinimumHorizontalDragDistance ||
        Math.Abs(dragStartPoint.Y - e.GetPosition(null).Y) > SystemParameters.MinimumVerticalDragDistance))
    {
        // Get the ListBoxItem object from the object being dragged
        item = FindParent<ListBoxItem>((DependencyObject)e.OriginalSource);

        if (null != item)
        {
            listBox = sender as ListBox;
            page = (DocPage)listBox.ItemContainerGenerator.ItemFromContainer(item);
            dragData = new DataObject("pages", page);

            DragDrop.DoDragDrop(item, dragData, DragDropEffects.Move);
        }
    }
}

private void lbx_Pages_PagesDrop(object sender, DragEventArgs e)
{
    if (!e.Data.GetDataPresent("pages"))
        return;

    DocPage draggedItem = e.Data.GetData("pages") as DocPage;
    // Hit-test needed for rearranging items in the same ListBox
    HitTestResult hit = VisualTreeHelper.HitTest((ListBox)sender, e.GetPosition((ListBox)sender));
    DocPage target = (DocPage)FindParent<ListBoxItem>(hit.VisualHit).DataContext;


    int removeIdx = lbx_Pages.Items.IndexOf(draggedItem);
    int targetIdx = lbx_Pages.Items.IndexOf(target);

    if(removeIdx < targetIdx)
    {
        pages.Insert(targetIdx + 1, draggedItem);
        pages.RemoveAt(removeIdx);
    }
    else
    {
        removeIdx++;
        if(pages.Count+1 > removeIdx)
        {
            pages.Insert(targetIdx, draggedItem);
            pages.RemoveAt(removeIdx);
        }
    }
}

private void lbx_Pages_DragEnter(object sender, DragEventArgs e)
{
    if (!e.Data.GetDataPresent("pages") || sender == e.Source)
        e.Effects = DragDropEffects.None;
}
CheeseMo
  • 167
  • 2
  • 3
  • 12