17306


`ListBox`上の`ListBoxItem`、または`ListBox`上の`ListBox.Background`が見えている領域に対してDrag&Dropを行い、要素の並べ替え/挿入を行おうとしています。

この時、`ListBox`の背景要素にDropした際、Dropした位置によって適切な挿入先のIndexを計算する方法が分からず、詰まってしまっています。

**\- 出来たこと**  
`ListBox`上の`ListBoxItem`に対するDropは、`DragEventArgs.OriginalSource`を使って現在Dropしようとしている先の`FrameworkElement`を特定→`ListBox.ItemsSource`から`DataContext`の内容で探す……などの方法でDrop先Indexを計算する事ができました。

**\- 出来なくて困っていること(知りたいこと)**  
`ListBox`上のDrop位置において、以下の図のような領域⇔Index値となる計算をしたいのですが、以下の3点を計算・区別する方法が分かりませんでした。

  1. `ListBoxItem`間のMarginの位置へのDrop時のIndex計算
  2. `WrapPanel`での折り返し位置(図上のIndex 4の位置)へのDrop時のIndex計算
  3. 1,2に含まれない末尾全般領域(図上のIndex 6の位置)へのDrop時のIndex計算

[![WrapPanel上のDrop位置と期待するIndex値](https://i.stack.imgur.com/8L9NI.png)](https://i.stack.imgur.com/8L9NI.png)

実現方法が分かる方、よろしくお願いします。


CC BY-SA 3.0
2015-10-05T12:56:39.553
17283
2015-10-09T06:27:20.587
6119
question
c# .net wpf xaml
ListBoxの背景にDrap&Dropする際にDrop先Indexを計算するには？


泥臭い解法ですが…`ListBoxItem`の領域を左上から順に取得し、マウス座標の手前にあるかどうかを判定する方法で実装できます。

下記のサンプルコードでは、`GetNextItem`メソッドでマウス座標直後にある`ListBoxItem`を取得し、リスト内のインデックスは`MoveItem`メソッド内の`IList.IndexOf`で取得しています。

**MainWindow.xaml.cs**

[code]

    using System.Collections;
    using System.Collections.Generic;
    using System.Linq;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Input;
    using System.Windows.Media;
    
    namespace WpfApplication1
    {
        /// <summary>
        /// MainWindow.xaml の相互作用ロジック
        /// </summary>
        public partial class MainWindow : Window
        {
            private ListBoxItem DropTarget;
    
            public MainWindow()
            {
                InitializeComponent();
    
                Loaded += MainWindow_Loaded;
                LstSample.MouseMove += LstSample_MouseMove;
                LstSample.PreviewMouseLeftButtonUp += LstSample_PreviewMouseLeftButtonUp;
                LstSample.Drop += LstSample_Drop;
            }
    
            private void LstSample_MouseMove(object sender, MouseEventArgs e)
            {
                if (e.LeftButton != MouseButtonState.Pressed || DropTarget == null)
                {
                    return;
                }
                DragDrop.DoDragDrop((ListBox)sender, DropTarget, DragDropEffects.Move);
            }
    
            private void LstSample_PreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
            {
                DropTarget = null;
            }
    
            private void LstSample_Drop(object sender, DragEventArgs e)
            {
                if (DropTarget == null)
                {
                    return;
                }
                var listBox = (ListBox)sender;
                var list = (IList)listBox.ItemsSource;
                MoveItem(list, DropTarget, GetNextItem(listBox, e.GetPosition(listBox)));
                listBox.ItemsSource = null;
                listBox.ItemsSource = list;
            }
    
            /// <summary>
            /// Dropした位置の直後にあるListBoxItemを取得する。該当がなければnullを返す
            /// </summary>
            /// <param name="listBox"></param>
            /// <param name="drop"></param>
            /// <returns></returns>
            private static ListBoxItem GetNextItem(ListBox listBox, Point drop)
            {
                return listBox.ItemsSource.OfType<ListBoxItem>().FirstOrDefault(q =>
                {
                    //ListBoxItemの左上座標
                    var p = q.TranslatePoint(new Point(), listBox);
                    //ドロップした位置が、現在のListBoxItem上端より上
                    if (drop.Y < p.Y - q.Margin.Top) return true;
                    //ListBoxItem中央下部座標
                    p.Offset(q.Width / 2d, q.Height);
                    //ドロップした位置が、現在のListBoxItem下端より下
                    if (p.Y < drop.Y) return false;
                    //ドロップした位置が、現在のListBoxItem行内
                    return (drop.X < p.X);
                });
            }
    
            private static void MoveItem(IList list, ListBoxItem target, ListBoxItem next)
            {
                var delIndex = list.IndexOf(target);
                if (next == null)
                {
                    list.Add(target);
                }
                else
                {
                    var index = list.IndexOf(next);
                    delIndex += (index < delIndex) ? 1 : 0;
                    list.Insert(index, target);
                }
                list.RemoveAt(delIndex);
            }
    
            private void MainWindow_Loaded(object sender, RoutedEventArgs e)
            {
                var list = new List<ListBoxItem>
                {
                    GetItem("1"),
                    GetItem("2"),
                    GetItem("3"),
                    GetItem("4"),
                    GetItem("5"),
                };
                LstSample.ItemsSource = list;
                LstSample.Items.IsLiveSorting = true;
            }
    
            private ListBoxItem GetItem(string content)
            {
                var item = new ListBoxItem
                {
                    Content = content,
                    Width = 150,
                    Height = 150,
                    Margin = new Thickness(15),
                    HorizontalContentAlignment = HorizontalAlignment.Center,
                    Background = new SolidColorBrush(Colors.AliceBlue),
                };
                item.PreviewMouseLeftButtonDown += (sender, e) =>
                {
                    DropTarget = item;
                };
                return item;
            }
        }
    }
    
[/code]

**MainWindow.xaml**

[code]

    <Window x:Class="WpfApplication1.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
            xmlns:local="clr-namespace:WpfApplication1"
            mc:Ignorable="d"
            Title="MainWindow" Height="350" Width="525" >
        <Grid>
            <ListBox Name="LstSample" ScrollViewer.HorizontalScrollBarVisibility="Disabled" AllowDrop="True">
                <ListBox.ItemsPanel>
                    <ItemsPanelTemplate>
                        <WrapPanel />
                    </ItemsPanelTemplate>
                </ListBox.ItemsPanel>
            </ListBox>
        </Grid>
    </Window>
    
[/code]


CC BY-SA 3.0
2015-10-06T04:53:52.553
17306
2015-10-06T04:53:52.553
9820
17283
answer


Drop可能な領域にDrop用の`ListBoxItem`を敷き詰めたダミーの`ListBox`を新たに設けてはどうでしょうか。これで元の`ListBoxItem`のMargin領域、折り返し領域、要素未配置領域は考慮せずに済むかと思います。図表の黒枠の通りの`ListBox`を新たに作成するようなイメージです。

処理の流れのみで申し訳ないですが、  
仮に元の並べ替え・挿入対象の`ListBox`を「ItemsPanel」、新たな`ListBox`を「DropItemsPanel」として、「ItemsPanel」の`ListBoxItem`Drag開始で「DropItemsPanel」を有効化し、「DropItemsPanel」の`ListBoxItem`へDropします。そのIndex値を取得し、「ItemsPanel」の並べ替え・挿入処理を行い、「DropItemsPanel」を再び非表示、という実装です。

**Window1.xaml**

[code]

    <Window x:Class="WpfApplication1.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Window1" Height="400" Width="600">
    <Grid>        
        <ListBox Name="ItemsPanel" ScrollViewer.HorizontalScrollBarVisibility="Disabled">
            <ListBox.ItemsPanel>
                <ItemsPanelTemplate>
                    <WrapPanel />
                </ItemsPanelTemplate>
            </ListBox.ItemsPanel>
            <ListBoxItem Background="Aquamarine" Width="100" Height="100" Margin="10" Content="1" HorizontalContentAlignment="Center" />
            <ListBoxItem Background="Aquamarine" Width="100" Height="100" Margin="10" Content="2" HorizontalContentAlignment="Center" />
            <ListBoxItem Background="Aquamarine" Width="100" Height="100" Margin="10" Content="3" HorizontalContentAlignment="Center" />
            <ListBoxItem Background="Aquamarine" Width="100" Height="100" Margin="10" Content="4" HorizontalContentAlignment="Center" />
            <ListBoxItem Background="Aquamarine" Width="100" Height="100" Margin="10" Content="5" HorizontalContentAlignment="Center" />
            <ListBoxItem Background="Aquamarine" Width="100" Height="100" Margin="10" Content="6" HorizontalContentAlignment="Center" />
        </ListBox>
        <ListBox Name="DropItemsPanel" ScrollViewer.HorizontalScrollBarVisibility="Disabled" AllowDrop="True" Opacity="0.5" Margin="-60,0,-60,0">
            <ListBox.ItemsPanel>
                <ItemsPanelTemplate>
                    <WrapPanel />
                </ItemsPanelTemplate>
            </ListBox.ItemsPanel>
            <ListBoxItem Width="120" Height="120" BorderBrush="DarkBlue" BorderThickness="1" Content="1" />
            <ListBoxItem Width="120" Height="120" BorderBrush="DarkBlue" BorderThickness="1" Content="2" />
            <ListBoxItem Width="120" Height="120" BorderBrush="DarkBlue" BorderThickness="1" Content="3" />
            <ListBoxItem Width="120" Height="120" BorderBrush="DarkBlue" BorderThickness="1" Content="4" />
            <ListBoxItem Width="120" Height="120" BorderBrush="DarkBlue" BorderThickness="1" Content="5" />
            <ListBoxItem Width="120" Height="120" BorderBrush="DarkBlue" BorderThickness="1" Content="6" />
            <ListBoxItem Width="120" Height="120" BorderBrush="DarkBlue" BorderThickness="1" Content="7" />
            <ListBoxItem Width="120" Height="120" BorderBrush="DarkBlue" BorderThickness="1" Content="8" />
            <ListBoxItem Width="120" Height="120" BorderBrush="DarkBlue" BorderThickness="1" Content="8" />
            <ListBoxItem Width="120" Height="120" BorderBrush="DarkBlue" BorderThickness="1" Content="8" />
            <ListBoxItem Width="120" Height="120" BorderBrush="DarkBlue" BorderThickness="1" Content="8" />
            <ListBoxItem Width="120" Height="120" BorderBrush="DarkBlue" BorderThickness="1" Content="8" />
            <ListBoxItem Width="120" Height="120" BorderBrush="DarkBlue" BorderThickness="1" Content="8" />
            <ListBoxItem Width="120" Height="120" BorderBrush="DarkBlue" BorderThickness="1" Content="8" />
        </ListBox>
    </Grid>
    
[/code]


CC BY-SA 3.0
2015-10-09T06:27:20.587
17469
2015-10-09T06:27:20.587
5133
17283
answer