WPF – Overlapped Image Control


I was recently trying to find a way to display some data that gave the user a relatively easy way to gauge the number of items in a dataset as well as still being functional enough to interact with those data items.

An example of this need might be a user administration module for an application where a user can have several states:

  • Suspended Logins
  • Awaiting approval
  • Requiring password resets

So I thought an interesting and appealing way to display this information would be a in a graph format where each category was a point on the axis and the data points (users) would be represented graphically in a overlapped manner (think similar to a stack of dimes).

My requirements were simply to allow items to overlap by a certain percentage of their width or height.

Here’s is what I ended up with.

image 

My first attempt at creating the overlapped effect was to use a translation render transform. This didn’t work because the translation needed to be performed by more than a fixed amount per item.

So I decided to create my own custom panel to handle the placement of items.

I chose the StackPanel as a basis for my custom panel. Breaking it down there were 3 steps involved in creating the custom panel:

  • Add a dependency property to allow setting the percentage of overlap
  • Define the MeasureOverride() logic needed
  • Define the ArrangeOverride() logic needed

 

Add the Dependency Property

I started by creating the dependency property to allow the percentage of overlap to be specified by a caller.

/// <summary>
/// Percentage of overlap
/// </summary>
public int OverlapPercentage
{
   get { return (int)GetValue(OverlapPercentageProperty); }
   set { SetValue(OverlapPercentageProperty, value); }
}

public static readonly DependencyProperty OverlapPercentageProperty =
   DependencyProperty.Register("OverlapPercentage", typeof(int), typeof(OverlapImageStackPanel), 
   new FrameworkPropertyMetadata(0, FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsArrange));
 

Define MeasureOverride Logic

The last step is to implement our overrides for the MeasureOverride() and ArrangeOverride().  For the measurement pass we sum up the widths or heights (depending on the orientation) taking into account the percentage of overlap requested.

protected override Size MeasureOverride(Size availableSize)
{
    Size infinietSize = new Size(double.PositiveInfinity, double.PositiveInfinity);
    double overlapPercentageDouble = (double)OverlapPercentage / 100.0;
    double unoverlapPercentageDouble = 1.0 - ((double)OverlapPercentage / 100.0);

    double totalWidths = 0;
    double totalHeights = 0;

    // get total widths of each child                       
    foreach (UIElement child in Children)
    {
        child.Measure(availableSize);
        if (child.Visibility != Visibility.Collapsed)
        {
            if (Orientation == Orientation.Horizontal)
            {
                totalWidths += child.DesiredSize.Width * overlapPercentageDouble;
                totalHeights = (child.DesiredSize.Height > totalHeights) ? child.DesiredSize.Height : totalHeights;
            }
            else
            {
                totalHeights += child.DesiredSize.Height * overlapPercentageDouble;
                totalWidths = (child.DesiredSize.Width > totalWidths) ? child.DesiredSize.Width : totalWidths;
            }
        }
    }

    if (Orientation == Orientation.Horizontal)
        totalWidths += unoverlapPercentageDouble;
    else
        totalHeights += unoverlapPercentageDouble;


    Size resultSize = new Size();
    resultSize.Width = double.IsPositiveInfinity(availableSize.Width) ? totalWidths : availableSize.Width;
    resultSize.Height = double.IsPositiveInfinity(availableSize.Height) ? totalHeights : availableSize.Height;
                
    return resultSize;
}

Define ArrangeOverride Logic 

For the Arrange layout pass we layout the children taking into account the percentage of overlap for each child item as set on the control.

protected override Size ArrangeOverride(Size finalSize)
{
    if (Children.Count == 0)
        return finalSize;

    double availableWidth = finalSize.Width;
    double availableHeight = finalSize.Height;
    double totalWidths = 0;
    double totalHeights = 0;

    double overlapPercentageDouble = (double)OverlapPercentage / 100.0;
    double unoverlapPercentageDouble = 1.0 - overlapPercentageDouble;

    // get total widths of each child                        
    foreach (UIElement child in Children)
    {
        if (child.Visibility != Visibility.Collapsed)
        {
            if (Orientation == Orientation.Horizontal)
            {
                totalWidths += child.DesiredSize.Width * overlapPercentageDouble;
                totalHeights = finalSize.Height;
            }
            else
            {
                totalHeights += child.DesiredSize.Height * overlapPercentageDouble;
                totalWidths = finalSize.Width;
            }
        }
    }

    if ((Orientation == Orientation.Horizontal && (availableWidth < 0 || totalWidths <= 0)) ||
        (Orientation == Orientation.Vertical && (availableHeight < 0 || totalHeights <= 0)))
    {
        return finalSize;
    }

    if (Orientation == Orientation.Horizontal)
        totalWidths += unoverlapPercentageDouble;
    else
        totalHeights += unoverlapPercentageDouble;

    double left = 0;
    double top = 0;
    foreach (UIElement child in Children)
    {
        child.Arrange(new Rect(new Point(left, top), new Size(child.DesiredSize.Width, child.DesiredSize.Height)));
        if (Orientation == Orientation.Horizontal)
        {
            left += child.DesiredSize.Width * unoverlapPercentageDouble;
        }
        else
        {
            top += child.DesiredSize.Height * unoverlapPercentageDouble;
        }
    }

    return new Size(totalWidths, totalHeights);//finalSize;
}
That sums up what’s needed to implement a fairly handy overlapped StackPanel. Similar logic can be used for a WrapPanel as well.
Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s