Circular Layout Panel v2
On the flight to Remix09 last week I had fun putting together a simple WPF Circular Layout Panel. Today I decided that a nice “extra” feature would be to have the child elements optionally rotated so that they are normalised with the centre of the layout panel.
So after adding a new attached dependency property, IsNormalised I updated my sample clock and fan menu. The following XAML…
<Window x:Class="PanelTest.Window1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Circular Panel Examples" Height="405" Width="383" xmlns:local="clr-namespace: PanelTest" xmlns:panels="clr-namespace:Spencen.Panels;assembly=Spencen.Panels"> <DockPanel> <DockPanel.Resources>
<!-- Stuff to make buttons pretty --> </DockPanel.Resources> <Ellipse Fill="LightYellow" Stroke="LightGray" StrokeThickness="1" StrokeDashArray="0,1,1,0" /> <panels:CircularPanel StartAngle="-90" EndAngle="630" TextBlock.FontSize="24pt"> <TextBlock panels:CircularPanel.IsNormalised="True">XII</TextBlock> <TextBlock panels:CircularPanel.IsNormalised="True">I</TextBlock> <TextBlock panels:CircularPanel.IsNormalised="True">II</TextBlock> <TextBlock panels:CircularPanel.IsNormalised="True">III</TextBlock> <TextBlock panels:CircularPanel.IsNormalised="True">IV</TextBlock> <TextBlock panels:CircularPanel.IsNormalised="True">V</TextBlock> <TextBlock panels:CircularPanel.IsNormalised="True">VI</TextBlock> <TextBlock panels:CircularPanel.IsNormalised="True">VII</TextBlock> <TextBlock panels:CircularPanel.IsNormalised="True">VIII</TextBlock> <TextBlock panels:CircularPanel.IsNormalised="True">IX</TextBlock> <TextBlock panels:CircularPanel.IsNormalised="True">X</TextBlock> <TextBlock panels:CircularPanel.IsNormalised="True">XI</TextBlock> <Ellipse Width="5" Height="5" Fill="Black"
panels:CircularPanel.RadiusScaleX="0.9" panels:CircularPanel.RadiusScaleY="0.9"/> <Ellipse Width="5" Height="5" Fill="Black"
panels:CircularPanel.RadiusScaleX="0.9" panels:CircularPanel.RadiusScaleY="0.9"/> <Ellipse Width="5" Height="5" Fill="Black"
panels:CircularPanel.RadiusScaleX="0.9" panels:CircularPanel.RadiusScaleY="0.9"/> <Ellipse Width="5" Height="5" Fill="Black"
panels:CircularPanel.RadiusScaleX="0.9" panels:CircularPanel.RadiusScaleY="0.9"/> <Ellipse Width="5" Height="5" Fill="Black"
panels:CircularPanel.RadiusScaleX="0.9" panels:CircularPanel.RadiusScaleY="0.9"/> <Ellipse Width="5" Height="5" Fill="Black"
panels:CircularPanel.RadiusScaleX="0.9" panels:CircularPanel.RadiusScaleY="0.9"/> <Ellipse Width="5" Height="5" Fill="Black"
panels:CircularPanel.RadiusScaleX="0.9" panels:CircularPanel.RadiusScaleY="0.9"/> <Ellipse Width="5" Height="5" Fill="Black"
panels:CircularPanel.RadiusScaleX="0.9" panels:CircularPanel.RadiusScaleY="0.9"/> <Ellipse Width="5" Height="5" Fill="Black"
panels:CircularPanel.RadiusScaleX="0.9" panels:CircularPanel.RadiusScaleY="0.9"/> <Ellipse Width="5" Height="5" Fill="Black"
panels:CircularPanel.RadiusScaleX="0.9" panels:CircularPanel.RadiusScaleY="0.9"/> <Ellipse Width="5" Height="5" Fill="Black"
panels:CircularPanel.RadiusScaleX="0.9" panels:CircularPanel.RadiusScaleY="0.9"/> <Ellipse Width="5" Height="5" Fill="Black"
panels:CircularPanel.RadiusScaleX="0.9" panels:CircularPanel.RadiusScaleY="0.9"/>
<Line x:Name="secondHand" Fill="Black" X1="0" X2="0" Y1="0" Y2="100"
Stroke="Red" StrokeThickness="1" StrokeStartLineCap="Triangle" StrokeEndLineCap="Round" panels:CircularPanel.FixedAngle="-90" panels:CircularPanel.RadiusScaleX="0.4" panels:CircularPanel.RadiusScaleY="0.4" panels:CircularPanel.IsNormalised="True"/> <Line x:Name="minuteHand" Fill="Black" X1="2" X2="2" Y1="0" Y2="70"
Stroke="Black" StrokeThickness="5" StrokeStartLineCap="Triangle" StrokeEndLineCap="Round" panels:CircularPanel.FixedAngle="-60" panels:CircularPanel.RadiusScaleX="0.3" panels:CircularPanel.RadiusScaleY="0.3" panels:CircularPanel.IsNormalised="True"/> <Line x:Name="hourHand" Fill="Black" X1="5" X2="5" Y1="0" Y2="50"
Stroke="Black" StrokeThickness="10" StrokeStartLineCap="Triangle" StrokeEndLineCap="Round" panels:CircularPanel.FixedAngle="-180" panels:CircularPanel.RadiusScaleX="0.2" panels:CircularPanel.RadiusScaleY="0.2" panels:CircularPanel.IsNormalised="True"/> <Ellipse Fill="Black" panels:CircularPanel.FixedAngle="0" panels:CircularPanel.RadiusScaleX="0" panels:CircularPanel.RadiusScaleY="0" Width="30" Height="30" /> <panels:CircularPanel.Triggers> <EventTrigger RoutedEvent="Loaded"> <BeginStoryboard> <Storyboard> <DoubleAnimation Duration="0:1:0" By="360" RepeatBehavior="Forever"
Storyboard.TargetName="secondHand"
Storyboard.TargetProperty="(panels:CircularPanel.FixedAngle)"/> <DoubleAnimation Duration="1:0:0" By="360" RepeatBehavior="Forever"
Storyboard.TargetName="minuteHand"
Storyboard.TargetProperty="(panels:CircularPanel.FixedAngle)"/> <DoubleAnimation Duration="12:0:0" By="360" RepeatBehavior="Forever"
Storyboard.TargetName="hourHand"
Storyboard.TargetProperty="(panels:CircularPanel.FixedAngle)"/> </Storyboard> </BeginStoryboard> </EventTrigger> </panels:CircularPanel.Triggers> </panels:CircularPanel> <panels:CircularPanel Padding="45" StartAngle="-90" EndAngle="-90"> <Button panels:CircularPanel.IsNormalised="true">1</Button> <Button panels:CircularPanel.IsNormalised="true">2</Button> <Button panels:CircularPanel.IsNormalised="true">3</Button> <Button panels:CircularPanel.IsNormalised="true">4</Button> <Button Panel.ZIndex="1" Background="Gray">Menu</Button> <Button panels:CircularPanel.IsNormalised="true">5</Button> <Button panels:CircularPanel.IsNormalised="true">6</Button> <Button panels:CircularPanel.IsNormalised="true">7</Button> <Button panels:CircularPanel.IsNormalised="true">8</Button> <panels:CircularPanel.Triggers> <EventTrigger RoutedEvent="MouseEnter"> <BeginStoryboard> <Storyboard> <DoubleAnimation To="-180" Storyboard.TargetProperty="StartAngle"> <DoubleAnimation.EasingFunction> <ElasticEase Springiness="10" Oscillations="2"/> </DoubleAnimation.EasingFunction> </DoubleAnimation> <DoubleAnimation To="0" Storyboard.TargetProperty="EndAngle"> <DoubleAnimation.EasingFunction> <ElasticEase Springiness="10" Oscillations="2"/> </DoubleAnimation.EasingFunction> </DoubleAnimation> </Storyboard> </BeginStoryboard> </EventTrigger> <EventTrigger RoutedEvent="MouseLeave"> <BeginStoryboard> <Storyboard> <DoubleAnimation To="-90" AccelerationRatio="0.5" DecelerationRatio="0.5"
Storyboard.TargetProperty="StartAngle"/> <DoubleAnimation To="-90" AccelerationRatio="0.5" DecelerationRatio="0.5"
Storyboard.TargetProperty="EndAngle"/> </Storyboard> </BeginStoryboard> </EventTrigger> </panels:CircularPanel.Triggers> </panels:CircularPanel> </DockPanel> </Window>
Now produces…
For reference this is so much easier than doing the same thing in WinForms. Custom layout panels really were quite a pain.
Updated source code for CircularPanel is here.



Comments