Posted by: Zeeshan Amjad | May 7, 2010

Using Custom Radial Panel in TabControl


We already played with item panel template of Tab control here and here. Now lets continue our exploration and use custom panel. For this purpose we are going to use a Radial Panel provided in the sample of WPF from Microsoft. We did little bit change in it and display the radial at the center of available width of the parent.

Here is a little bit modified code of Microsoft’s Panel class.

  1: // Class from Microsoft Sample
  2: public class RadialPanel : Panel
  3: {
  4:     // This Panel lays its children out in a circle
  5:     // keeping the angular distance from each child
  6:     // equal; MeasureOverride is called before ArrangeOverride.
  7:     double _maxChildHeight, _perimeter, _radius, _adjustFactor;
  8: 
  9:     protected override Size MeasureOverride(Size availableSize)
 10:     {
 11:         _perimeter = 0;
 12:         _maxChildHeight = 0;
 13: 
 14:         // Find the tallest child and determine the perimeter
 15:         // based on the width of all of the children after
 16:         // measuring all of the them and letting them size
 17:         // to content by passing Double.PositiveInfinity as
 18:         // the available size.
 19: 
 20:         foreach (UIElement uie in Children)
 21:         {
 22:             uie.Measure(new Size(Double.PositiveInfinity, Double.PositiveInfinity));
 23:             _perimeter += uie.DesiredSize.Width;
 24:             _maxChildHeight = Math.Max(_maxChildHeight, uie.DesiredSize.Height);
 25:         }
 26: 
 27:         // If the marginal angle is not 0, 90 or 180
 28:         // then the adjustFactor is needed.
 29:         if (Children.Count > 2 && Children.Count != 4)
 30:             _adjustFactor = 10;
 31: 
 32:         // Determine the radius of the circle layout and determine
 33:         // the RadialPanel's DesiredSize.
 34: 
 35:         _radius = _perimeter / (2 * Math.PI) + _adjustFactor;
 36:         double _squareSize = 2 * (_radius + _maxChildHeight);
 37:         return new Size(_squareSize, _squareSize);
 38:     }
 39: 
 40:     // Perform arranging of children based on 
 41:     // the final size.
 42:     protected override Size ArrangeOverride(Size finalSize)
 43:     {
 44:         // Necessary variables.
 45:         double _currentOriginX = 0,
 46:                 _currentOriginY = 0,
 47:                 _currentAngle = 0,
 48:                 _centerX = 0,
 49:                 _centerY = 0,
 50:                 _marginalAngle = 0;
 51: 
 52: 
 53:         // During measure, an adjustFactor was added to the radius
 54:         // to account for rotated children that might fall outside
 55:         // of the desired size.  Now, during arrange, that extra
 56:         // space isn't needed
 57: 
 58:         _radius -= _adjustFactor;
 59: 
 60:         // Find center of the circle based on arrange size.
 61:         // DesiredSize is not used because the Panel
 62:         // is potentially being arranged across a larger
 63:         // area from the default alignment values.
 64: 
 65:         _centerX = finalSize.Width / 2;
 66:         _centerY = finalSize.Height / 2;
 67: 
 68:         // Determine the marginal angle, the angle between
 69:         // each child on the circle.
 70: 
 71:         if (Children.Count != 0)
 72:             _marginalAngle = 360 / Children.Count;
 73: 
 74:         foreach (UIElement uie in Children)
 75:         {
 76:             // Find origin from which to arrange 
 77:             // each child of the RadialPanel (its top
 78:             // left corner.)
 79: 
 80:             _currentOriginX = _centerX;
 81:             _currentOriginY = _centerY;
 82: 
 83:             // Apply a rotation on each child around the center of the
 84:             // RadialPanel.
 85:             uie.RenderTransform = new RotateTransform(_currentAngle);
 86:             uie.Arrange(new Rect(new Point(_currentOriginX, _currentOriginY), new Size(uie.DesiredSize.Width, uie.DesiredSize.Height)));
 87: 
 88:             // Increment the _currentAngle by the _marginalAngle
 89:             // to advance the next child to the appropriate position.
 90:             _currentAngle += _marginalAngle;
 91:         }
 92: 
 93:         // In this case, the Panel is sizing to the space
 94:         // given, so, return the finalSize which will be used
 95:         // to set the ActualHeight & ActualWidth and for rendering.
 96:         return finalSize;
 97:     }
 98: }
 99: 

Then we introduce its reference in XAML.

  1: xmlns:local="clr-namespace:WpfTabPanel"

