Wednesday, February 22, 2012

WPF : Enhancing GridSplitter control to show Left, Right , Up and Down button like lotus notes

In this article , I am going to enhance Grid splitter in WPF to show Up/Down or Left/Right button like Lotus notes application.

Step 1) We start with creating a custom control which is derived from Existing GridSplitter control and change its control template to have four buttons as needed in Vertical Template and Horizontal Template.

using System.Diagnostics.CodeAnalysis;
using System.Windows;
using System.Windows.Controls;

namespace Controls
{
    [TemplatePart(Name = VerticalLeftButtonElement, Type = typeof (Button))]
    [TemplatePart(Name = VerticalRightButtonElement, Type = typeof (Button))]
    [TemplatePart(Name = HorizontalUpButtonElement, Type = typeof (Button))]
    [TemplatePart(Name = HorizontalDownButtonElement, Type = typeof (Button))]
    public class CustomGridSplitter : GridSplitter
    {
        #region Constants

        const string VerticalLeftButtonElement = "VerticalLeftButton";
        const string VerticalRightButtonElement = "VerticalRightButton";

        const string HorizontalUpButtonElement = "HorizontalUpButton";
        const string HorizontalDownButtonElement = "HorizontalDownButton";

        const string LowerButtonGridElement = "LowerButtonGrid";
        const string UpperButtonGridElement = "UpperButtonGrid";

        const string MiddleVerticalButtonElement = "MiddleVerticalButton";
        #endregion

        #region Private Variables

        protected Button HorizontalDownButton;
        protected Button HorizontalUpButton;
        protected Button VerticalLeftButton;
        protected Button VerticalRightButton;
        protected Button MiddleVerticalButton;
        #endregion

        #region Methods

        /// <summary>
        /// It is called while applying templates.
        /// </summary>
        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();

            VerticalLeftButton = GetTemplateChild(VerticalLeftButtonElement) as Button;
            if (VerticalLeftButton != null)
            {
                VerticalLeftButton.Click += OnLeftButtonClickEvent;
            }

            VerticalRightButton = GetTemplateChild(VerticalRightButtonElement) as Button;
            if (VerticalRightButton != null)
            {
                VerticalRightButton.Click += OnRightButtonClickEvent;
            }

            HorizontalUpButton = GetTemplateChild(HorizontalUpButtonElement) as Button;
            if (HorizontalUpButton != null)
            {
                HorizontalUpButton.Click += OnUpButtonClickEvent;
            }

            HorizontalDownButton = GetTemplateChild(HorizontalDownButtonElement) as Button;
            if (HorizontalDownButton != null)
            {
                HorizontalDownButton.Click += OnDownButtonClickEvent;
            }

            MiddleVerticalButton = GetTemplateChild(MiddleVerticalButtonElement) as Button;
            if (MiddleVerticalButton != null)
            {
                MiddleVerticalButton.Click += OnMiddleButtonClick;
            }
        }

     

        /// <summary>
        /// Its called to set width and hight of buttons.
        /// </summary>
        /// <param name="availableSize"></param>
        /// <returns></returns>
        protected override Size MeasureOverride(Size availableSize)
        {
            VerticalRightButton.Width = 8;
            VerticalLeftButton.Width = 8;
            HorizontalUpButton.Height = 8;
            HorizontalDownButton.Height = 8;

            return base.MeasureOverride(availableSize);
        }

        /// <summary>
        /// it invokes left click event.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OnLeftButtonClickEvent(object sender, RoutedEventArgs e)
        {
            if (LeftButtonClickEvent != null) LeftButtonClickEvent(sender);
        }

        /// <summary>
        /// it invokes right click event.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OnRightButtonClickEvent(object sender, RoutedEventArgs e)
        {
            if (RightButtonClickEvent != null) RightButtonClickEvent(sender);
        }

        /// <summary>
        /// it invokes up click event.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OnUpButtonClickEvent(object sender, RoutedEventArgs e)
        {
            if (UpButtonClickEvent != null) UpButtonClickEvent(sender);
        }

        /// <summary>
        /// it invokes middle button click event.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OnMiddleButtonClick(object sender, RoutedEventArgs e)
        {
            if (MiddleButtonClickEvent != null) MiddleButtonClickEvent(sender);
        }

        /// <summary>
        ///
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OnDownButtonClickEvent(object sender, RoutedEventArgs e)
        {
            if (DownButtonClickEvent != null) DownButtonClickEvent(sender);
        }



        #endregion

        #region Properties

        #region Delegates

        public delegate void ButtonClickEventHandler(object sender);

        #endregion

        #region Events
     
        public event ButtonClickEventHandler LeftButtonClickEvent;
        public event ButtonClickEventHandler RightButtonClickEvent;
        public event ButtonClickEventHandler UpButtonClickEvent;
        public event ButtonClickEventHandler DownButtonClickEvent;
        public event ButtonClickEventHandler MiddleButtonClickEvent;
        #endregion

        #region UseHorizontally

        /// <summary>
        /// UseHorizontally Dependency Property
        /// </summary>
        public static readonly DependencyProperty UseHorizontallyProperty =
            DependencyProperty.Register("UseHorizontally", typeof (bool), typeof (ExtendedGridSplitter),
                                        new FrameworkPropertyMetadata(false));

        /// <summary>
        /// Gets or sets the UseHorizontally property. This dependency property
        /// indicates ....
        /// </summary>
        public bool UseHorizontally
        {
            get { return (bool) GetValue(UseHorizontallyProperty); }
            set { SetValue(UseHorizontallyProperty, value); }
        }

        #endregion

        #endregion

        #region Static Constructor

        /// <summary>
        /// its a default static contructor.
        /// </summary>
        static ExtendedGridSplitter()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof (ExtendedGridSplitter),
                                                     new FrameworkPropertyMetadata(typeof (ExtendedGridSplitter)));
        }

        #endregion
    }
}

2) You define control template for this control as 
 <Style TargetType="{x:Type controls:CustomGridSplitter}">
        <Setter Property="Background" Value="Transparent"/>
        <Setter Property="IsTabStop" Value="true"/>
        <Setter Property="Width" Value="15"/>
        <Setter Property="PreviewStyle" Value="{StaticResource GridSplitterPreviewStyle}"/>
        <Setter Property="HorizontalAlignment" Value="Right"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="controls:CustomGridSplitter">
                    <Grid x:Name="Root" IsHitTestVisible="{TemplateBinding IsEnabled}">

                        <!-- VSM -->
                        <VisualStateManager.VisualStateGroups>
                            <VisualStateGroup x:Name="CommonStates">
                                <VisualState x:Name="Normal" />
                                <VisualState x:Name="MouseOver" />
                                <VisualState x:Name="Disabled">
                                    <Storyboard>
                                        <DoubleAnimation Storyboard.TargetName="Root" Storyboard.TargetProperty="Opacity" To="0.5" Duration="0" />
                                    </Storyboard>
                                </VisualState>
                            </VisualStateGroup>
                            <VisualStateGroup x:Name="FocusStates">
                                <VisualStateGroup.Transitions>
                                    <VisualTransition GeneratedDuration="0" />
                                </VisualStateGroup.Transitions>
                                <VisualState x:Name="Unfocused" />
                                <VisualState x:Name="Focused">
                                    <Storyboard>
                                        <DoubleAnimation Storyboard.TargetName="FocusVisual" Storyboard.TargetProperty="Opacity" To="1" Duration="0" />
                                    </Storyboard>
                                </VisualState>
                            </VisualStateGroup>
                        </VisualStateManager.VisualStateGroups>

                        <!-- Background -->
                        <Border CornerRadius="1"  BorderBrush="#8CACDB" BorderThickness="1,1,2,1"  >
                            <Border CornerRadius="1" BorderThickness="0,0,1,0" BorderBrush="DarkBlue" />
                        </Border>
                        
                        
                        <!-- Horizontal Template -->
                        <Grid x:Name="HorizontalTemplate" 
                              Visibility="{Binding UseHorizontally, RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type controls:ExtendedGridSplitter}},Converter={StaticResource BoolToVisibilityConverter}}"  
                              HorizontalAlignment="Center" VerticalAlignment="Center">
                            <StackPanel Orientation="Horizontal"  >
                            <Button Style="{StaticResource SplitterButtonStyle}" Content="5" x:Name="HorizontalUpButton" 
                                Width="Auto"/>
                            <Button Style="{StaticResource SplitterButtonStyle}" Content="6" x:Name="HorizontalDownButton" 
                                Width="Auto"/>
                            </StackPanel>    
                        </Grid>

                        <!-- Vertical Template -->
                        <Grid x:Name="VerticalTemplate" HorizontalAlignment="Center" 
                              Visibility="{Binding UseHorizontally, 
                                RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type controls:ExtendedGridSplitter}},
                                Converter={StaticResource NotBoolToVisibilityConverter}}"  
                              VerticalAlignment="Stretch"  >
                            <Grid.RowDefinitions>
                                <RowDefinition Height="0.3*"/>
                                <RowDefinition Height="0.3*"/>
                                <RowDefinition Height="0.3*"/>
                            </Grid.RowDefinitions>

                            <Button Grid.Row="0" ToolTip="Expand Right"  Style="{StaticResource SplitterButtonStyle}"  VerticalContentAlignment="Bottom"  Content="3"  x:Name="VerticalLeftButton" 
                            Height="Auto"/>

                            <Button Grid.Row="2" ToolTip="Expand Left" Style="{StaticResource SplitterButtonStyle}" VerticalContentAlignment="Top" Content="4"  x:Name="VerticalRightButton"
                            Height="Auto"/>

                            <Button Grid.Row="1" ToolTip="Restore Center" Style="{StaticResource SplitterButtonStyle}" VerticalContentAlignment="Center" x:Name="MiddleVerticalButton"
                            Height="Auto"/>
                            
                        </Grid>

                        <!-- Focus Visual -->
                        <Rectangle x:Name="FocusVisual" Stroke="#FF45D6FA" StrokeThickness="1" Opacity="0" IsHitTestVisible="false" />
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>



Step 3) You add this Control in your window as shown below

 <Grid Grid.Row="1" x:Name="ContainerGrid"  >
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="0.5*" />
                <ColumnDefinition Width="Auto" />
                <ColumnDefinition Width="0.5*" />
            </Grid.ColumnDefinitions>
            <TabControl Grid.Column="0" />
            <controls:ExtendedGridSplitter Grid.Column="1"                                           MouseDoubleClick="GridSplitter_MouseDoubleClick"                                            x:Name="GridSplitter" Margin="0,5"                                            ShowsPreview="True" Background="Transparent" DragCompleted="GridSplitter_DragCompleted"                                             Width="10" LeftButtonClickEvent="GridSplitter_LeftButtonClickEvent" MiddleButtonClickEvent="GridSplitter_MiddleButtonClickEvent"                                         RightButtonClickEvent="GridSplitter_RightButtonClickEvent"                                            HorizontalAlignment="Center" VerticalAlignment="Stretch"  >                           </controls:ExtendedGridSplitter>
            <TabControl Grid.Column="2" Background="Transparent"Name="Right" />
        </Grid>



Step 4) Put code for GridSplitter_LeftButtonClickEvent and GridSplitter_RightButtonClickEvent to change width of left and right portion as indicated below.

        private void GridSplitter_LeftButtonClickEvent(object sender)
        {
            SetLeftScreenWidth();
        }
        private void GridSplitter_RightButtonClickEvent(object sender)
        {
            SetRightScreenWidth();
        }

In  SetLeftScreenWidth method you will increase size for left side and in 
SetRightScreenWidth method you will increase size for right section.

1 comment:

  1. Hi,
    I was reading your article and I would like to appreciate you for making it very simple and understandable. This article gives me a basic idea of GridSplitter control in WPF. Some good articles also I was found during searching time which also explained very well about WPF GridSplitter Control, that post url are...
    http://msdn.microsoft.com/en-us/library/system.windows.controls.gridsplitter.aspx
    and
    http://www.mindstick.com/Articles/6c64bf8a-6704-4bd7-90ab-524689c390e5/?GridSpliter%20control%20in%20WPF

    ReplyDelete