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