I've been working on a massive cross browser, cross platform Silverlight application for the last year or so. One of the major components is file drag and drop (heretofore D&D, not to of course be confused with Dungeons and Dragons). Getting this to work on Windows is trivial, and there are myriad resources out there to get you started. However, over on the Mac, things were much more difficult.
Our app went into beta, and the Mac users immediately started logging bugs about D&D not working on FireFox. So we dusted off our test Mac, and everything seemed fine. The disconnect turned out to be, after some quick investigation, a version conflict. We were running one of the last builds of FireFox 3; the users were all on 5 (if you recall, version 5 came out rather quickly after 4 shipped).
So we upgraded, and quickly saw the problem: although drag still worked, drop was completely dead. FireFox version 4 and up had broken support for Silverlight D&D (or, technically, just the second D). And I use the term "support" loosely because official backing from Microsoft wasn't really there. (It was kind of like Silverlight 4 "not supporting" Chrome, although it works just fine.)
What I want to discuss here is how to get Silverlight D&D working on Macs running FireFox 5.
We went the full nine yards when we built our Silverlight D&D infrastructure: using a Silverlight Behavior for the infrastructure that allowed us to attach the functionality to any UIElement. It used a VisualStateManager to provide cues to the user that a dragged file could be dropped at that particular area (by "lighting" up the text and animating a glowing DropShadow around the boarder). Additionally, we had a cursor control that hid the mouse pointer and showed different images in its place that provided additional visual indications where D&D was enabled. Finally, a static helper class provided common functionality, such as flags to track if we were dragging and if a drop action, based on the current cursor location, was possible.
Check out my colleague Jonathan Rupp's post on how he got us this far. I'm going to take the next step here and get our Silverlight D&D logic working for FireFox 5 on a Mac. The basic approach is to leverage the fact that drag still works, and use a combination of HTML 5, the Silverlight HTML bridge, and jQuery to basically "fake" a drop. Do read Jonathan's post, as I'll be referring to the code he presents.
The first thing to do is create a div in the ASPX page that hosts our Silverlight control. This is what we'll be using as our drop surface; there's nothing too special about it (yet):
As you can tell, the styling implies that this div will be absolutely positioned and dimensioned to cover a certain portion of the screen. One thing to note: this technique will only work on a Mac, where Silverlight automatically sets the windowless mode to true, allowing the Silverlight region to participate in HTML Z-indexing. If you set windowless to true on a PC, you'll break D&DN altogether, as well as introduce some performance hits. I wrote more about this here.
Next we need to hook up the HTML 5 D&D events on our drop surface. Call the following method in jQuery's document ready event:
The next step is to position this div directly on top of a particular area of your Silverlight control when a drag is detected. (Recall that in Silverlight, you hook drag events on a particular UIElement, not the entire application.) Our Silverlight D&D behavior keeps a reference to the UIElement that it's supporting. So when a file is being dragged over a valid drop zone, an event is fired that basically updates the VisualStateManager for that UIElement. What I added to this was logic to get the HTML coordinates of this UIElement, and position our drop surface div over it.
The flag in Line #2 is maintained in the aforementioned D&D helper utility. Line #5 is another helper method that takes in the current mouse position (gotten from the drag event), gets the UIElement at that location, and grabs the associated behavior (made possible by the gluey nature of attached properties). Everything else is pretty straight forward, utilizing the beauty of the Silverlight HTML bridge.
Now if the user were to release the mouse button, the drop would happen. But before we drop, we need to handle the case were the user drags off the control without dropping. This interaction is important, as it not only mimics what our D&D behavior is doing for us automatically in other environments, but can be reused upon a drop (since the UI needs to be reset properly).
Then we hook its click event in Silverlight (as part of the behavior).
We're using the Silverlight HTML bridge and jQuery chaining to set the value of the hidden HTML button to "CLEAR" and then click it. That click event will be handled in Silverlight by the aforementioned HandleFileReceived method, which we're still not quite ready to discuss. First, let's talk about the drop workflow.
While the drop surface is positioned over the drop zone, Silverlight won't be receiving any mouse input. But that's okay, since the visual state won't need to change until the drop surface receives either a drag leave or a drop event. We've covered what happens in the former case, so without further ado, let's talk about drop.
Here's the FFDrop method in all its glory:
Most of the magic happens in ProcessFile, which follows.
In Line #4, we new up an HTML 5 FileReader, which when fed the metadata of a drop event, gives us a nice OO representation of a file. The error event is hooked in Line #6, and processing is done according W3C protocol. Line #'s 30 and 31 load the file; the fact that this is an asynchronous process is what forces us to use global variables. In Line #38, we build the XML representation of the file array we're going to be passing to Silverlight. The _fileCounter variable is decremented in Line #40 each time a file is successfully collected, so that the check in Line #42 can determine if all asynchronous operations have completed, the XML can be capped off, and finally be sent to Silverlight via the aforementioned bridge-and-change method in Line #47.
So we have both our drag leave and our drop methods setting the value of a hidden HTML button and then clicking it. The click event is handled by Silverlight, and deals with both cases.
Once again, Line #41 gets the D&D behavior, and fires the drop event (with the files) on the target UIElement. Line #7 determines if this is a "CLEAR" event (in which case the drop surface is hidden) or if this is an actual drop. Drag leave still works normally in FireFox 5 on a Mac, so the behavior itself can update the VisualStateManager and reset any "dragging" flags in either case. The rest is not too exciting: Line #31 builds an XDocument from the raw XML and iterates the child "file" nodes. It then pulls out the file name and size properties, as well as converts the data from a base64 string to a byte array, and feeds it to our DroppedFileWrapper DTO (which is a wrapper around a FileInfo object).
If time allowed, this would have all been baked into our behavior and done proper: implementing XML deserialization for the file bits, not using buttons to pass data around, and generally less piece-wising these technologies together. But with the timeline I had, the complexity of the solution, and the existing infrastructure it had to fit into, (which itself was already quite complex due to cross-browser, cross-platform Silverlight D&D support) this is how it was born.
To summarize, if you're not using a behavior or any other existing D&D implementation, here's what you need to do to get Silverlight D&D working in FireFox 5 on a Mac:
And there you have it: Silverlight drag and drop in FireFox 5 on a Mac. Have fun!