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 (45)

  1. samcov says:

    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.

  2. martin.grayson says:

    Hey samcov, thanks again for looking at the samples!

    Expanding / collapsing lists is VERY high on the list. I think I will deal with reusable drag and drop first, then do the list stuff.

    I actually set up a codeplex project last week, so at some point in the very near future, I will put all the code on there so folk can look at it any time, and check out some of my other samples!

    I will post here once its up and running.

    Thanks for reading!

  3. Prabhakar says:

    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,

    Prabakar

  4. Koen Zwikstra with Silverlight Spy, John Papa on UserControl from Popup Control, Shawn Wildermuth on

  5. Dargos says:

    Hi martin,

    thanks for this amazing usefull article !

    that’s clean stuff and I learned a lot, thanks 😉

    regards,

    Dargos

  6. martin.grayson says:

    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!

    Martin

  7. Gabriel Valencia says:

    Hi.

    this is a very useful sample. I try to convert to WPF but I have some problems, the first error is that can’t create the DragDockPanel.

    debuging It seems that the error is in the AnimatedContentControl.cs

    I change the XamlReader.Load line for the next lines:

               // Load the XAML

               StringReader stringReader = new StringReader(animatedElementXaml);

               XmlReader xmlReader = XmlReader.Create(stringReader);

               // Create a canvas from the XAML

               Canvas animatedElement = XamlReader.Load(xmlReader) as Canvas;

    unfortunately is not working. what I missing??

  8. martin.grayson says:

    Hey, to fix this, where you construct the animatedElementXAML, the first line, currently this…

    animatedElementXaml += "<Canvas xmlns=’http://schemas.microsoft.com/client/2007‘ xmlns:x=’http://schemas.microsoft.com/winfx/2006/xaml‘>";

    Needs to be replaced with this…

    animatedElementXaml += "<Canvas xmlns=’http://schemas.microsoft.com/winfx/2006/xaml/presentation’ xmlns:x=’http://schemas.microsoft.com/winfx/2006/xaml‘>";

    This is beacuse of the different XAML namespaces with WPF and Silverlight.

    HTH,

    Martin

  9. Jim says:

    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.

  10. Gabriel Valencia says:

    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?

  11. martin.grayson says:

    Hi Gabriel,

    For the first issue, I think you need to call UpdateLayout from the hosts loaded event (this is down to the order of eventing in WPF).

    And yes, the toggle button wont port just yet. You will need to redo the template in blend using triggers.

    I am thinking about producing a WPF version as there have been many request, I cant promise a date, but hopefully by the end of october.

    Thanks,

    Martin

  12. martin.grayson says:

    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,

    Martin

  13. scromer says:

    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,

    Sam

  14. Gabriel Valencia says:

    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?

  15. martin.grayson says:

    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, Martin

  16. martin.grayson says:

    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.

    Martin

  17. reyn_L says:

    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_l

  18. martin.grayson says:

    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.

    Martin

  19. reyn_L says:

    Hello Martin,

    Thanks very much for the prompt response and help on this issue.

    Sincerely,

    reyn_L

  20. Harlequin says:

    Was wondering if there’s a way for it to only do columns. Instead of 4 boxes being 2×2. you want them like this:

    [BOX EXPANDED]

    [BOX]

    [BOX]

    [BOX]

    Then if you maximize the 3rd box, it would squeeze the other ones shut and open itself up.

    Could probably be done in the C#, but I’d like to add a property to DragDockPanelHost or something. Better approach.

  21. martin.grayson says:

    Hi all,

    I have knocked up a quick version of this in WPF. I will make sure it is in Blacklight (http://www.codeplex.com/blacklight) for the release at the end the this month, with tidy code and nice templates etc…

    … However, if you wanted it now, you can get the source code from here…

    http://mightymeaty.members.winisp.net/samples/TestDragDockPanel.zip

    Cheers,

    Martin

  22. Rui Marinho says:

    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 Marinho

  23. martin.grayson says:

    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,

    Martin

  24. Naidu says:

    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….

  25. martin.grayson says:

    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.

  26. john79 says:

    hi guys,

    do you know how to show a page.xaml in a panel?

  27. martin.grayson says:

    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…?

  28. Noam says:

    Hello,

    I have been working with the new Blacklight version of this control and found a bug. If you remove the maximized panel, the host gets mixed up. To resolve I added a line in the RemovePanel method that checks if it the correct control, and if so, nulls the maximizedPanel member. Seems to resolve the issue.

    Thanks.

  29. martin.grayson says:

    Hi Noam,

    Thanks for reporting the bug, and giving us a fix. Will get this in for the release on the 5th December!

    Thanks,

    Martin

  30. Jurie Smit says:

    Hi Martin,

    I am using your blacklight dragdockpanel to develop a dashboard with an extremely high level of customization. Before I put too much effort and time into customizing your control to suite my excact requirements I was wondering whether you have seen any implementations of the "minimize" button, as well as the dynamic run-time addition of panels into you control? Any implementation/code along those lines would obviously be very usefull to me.

    Another idea I have is combining your control with gridsplitters. Somehow struggling to get started with this though, not sure how to approach the problem… any possible pointers from you maybe?

    Any help would obviously be greatly appreciated. Thank you, smit.jurie@gmail.com

  31. Jurie Smit says:

    (Afterthought: forget about the minimize button… Seems fairly straightforward to implement.

    Those gridsplitter though…)

  32. martin.grayson says:

    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,

    Martin

  33. Jeff says:

    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!

  34. martin.grayson says:

    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!

  35. GuruC says:

    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?

    Thanks

  36. LynYo says:

    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=149875

  37. LynYo says:

    BTW one more video in  Jesse Liberty’s on skinnable custom controls  series:

    http://silverlight.net/learn/learnvideo.aspx?video=149876

  38. martin.grayson says:

    Hi GuruC,

    Your change is valid, and I will put it into the next release.

    Thanks,

    Martin

  39. John D Costa says:

    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…

  40. Michael says:

    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.

  41. AKS says:

    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!

  42. Ian says:

    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.

  43. Vladislav says:

    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!

  44. Is that possible to generate content via XMLDataProvider? says:

    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.

  45. Maka says:

    Hi !

    How can I find each position oh each panel after a drag and drop ?

    Thanks for your response.