And then use this class in the control template of tab control as a item panel template.

  1: <ControlTemplate TargetType="{x:Type TabControl}">
  2: 	<Grid KeyboardNavigation.TabNavigation="Local"
  3:   SnapsToDevicePixels="true"
  4:   ClipToBounds="true">
  5: 		<Grid.RowDefinitions>
  6: 			<RowDefinition Height="Auto"/>
  7: 			<RowDefinition Height="*"/>
  8: 		</Grid.RowDefinitions>
  9: 		<local:RadialPanel
 10: 			Panel.ZIndex ="1" 
 11: 			KeyboardNavigation.TabIndex="1"
 12: 			Grid.Column="0"
 13: 			Grid.Row="0"
 14: 			Margin="0, 0, 0, -1"
 15: 			IsItemsHost="true"/>
 16: 		<Border
 17: 			Background="{TemplateBinding Background}"
 18: 			BorderThickness="{TemplateBinding BorderThickness}"
 19: 			BorderBrush="{TemplateBinding BorderBrush}"
 20: 			KeyboardNavigation.TabNavigation="Local"
 21: 			KeyboardNavigation.DirectionalNavigation="Contained"
 22: 			KeyboardNavigation.TabIndex="2"
 23: 			Grid.Row="1">
 24: 			<ContentPresenter                                    
 25: 				SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
 26: 				Margin="{TemplateBinding Padding}"
 27: 				ContentSource="SelectedContent"/>
 28: 		</Border>
 29: 	</Grid>
 30: </ControlTemplate>
 31: 

Because it is not a Canvas class, therefore we don’t need to pass the position ourselves. Here our data point is just a list of string.

  1: data.Add("Blue");
  2: data.Add("Green");
  3: data.Add("Red");
  4: data.Add("Yellow");
  5: data.Add("Black");
  6: data.Add("Brown");
  7: 
  8: DataContext = data;

Rest of the code is almost similar to previous examples. Here is complete XAML code of our program.

  1: <Window x:Class="WpfTabPanel.Window1"
  2:     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  3:     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  4:     xmlns:local="clr-namespace:WpfTabPanel"
  5:     Title="TabControl" Height="400" Width="400">
  6:     <Window.Resources>
  7:         <Style x:Key="myStyle" TargetType="TabItem">
  8:             <Setter Property="Width" Value="50"/>
  9:             <Setter Property="Height" Value="25"/>
 10:         </Style>
 11:         <Style TargetType="TabControl">
 12:             <Setter Property="Template">
 13:                 <Setter.Value>
 14:                     <ControlTemplate TargetType="{x:Type TabControl}">
 15:                         <Grid KeyboardNavigation.TabNavigation="Local"
 16:                       SnapsToDevicePixels="true"
 17:                       ClipToBounds="true">
 18:                             <Grid.RowDefinitions>
 19:                                 <RowDefinition Height="Auto"/>
 20:                                 <RowDefinition Height="*"/>
 21:                             </Grid.RowDefinitions>
 22:                             <local:RadialPanel
 23:                                 Panel.ZIndex ="1" 
 24:                                 KeyboardNavigation.TabIndex="1"
 25:                                 Grid.Column="0"
 26:                                 Grid.Row="0"
 27:                                 Margin="0, 0, 0, -1"
 28:                                 IsItemsHost="true"/>
 29:                             <Border
 30:                                 Background="{TemplateBinding Background}"
 31:                                 BorderThickness="{TemplateBinding BorderThickness}"
 32:                                 BorderBrush="{TemplateBinding BorderBrush}"
 33:                                 KeyboardNavigation.TabNavigation="Local"
 34:                                 KeyboardNavigation.DirectionalNavigation="Contained"
 35:                                 KeyboardNavigation.TabIndex="2"
 36:                                 Grid.Row="1">
 37:                                 <ContentPresenter                                    
 38:                                     SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
 39:                                     Margin="{TemplateBinding Padding}"
 40:                                     ContentSource="SelectedContent"/>
 41:                             </Border>
 42:                         </Grid>
 43:                     </ControlTemplate>
 44:                 </Setter.Value>
 45:             </Setter>
 46:         </Style>
 47:     </Window.Resources>
 48:     <Grid>
 49:         <TabControl ItemsSource="{Binding}" ItemContainerStyle="{StaticResource myStyle}" >
 50:             <TabControl.ItemTemplate>
 51:                 <DataTemplate>
 52:                     <Border>
 53:                         <TextBlock Text="{Binding}"/>
 54:                     </Border>
 55:                 </DataTemplate>
 56:             </TabControl.ItemTemplate>
 57:             <TabControl.ContentTemplate>
 58:                 <DataTemplate>
 59:                     <Border Margin="5" BorderThickness="1" CornerRadius="10" BorderBrush="Black">
 60:                         <Rectangle Margin="10" Fill="{Binding}"/>
 61:                     </Border>
 62:                 </DataTemplate>
 63:             </TabControl.ContentTemplate>
 64:         </TabControl>
 65:     </Grid>
 66: </Window>
 67: 

 

