Click here to Skip to main content
15,868,000 members
Please Sign up or sign in to vote.
0.00/5 (No votes)
See more:
Hi everybody:
I'm following the drag and drop example:
https://www.c-sharpcorner.com/UploadFile/raj1979/drag-and-drop-datagrid-row-in-wpf/ but sometimes using the scrollbar (horizontal or vertical) causes me problems.
I am not using the data from the example, but from a table with many columns and many rows.
In the example code, in the GetMouseTargetRow procedure it tells me if I'm clicking outside the cell area of the datagrid, in my case, when I'm moving the scrollbar button (horizontal or vertical).
But if the columns go beyond the width of the datagrid area, the GetMouseTargetRow procedure returns false because it doesn't recognize I've touched the scrollbar. If I make all the columns small to take up less area than the width of the data grid, everything works fine.
The same thing happens to me when I move the horizontal scrollbar, since at certain times the datagrid crashes and doesn't let me adjust the width of the columns.
The only difference with my tests is that in my XAML code, in my datagrid I don't use d:LayoutOverrides="Width". Does that explain the problem? What are LayoutOverrides used for?
Thanks for the help.

What I have tried:

This is my first WPF datagrid testing.
Posted
Updated 4-Jan-23 17:36pm

1 solution

I wanted to give you a link to a simple solution. This turned out not possible to do.

So I have adapted a solution from the following links:
* Drag and Drop DataGrid Row in WPF[^] = initial attempt - not a good solution for any Drag'n'Drop!
* WPF Tutorial | Drag & Drop[^] = a better way of implementing Drag'n'Drop but for the wrong control
* WPF: Scroll content of control when drag & drop is in progress[^] = a working solution with no example

1. ScrollOnDragOverBehavior from the link above required no modification, so grab the code from there.

2. ObservableObject class for wrapping the INotifyPropertyChanged
C#
public abstract class ObservableObject : INotifyPropertyChanged
{
    public void Set<TValue>(ref TValue field, TValue newValue, [CallerMemberName] string propertyName = "")
    {
        if (!EqualityComparer<TValue>.Default.Equals(field, default) && field!.Equals(newValue))
            return;

        field = newValue;
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    public event PropertyChangedEventHandler? PropertyChanged;
}

3. Employee Data Model:
C#
public class EmployeeModel : ObservableObject
{
    private int empNo;
    private string empName;
    private int salary;

    public int EmpNo
    {
        get => empNo;
        set => Set(ref empNo, value);
    }

    public string EmpName
    {
        get => empName;
        set => Set(ref empName, value);
    }

    public int Salary
    {
        get => salary;
        set => Set(ref salary, value);
    }
}

4. MainWindow code-behind
C#
public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        InitData();

