karollo
karollo

Reputation: 595

WPF Keep MouseButtonEvent tunneling down

I have a Data Grid, in which I use DragAndDropBehavior to move items up/down :

public class DataGridRowDragAndDropBehavior
    {    
        public  delegate  Point GetPosition(IInputElement element);


        int   rowIndex = -1;

        public static DependencyProperty DataGridDragAndDropEnabled =
            DependencyProperty.RegisterAttached("DataGridDragAndDropEnabled", typeof(bool),
                typeof(DataGridRowDragAndDropBehavior), new UIPropertyMetadata(false, OnDataGridDragAndDropEnabled));

        private static void OnDataGridDragAndDropEnabled(object sender, DependencyPropertyChangedEventArgs e)
        {
            var dataGrid = (DataGrid) sender;
          dataGrid.PreviewMouseLeftButtonDown += Instance.productsDataGrid_PreviewMouseLeftButtonDown;
           dataGrid.Drop +=Instance.productsDataGrid_Drop;

        }

        public static bool GetDataGridDragAndDropEnabled(DependencyObject obj)
        {
            return (bool) obj.GetValue(DataGridDragAndDropEnabled);
        }
        public static void SetDataGridDragAndDropEnabled(DependencyObject obj, bool value)
        {
            obj.SetValue(DataGridDragAndDropEnabled, value);
        }


        private static DataGridRowDragAndDropBehavior instance = new DataGridRowDragAndDropBehavior();

        public static DataGridRowDragAndDropBehavior Instance
        {
            get { return instance; }
            set { instance = value; }
        }

         void productsDataGrid_Drop(object sender, DragEventArgs e)
         {

             var dataGrid = (DataGrid) sender;
             var dataGridSource = (System.Collections.IList)dataGrid.ItemsSource;

            if (rowIndex < 0)
                    return;
                var index = GetCurrentRowIndex(e.GetPosition,dataGrid);
                if (index < 0)
                    return;
                if (index == rowIndex)
                    return;
                //if (index == dataGrid.Items.Count - 1)
                //{
                //    MessageBox.Show("This row-index cannot be drop");
                //    return;
                //}

                var  changedItem = dataGridSource[rowIndex];
                dataGridSource.RemoveAt(rowIndex);
                dataGridSource.Insert(index, changedItem);


         }

         void   productsDataGrid_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
         {

             var dataGrid = (DataGrid) sender;

            rowIndex = GetCurrentRowIndex(e.GetPosition,dataGrid);
            if (rowIndex < 0)
                return;
            dataGrid.SelectedIndex = rowIndex;
            Register selectedEmp = dataGrid.Items[rowIndex] as Register;
            if (selectedEmp == null)
                return;
            DragDropEffects dragdropeffects = DragDropEffects.Move;
            if (DragDrop.DoDragDrop(dataGrid, selectedEmp, dragdropeffects)
                                != DragDropEffects.None)
            {
                dataGrid.SelectedItem = selectedEmp;
            }


         }

        private  bool GetMouseTargetRow(Visual theTarget, GetPosition position)
        {
            Rect rect = VisualTreeHelper.GetDescendantBounds(theTarget);
            Point point = position((IInputElement)theTarget);
            return rect.Contains(point);
        }

        private  DataGridRow GetRowItem(int index,DataGrid dataGrid)
        {
            if (dataGrid.ItemContainerGenerator.Status
                    != GeneratorStatus.ContainersGenerated)
                return null;
            return dataGrid.ItemContainerGenerator.ContainerFromIndex(index)
                                                            as DataGridRow;
        }

        private   int GetCurrentRowIndex(GetPosition pos,DataGrid dataGrid)
        {
            int curIndex = -1;
            for (int i = 0; i < dataGrid.Items.Count; i++)
            {
                DataGridRow itm = GetRowItem(i,dataGrid);
                if (GetMouseTargetRow(itm, pos))
                {
                    curIndex = i;
                    break;
                }
            }
            return curIndex;
        }
    }

Problem is, that in this DataGrid I have a column with checkbox:

<DataGridTemplateColumn Header="CheckBox Column" IsReadOnly="False">
  <DataGridTemplateColumn.CellTemplate>
    <DataTemplate>                                   
       <CheckBox>My checkbox</CheckBox>
    </DataTemplate>                          
  </DataGridTemplateColumn.CellTemplate> 
</DataGridTemplateColumn>

So because of this behavior, the checkbox is not responding (when i disable DragAndDrop it works).

I suspect that this is because of my behaviour handles the click event, which is never being tunneled down to my checkbox.

How can I prevent that? I tried setting e.Handled = false somewhere in my behavior, but it didn't work.

P.S Just to be clear I am using my behavior in this way:

 <DataGrid behaviors:DataGridRowDragAndDropBehavior.DataGridDragAndDropEnabled="true">

Upvotes: 0

Views: 143

Answers (1)

icebat
icebat

Reputation: 4774

A better solution would be to use MouseMove event instead and check if left button is pressed there to start drag and drop: e.LeftButton == MouseButtonState.Pressed.

But if you want a solution using MouseDown like you suggested in comments, then you either need to bubble the event up and\or delay the DnD execution because it's the reason the further routing stops.

There are probably some robust solutions already, but from the top of my head we can make it work with simple timer. For that we'll need PreviewMouseLeftButtonUp also:

private static void OnDataGridDragAndDropEnabled(object sender, DependencyPropertyChangedEventArgs e)
{
    var dataGrid = (DataGrid)sender;
    dataGrid.PreviewMouseLeftButtonDown += Instance.productsDataGrid_PreviewMouseLeftButtonDown;
    dataGrid.PreviewMouseLeftButtonUp += Instance.productsDataGrid_PreviewMouseLeftButtonUp;
    dataGrid.Drop += Instance.productsDataGrid_Drop;
}

Then we just need to start it with a delay (400 ms here) so that all controls have time to respond. And we stop when mouse button is up:

private System.Timers.Timer dragTimer;

void productsDataGrid_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
   var dataGrid = (DataGrid)sender;

   dragTimer = new System.Timers.Timer(400);
   System.Timers.ElapsedEventHandler elapsed = null;
   elapsed = (s, ee) =>
   {
      dragTimer.Elapsed -= elapsed;

      dataGrid.Dispatcher.Invoke(() =>
      {
         rowIndex = GetCurrentRowIndex(e.GetPosition, dataGrid);
         if (rowIndex < 0) return;

         dataGrid.SelectedIndex = rowIndex;
         object selectedEmp = dataGrid.Items[rowIndex];
         if (selectedEmp == null) return;

         DragDropEffects dragdropeffects = DragDropEffects.Move;
         if (DragDrop.DoDragDrop(dataGrid, selectedEmp, dragdropeffects)
               != DragDropEffects.None)
         {
            dataGrid.SelectedItem = selectedEmp;
         }
      });
   };

   dragTimer.Elapsed += elapsed;
   dragTimer.Start();
}

private void productsDataGrid_PreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
   if (dragTimer == null) return;

   dragTimer.Stop();
   dragTimer.Dispose();
   dragTimer = null;
}

There are ways to make it better but it should work like this.

Upvotes: 1

Related Questions