Drag & Drop en WPF

Una de las preguntas mas recurrentes en los foros, cursos.. es como hacer Drag &Drop en WPF, hay muy buenos y muchísimos ejemplos en la red, yo voy a intentar explicarlo sin un gran ejemplo pero que se entiendan todos los conceptos.

Si leemos el capitulo de MSDN relativo al drag&drop , la secuencia de un drag &drop es la siguiente

 

1. Draging es inicializado por la llamada al método DoDragDrop

El metodo DoDragDrop tiene dos parámetros:

  • data, se especifica los datos a pasar
  • allowedEffects, se especifica cual operación (copiar y/o mover) son permitidas

2. En este momento se lanza el evento GiveFeedback que te permite por ejemplo displayar un puntero del ratón diferente durante el drag.

3. Cualquier control que tenga la propiedad AllowDrop a True es un potencial control destino del drag & drop

4. Cuando el ratón pasa por el control , el evento DragEnter es lanzado, con el metodo GetDataPresent compruebas que los datos que estan siendo arrastrados cumplen  el formato para tu control y la propiedad Effect es usada para mostrar un adecuado cursor.

5. Si el usuario suelta el arrastre en un control que cumpla lo anterior se lanza el evento DragDrop, en esete evento se extraen los datos del objeto DataObject y se displayan

 

Si vamos paso a paso

1.Detectando el Drag & Drop

Antes de que el DoDragDrop sea llamado, hay que detectar una operación de Drag con el ratón, esta operación suele ser los eventos MouseLeftButtonDown + un MouseMove, por lo que necesitamos subscribirnos a estos dos eventos

void Window1_Loaded(object sender, RoutedEventArgs e)
{
this.DragSource.PreviewMouseLeftButtonDown += new MouseButtonEventHandler(DragSource_PreviewMouseLeftButtonDown);
this.DragSource.PreviewMouseMove += new MouseEventHandler(DragSource_PreviewMouseMove);
} 

Para prevenir falsos drag & drop se pueden utilizar SystemParameters.MinimumHorizontalDragDistance  y SystemParameters.MinimumVerticalDragDistance, una vex que detectamos que no es un falso drag&drop guardamos la posición inicial

 

  void DragSource_PreviewMouseMove(object sender, MouseEventArgs e)
        {
            if (e.LeftButton == MouseButtonState.Pressed && !IsDragging)
            {
                Point position = e.GetPosition(null);

                if (Math.Abs(position.X - _startPoint.X) > SystemParameters.MinimumHorizontalDragDistance ||
                    Math.Abs(position.Y - _startPoint.Y) > SystemParameters.MinimumVerticalDragDistance)
                {
                  StartDrag(e); 

                }
            }   
        }

        void DragSource_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            _startPoint = e.GetPosition(null);
        }

2.Una vez detectado el drag

Necesitamos indicar los datos que queremos arrastrar para que el objeto al que se le va ha hacer el Drop luego sepa que datos le están llegado, estos datos se pasan a través del objeto DataObject a  través del método SetData que tiene dos parámetros el tipo de formato que se pasa y el objeto SetData (  Type format, object data ), el código del método  StartDrag quedaría de la siguiente forma

private void StartDrag(MouseEventArgs e)
        {
            IsDragging = true;
            DataObject data = new DataObject(System.Windows.DataFormats.Text.ToString(), "abcd");
            DragDropEffects de = DragDrop.DoDragDrop(this.DragSource, data, DragDropEffects.Move);
            IsDragging = false;
        }

En este caso no hemos definido ningún cursor diferente, si lo quisiéramos el código sería

private void StartDragCustomCursor(MouseEventArgs e)
        {

            GiveFeedbackEventHandler handler = new GiveFeedbackEventHandler(DragSource_GiveFeedback);
            this.DragSource.GiveFeedback += handler; 
            IsDragging = true;
            DataObject data = new DataObject(System.Windows.DataFormats.Text.ToString(), "abcd");
            DragDropEffects de = DragDrop.DoDragDrop(this.DragSource, data, DragDropEffects.Move);
            this.DragSource.GiveFeedback -= handler; 
            IsDragging = false;
        }

 

Como veis manejamos el evento GiveFeedback cuya implementación sería

 

void DragSource_GiveFeedback(object sender, GiveFeedbackEventArgs e)
        {
                try
                {
                    //This loads the cursor from a stream .. 
                    if (_allOpsCursor == null)
                    {
                        using (Stream cursorStream = System.Reflection.Assembly.GetExecutingAssembly().GetManifestResourceStream(
			"SimplestDragDrop.DDIcon.cur"))
                        {
                            _allOpsCursor = new Cursor(cursorStream);
                        } 
                    }
                    Mouse.SetCursor(_allOpsCursor);

                    e.UseDefaultCursors = false;
                    e.Handled = true;
                }
                finally { }
        }

 

3. Creando un efecto visual al arrastrar

 

Queda muy chulo que cuando arrastramos un control veamos difuminado el propio control que arrastramos, para ello utilizaremos un Adorner que ya explicamos, el constructor de este Adorner sería

 

public DragAdorner(UIElement owner, UIElement adornElement, bool useVisualBrush, double opacity)
            : base(owner)
        {
            _owner = owner;
            if (useVisualBrush)
            {
                VisualBrush _brush = new VisualBrush(adornElement);
                _brush.Opacity = opacity;
                Rectangle r = new Rectangle();
                r.RadiusX = 3;
                r.RadiusY = 3;
                r.Width = adornElement.DesiredSize.Width;
                r.Height = adornElement.DesiredSize.Height;

                XCenter = adornElement.DesiredSize.Width / 2;
                YCenter = adornElement.DesiredSize.Height / 2;

                r.Fill = _brush;
                _child = r;

            }
            else
                _child = adornElement;

        }

 

