Enabling Drag and Drop over a Grid in WPF

Recently I needed to enable drag and drop in a WPF application.  The idea was to allow users to drop a file on the application window and the application would automatically open that file.

Sounds simple, right?

All it should take is to set the AllowDrop property to true on the Grid:

<Grid HorizontalAlignment="Stretch" DockPanel.Dock="Top" Height="Auto" 
  AllowDrop="True" Drop="Grid_Drop" DragOver="Grid_DragOver">

and then handle the Drop and DragOver events:

private void Grid_Drop(object sender, DragEventArgs e) {
	if (null != e.Data && e.Data.GetDataPresent(DataFormats.FileDrop)) {
		var data = e.Data.GetData(DataFormats.FileDrop) as string[];
		// handle the files here!
	}
}
 
private void Grid_DragOver(object sender, DragEventArgs e) {
	if (e.Data.GetDataPresent(DataFormats.FileDrop)) {
		e.Effects = DragDropEffects.Copy;
	} else {
		e.Effects = DragDropEffects.None;
	}
}

Everything looked great, right up until I tried to run it.   That’s when I discovered that this was apparently going to be more complicated than I thought.  When I dragged a file over the Grid, the DragOver event never fired, and the mouse showed the “Drop Not Allowed” cursor.

Of course, just to throw a wrench in the works, if I dragged the file over any of the Grid’s child controls, the DragOver event immediately fired and I could successfully drop the file.

Weird.

This was driving me crazy.  Why would dragging over the Grid not trigger the events, but dragging over its children would?

My first thought was to move the Drop functionality to the Grid parent.  Since apparently only children could trigger the necessary drop events, I (wrongly) guessed that this would solve the problem, and I could worry about the reasons later.

Of course, moving the functionality to the parent of the Grid (a DockPanel), did absolutely nothing.  It turns out, that none of my container controls would trigger the events, only when the file was dragged over a drawn control would the drop functionality trigger.

Google to the Rescue!

It turns out the solution is simple (and somewhat obvious in retrospect), but not at all clear on first glance.  After several searches led me to a variety of dead-ends, I finally found a result that worked.  It turns out that if you do not set a background on a UIElement, the element’s background will not participate in hit-testing. 

What this means is that  for a container element like a Grid, by default only children that actually draw elements will trigger events that rely on hit-testing (by bubbling up from the successful hit-test on the child).

Since drag and drop relies on hit-testing, the drag would appear to fail until it encountered a child, which would then bubble up the successful hit until the dragging code found an element that accepted drops.  In our case, that would be the Grid, which would trigger our drop functionality. 

Forcing a Hit-Test Over the Entire Grid

Fortunately, there is an easy way to get the entire Grid to participate in hit-testing – add a background to the Grid.  This way when the mouse is over any portion of the Grid it will successfully “hit” the control.  Of course, we don’t want the Grid background to interfere with any other controls or backgrounds we may have drawn, so we can set it to be “Transparent” and it will look to the user as if the Grid has no background at all. 

<Grid HorizontalAlignment="Stretch" DockPanel.Dock="Top" Height="Auto"
AllowDrop="True" Drop="Grid_Drop" DragOver="Grid_DragOver" Background="Transparent">

Its that simple!