Silverlight 2 Samples: Dragging, docking, expanding panels (Part 3)
UPDATE: Get the latest Dragging, docking, expanding panel code from Blacklight, our new CodePlex project!
In Part 1, we looked at how we construct a Dragging, docking, expanding panel, and added the ‘dragging’ functionality by placing the panel in a Canvas. In Part 2 we looked at the host panel that controls the grid layout and the docking functionality. In this part, we are going to look at how we add the maximising functionality.
We will start with some additional code we need to add the DragDockPanel. The toggle button for toggling a panel’s maximised state is already in the template (see Part 1). We now need to get the toggle button out, and hook up the checked and unchecked events...
ToggleButton maximizeToggle =
this.GetTemplateChild("PART_MaximizeToggle") as ToggleButton;
if (maximizeToggle != null)
{
maximizeToggle.Checked +=
new RoutedEventHandler(maximizeToggle_Checked);
maximizeToggle.Unchecked +=
new RoutedEventHandler(maximizeToggle_Unchecked);
}
Next, we add a private variable that will be used as a flag to store whether a control was minimised by the toggle button, or minimised programmatically...
private bool ignoreUnCheckedEvent = false;
We now add a public member to get / set whether the panel is maximised or not.
private bool isMaximized = false;
public bool IsMaximized
{
get { return this.isMaximized; }
set
{
this.isMaximized = value;
ToggleButton maximizeToggle =
this.GetTemplateChild("PART_MaximizeToggle")
as ToggleButton;
if (maximizeToggle != null)
{
this.ignoreUnCheckedEvent = true;
maximizeToggle.IsChecked = this.isMaximized;
}
}
}
In the setter for IsMaximized, we store the value locally, and also update the toggle’s button checked state. This is where we use ignoreUnCheckedEvent. When we programmatically set the checked state of a toggle button, the Checked / UnChecked event is also raised from the button, so here we set a flag to say that we have programmatically set the property so we can ignore the next event that is raised.
Let’s take a look at the event handler for the toggle button’s checked event...
void maximizeToggle_Checked(object sender, RoutedEventArgs e)
{
// Bring the panel to the front
Canvas.SetZIndex(this, currentZIndex++);
this.ignoreUnCheckedEvent = false;
// Fire the panel maximized event
if (this.Maximized != null)
this.Maximized(this, e);
}
The handler firstly brings the panel to the front, sets the flag to false again, and finally fires the panel’s own Maximized event for the host to pick up and respond too.
The unchecked handler is similar...
void maximizeToggle_Unchecked(object sender, RoutedEventArgs e)
{
if (!this.ignoreUnCheckedEvent)
{
this.IsMaximized = false;
// Fire the panel minimized event
if (this.Minimized != null)
this.Minimized(this, e);
}
else
{
this.ignoreUnCheckedEvent = false;
}
}
Here, we check the flag, and if we don’t ignore the event, we set IsMaximized to false, and raise the panel’s own Minimized event. Otherwise we just reset the flag to false.
That’s it for DragDockPanel. Let’s take a look at DragDockPanelHost where we handle the layout rearranging...
Firstly, we add a private member to store the maximised panel...
private DragDockPanel maximizedPanel = null;
When a panel is maximised, the other panels move to a stack on the right hand side. We have a public property that allows you to set how wide you wish to make that stack...
private double minimizedColumnWidth = 250.0;
public double MinimizedColumnWidth
{
get { return this.minimizedColumnWidth; }
set { this.minimizedColumnWidth = value; }
}
In Part 2, in the DragDockPanelHost Loaded event handler, we hooked up the events necessary to handle the panel dragging. In the same place, we can add handlers to the panels’ minimised and maximised events...
panel.Maximized +=
new EventHandler(dragDockPanel_Maximized);
panel.Minimized +=
new EventHandler(dragDockPanel_Minimized);
The 2 handlers are quite simple - that bulk of the work takes place in our layout methods. The maximised handler looks like this...
void dragDockPanel_Maximized(object sender, EventArgs e)
{
DragDockPanel maximizedPanel =
sender as DragDockPanel;
// Store max'ed panel
this.maximizedPanel = maximizedPanel;
// Loop through children to disable dragging
foreach (UIElement child in this.Children)
{
DragDockPanel panel =
child as DragDockPanel;
panel.DraggingEnabled = false;
if (panel != this.maximizedPanel)
panel.IsMaximized = false;
}
// Update sizes and layout
this.AnimatePanelSizes();
this.AnimatePanelLayout();
}
When a panel is maximised, we keep a reference to the maximised panel, loop through all of the children disabling dragging, and setting all of the other panels IsMaximized property to false, and finally, updating the layout with the animating methods.
The minimised handler is very similar...
void dragDockPanel_Minimized(object sender, EventArgs e)
{
// Set max'ed panel to null
this.maximizedPanel = null;
// Loop through children to disable dragging
foreach (UIElement child in this.Children)
{
DragDockPanel panel =
child as DragDockPanel;
panel.DraggingEnabled = true;
}
// Update sizes and layout
this.AnimatePanelSizes();
this.AnimatePanelLayout();
}
We clear the reference to the maximised panel, and then loop through all of the children, re-enabling dragging. Finally, we update the layout using the animated methods.
Let’s take a look at the animated size and layout methods, firstly, AnimatePanelLayout.
private void AnimatePanelLayout()
{
// If we are not in max'ed panel mode...
if (this.maximizedPanel == null)
{
// Place panels in a grid...
}
else // If a panel is maximized...
{
// Layout the children with one panel maxmisised.
}
}
Firstly, we check to see if there is a maximised panel - if not, we use the code we already have to put them into a grid, if so, then we lay them out, with one maximised.
Something we need to consider here is that the panels may have been shuffled around, so they are no longer displayed in the same order that the host has them in the visual tree. So firstly, we need to get the current order of the panels, going left to right across rows, and down each row.
Dictionary<int, DragDockPanel> orderedPanels =
new Dictionary<int, DragDockPanel>();
// Loop through children to order them according to their
// current row and column...
foreach (UIElement child in this.Children)
{
DragDockPanel panel = (DragDockPanel)child;
orderedPanels.Add(
(Grid.GetRow(panel) * this.columns) +
Grid.GetColumn(panel),
panel);
}
Here we create a dictionary that stores an index of a panel, and the panel itself. The index is calculated using the panel’s row and column properties. This gives us the real position of the panel (in terms of a stacking order). Now we have the panels in order, we can loop through and position them.
// Set initial top of minimized panels to 0
double currentTop = 0.0;
// For each of the panels (as ordered in the grid)
for (int i = 0; i < orderedPanels.Count; i++)
{
// If the current panel is not the maximized panel
if (orderedPanels[i] != this.maximizedPanel)
{
// Animate the size
orderedPanels[i].AnimatePosition(
this.ActualWidth -
this.minimizedColumnWidth,
currentTop
);
// Increment current top
currentTop +=
this.ActualHeight /
(double)(this.Children.Count - 1);
}
else // If the current panel is the maxmized panel
{
// Animate it to 0,0
orderedPanels[i].AnimatePosition(0, 0);
}
The simple loop goes through each panel. If it’s not the maximised panel, it stacks it on the right hand side, and keeps track of the position in a variable currentTop. If the panel is the maximised panel, then we move it to 0,0.
That’s all that’s required to position the panels when one is maximised. Let’s take a quick look at the panel sizing.
private void AnimatePanelSizes()
{
// If there is not a maxmized panel...
if (this.maximizedPanel == null)
{
// Size to grid cells
...
}
else // If there is a maximized panel
{
// Size for a maximised panel.
}
}
You will see that first we check if there is a maximised panel - if not, we use the code we had before to size the elements for a grid. Otherwise we loop through children, giving the appropriate sizes.
foreach (UIElement child in this.Children)
{
DragDockPanel panel =
(DragDockPanel)child;
// Set the size of the non
// maximized children
if (panel != this.maximizedPanel)
{
panel.AnimateSize(
minimizedColumnWidth -
panel.Margin.Left -
panel.Margin.Right,
(this.ActualHeight /
(double)(this.Children.Count - 1)) -
panel.Margin.Top - panel.Margin.Bottom
);
}
else // Set the size of the maximized child
{
panel.AnimateSize(
this.ActualWidth -
this.minimizedColumnWidth -
panel.Margin.Left - panel.Margin.Right,
this.ActualHeight -
panel.Margin.Top - panel.Margin.Bottom
);
}
}
As we loop through the children, we check to see if the current panel is the maximised one - if it isn’t then we size the panel to fit on the stack on the right hand side, if it is, then we size the panel to fill the rest of the space. Cool.
The same logic is added to the UpdatePanelLayout method, but we won’t go through that here.
I have decided to do templating of the panels in a video, showing off Blend 2.5. That will be the final part of the Drag-Dock-Expanding panel sample. We will move onto something new after that!
Source code is available from www.codeplex.com/blacklight.
Check out a running sample here!
Martin
Comments
Anonymous
August 29, 2008
Sweet! I've become more comfortable about where to use your visionary techniques in my applications going forward. This is very good stuff I'm also interested in something you said you want to get more into, namely expanding / collapsing lists. While it may not be your final piece, I think it's probably the next progression. Again, thanks for the code, and at some point, it may be a good idea to get this on CodePlex.Anonymous
August 29, 2008
The comment has been removedAnonymous
August 31, 2008
Hi Martin, This sample is great and thanks for taking this initiative to publish the sample similar to the CUI demonstration. I quickly tried to convert this sample into WPF and am getting some flickering during the animation for drag and drop. Any idea on this. regds, PrabakarAnonymous
September 02, 2008
Koen Zwikstra with Silverlight Spy, John Papa on UserControl from Popup Control, Shawn Wildermuth onAnonymous
September 03, 2008
Hi martin, thanks for this amazing usefull article ! that's clean stuff and I learned a lot, thanks ;) regards, DargosAnonymous
September 17, 2008
Prabhakar, Not sure why you are seeing flickering - quite possible to do with the way we do animation. If possible, could you send me your wpf sample to take a look? martin.grayson@microsoft.com. Thanks! MartinAnonymous
September 17, 2008
The comment has been removedAnonymous
September 17, 2008
The comment has been removedAnonymous
September 18, 2008
Hi I was woundering if you could help me with an expression blend problem. When I edit a button' control part's like in your creating a glass button lesson, I would like to be able to have the content presenter's Forecolor change from white to black when the mouse is over, and to white again when the mouse leaves. Thanks for your help.Anonymous
September 18, 2008
Hi again. It seems that work with the change of the xmlns, now I having problem with the animations. first show only one panel, then when I do a resize shows correctly (the 6 panels) also anyone know how to migrate the animations of the maximize/minimize button that only works on silverlight?Anonymous
September 18, 2008
The comment has been removedAnonymous
September 18, 2008
Jim, wrong post, but hey! You should be able to do this in the triggers panel. Add a mouse over = true trigger that sets the content presenters forground to a color, then back again when the property becomes false. Thanks, MartinAnonymous
September 19, 2008
Very cool controls. I downloaded the sample but it when I try to open it in Visual Studio 2008 I get an error message saying the DragDockPanelSample.csproj cannot be opened. The project type is not supported by this installation. Anyone else see this? Thanks, SamAnonymous
September 19, 2008
Hello I fix the problem with the Toggle button. if anyone is intereseted, I use the next library http://blogs.msdn.com/johngossman/archive/2008/08/08/visualstatemanager-for-desktop-wpf.aspx this library enable the use of VSM in WPF. Note. you need to add the support to the three states of the Togle Button (Checked, Unchecked, Indeterminate) just take a look of the code. you can mail me at mxsmax@msn.com in other hands, I had the same problem the flick when you try to do the drag & drop effect. is like the panel move but the grid doesn't the move. anyone had have the same problem?Anonymous
September 20, 2008
Hey scromer, To modify, compile and run the source code, you need Visual Studio 2008 + .NET 3.5 with the Siverlight 2 Beta 2 tools and developer runtimes. You can get access to all the bits from here... :http://silverlight.net/GetStarted/ HTH, MartinAnonymous
September 29, 2008
Hey gabriel, Someone sent me a video of the flickring issue. I think this is down to the differences in WPF and Silverlight mouse move events. A workaround for WPF would be to use the WPF thumb control as the grip bar, and then hook up the Thumb controls DragDelta event, and pass the HoriztonalChange and VerticalChange up in the drag event args. I have tried this, but think that should do the trick. MartinAnonymous
October 01, 2008
Hi Martin, I've tried this sample on the SL2Beta2 and it works perfect. And when I updated to the RCO, on the initial load, it just stacks them together. But when I resize, then it displays properly. I created just XAMLs without controls in them, it displays them properly when the page loads. But as soon as I start adding controls, the page are stack when it loads the first time. Thanks, reyn_lAnonymous
October 01, 2008
Hey reyn_L, This is a known bug in the drag dock panel host. It is because in Beta 2 the Loaded event fired before the SizeChanged. In RC0, its the other way around. To fix in the meantime, you need to just add a... this.UpdatePanelLayout(); ...to the end of the loaded event handler. This will be fixed in the version of the control that appears in http://www.codeplex.com/blacklight at the end of October release. MartinAnonymous
October 02, 2008
Hello Martin, Thanks very much for the prompt response and help on this issue. Sincerely, reyn_LAnonymous
October 02, 2008
The comment has been removedAnonymous
October 17, 2008
The comment has been removedAnonymous
October 20, 2008
Hello Martin, and thank you very much for the wpf version, is impressive :) i would like to suggest a feature, that is the abillity to have all panels minimized, for example, imagine ou want to have all panels with minimize height of 100, and minimize width of 50, and minimize on the left, to act like tabs, but when you minimize the one is opens it should not go to the normal stat, but keep minimized like the others. and another question i would like to ask, is how is it possible to determinate the rows and columns of the panel host? Thank very much once again.. Rui MarinhoAnonymous
October 22, 2008
Hi Rui, Nice suggestion. I have thought about adding a minimise button to each panel, so you can minimise them one by one. This would probably achieve the scenario you have described above. I will keep the idea open! If you get round to having a go yourself, let me know! The host actually does store the number of columns and rows, in private variables. You could add public get's to these no problem! Thanks, MartinAnonymous
October 22, 2008
Maritin, That was quite excellent showcasing about WPF capabilities. i have a query for you i have gone through the code of your application, but i could not see that the panels can be arranged in vertical position(i mean one above the other) can we do that or can't we ? if so how....Anonymous
November 03, 2008
Hi Naidu, there is now a property on the Drag Dock host that allows you to pick where the panels go... Minimized position i think. If you get the latest release from Codeplex (came out 2 days ago) then you will see the property in there.Anonymous
November 21, 2008
hi guys, do you know how to show a page.xaml in a panel?Anonymous
November 24, 2008
The Page.xaml should be a user control, and so, can be placed inside a drag dock panel. This is assuming that the Page.xaml isnt the root page that you load in App.xaml.cs...?Anonymous
November 25, 2008
The comment has been removedAnonymous
November 25, 2008
Hi Noam, Thanks for reporting the bug, and giving us a fix. Will get this in for the release on the 5th December! Thanks, MartinAnonymous
February 27, 2009
The comment has been removedAnonymous
February 27, 2009
(Afterthought: forget about the minimize button... Seems fairly straightforward to implement. Those gridsplitter though...)Anonymous
March 06, 2009
Hmmmm. grid splitters wouldnt work well in the control as it is today. You would need to embark upon creating a new control, or inherit the existing host and add a bunch more code. As for dynamic adding of panels, that has been supported since V2. Look at the showcase sample page code. Thanks, MartinAnonymous
March 09, 2009
Hi, We're looking at creating an application modeled after the MS Patient Journey demo app. Also, we're probably going to use Prism as well. Can you show how your panels can be hooked up to Prism?? Thanks!Anonymous
March 09, 2009
Hi Jeff, I havent done any work with Prism yet, so couldnt provide an example, however, the panels are just content controls, and you can place what ever content you like inside. So, if you built a UserControl is prism capabilities, you could just drop that inside a panel! Simple!Anonymous
March 17, 2009
Hi Martin, Thanks for sharing this excellent work! I tried to extend the DradDockPanel class to override some minimized/maximized actions. However, there would be a runtime failure when I used my class inside the DragDockPanelHost. On debugging the problem is caused in private void DragDockPanelHost_Loaded(object sender, RoutedEventArgs e) { .... for (int i = 0; i < this.Children.Count; i++) { if (this.Children[i].GetType() == typeof(DragDockPanel)) { ... } This type checking seems to fail. I replaced it with if (this.Children[i] is DragDockPanel) and now I can use the derived class. Please let me know if this is okay or I missed something? ThanksAnonymous
March 24, 2009
Great work. Awesome example of what can be done with Silverlight. But if, like me, you need a little more handholding in order to grasp the delicasies of custom control building, then you probably want to have a look at Jesse Liberty's 3 part video tutorial on skinnable custom controls (links below) FIRST before you even attempt looking at the sourcecode or try to make sense of the blog posting. Trust me. http://silverlight.net/learn/learnvideo.aspx?video=116200 http://silverlight.net/learn/learnvideo.aspx?video=141967 http://silverlight.net/learn/learnvideo.aspx?video=149875Anonymous
March 24, 2009
BTW one more video in Jesse Liberty's on skinnable custom controls series: http://silverlight.net/learn/learnvideo.aspx?video=149876Anonymous
April 20, 2009
Hi GuruC, Your change is valid, and I will put it into the next release. Thanks, MartinAnonymous
July 09, 2009
hey martin.. in a dragdock panel i want that each panel should be minimized initially..by minimize i mean only the header should be visible not the content... pointers to this will be appreciated and Is it possible with minimum change of code...Anonymous
September 10, 2009
Just a suggestion.... you need to make a Silverlight tab in your main page. While SL is a subset of WPF, it is worthy of it's own section. I'm on the learning curve for WPF and I'm looking forward to digging into SL when I'm ready. Great article.Anonymous
September 19, 2009
Is it possible to use the DragDockPanel & Host as-is to create a more complex layout as found here: http://www.mscui.net/PatientJourneyDemonstrator/PrimaryCare.htm Thanks in advance!Anonymous
May 03, 2010
Hi Martin, Great job here with this project. Just a question: How to Span a panel across muliple rows (or columns) ? An example is featured on the Patient Journey Demonstrator - Primary Care Demonstrator. Thanks for your response.Anonymous
November 21, 2010
Thank you for sharing this. I am wondering if this can be used within a wrappanel. Did you try doing something like that in WrapPanel? Do you have any available sample? Thank you in advance!Anonymous
November 24, 2010
I am wondering if the content in drag and drop prototype can be generated with XMLDataProvider and keep all interactions available. I tried it but without success yet. Please let me know. Thank you.Anonymous
August 24, 2011
Hi ! How can I find each position oh each panel after a drag and drop ? Thanks for your response.