Si os  fijáis se utiliza un VisualBrush para reflejar el contenido del elemento que arrastramos, si queremos utilizarlo el metodo StartDrag, cambia un “poquito”

 

private void StartDragInProcAdorner(MouseEventArgs e)
        {

            
            DragScope = Application.Current.MainWindow.Content as FrameworkElement;
            System.Diagnostics.Debug.Assert(DragScope != null);

                        bool previousDrop = DragScope.AllowDrop;
            DragScope.AllowDrop = true;            

          

            GiveFeedbackEventHandler feedbackhandler = new GiveFeedbackEventHandler(DragSource_GiveFeedback);
            this.DragSource.GiveFeedback += feedbackhandler;

           
            DragEventHandler draghandler = new DragEventHandler(Window1_DragOver);
            DragScope.PreviewDragOver += draghandler; 

           
            DragEventHandler dragleavehandler = new DragEventHandler(DragScope_DragLeave);
            DragScope.DragLeave += dragleavehandler; 

                       QueryContinueDragEventHandler queryhandler = new QueryContinueDragEventHandler(DragScope_QueryContinueDrag);
            DragScope.QueryContinueDrag += queryhandler; 

          
            _adorner = new DragAdorner(DragScope, (UIElement)this.dragElement, true, 0.5);
            _layer = AdornerLayer.GetAdornerLayer(DragScope as Visual);
            _layer.Add(_adorner);


            IsDragging = true;
            _dragHasLeftScope = false; 
                       DataObject data = new DataObject(System.Windows.DataFormats.Text.ToString(), "abcd");
            DragDropEffects de = DragDrop.DoDragDrop(this.DragSource, data, DragDropEffects.Move);

                       DragScope.AllowDrop = previousDrop;
            AdornerLayer.GetAdornerLayer(DragScope).Remove(_adorner);
            _adorner = null;

            DragSource.GiveFeedback -= feedbackhandler;
            DragScope.DragLeave -= dragleavehandler;
            DragScope.QueryContinueDrag -= queryhandler;
            DragScope.PreviewDragOver -= draghandler;  

            IsDragging = false;
        }

4. Control destino

En el control destino además de haber habilitado la propiedad AllowDrop a True  podemos subscribirnos a los eventos

  • DragEnter:
  • DragLeave
  • DragOver

Estos eventos se suelen utilizar para cambiar los efectos o Adorner y dar un feedback al usuario

El evento mas importante es el Drop que se disparara cuando el usuario suelte el botón del ratón, aquí deberemos de hacer las acciones necesarias.

void Window1_Loaded(object sender, RoutedEventArgs e)
     {
         this.DragSource.PreviewMouseLeftButtonDown += new MouseButtonEventHandler(DragSource_PreviewMouseLeftButtonDown);
         this.DragSource.PreviewMouseMove += new MouseEventHandler(DragSource_PreviewMouseMove); 
         // DROP 
         this.DropTargetImages.DragEnter += new DragEventHandler(DropTargetImages_DragEnter);
         this.DropTargetImages.DragLeave += new DragEventHandler(DropTargetImages_DragLeave);
         this.DropTargetImages.DragOver += new DragEventHandler(DropTargetImages_DragOver);
         this.DropTargetText.DragOver += new DragEventHandler(DropTargetText_DragOver);
         this.DropTargetText.Drop += new DragEventHandler(DropTargetText_Drop); 
     } 
     void DropTargetText_Drop(object sender, DragEventArgs e)
     {
         IDataObject data = e.Data; 
         if (data.GetDataPresent(DataFormats.Text))
         {
             MessageBox.Show( 
                 string.Format("formato correcto,  '{0}'" , 
                 ((string)data.GetData(DataFormats.Text)))); 
         } 
     } 
     void DropTargetImages_DragOver(object sender, DragEventArgs e)
     {
         if (!e.Data.GetDataPresent("Images"))
         {
             e.Effects = DragDropEffects.None;
             e.Handled = true;
         }
     } 
     void DropTargetText_DragOver(object sender, DragEventArgs e)
     {
         if (!e.Data.GetDataPresent("Text"))
         { 
             e.Effects = DragDropEffects.None;
             e.Handled = true;
         } 
     } 
     void DropTargetImages_DragLeave(object sender, DragEventArgs e)
     {
         RunStoryboard("Timeline2"); 
     } 
     void RunStoryboard(string name)
     {
         Storyboard sb = this.FindResource(name) as Storyboard ;
         System.Diagnostics.Debug.Assert(sb != null);
         sb.Begin(DropTargetImages); 
     } 
     void DropTargetImages_DragEnter(object sender, DragEventArgs e)
     {
         if (!e.Data.GetDataPresent("Image"))
         {
             RunStoryboard("Timeline1"); 
             e.Effects = DragDropEffects.None;
             e.Handled = true;
         } 
     }

Aquí tenéis el código

4 comentarios sobre “Drag & Drop en WPF”

  1. Estoy haciendo una lista que lee sus datos desde un xml, en el xml tengo un elemento que se llama ordersequence que va sumando 1 cada ves que se agrega un item a la lista y se agrega un nodo al xml con un elemento ordersequence. Ahora, quiero mover algun item de su indice con drag and drop y modificar el xml (ordersequence) para luego hacer un refresh de la lista y ordenarlo por ordersequence y no c por donde empezar quisa necesite un poco de guia, .. espero respuesta …
    mi correo es

    alberto.burquez@twice-interactive.com

Responder a anonymous Cancelar respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *