Q039. DataGrid で選択行をドラッグドロップで移動するにはどうすればいいのですか?

A. 海外のサイトですが、以下たいへん素晴らしいチュートリアルが公開されています。


Moving WPF DataGrid Rows using Drag and Drop



ただし上記チュートリアルで公開されているサンプルはコレクションとバインドするよう書かれているため、DataTable とバインドするケースには応用しづらいかも知れません。そこで DataTable を使ったサンプルに書き直してみました。サンプルをダウンロードして Window1.xaml と Window1.xaml.cs を入れ替えビルドすると DataTable とバインドするサンプルを実行できます。
なお以下のコードは Visual Studio 2010 でテストしたので、Visual Studio 2008 で動作するかは定かではありません。


Window1.xaml

<Window x:Class="DataGridDragAndDrop.Window1"
    x:Name="me"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="DndSample" Height="316" Width="563">

    <!-- the layout root registers mouse event listeners -->
    <Grid x:Name="layoutRoot"
        MouseLeftButtonUp="OnMouseLeftButtonUp"
        MouseMove="OnMouseMove">

        <!-- Data Grid -->
        <DataGrid     
            x:Name="shareGrid"
            BeginningEdit="OnBeginEdit"
            CellEditEnding="OnEndEdit"
            PreviewMouseLeftButtonDown="OnMouseLeftButtonDown"
            AutoGenerateColumns="False"
            SelectionMode="Single"
            CanUserDeleteRows="False"
            CanUserAddRows="False"
            CanUserReorderColumns="False"
            CanUserSortColumns="False">

            <DataGrid.Columns>
                <DataGridTextColumn
                    SortDirection="Ascending"
                    IsReadOnly="False" Header="Name" Width="2*"
                    FontWeight="Bold" MinWidth="100"
                    Binding="{Binding Path=Name}" />
                <DataGridTextColumn
                    Header="Network Path"
                    IsReadOnly="True" Width="3*"
                    Binding="{Binding Path=NetworkLocation}" />
            </DataGrid.Columns>
        </DataGrid>

        <!-- Drag and Drop Popup -->
        <Popup x:Name="popup1" IsHitTestVisible="False" Placement="RelativePoint"
               PlacementTarget="{Binding ElementName=me}" AllowsTransparency="True">
            <Border
                BorderBrush="LightSteelBlue"
                BorderThickness="2" Background="White" Opacity="0.75">
                <StackPanel
                    Orientation="Horizontal" Margin="4,3,8,3">
                    <Image Source="/DragInsert.png"
                           Width="16" Height="16" />
                    <TextBlock
                        FontSize="14" FontWeight="Bold"
                        VerticalAlignment="Center" Margin="8,0,0,0" 
                        Text="{Binding ElementName=me, Path=DraggedItem.Name}" />
                </StackPanel>
            </Border>
        </Popup>
    </Grid>
</Window>


Window1.xaml.cs

using System.Data;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using Hardcodet.Wpf.Util;


namespace DataGridDragAndDrop {

    /// <summary>
    /// Interaction logic for Window1.xaml
    /// </summary>
    public partial class Window1 : Window {

        private bool _isDragging;
        private bool _isEditing;
        private DataTable _shareTable;

        /// <summary>
        /// DraggedItem Dependency Property
        /// </summary>
        public static readonly DependencyProperty DraggedItemProperty =
                DependencyProperty.Register("DraggedItem", typeof(DataRowView), typeof(Window1));

        /// <summary>
        /// Gets or sets the DraggedItem property. This dependency property indicates ....
        /// </summary>
        public DataRowView DraggedItem {
            get { return (DataRowView)GetValue(DraggedItemProperty); }
            set { SetValue(DraggedItemProperty, value); }
        }

        public Window1() {
            InitializeComponent();

            _shareTable = new DataTable();
            _shareTable.Columns.Add("Name", typeof(string));
            _shareTable.Columns.Add("NetworkLocation", typeof(string));

            _shareTable.Rows.Add(new [] { "Home", @"\\10.0.0.100\home"});
            _shareTable.Rows.Add(new [] { "Media", @"\\10.0.0.50\media" });
            _shareTable.Rows.Add(new [] { "Backup", @"\\10.0.0.50\backup" });
            _shareTable.Rows.Add(new [] { "Blog", @"\\10.0.0.100\blog" });

            this.shareGrid.ItemsSource = _shareTable.DefaultView;
        }

        /// <summary>
        /// State flag which indicates whether the grid is in edit
        /// mode or not.
        /// </summary>
        public void OnBeginEdit(object sender, DataGridBeginningEditEventArgs e) {
            _isEditing = true;
            if (_isDragging) ResetDragDrop();
        }

        public void OnEndEdit(object sender, DataGridCellEditEndingEventArgs e) {
            _isEditing = false;
        }

        /// <summary>
        /// Initiates a drag action if the grid is not in edit mode.
        /// </summary>
        private void OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e) {
            if (_isEditing) return;

            var row = UIHelpers.TryFindFromPoint<DataGridRow>((UIElement) sender, e.GetPosition(shareGrid));
            if (row == null || row.IsEditing) return;

            //set flag that indicates we're capturing mouse movements
            _isDragging = true;
            DraggedItem = (DataRowView)row.Item;
        }

        /// <summary>
        /// Completes a drag/drop operation.
        /// </summary>
        private void OnMouseLeftButtonUp(object sender, MouseButtonEventArgs e) {

            if (!_isDragging || _isEditing) {
                return;
            }

            //get the target item
            DataRowView targetItem = (DataRowView)shareGrid.SelectedItem;

            if (targetItem == null || !ReferenceEquals(DraggedItem, targetItem)) {
                
                // create tempporary row
                var temp = DraggedItem.Row.Table.NewRow();
                temp.ItemArray = DraggedItem.Row.ItemArray;
                int tempIndex = _shareTable.Rows.IndexOf(DraggedItem.Row);

                //remove the source from the list
                _shareTable.Rows.Remove(DraggedItem.Row);

                //get target index
                var targetIndex = _shareTable.Rows.IndexOf(targetItem.Row);

                //insert temporary at the target's location
                _shareTable.Rows.InsertAt(temp, targetIndex);

                //select the dropped item
                shareGrid.SelectedItem = shareGrid.Items[targetIndex];
            }

            //reset
            ResetDragDrop();
        }

        /// <summary>
        /// Updates the popup's position in case of a drag/drop operation.
        /// </summary>
        private void OnMouseMove(object sender, MouseEventArgs e) {

            if (!_isDragging || e.LeftButton != MouseButtonState.Pressed) return;

            //display the popup if it hasn't been opened yet
            if (!popup1.IsOpen) {
                //switch to read-only mode
                shareGrid.IsReadOnly = true;

                //make sure the popup is visible
                popup1.IsOpen = true;
            }

            Size popupSize = new Size(popup1.ActualWidth, popup1.ActualHeight);
            popup1.PlacementRectangle = new Rect(e.GetPosition(this), popupSize);

            //make sure the row under the grid is being selected
            Point position = e.GetPosition(shareGrid);
            var row = UIHelpers.TryFindFromPoint<DataGridRow>(shareGrid, position);
            if (row != null) shareGrid.SelectedItem = row.Item;
        }

        /// <summary>
        /// Closes the popup and resets the
        /// grid to read-enabled mode.
        /// </summary>
        private void ResetDragDrop() {
            _isDragging = false;
            popup1.IsOpen = false;
            shareGrid.IsReadOnly = false;
        }
    }
}


WPF FAQ の目次に戻る