And here is complete C# code of the program.

  1: using System;
  2: using System.Collections.Generic;
  3: using System.Linq;
  4: using System.Text;
  5: using System.Windows;
  6: using System.Windows.Controls;
  7: using System.Windows.Data;
  8: using System.Windows.Documents;
  9: using System.Windows.Input;
 10: using System.Windows.Media;
 11: using System.Windows.Media.Imaging;
 12: using System.Windows.Navigation;
 13: using System.Windows.Shapes;
 14: 
 15: namespace WpfTabPanel
 16: {
 17:     /// <summary>
 18:     /// Interaction logic for Window1.xaml
 19:     /// </summary>
 20:     public partial class Window1 : Window
 21:     {
 22:         private List<String> data = new List<String>();
 23: 
 24:         public Window1()
 25:         {           
 26:             InitializeComponent();
 27: 
 28:             data.Add("Blue");
 29:             data.Add("Green");
 30:             data.Add("Red");
 31:             data.Add("Yellow");
 32:             data.Add("Black");
 33:             data.Add("Brown");
 34: 
 35:             DataContext = data;
 36:         }
 37:     }
 38: 
 39:     // Class from Microsoft Sample
 40:     public class RadialPanel : Panel
 41:     {
 42:         // This Panel lays its children out in a circle
 43:         // keeping the angular distance from each child
 44:         // equal; MeasureOverride is called before ArrangeOverride.
 45:         double _maxChildHeight, _perimeter, _radius, _adjustFactor;
 46: 
 47:         protected override Size MeasureOverride(Size availableSize)
 48:         {
 49:             _perimeter = 0;
 50:             _maxChildHeight = 0;
 51: 
 52:             // Find the tallest child and determine the perimeter
 53:             // based on the width of all of the children after
 54:             // measuring all of the them and letting them size
 55:             // to content by passing Double.PositiveInfinity as
 56:             // the available size.
 57: 
 58:             foreach (UIElement uie in Children)
 59:             {
 60:                 uie.Measure(new Size(Double.PositiveInfinity, Double.PositiveInfinity));
 61:                 _perimeter += uie.DesiredSize.Width;
 62:                 _maxChildHeight = Math.Max(_maxChildHeight, uie.DesiredSize.Height);
 63:             }
 64: 
 65:             // If the marginal angle is not 0, 90 or 180
 66:             // then the adjustFactor is needed.
 67:             if (Children.Count > 2 && Children.Count != 4)
 68:                 _adjustFactor = 10;
 69: 
 70:             // Determine the radius of the circle layout and determine
 71:             // the RadialPanel's DesiredSize.
 72: 
 73:             _radius = _perimeter / (2 * Math.PI) + _adjustFactor;
 74:             double _squareSize = 2 * (_radius + _maxChildHeight);
 75:             return new Size(_squareSize, _squareSize);
 76:         }
 77: 
 78:         // Perform arranging of children based on 
 79:         // the final size.
 80:         protected override Size ArrangeOverride(Size finalSize)
 81:         {
 82:             // Necessary variables.
 83:             double _currentOriginX = 0,
 84:                     _currentOriginY = 0,
 85:                     _currentAngle = 0,
 86:                     _centerX = 0,
 87:                     _centerY = 0,
 88:                     _marginalAngle = 0;
 89: 
 90: 
 91:             // During measure, an adjustFactor was added to the radius
 92:             // to account for rotated children that might fall outside
 93:             // of the desired size.  Now, during arrange, that extra
 94:             // space isn't needed
 95: 
 96:             _radius -= _adjustFactor;
 97: 
 98:             // Find center of the circle based on arrange size.
 99:             // DesiredSize is not used because the Panel
100:             // is potentially being arranged across a larger
101:             // area from the default alignment values.
102: 
103:             _centerX = finalSize.Width / 2;
104:             _centerY = finalSize.Height / 2;
105: 
106:             // Determine the marginal angle, the angle between
107:             // each child on the circle.
108: 
109:             if (Children.Count != 0)
110:                 _marginalAngle = 360 / Children.Count;
111: 
112:             foreach (UIElement uie in Children)
113:             {
114:                 // Find origin from which to arrange 
115:                 // each child of the RadialPanel (its top
116:                 // left corner.)
117: 
118:                 _currentOriginX = _centerX;
119:                 _currentOriginY = _centerY;
120: 
121:                 // Apply a rotation on each child around the center of the
122:                 // RadialPanel.
123:                 uie.RenderTransform = new RotateTransform(_currentAngle);
124:                 uie.Arrange(new Rect(new Point(_currentOriginX, _currentOriginY), new Size(uie.DesiredSize.Width, uie.DesiredSize.Height)));
125: 
126:                 // Increment the _currentAngle by the _marginalAngle
127:                 // to advance the next child to the appropriate position.
128:                 _currentAngle += _marginalAngle;
129:             }
130: 
131:             // In this case, the Panel is sizing to the space
132:             // given, so, return the finalSize which will be used
133:             // to set the ActualHeight & ActualWidth and for rendering.
134:             return finalSize;
135:         }
136:     }
137: }
138: 

 

This is the output of the program when we run it.

TabControl_RadialPanel

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

Categories

%d bloggers like this: