Welcome to MSDN Blogs Sign in | Join | Help

It has been a little while since I last posted, and a little longer since I was last able to provide an update to the Blacklight project. This short post is just to give an update on where we are, and when the next big release can be expected.

First off, Blacklight has been a pleasure to work on so far, and I love all the great feedback and ideas the community has been generating. It is, however, something that we do in our ‘spare time’, and as such, we have found that a new release every month has been a stretch, and certainly with the new focus that we have applied to our day jobs, what with the economy, etc...

The good news, though, is we are nearing the end of our financial year, and from July, Blacklight updates will resume. I will definitely be making it one of my commitments for the year.

The format will more likely follow a big release every 3 months, with fixes and patches in between when possible. This will enable the team to deliver a far better quality product.

In the mean time, the discussion boards are full of patches, fixes and improvements to the existing controls, so if you are happy to play with the source, there is a lot there.

Thanks again for supporting the project, and watch this space in July!

After a ‘short delay’, V3.0 of the Blacklight controls are here! Thank very much for you patience with this release.

The showcase will be updated very shortly for you to see all of the controls in action!

Get the latest release here!

As always, here are the highlights for this release…

 

BRAND NEW ‘BLACKLIGHT CONNECTED’ CONTROLS

We have focused on providing controls that allow designers to start adding rich data and content to their application, without the need for code. This release sees 3 such controls: LiveSearchListBox, SyndicationFeedListBox and DeepZoomViewer!

 

Live Search List Box

imageimageThe Live Search List Box provides Live Search functionality. The XAML below shows how to incorporate this control – no C# required!

image

The Live Search List Box links to the search text box, and buttons by passing in their names. Simple! Get an application ID from dev.live.com right now to get going!

Syndication Feed List Box

image

The Syndication Feed List Box makes it very simple to include RSS feeds into your application.

 

Deep Zoom Viewer

The Deep Zoom Viewer provides a drag and drop control that displays deep zoom images and give you all the interactivity required to navigate around, including mouse wheel support!

image

 

OTHER NEW BITS

Animated Expander

image

The Animated Expander is a control provides the ability to expand and collapse UI elements with an animation.

Updated Showcase Navigation

As more controls became available, we have reshuffled the showcase navigation to make it easier to find the control you are after…

image

 

FINALLY…

The project has had a BIG structural overhaul, and some namespace changes. This could result in some breaking changes in you application. Please use the codeplex discussion boards for further assistance.

The overhaul has been necessary to allow WPF and Silverlight to share controls, meaning plenty more controls for WPF too. The list below shows what is available for WPF now..

image

Thanks again for you patience.

Get the latest stuff from the code plex site here.

See the showcase here.

 

Martin

A video from Mike Parker, introducing the Animated Layout Panel control, found in Blacklight!

Get the controls from here.

See the showcase here.

Probably the last post before the holidays, however, I am very pleased to announce a new release of the Patient Journey Demonstrator. This is a smoking hot release with some super rich and super cool new features.

You can plenty of details on the new features at the Microsoft Health CUI Team Blog.

To summarise though, the key new features are....

 

Clinical Noting Using Health Language

We have partnered with Health Language to provide an information rich clinical noting environment. The demonstrator now connects to their web service to allow you to encode clinical notes...

image 

image

 

Highlighting and Annotating Deep Zoom

One of my favourite new features - you can now highlight areas and add notes to the patient ECG data in Deep Zoom. The really cool part is that when a note disappears off of the screen, a little marker points to where it is to let you know there is something you can't see!

image

image

 

Brand new Show Guide Feature

Clicking on 'Show Guide' in the top right of the application gives a new experience that we think, re-defines help and guidance! :)

Overlays appears over the live application, letting you know what you can do on any screen, and where we have used Microsoft Health CUI Guidance and Controls.

image

 

Check out the demonstrator for yourself, read all about all the latest features in detail, or just watch the video below!

 

 

Download the video from here.

A short video (02:16) on how to start using Blacklight Controls in Blend!

Get the controls from here.

See the showcase here.

It has been a really busy November for me, however, the December release of Blacklight has arrived. This release contains tonnes of bug fixes, as well as some really cool new features.

Check out the the new showcase here.

Get the latest release code from here.

Some of the new fully customisable controls and features include...

GLASS BORDER

The glass border give a glossy effect either over the top of content or behind.

image

PERSPECTIVE SHADOW BORDER

The perspective shadow draws a shadow behind the border giving a feeling of perspective, as if the border is standing upright...

image 

RADIAL SHADOW BORDER

The radial shadow border give the effect that the border and its contents are hovering off of the ground. The shadow width and vertical offset are all customisable enabling you to create a greater sense of depth.

image  image

ANIMATED LAYOUT PANEL

Mike Parker has provided his first control to the project - the AnimatedLayoutPanel. This panel allows incredible deep levels of configuration to achieve the exact effect you are looking for. This must be seen... (Thanks Mike!)

image

LOADING ANIMATION

The cute little control gives you something to display when buffering a video, downloading an image / deep zoom content, waiting on a web service etc. You can also set the content to be what ever you please!

image 

Go check it out at www.codeplex.com/blacklight.

Thanks,

Martin

The first release of Blacklight is here! This release has really focussed on creating a set of controls that create visual effects that designers use in everyday work.

I have also worked on some improvements to the Drag Dock Panel controls, including being able to dynamically add panels at runtime, and change which side the panels dock on when one is maximised. Check out all of the details of this release below... and check out the new showcase here.

Silverlight Controls

For Silverlight 2, this release introduces 3 new Border controls – Clipping Border, Drop Shadow Border and Inner Glow Border, 2 new text controls – Drop Shadow Text Block and Stroke Text Block (Alpha), a set of control styles, from Dave Crawford, and some improvements to Drag Dock Panel.

Clipping Border

In WPF, each border, panel and content control has a ClipToBounds property. This meant that if anything was placed inside a control that extends outside of the edge, you could tell the control to clip it. Silverlight doesn’t have this property, and code is always required to draw clipping regions around each control you want to clip. This became especially hard with borders and rounded corners.

The clipping border is a border, just like the Silverlight border, that allows you clip the content. Even with a different corner radius on each corner.

Below, shows a border with a rectangle inside, where the content in not clipped.

The clipping border can clip the content as shown below...

Clipping border is at the base of all of the border controls, so all Blacklight borders have a ClipContent property.

 

Drop Shadow Border

A fairly obvious control, the Drop Shadow Border draws a drop shadow below your main border. This effect is great if you want your border to look as though it is raise off of the page. You can adjust things like the angle of the drop shadow, the distance from the main border, the softness of the shadow (or spread) and the opacity and colour.

Here are some examples I have made...

 

Outer Glow Border

This feature in v0.1, however, didn’t get a mention here. The Outer Glow border, is similar to the drop shadow border, however, the ‘shadow’ is drawn around all edges, creating more of a glow effect. You can change the glow size, colour and opacity, as well as all the normal border properties like border thickness, corner radius etc.

Inner Glow Border

Just like the outer glow border, however, the glow appears on the inside of the edges. This is a good effect for making a border look as though it has sunk into the page. You can change the glow size, colour and opacity.

The difference here though, is you can choose the glow size for each edge, allowing you to really customise the effect, for example...

There is also a ContentZIndex property, which allows you to chose whether the borders content sits on top of the shadow, or underneath it. (0 for underneath, 1 for on top).

Drop Shadow Text Block

The drop shadow text block is just like a regular text block, with a drop shadow drawn below.

Just like the drop shadow border, you can change the angle, distance, colour and opacity of the shadow.

Although this effect is quite easy to achieve in Blend, it can be a pain to move around, duplicate and edit. Putting it into a control has hopefully made it very easy to manage.

Stroke Text Block (Alpha)

This text block has a stroke effect around the edges...

You can adjust all of the normal text properties, as well as the stroke thickness, colour and opacity.

This is an Alpha version of this control, so it should be used VERY sparingly, with as smaller stroke thickness as possible. Why? The control works by replicating many text blocks in the background, and drawing them around the edge of a main text block. This can have performance implications, as the thicker the stroke, the more new text blocks are put on the screen. I hope to work on improvements to this control in the future.

Drag Dock Panel

The main changes here from v0.1 are...

1.       Improved default template for the Drag Dock Panel

2.       Can chose where the other panels go when one is maximised – either the top, right, bottom or left sides

3.       Can add and remove panels dynamically at runtime to the host.

Head over to the showcase to see the sample running.

Silverlight Styles

In this release, Dave Crawford has kindly shared his glossy control styles. You will find the styles and templates for use in your application in the ResourceDictionaries folder in the Showcase.

WPF Controls

This release sees the first WPF control, under the Blacklight.Wpf namespace. It is actually the Drag Dock Panel control, ported over to WPF. There is a sample viewer that you can download from the releases page (in the binaries download) where you can see the panel in action.

Showcase Updates

There have been a few showcase updates, but most importantly, a guide to how to use Blacklight in Blend or Visual Studio 2008. So check those out. There is also a new ‘Who’s involved?’ page, so you can keep track on who is contributing to the project!

You can see all the bits running in the current showcase here. And you can download this release from here.

Next release is due end of November, and with lots planned; it should be a big one.

Enjoy,

Martin 

What a week! Silverlight 2 is here - horray! This release really feels like it has come out of nowhere.

First up - where to get the stuff...

I have been pretty busy over the last couple of weeks getting my 2 current project up to speed with RTW, and they are now available.

The Patient Journey Demonstrator has now been upgraded to Silverlight 2 RTW...

The current release of Blacklight (v0.1) has also been updated along with the showcase. There is also now a pre-release showcase, giving you a taste for what is coming at the end of October! Check-it out now...

 

Finally, my UX buddy, Dave Crawford, has updated his Glossy Control styles for the RTW of Silverlight 2. The are real nice, and now, everyone, demand more from Dave!!! See them running live here...

Back from holiday, and just a short post to let you know that Blacklight has been updated for Silverlight 2 RC0.

To get the release candidate for Silverlight 2 go here: http://silverlight.net/GetStarted/sl2rc0.aspx

To view the RC0 compatible version of the Showcase, you can go to: http://mightymeaty.members.winisp.net/blacklight.silverlight.rc0/

You can still see the Beta 2 version of the Showcase here, however: http://mightymeaty.members.winisp.net/blacklight.silverlight/

I have updated release v0.1 to also have the RC0 ready source code too. So head over to http://www.codeplex.com/blacklight for the bits.

Martin

Hey - very exciting news today... I finally have a CodePlex project set up. From the site...

'Blacklight is a UX focused code sharing project. Microsoft has released a bunch of technologies that allow designers and developers to work closely together to make beautiful software. This project is a collection of controls, samples, visual assets and ideas that has been put together by User Experience designers and developers to both show you what the technology is capable of (from a UX point of view), and give you code and samples that you can use in your own projects.'

We are currently delivering stuff in Silverlight, but have plenty more technologies to follow in the near future.

We also have a snazzy showcase that you can view the content through too. So, without further adue, here are the links to get to the stuff...

See Blacklight on CodePlex here.

See the Blacklight showcase site right here.

 [SCREENSHOT OF THE SHOWCASE SITE]

I hope to do releases on a monthly basis, each release introducing new controls and samples. I will also be posting about new content right here on my blog too,so stay tuned!

Right off to Remix 08 tomorrow, and then on vacation for a week! Woohoo.

Laters, Martin.

Hi all - great news, an updated version of the Patient Journey Demonstrator, the project I am currently working on, has been released. Check it out at http://www.mscui.net/PatientJourneyDemonstrator.

In this release we fixed lots of bugs, as well as added some new features around capturing input from the user. Below is a list of some of the new features and a video walkthrough of the whole demonstrator...

Presence Indicators

Presence and availability is a really important theme in health care. Everywhere where we display an employee’s name, we show their presence...

Updated Navigation Bar

The updated navigation bar allows you to move backwards and forwards through different demonstrators too.

Silverlight Patient Banner

We have incorporated the new Silverlight patient banner, from the Microsoft Health CUI controls library.

Menu Pearl

We have a new menu pearl that will allow you access to some of the new features within the demonstrator - in this release, the ability to add a consultation!

Creating a Consultation

In the primary care demonstrator, you can now create a new consultation, prescribe medications and record observations. You can then save this to the patient record and see the activity list and meds list update!

SNOMED Terminology Lookup

We have taken a subset of the SNOMED database of clinical terms, and made these searchable when creating a consultation. Try typing ‘Chest Pain’.

Search and Prescribe Medications

Search for medications and prescribe to see the meds list view update.

Styled Meds List View

We have styled the Microsoft Health CUI Meds List View control to fit in visually with the demonstrator.

Drug details on Patient Chart’s table view

We have added columns to the table view in Patient Charts for showing the drugs the patient is taking.

Visual Makeover of the ECG charts

We are looking at different ways of representing the deep zoom, digital ECG data (in the Secondary Care Demonstrator). One of those explorations was to make the full result set look more like the live ECG. This is an easter egg, and requires you to press F8 once, as soon as the Secondary Care Demonstrator has loaded!

Annotating the Angiogram

We have added a bunch of features that allow you to annotate the ECG video in the secondary care demonstrator...

Including a tool bar for measuring, inking, drawing marquees, zooming in and going full screen...

A collapsible list of the annotation history...

The ability to attach a text note to each annotation...

A frame by frame selector allowing you to see what annotations are already on the video and provide navigation to those frames...

Walkthrough Video

Check out the video below for a complete walkthrough of the demonstrator - or better still - check it out at http://www.mscui.net/PatientJourneyDemonstrator.

Download the video here.

Keep checking here for future Patient Journey Demonstrator updates :)

Martin

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

UPDATE: Get the latest blog media player code from Blacklight, our new CodePlex project!  

I have been meaning to start video blogging (vlogging) for some time. I think you can get stuff across far more simply that way. However, I hadn't gotten around to building a simple video player for hosting on my blog - well, problem solved. I finally got off my backside and made one.

I decided to make my first video blog on the media player itself, so sit back and enjoy.

Introducing the new Video Blog Media Player

You can download the video too. 

The source code for the media player is available from www.codeplex.com/blacklight if you want to change / host it yourself. Thanks to Tim Sneath for giving the example on how to host Silverlight on your blog

You could also point to where I have hosted the page (if you don't have hosting yourself) - however, I cannot guarantee the connection or that the MediaPlayer will be bug free.

The player is hosted on this page... http://mightymeaty.members.winisp.net/MediaPlayer/MediaPlayer.html

You need to pass in a source to activate the player - i.e. http://mightymeaty.members.winisp.net/MediaPlayer/MediaPlayer.html?mediaSource=http://silverlight.services.live.com/11264/vlog_200808/video.wmv.

Happy vlogging!

Martin

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 this part, we are going to look at how we do the docking element.

Take a peek at the finished sample running, here.

The positioning and docking logic takes place in a host control, called DragDockPanelHost. This is a panel control, that derives Canvas, that positions the panels in a grid and then moves them around when the user is dragging DragDockPanel.

[But first, a small digression...]

Why derive Canvas and not Panel I hear you cry??? Dave Relyea posted a while back on why he doesn’t like Canvas. Whilst he makes some good points, I feel he missed a trick when it comes to considering Canvas for the type of layout we do here.

Deriving Panel gives you 2 methods - Measure and Arrange - tell the layout system how big you want to be, then layout your children. Simple for when you want your children to go to a specific place every time the layout updates. However, with our layout system, we are more complicated. We have panels that can be dragged, shuffled around, maximised etc. etc.

To do all of this by deriving Panel seemed difficult and cumbersome, however, when starting with Canvas, you have a superbly basic layout system, using absolute positioning and giving you animatable positioning properties. I didn’t need to worry about layout updated events at all, but just consume the custom events my panels raised.

Simple. I like Canvas for this kind of purpose. Please challenge me if you feel differently!

[Digression ends.]

OK. So first off, we create out class and derive from Canvas...

    public class DragDockPanelHost : Canvas

    {

        #region Private members

        // A local store of the number of rows

        private int rows = 1;

 

        // A local store of the number of columns

        private int columns = 1;

 

        // The panel currently being dragged

        private DragDockPanel draggingPanel = null;

        #endregion

 

        #region Constructors

        /// <summary>

        /// Constructor

        /// </summary>

        public DragDockPanelHost()

        {

            this.Loaded +=

                new RoutedEventHandler(DragDockPanelHost_Loaded);

            this.SizeChanged +=

                new SizeChangedEventHandler(DragDockPanelHost_SizeChanged);

        }

        #endregion

    }

We also have some private variables that store the number of rows and columns (so we don’t have to recalculate every time) as well as the currently dragging panel. In the constructor, we hook up the Loaded events and SizeChanged events.

When we get the Loaded event, we do the work to calculate how may rows and columns we require (based on how many children the panel has), place each panel in a grid and row, and hook up some events from the panel.

(NOTE - we only do this in the Loaded event, meaning panels added later won’t be ‘hooked up’. Please check out the limitations section at the end of the post for more details on this.)

Firstly, we work out the number of rows. When placing the panels in a grid like layout, we try and lay them out in a square, giving preference to width over height (as most resolutions are 4:3 or wider). Let me give you an example...

If we had 6 panels, we would have 2 rows with 3 panel in each row, rather than 3 rows with 2 panels in each. If we had 8 panels, we would have 2 rows with 4 panels in each. If we had 9 panels, we would have 3 rows, with 3 panels in each.

To work out the rows, we take the square root of the number of children, and round it down.

Once we know how many rows we have, we can work out how many columns are required...

    // Calculate the number of rows and columns required

    this.rows =

        (int)Math.Floor(Math.Sqrt((double)this.Children.Count));

 

    this.columns =

        (int)Math.Ceiling((double)this.Children.Count / (double)rows);

We then loop through the rows and columns, assigning each panel a row and column, and hooking up the panels events...

    int child = 0;

    // Loop through the rows and columns and assign to children

    for (int r = 0; r < this.rows; r++)

    {

        for (int c = 0; c < this.columns; c++)

        {

            DragDockPanel panel = this.Children[child] as DragDockPanel;

 

            // Set starting row and column

            Grid.SetRow(panel, r);

            Grid.SetColumn(panel, c);

 

            // Hook up panel events

            panel.DragStarted +=

                new DragEventHander(dragDockPanel_DragStarted);

            panel.DragFinished +=

                new DragEventHander(dragDockPanel_DragFinished);

            panel.DragMoved +=

                new DragEventHander(dragDockPanel_DragMoved);

 

            child++;

 

            // if we are on the last child, break out of the loop

            if (child == this.Children.Count)

                break;

        }

 

        // if we are on the last child, break out of the loop

        if (child == this.Children.Count)

            break;

    }

You will see that we actually use the Grid.Row and Grid.Column attached properties to record the row and column, even though the panels aren’t actually in a Grid control. We couldn’t think of a reason why this would be bad!

There are 3 layout methods in DragDockPanelHost - UpdatePanelLayout, AnimatePanelSizes, AnimatePanelLayout. The first sets the child panels size and positions without animation, the other two animate the sizes and positions respectively, using AnimateSize and AnimatePosition (from the AnimatedContentControl base class).

Let’s look at UpdatePanelLayout...

    private void UpdatePanelLayout()

    {

        // Layout children as per rows and columns

        foreach (UIElement child in this.Children)

        {

            DragDockPanel panel = (DragDockPanel)child;

 

            Canvas.SetLeft(

                panel,

                (Grid.GetColumn(panel) *

                    (this.ActualWidth / (double)this.columns))

                );

           

            Canvas.SetTop(

                panel,

                (Grid.GetRow(panel) *

                    (this.ActualHeight / (double)this.rows))

                );

 

            panel.Width =

                (this.ActualWidth / (double)this.columns) -

                panel.Margin.Left - panel.Margin.Right;

           

            panel.Height =

                (this.ActualHeight / (double)this.rows) -

                panel.Margin.Top - panel.Margin.Bottom;

        }

    }

In this method, we loop through the children in the host, setting the position and size of each panel. To set the position, we get the column and row the panel sits in, and multiply by the size of the host over the number of columns and rows.

For the size, we set the width and height to be the size of the host over the number of columns or rows and subtracting any margin the panel has.

The UpdatePanelLayout method is called every time the host changes size...

    void DragDockPanelHost_SizeChanged(

        object sender, SizeChangedEventArgs e)

    {

        this.UpdatePanelLayout();

    }

So, we now have our grid layout, and as the host resizes, the panels will stay in position.

Next, let’s deal with the dragging / docking.

In the loaded event, we hooked up 3 events for each of the panels - DragStarted, DragFinished and DragMoved. These are the 3 events that tell the host when a panel is being dragged about and when it has been dropped.

The handlers for DragStarted and DragFinished are very simple...

    void dragDockPanel_DragStarted(object sender, DragEventArgs args)

    {

        DragDockPanel panel = sender as DragDockPanel;

 

        // Keep reference to dragging panel

        this.draggingPanel = panel;

    }

In DragStarted we just keep a reference to the panel that is being dragged.

    void dragDockPanel_DragFinished(object sender, DragEventArgs args)

    {

        // Set dragging panel back to null

        this.draggingPanel = null;

 

        // Update the layout (to reset all panel positions)

        this.UpdatePanelLayout();

    }

In DragFinished, we clear the reference the dragging panel and call UpdatePanelLayout to reset all of the panels to their current position and size.

The smart stuff happens in the DragMoved event. This handler is called every time the mouse moves when a panel is being dragged. It works out the position of the mouse (which row and column it’s in), whether there is a panel in that row and column (that is not the panel being dragged), and if so, slides that panel into the available space. Lets look at the handler...

    void dragDockPanel_DragMoved(object sender, DragEventArgs args)

    {

        ...

    }

First thing worth noting is the argument type - DragEventArgs. These event arguments contain the source mouse event arguments for getting the position. We use these arguments to work out which row and column we are in (using the same logic we used to position the panels in UpdatePanelLayout)...

    Point mousePosInHost =

        args.MouseEventArgs.GetPosition(this);

   

    int currentRow =

        (int)Math.Floor(mousePosInHost.Y /

        (this.ActualHeight / (double)this.rows));

 

    int currentColumn =

        (int)Math.Floor(mousePosInHost.X /

        (this.ActualWidth / (double)this.columns));

Once we know what column and row the mouse is in, we can loop through the children and work out which panel is in that row and column. If it’s not the panel being dragged, we store it.

    // Stores the panel we will swap with

    DragDockPanel swapPanel = null;

 

    // Loop through children to see if there is a panel to swap with

    foreach (UIElement child in this.Children)

    {

        DragDockPanel panel = child as DragDockPanel;

 

        // If the panel is not the dragging panel and is in the current row

        // or current column... mark it as the panel to swap with

        if (panel != this.draggingPanel &&

            Grid.GetColumn(panel) == currentColumn &&

            Grid.GetRow(panel) == currentRow)

        {

            swapPanel = panel;

            break;

        }

    }

Finally, if we found a panel to swap with, we swap the row and column for it with the dragging panel’s row and column and animate the all the panels to their new positions...

    // If there is a panel to swap with

    if (swapPanel != null)

    {

        // Store the new row and column

        int draggingPanelNewColumn = Grid.GetColumn(swapPanel);

        int draggingPanelNewRow = Grid.GetRow(swapPanel);

 

        // Update the swapping panel row and column

        Grid.SetColumn(swapPanel, Grid.GetColumn(this.draggingPanel));

        Grid.SetRow(swapPanel, Grid.GetRow(this.draggingPanel));

 

        // Update the dragging panel row and column

        Grid.SetColumn(this.draggingPanel, draggingPanelNewColumn);

        Grid.SetRow(this.draggingPanel, draggingPanelNewRow);

 

        // Animate the layout to the new positions

        this.AnimatePanelLayout();

    }

The AnimatePanelLayout method is almost the same as UpdatePanelLayout, only, it just updates the panels positions, and uses AnimatePosition rather than setting directly...

    private void AnimatePanelLayout()

    {

        // Loop through children and size to row and columns

        foreach (UIElement child in this.Children)

        {

            DragDockPanel panel = (DragDockPanel)child;

 

            if (panel != this.draggingPanel)

            {

                panel.AnimatePosition(

                    (Grid.GetColumn(panel) *

                    (this.ActualWidth / (double)this.columns)),

                    (Grid.GetRow(panel)

                    * (this.ActualHeight / (double)this.rows))

                    );

            }

        }

    }

And there we have it - dragging, docking panels. We are now ready to use this on our page. In part one, where we previously had 6 panels in a canvas, we can replace the canvas with our DragDockPanelHost...

  <local:DragDockPanelHost Margin="50">

 

    <local:DragDockPanel Margin="10">

      <MediaElement Source="..." />

    </local:DragDockPanel>

 

    <local:DragDockPanel Margin="10">

      <MediaElement Source="..." />

    </local:DragDockPanel>

 

    <local:DragDockPanel Margin="10">

      <MediaElement Source="..." />

    </local:DragDockPanel>

 

    <local:DragDockPanel Margin="10" >

      <MediaElement Source="..." />

    </local:DragDockPanel>

 

    <local:DragDockPanel Margin="10">

      <MediaElement Source="..." />

    </local:DragDockPanel>

 

    <local:DragDockPanel Margin="10" >

      <MediaElement Source="..." />

    </local:DragDockPanel>

   

  </local:DragDockPanelHost>

The result should look like this...

Experiment with resizing your browser, dragging the panels around, and, adding more panels to the UI!

I mentioned earlier on that there is a limitation with this implementation of DragDockPanelHost... we can’t add new panels dynamically at run-time. This could be worked around by creating an Add method that will place the child in the visual tree, add an addition row / column is required, hook up the events and update the layout, however, if a consumer of the control attempted to add a child using the panels Children.Add(...) then this would go ignored. I had thought about designing this control as an ItemsControl, but wanted to keep this example simple, and focus on the layout, however, and ItemsControl would be a good solution, allowing you to be notified when a child is added the collection.

I would be more than interested to hear people’s ideas about how this could be done cleanly!

I hope you have enjoyed this post - watch out for Part 3 which will deal with Maximising panels and creating new templates J

As always, source code is at www.codeplex.com/blacklight.

Martin

UPDATE: Get the latest Dragging, docking, expanding panel code from Blacklight, our new CodePlex project!  

NOTE: Part 2 and Part 3 now posted, with a demo of the final sample here

A control that we used in a number of places in the MSCUI Patient Journey Demonstrator was the dragging, docking, expanding panel control. When designing the dashboards in the application, we wanted to create an experience that was customisable, fun to use and allowed the user to focus in on the important information when they needed it.

The shots below show panels in action...

The controls provide the following...

·         Automatic grid layout of panels

·         Picking up and moving panels around and changing their position in the grid (dragging and docking)

·         ‘Maximising’ a panel causing the panel to take up much more space, whilst the others stack up on the right hand side of the application

As we used the panels in a number of places, building them for re-use was a big priority too. In this article, I will show how to build a similar customisable layout system.

There are 2 parts to the DragDockPanel system. The DragDockPanel itself (the thing you pick up and drag around) and the container for multiple DragDockPanel’s - I call it DragDockPanelHost. We will look at DragDockPanel today, and have it working just in a regular Canvas, and the host container in Part 2.

As a panel needs to contain something, it made sense for DragDockPanel to extend ContentControl. However, I also want DragDockPanel to have to easy methods to animate size and position, so I actually inherit AnimatedContentControl (a custom control that generates size and position animations and methods - see Silverlight 2 Samples: Animating controls).

As we are extending AnimatedContentControl, we also need to create a template in XAML for the control. The great thing about templates is that they allow a designer / developer to define the building blocks and visuals for a control. The only thing we need to ask of them is that they include a couple of specifically named elements, so that we can implement the functionality. For DragDockPanel, there are 3 things a designer / developer need to include in the template to get the full functionality of the panel...

·         A ContentPresenter control - as we are extending ContentControl (via AnimatedContentControl) we need a ContentPresenter in the template to be the site of the panel’s content

·         An element named “PART_GripBar” - this is the portion of the panel that allows the user to pick up the panel and drag it around

·         A ToggleButton control (which could also be a CheckBox or RadioButton) named “PART_MaximizeToggle” - this is the button that when clicked, the panel will either maximize or collapse

When writing the code behind for the control, the developer has the choice to implement none, some or all of the above. There would obviously be missing functionally, however, if they don’t include, say, the grip bar - they would not be able to drag the panel around.

Below shows a style with an outline template (included in the attached sample) with the above parts included...

<Style TargetType="local:DragDockPanel">

    <Setter Property="Template">

      <Setter.Value>

        <ControlTemplate TargetType="local:DragDockPanel">

          <Grid>

           

            <!-- Border with white background -->

            <Border CornerRadius="3,3,3,3"

                    Background="#ffffffff"

                    BorderBrush="#ff999999"

                    BorderThickness="1"

                    Padding="2">

              <Grid>

 

                <!-- Content presenter for hosting the content -->

                <ContentPresenter />

 

                <!--

                  Element named PART_GripBar for

                  handling the dragging of the panel 

                  -->

                <Border x:Name="PART_GripBar"

                        Background="#7fffffff"

                        VerticalAlignment="Top"

                        Height="30" Cursor="Hand">

                  ...

                </Border>

 

                <!-- ToggleButton for maximizing function -->

                <ToggleButton x:Name="PART_MaximizeToggle"

                              VerticalAlignment="Top"

                              HorizontalAlignment="Right"

                              Margin="0,5,5,0" Width="20" Height="20"

                              Cursor="Hand"

                              />

              </Grid>

            </Border>

          </Grid>

        </ControlTemplate>

      </Setter.Value>

    </Setter>

  </Style>

You will find the above style in generic.xaml in the attached project. Placing the style in generic.xaml means that this will be the default style for DragDockPanel. Using the XAML shown below, we can now put a panel on the page, with a media element inside...

<local:DragDockPanel>

<MediaElement Source="..." />

</local:DragDockPanel>

The result...

You can see that the panel contains a video, with a grip bar and maximize button across the top of the panel. Great. Lets get it moving around the screen...

First things first, we need to get hold of the elements in the template so we can hook up the mouse events. When creating a custom control, you can override a method - public override void OnApplyTemplate() - which is called as soon as the template is applied to your control. You can then use this.GetTemplateChild("PART_ElementName") to get your elements out, and hook up events. I have mentioned before that we needed to have specially named elements in the default template in generic.xaml - this is why. Our override in DragDockPanel looks like this...

public override void OnApplyTemplate()

        {

            base.OnApplyTemplate();

 

            FrameworkElement gripBar =

                this.GetTemplateChild("PART_GripBar") as FrameworkElement;

 

            if (gripBar != null)

            {

                gripBar.MouseLeftButtonDown +=

                    new MouseButtonEventHandler(gripBar_MouseLeftButtonDown);

 

                gripBar.MouseMove +=

                   new MouseEventHandler(gripBar_MouseMove);

 

                gripBar.MouseLeftButtonUp +=

                    new MouseButtonEventHandler(gripBar_MouseLeftButtonUp);

            }

 

        }

Here, we are pulling out the grip bar into a private member, and hooking up some events.

For the grip bar, I hook up the MouseLeftButtonDown, MouseMove, MouseLeftButtonUp. I need these to move the panel about when the grip bar is dragged.

The MouseLeftButtonDown simply checks to see if dragging is enable on this panel (another private member), we then bring the panel to the front (setting the ZIndex), capture the mouse, store the current position, flag that we are dragging, and then raise an event to say the panel has started to be dragged.

void gripBar_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)

        {

            if (this.draggingEnabled)

            {

                // Bring the panel to the front

                Canvas.SetZIndex(this, currentZIndex++);

 

                // Capture the mouse

                ((FrameworkElement)sender).CaptureMouse();

 

                // Store the start position

                this.lastDragPosition = e.GetPosition(sender as UIElement);

 

                // Set dragging to true

                this.isDragging = true;

 

                // Fire the drag started event

                if (this.DragStarted != null)

                    this.DragStarted(this, new DragEventArgs(0, 0, e));

            }

           

        } 

 

Our MouseMove firstly checks to see if we are dragging (i.e. if the mouse is down), we then use the last position variable to calculate the panels new position. We then set Canvas.Left and Canvas.Top  which moves the panel within a Canvas.

Finally, we fire an event to say the panel has moved, and store the last position again.

void gripBar_MouseMove(object sender, MouseEventArgs e)

        {

            if (this.isDragging)

            {

                Point position = e.GetPosition(sender as UIElement);

 

                // Move the panel

                Canvas.SetLeft(

                    this,

                    Canvas.GetLeft(this) + position.X - this.lastDragPosition.X

                    );

               

                Canvas.SetTop(

                    this,

                    Canvas.GetTop(this) + position.Y - this.lastDragPosition.Y

                    );

                

                // Fire the drag moved event

                if (this.DragMoved != null)

                    this.DragMoved(

                        this,

                        new DragEventArgs(

                            position.X - this.lastDragPosition.X,

                            position.Y - this.lastDragPosition.Y, e));

               

                // Update the last mouse position

                this.lastDragPosition = e.GetPosition(sender as UIElement);

               

            }

        }

In MouseLeftButtonUp we simple release mouse capture, set the dragging flag to false, and raise an event to say the dragging has finished.

void gripBar_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)

        {

            if (this.draggingEnabled)

            {

                // Capture the mouse

                ((FrameworkElement)sender).ReleaseMouseCapture();

 

                // Set dragging to true

                this.isDragging = false;

 

                Point position = e.GetPosition(sender as UIElement);

 

                // Fire the drag finished event

                if (this.DragFinished != null)

                    this.DragFinished(

                        this,

                        new DragEventArgs(

                            position.X - this.lastDragPosition.X,

                            position.Y - this.lastDragPosition.Y, e));

            }

        }

And that’s it! We can now drag our panel around a canvas, bringing it to the front with each mouse down action. On the project page, you can use the panels in a canvas like so...

<Canvas>

 

            <local:DragDockPanel Width="400" Height="300">

                <MediaElement Source="..." />

            </local:DragDockPanel>

 

            <local:DragDockPanel  Width="400" Height="300">

                <MediaElement Source="..." />

            </local:DragDockPanel>

 

            <local:DragDockPanel  Width="400" Height="300">

                <MediaElement Source="..." />

            </local:DragDockPanel>

 

            <local:DragDockPanel  Width="400" Height="300" >

                <MediaElement Source="..." />

            </local:DragDockPanel>

 

            <local:DragDockPanel  Width="400" Height="300">

                <MediaElement Source="..." />

            </local:DragDockPanel>

 

            <local:DragDockPanel  Width="400" Height="300" >

                <MediaElement Source="..." />

            </local:DragDockPanel>

 

        </Canvas>

The result should look like this (after you drag the panels about a bit!)

Take a look at the sample running here. 

In part 2 we will look at how to arrange the panels automatically into a grid, move and shuffle the panels around, maximise / minimise panels and also how to deal with any amount of panels on the screen.

Source code is available from www.codeplex.com/blacklight.

Enjoy J

 

More Posts Next page »
 
Page view tracker