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