        DataGrid.PreviewMouseLeftButtonDown += OnPreviewMouseLeftButtonDown;
        DataGrid.MouseMove += OnMouseMove;
        DataGrid.DragEnter += OnDragEnter;
        DataGrid.Drop += OnDrop;
    }

    private void InitData()
    {
            Employees.Add(new() { EmpNo = 101, EmpName = "Yudhistir", Salary = 56000 });
            Employees.Add(new() { EmpNo = 102, EmpName = "Bhim", Salary = 36000 });
            Employees.Add(new() { EmpNo = 103, EmpName = "Arjun", Salary = 45000 });
            Employees.Add(new() { EmpNo = 104, EmpName = "Sahedev", Salary = 24000 });
            Employees.Add(new() { EmpNo = 105, EmpName = "Nakul", Salary = 22000 });
            Employees.Add(new() { EmpNo = 101, EmpName = "Yudhistir", Salary = 56000 });
            Employees.Add(new() { EmpNo = 102, EmpName = "Bhim", Salary = 36000 });
            Employees.Add(new() { EmpNo = 103, EmpName = "Arjun", Salary = 45000 });
            Employees.Add(new() { EmpNo = 104, EmpName = "Sahedev", Salary = 24000 });
            Employees.Add(new() { EmpNo = 105, EmpName = "Nakul", Salary = 22000 });
            Employees.Add(new() { EmpNo = 101, EmpName = "Yudhistir", Salary = 56000 });
            Employees.Add(new() { EmpNo = 102, EmpName = "Bhim", Salary = 36000 });
            Employees.Add(new() { EmpNo = 103, EmpName = "Arjun", Salary = 45000 });
            Employees.Add(new() { EmpNo = 104, EmpName = "Sahedev", Salary = 24000 });
            Employees.Add(new() { EmpNo = 105, EmpName = "Nakul", Salary = 22000 });
            Employees.Add(new() { EmpNo = 101, EmpName = "Yudhistir", Salary = 56000 });
            Employees.Add(new() { EmpNo = 102, EmpName = "Bhim", Salary = 36000 });
            Employees.Add(new() { EmpNo = 103, EmpName = "Arjun", Salary = 45000 });
            Employees.Add(new() { EmpNo = 104, EmpName = "Sahedev", Salary = 24000 });
            Employees.Add(new() { EmpNo = 105, EmpName = "Nakul", Salary = 22000 });
            Employees.Add(new() { EmpNo = 101, EmpName = "Yudhistir", Salary = 56000 });
            Employees.Add(new() { EmpNo = 102, EmpName = "Bhim", Salary = 36000 });
            Employees.Add(new() { EmpNo = 103, EmpName = "Arjun", Salary = 45000 });
            Employees.Add(new() { EmpNo = 104, EmpName = "Sahedev", Salary = 24000 });
            Employees.Add(new() { EmpNo = 105, EmpName = "Nakul", Salary = 22000 });
            Employees.Add(new() { EmpNo = 101, EmpName = "Yudhistir", Salary = 56000 });
            Employees.Add(new() { EmpNo = 102, EmpName = "Bhim", Salary = 36000 });
            Employees.Add(new() { EmpNo = 103, EmpName = "Arjun", Salary = 45000 });
            Employees.Add(new() { EmpNo = 104, EmpName = "Sahedev", Salary = 24000 });
            Employees.Add(new() { EmpNo = 105, EmpName = "Nakul", Salary = 22000 });
            Employees.Add(new() { EmpNo = 101, EmpName = "Yudhistir", Salary = 56000 });
            Employees.Add(new() { EmpNo = 102, EmpName = "Bhim", Salary = 36000 });
            Employees.Add(new() { EmpNo = 103, EmpName = "Arjun", Salary = 45000 });
            Employees.Add(new() { EmpNo = 104, EmpName = "Sahedev", Salary = 24000 });
            Employees.Add(new() { EmpNo = 105, EmpName = "Nakul", Salary = 22000 });
    }

    public ObservableCollection<EmployeeModel> Employees { get; set; } = new();

    private Point startPoint;

    // Store the mouse position
    private void OnPreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        => startPoint = e.GetPosition(null);

    private void OnMouseMove(object sender, MouseEventArgs e)
    {
        // Get the current mouse position
        Point mousePos = e.GetPosition(null);
        Vector diff = startPoint - mousePos;

        if (e.LeftButton == MouseButtonState.Pressed &&
            (Math.Abs(diff.X) > SystemParameters.MinimumHorizontalDragDistance ||
            Math.Abs(diff.Y) > SystemParameters.MinimumVerticalDragDistance))
        {
            // Get the dragged DataGridRow
            DataGridRow row = FindAncestor<DataGridRow>((DependencyObject)e.OriginalSource);
            
            // Find the data behind the DataGridRow
            EmployeeModel employee = (EmployeeModel)DataGrid.ItemContainerGenerator.ItemFromContainer(row);

            // Initialize the drag & drop operation
            DataObject dragDataObject = new DataObject("DataRow", employee);
            DragDrop.DoDragDrop(row, dragDataObject, DragDropEffects.Move);
        }
    }

    private void OnDragEnter(object sender, DragEventArgs e)
    {
        if (e.Data.GetDataPresent("DataRow") && sender != e.Source)
            return;

        e.Effects = DragDropEffects.None;
    }

    private void OnDrop(object sender, DragEventArgs e)
    {
        if (!e.Data.GetDataPresent("DataRow"))
            return;
         
        EmployeeModel source = e.Data.GetData("DataRow") as EmployeeModel;

        // Get the destination DataGridRow
        var row = FindAncestor<DataGridRow>((DependencyObject)e.OriginalSource);
            
        // Find the data behind the DataGridRow
        var destination = (EmployeeModel)DataGrid.ItemContainerGenerator.ItemFromContainer(row);

        // Move the EmployeeModel in the collection
        Employees.RemoveAt(Employees.IndexOf(source));
        Employees.Insert(Employees.IndexOf(destination) + 1, source);
    }

    // Helper to search up the VisualTree
    private static T FindAncestor<T>(DependencyObject current)
        where T : DependencyObject
    {
        do
        {
            if( current is T dependencyObject )
                return dependencyObject;

            current = VisualTreeHelper.GetParent(current);
        }
        while (current != null);

        return null;
    }
}

5. MainWindow XAML/UI
C#
<Window x:Class="WpfScrollOnDragOverBehavior.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:WpfScrollOnDragOverBehavior"
        mc:Ignorable="d"
        x:Name="Window"
        Title="MainWindow" Height="450" Width="800">

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>

        <TextBox HorizontalAlignment="Stretch" Margin="10" 
                 Text="EmployeeModel Information"
                 TextAlignment="Center" FontFamily="SimSun" 
                 FontSize="28" />

        <DataGrid x:Name="DataGrid"
                  Grid.Row="1"
                  ItemsSource="{Binding ElementName=Window, Path=Employees}"
                  AutoGenerateColumns="False"
                  HorizontalAlignment="Stretch" Margin="10"
                  ColumnWidth="*"
                  SelectionMode="Extended"
                  AllowDrop="True"
                  local:ScrollOnDragOverBehavior.IsScrollOnDragOverEnabled="True">
            <DataGrid.Columns>
                <DataGridTextColumn 
                    Binding="{Binding EmpNo}"
                    Header="ProductId"></DataGridTextColumn>
                <DataGridTextColumn
                    Binding="{Binding EmpName}"
                    Header="ProductName"></DataGridTextColumn>
                <DataGridTextColumn
                    Binding="{Binding Salary}"
                    Header="ProductPrice"></DataGridTextColumn>
            </DataGrid.Columns>
        </DataGrid>

    </Grid>
</Window>

Now when you drag a DataGridRow to the edge of the DataGrid, the DataGrid will scroll. When you drop, The item that is being dragged will drop after the target row.

BONUS
If you want to show the row being dragged next to the mouse cursor, have a look at Josh's excellent article Drag and Drop Items in a WPF ListView[^] - I will leave this one to you as it is outside the scope of this question.
 
Share this answer
 
v2
Comments
Member 15639943 5-Jan-23 10:08am    
These links will be very helpful. Thanks a lot for your time and support.

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900