Someone asked me how to implement a “panel-like” system using Expression / WPF. This led me to do a small take on Expression Interactive Designer itselfJ. Though the panel system presented here is only 1/100th as complex as our real Palette management infrastructure (and is my own code), it serves as a good example to get you started. Source for this example can be downloaded / re-used / re-implemented at will from here.
I wanted to highlight a few concepts in this example: a) How to implement a naive dragging system, b) How to create and raise your own events, c) How to do simple hit-testing in WPF so you can figure out what is under the mouse and drop into it. I also wanted to highlight the clear distinction in roles between a developer and designer (my design skills are found severely wanting at times!).
The Panels are all custom controls that extend HeaderedContentControl because we want the panels to be draggable only when clicking on the Header part and we also want the designer to be able to specify Header and Content for every Panel. It is expected (call it a contract if you will) that the designer will name one element in the template of the control to be “PART_Header”. As long as this contract is maintained, the control should work as expected; else the drag functionality simply won’t be there.
Next step is to pull out Interactive Designer (the real one!) and create the look for an individual Panel and layout the various Panels. All the Panels share the same “chrome” (consisting of the border and the close button) and this is done by applying the same template to each. Also, each Panel’ template has two ContentPresenter elements – one to render the Header (which is a string in our case), and one to render Content (which are simple images). These various panels are then added to two ItemsControl (for the left and right containers). Also, grid splitters have been used for resizing the panel columns – neat, ah?
Back to the developer role using and using VS. At this point, we have the capability to drag panels around. Now, we want to be able to drop them into other containers. Whenever a panel is dragged, it raises a custom event called “DragPanel”. Also, when the user lets go of the mouse, another event called “DropPanel” is raised. These events then bubble up to the parent Window which then performs required logic for changing containers around. One advantage of using eventing in this fashion is that I can then reuse my custom Panel class in other scenarios where I don’t really care about drop-drop and parents and such. Also, you will see the use of some simple hit-testing code to determine the ItemsControl into which a container is dropped. Personally, I don’t prefer writing low-level hit-testing code (as I have shown in the example) and I would rather use data-binding and custom controls for this purpose – this is just there for the purpose of showing the power of the platform.
Feel free to send feedback to me!
I have a question regarding "floating" windows...? To achieve something like dragging a single panel out of the main window, would it be neccessary to create a "temporary" new window object and this panel as child of that window or how would you handle this?
Do you have a VB version of this code?
Lesters blog at blogs.msdn.com\llobo has a much better example. It is int he form of a library - so is more useful to the users.
How can I close a HeaderedContentControl by clicking on the button "x" ??
I try a lot of way, and it's never work....
Great job !
Do you know if it's possible to place the dragged control between two others, rather than dropping it always on the end ?
Thank you :-)
hi dimitri, this is the method i used to remove the panel when you click on the button,
private void Button_Click(object sender, RoutedEventArgs e)
panel = (sender as Button).TemplatedParent as PanelControl;
knownParent = this.GetItemsControlContainingPanel(panel);
btw, is there a way to add more than one UIElement to the content?
i'm sorry, what i meant in the last post was that if there's a way to put a xaml file as the input for the panel content?
Great article, this has been very helpful.
Two things I discovered:
1. (To Leebert) The Mouse Capture stuff is there in case the mouse moves outside the current app window. I commented it out and ran into the bug which made me discover what the purpose was.
2. Bug - if nesting the panels inside each other, the inner PanelControl will cause its parents PanelControl's events to raise as well. So, I changed the RoutingStrategy from Bubble to Direct and this fixed the problem.