Showing posts with label Custom Control. Show all posts
Showing posts with label Custom Control. Show all posts

Thursday, 31 May 2012

Silverlight OverlapStackPanel

Here’s an example of displaying items within a panel with the optional benefit of letting them overlap, rotate and offset from the top or left hand side.

In this screenshot we have 3 OverlapStackPanels, each with 3 rectangles within them:

OverlapStackPanel

<UserControl x:Class="OverlapStackPanelDemo.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:OverlapStackPanelDemo"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
d:DesignHeight="600"
d:DesignWidth="400"
mc:Ignorable="d">

<Grid x:Name="LayoutRoot" Background="White">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto" />
</Grid.ColumnDefinitions>
<StackPanel>
<local:OverlapStackPanel LeftOffset="10"
Orientation="Vertical"
Overlap="90"
Rotation="355"
UpperOffset="10">
<Rectangle Width="100"
Height="100"
Fill="Blue" />
<Rectangle Width="100"
Height="100"
Fill="Red" />
<Rectangle Width="100"
Height="100"
Fill="Yellow" />
</local:OverlapStackPanel>
<local:OverlapStackPanel LeftOffset="10"
Orientation="Horizontal"
Overlap="90"
Rotation="355"
UpperOffset="10">
<Rectangle Width="100"
Height="100"
Fill="Green" />
<Rectangle Width="100"
Height="100"
Fill="Purple" />
<Rectangle Width="100"
Height="100"
Fill="Silver" />
</local:OverlapStackPanel>
<local:OverlapStackPanel LeftOffset="10"
Orientation="Vertical"
Overlap="90"
Rotation="355"
UpperOffset="10">
<Rectangle Width="100"
Height="100"
Fill="Blue" />
<Rectangle Width="100"
Height="100"
Fill="Red" />
<Rectangle Width="100"
Height="100"
Fill="Yellow" />
</local:OverlapStackPanel>
</StackPanel>
</Grid>
</UserControl>

using System;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;

namespace OverlapStackPanelDemo
{
public class OverlapStackPanel : Panel
{
#region Dependency Properties

public static readonly DependencyProperty OrientationProperty = DependencyProperty.Register("Orientation", typeof(Orientation), typeof(OverlapStackPanel), new PropertyMetadata((Orientation.Vertical)));
public Orientation Orientation { get { return (Orientation) GetValue(OrientationProperty); } set { SetValue(OrientationProperty, (Enum) value); } }

public static readonly DependencyProperty OverlapProperty = DependencyProperty.Register("Overlap", typeof(double), typeof(OverlapStackPanel), new PropertyMetadata(double.NaN));
public double Overlap { get { return (double)GetValue(OverlapProperty); } set { SetValue(OverlapProperty, value); } }

public static readonly DependencyProperty RotationProperty = DependencyProperty.Register("Rotation", typeof(double), typeof(OverlapStackPanel), new PropertyMetadata(double.NaN));
public double Rotation { get { return (double)GetValue(RotationProperty); } set { SetValue(RotationProperty, value); } }

public static readonly DependencyProperty LeftOffsetProperty = DependencyProperty.Register("LeftOffset", typeof(double), typeof(OverlapStackPanel), new PropertyMetadata(double.NaN));
public double LeftOffset { get { return (double)GetValue(LeftOffsetProperty); } set { SetValue(LeftOffsetProperty, value); } }

public static readonly DependencyProperty UpperOffsetProperty = DependencyProperty.Register("UpperOffset", typeof (double), typeof (OverlapStackPanel), new PropertyMetadata(double.NaN));
public double UpperOffset { get { return (double)GetValue(UpperOffsetProperty); } set { SetValue(UpperOffsetProperty, value); } }

#endregion

#region Overrides

protected override Size MeasureOverride(Size availableSize)
{
var desiredSize = new Size();
var childrenResolved = 0;

if (Orientation == Orientation.Vertical)
availableSize.Height = double.PositiveInfinity;
else
availableSize.Width = double.PositiveInfinity;

foreach (UIElement child in Children.Where(child => child != null))
{
child.Measure(availableSize);

if (Orientation == Orientation.Vertical)
{
desiredSize.Width = LeftOffset + Math.Max(desiredSize.Width, child.DesiredSize.Width);
desiredSize.Height += UpperOffset + child.DesiredSize.Height - Overlap;
}
else
{
desiredSize.Height = UpperOffset + Math.Max(desiredSize.Height, child.DesiredSize.Height);
desiredSize.Width += LeftOffset + child.DesiredSize.Width - Overlap;
}
childrenResolved++;
}

// take into account the first item doesn't overlap
if (Orientation == Orientation.Vertical)
{
desiredSize.Height += Overlap;
}
else
{
desiredSize.Width += Overlap;
}

return desiredSize;
}

protected sealed override Size ArrangeOverride(Size arrangeSize)
{
int childrenResolved = 0;
double itemX = 0;
double itemY = 0;
foreach (UIElement child in Children.Where(child => child != null))
{
double itemOverlap = (childrenResolved == 0)
? 0
: Overlap;

Rect targetRect;
if (Orientation == Orientation.Vertical)
{
targetRect = new Rect
{
X = LeftOffset + (LeftOffset * childrenResolved),
Y = itemY + UpperOffset + (UpperOffset * childrenResolved) - itemOverlap,
Width = child.DesiredSize.Width,
Height = child.DesiredSize.Height
};

itemY += child.DesiredSize.Height - itemOverlap;
}
else
{
targetRect = new Rect
{
X = itemX + LeftOffset + (LeftOffset * childrenResolved) - itemOverlap,
Y = UpperOffset + (UpperOffset * childrenResolved),
Width = child.DesiredSize.Width,
Height = child.DesiredSize.Height
};

itemX += child.DesiredSize.Width - itemOverlap;
}
child.Arrange(targetRect);

var rotate = new RotateTransform {Angle = Rotation};
child.RenderTransform = rotate;

childrenResolved++;
}
return arrangeSize;
}

#endregion

#region Constructor

public OverlapStackPanel()
{
}

#endregion
}
}

Source code:  http://stevenhollidge.com/blog-source-code/OverlapStackPanelDemo.zip

Tuesday, 27 March 2012

WPF Custom Control with Image

In this example we are going to create a custom control in WPF called MetroTile.

Our MetroTile control will expose three properties to let designers set an image icon, a display count and a text property.  The usual core properties for controls are also settable such as background, foreground, margin, etc.

This screenshot shows three instances of the custom control in use, all with the generic template applied.  The RabbitMQ and ZeroMq tiles override the default background. 

image

Class Diagram

image

Key concepts

Three dependency properties that allow the Xaml data binding system to do its work.

I’ve also shown how to expose a bubbling RoutedEvent (travels up the visual tree until handled) through the DisplayCountChanged event.  As is common, I’ve included a Preview RoutedEvent that tunnels down the visual tree, which fires just before the main event.

A public RoutedUICommand called ResetCountCommand is also included which you can bind to from Xaml.  Commands are preferred to methods as you cannot bind directly to methods.

MetroTile Custom Control

Default Template

default-template

The display icon is located in top left hand corner when set by the developer using the custom control.  When the developer first includes the control in their xaml, the following default values are visible on their design surface:

From Template Purple background
  White foreground
From Custom Control Empty display icon
  “Not Set” display text
  Zero display count

Example of MetroTile in use

image

For information on the functionality of this example application, visit the following blog post:  http://stevenhollidge.blogspot.co.uk/2012/03/metro-messaging.html

Custom Control: MetroTile

using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;

namespace Common.Controls
{
[TemplatePart(Name="PART_DISPLAY_ICON", Type=typeof(Image))]
[TemplatePart(Name = "PART_DISPLAY_COUNT_CONTAINER", Type = typeof(TextBlock))]
[TemplatePart(Name = "PART_DISPLAY_TITLE_CONTAINER", Type = typeof(TextBlock))]
[Description("Represents a metro styled tile that displays an icon, a count and a title")]
public class MetroTile : Control
{
#region Constructor

static MetroTile()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(MetroTile),
new FrameworkPropertyMetadata(typeof(MetroTile)));

CommandManager.RegisterClassCommandBinding(
typeof(MetroTile),
new CommandBinding(ResetCountCommand, OnResetCountCommand));
}

#endregion

#region Dependency Properties

#region DisplayIcon

public static readonly DependencyProperty DisplayIconProperty =
DependencyProperty.Register("DisplayIcon",
typeof (ImageSource),
typeof (MetroTile),
new PropertyMetadata(null));

[Description("The icon displayed in the tile."), Category("Common Properties")]
public ImageSource DisplayIcon
{
get { return (ImageSource)this.GetValue(DisplayIconProperty); }
set { this.SetValue(DisplayIconProperty, value); }
}

#endregion

#region DisplayCount

public static readonly DependencyProperty DisplayCountProperty =
DependencyProperty.Register("DisplayCount",
typeof(int),
typeof(MetroTile),
new UIPropertyMetadata(0));

[Description("The count displayed in the tile."), Category("Common Properties")]
public int DisplayCount
{
get { return (int)this.GetValue(DisplayCountProperty); }
set { this.SetValue(DisplayCountProperty, value); }
}

#endregion

#region DisplayText

[Description("The text displayed in the tile."), Category("Common Properties")]
public static readonly DependencyProperty DisplayTextProperty =
DependencyProperty.Register("DisplayText",
typeof(string),
typeof(MetroTile),
new PropertyMetadata("Not set"));

public string DisplayText
{
get { return (string)this.GetValue(DisplayTextProperty); }
set { this.SetValue(DisplayTextProperty, value); }
}
#endregion

#endregion

#region Events

public static readonly RoutedEvent DisplayCountChangedEvent =
EventManager.RegisterRoutedEvent("DisplayCountChanged",
RoutingStrategy.Bubble,
typeof(RoutedEventHandler),
typeof(MetroTile));

public event RoutedEventHandler DisplayCountChanged
{
add { AddHandler(DisplayCountChangedEvent, value); }
remove { RemoveHandler(DisplayCountChangedEvent, value); }
}

protected virtual void OnDisplayCountChanged(int oldValue, int newValue)
{
// 1. Pair of events: A preview that tunnels and a main event that bubbles
// 2. We could also create our own RoutedEventArgs that includes oldValue & new Value

var previewEvent = new RoutedEventArgs(PreviewDisplayCountChangedEvent);
RaiseEvent(previewEvent);

var e = new RoutedEventArgs(DisplayCountChangedEvent);
RaiseEvent(e);
}

public static readonly RoutedEvent PreviewDisplayCountChangedEvent =
EventManager.RegisterRoutedEvent("PreviewDisplayCountChanged",
RoutingStrategy.Tunnel,
typeof(RoutedEventHandler),
typeof(MetroTile));

public event RoutedEventHandler PreviewDisplayCountChanged
{
add { AddHandler(PreviewDisplayCountChangedEvent, value); }
remove { RemoveHandler(PreviewDisplayCountChangedEvent, value); }
}

#endregion

#region Commands

public static readonly ICommand ResetCountCommand =
new RoutedUICommand("ResetCount", "ResetCount", typeof(MetroTile));

private static void OnResetCountCommand(object sender, ExecutedRoutedEventArgs e)
{
var target = (MetroTile)sender;
target.DisplayCount = 0;
}

#endregion

}
}
<Style TargetType="{x:Type c:MetroTile}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type c:MetroTile}">
<Border Background="{StaticResource MetroPurpleBrush}">
<Grid Background="{Binding Path=Background, RelativeSource={RelativeSource TemplatedParent}}">

<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*" />
<RowDefinition Height="1.25*" />
</Grid.RowDefinitions>

<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>

<Image x:Name="PART_DISPLAY_ICON"
Grid.Row="0"
Grid.Column="0"
Margin="10,10,0,0"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Source="{Binding Path=DisplayIcon,
RelativeSource={RelativeSource TemplatedParent},
UpdateSourceTrigger=PropertyChanged}"
Stretch="None" />

<TextBlock x:Name="PART_DISPLAY_COUNT_CONTAINER"
Grid.Row="1"
Grid.Column="0"
Grid.ColumnSpan="2"
Margin="0,0,10,0"
HorizontalAlignment="Right"
FontSize="48"
Foreground="White"
Text="{Binding Path=DisplayCount,
StringFormat=N0,
RelativeSource={RelativeSource TemplatedParent},
UpdateSourceTrigger=PropertyChanged}" />

<TextBlock x:Name="PART_DISPLAY_TITLE_CONTAINER"
Grid.Row="2"
Grid.RowSpan="2"
Grid.Column="0"
Grid.ColumnSpan="3"
Margin="10"
HorizontalAlignment="Right"
VerticalAlignment="Bottom"
FontSize="24"
Foreground="White"
Text="{Binding Path=DisplayText,
RelativeSource={RelativeSource TemplatedParent},
UpdateSourceTrigger=PropertyChanged}"
TextWrapping="Wrap" />
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>

 


Source code:  http://stevenhollidge.com/blog-source-code/Messaging.zip