Saturday 24 May 2014

Create a Custom panel with Zindex ordering property for the child controls.

In this article we are going to see how to create a custom control with Zindex ordering property for the child controls, for example like Panel.ZIndex="1",and to set the value in Dynamic time , and also we can able to set the ZOrder directly in panel itself then no need of settings the Zindex or it cant work based on Zindex.

Output needed for us like this in display as well as in Code: OrderingPanel.ZIndexing="3"

       <local:OrderingPanel IsZorder="False"  x:Name="zxpanel">
            <Border local:OrderingPanel.ZIndexing="3"  Margin="45,15,10,10"  Height="185"                          Width="160" Background="Tomato"/>
            <Border local:OrderingPanel.ZIndexing="4" Margin="45,15,10,10"  Height="170"                            Width="170" Background="Gray"/>
            <Border local:OrderingPanel.ZIndexing="5"  Margin="45,15,10,10"  Height="150"                          Width="180" Background="Orange"/>
            <Border local:OrderingPanel.ZIndexing="6"  Margin="45,15,10,10"  Height="130"                          Width="190" Background="SteelBlue"/>
        </local:OrderingPanel>       

       

In the above output you can see the Zindex for orange color is high than any other control that's why it is on the top of the frame

Let we start the development , now we need a dependency property with attached because we are attach the property to the child elements , at the same time when the value changes it need to reflect it in design,another requirement is we have a ZOrder property where when this property is true, then makes the order is reverse manner rarther than seen based on index. 

First let we start from the Helper class , why we need helper class ? because when we have a dependency property there is  chance that we need the parent of property in that time we need that Helper class

static class UIHelper
    {
      
        public static T TryFindParent<T>(this DependencyObject child)
            where T : DependencyObject
        {           
            DependencyObject parentObject = GetParentObject(child);
           
            if (parentObject == null)
                return null;
           
            T parent = parentObject as T;
            if (parent != null)
            {
                return parent;
            }
            else
            {               
                return TryFindParent<T>(parentObject);
            }
        }


       
        public static DependencyObject GetParentObject(this DependencyObject child)
        {
            if (child == null) return null;
           
            ContentElement contentElement = child as ContentElement;
            if (contentElement != null)
            {
                DependencyObject parent = ContentOperations.GetParent(contentElement);
                if (parent != null) return parent;

                FrameworkContentElement framecontent = contentElement as                                                                                 FrameworkContentElement;
                return framecontent != null ? framecontent.Parent : null;
            }
           
            FrameworkElement frameworkElement = child as FrameworkElement;
            if (frameworkElement != null)
            {
                DependencyObject parent = frameworkElement.Parent;
                if (parent != null) return parent;
            }
           
            return VisualTreeHelper.GetParent(child);
        }

    } 

From the Above class we have the two methods we can get the parent object, to get the detailed explanation of that helper class read it here Helper class to get the parent of Dependency object

Let we now move on to the developing the control.

        public static readonly DependencyProperty ZIndexingProperty =                                                                                       DependencyProperty.RegisterAttached("ZIndexing",
                                                typeof(int),                                                
                                                typeof(OrderingPanel),
                                                new FrameworkPropertyMetadata(1,                                                                             FrameworkPropertyMetadataOptions.AffectsParentArrange|
                                                FrameworkPropertyMetadataOptions.AffectsParentMeasure,                                                       OnZindexingChanged));

        private static void OnZindexingChanged(DependencyObject d,                          
                                DependencyPropertyChangedEventArgs e)
        {
          ReDrawIndexChild(d);          
        }
  
        public static void SetZIndexing(DependencyObject obj,int zindex)
        {
            obj.SetValue(ZIndexingProperty,zindex);           
            ReDrawIndexChild(obj);

        }

private static void ReDrawIndexChild(DependencyObject d)
       {
            OrderingPanel pan = UIHelper.TryFindParent<OrderingPanel>(d);
            pan.ReOrderingChild();
            pan.InvalidateMeasure();
            pan.InvalidateArrange();

        }
    

Above property is very important in the Zindexing and method SetZIndexing is used to set the index value for each control.

Control:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;

namespace CustomPanel
{  
    class OrderingPanel:Panel
    {
        private Dictionary<int, int> _zindexorder = new Dictionary<int, int>();
        private List<int> _indexes = new List<int>();

        public bool IsZorder
        {
            get { return (bool)GetValue(IsZorderProperty); }
            set { SetValue(IsZorderProperty, value); }
        }

        public static readonly DependencyProperty ZIndexingProperty =                                                                                       DependencyProperty.RegisterAttached("ZIndexing",
                                                typeof(int),                                                
                                                typeof(OrderingPanel),
                                                new FrameworkPropertyMetadata(1,                                                                             FrameworkPropertyMetadataOptions.AffectsParentArrange|
                                                FrameworkPropertyMetadataOptions.AffectsParentMeasure,                                                       OnZindexingChanged));


        private static void OnZindexingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ReDrawIndexChild(d);
        }

        private static void ReDrawIndexChild(DependencyObject d)
        {
            OrderingPanel pan = UIHelper.TryFindParent<OrderingPanel>(d);
            pan.ReOrderingChild();
            pan.InvalidateMeasure();
            pan.InvalidateArrange();

        }
  
        public static void SetZIndexing(DependencyObject obj,int zindex)
        {
            obj.SetValue(ZIndexingProperty,zindex);
            ReDrawIndexChild(obj);
        } 

      public static readonly DependencyProperty IsZorderProperty =                                                                                           DependencyProperty.Register("IsZorder",
                                                typeof(bool),                                               
                                                typeof(OrderingPanel),
                                                new FrameworkPropertyMetadata(false,
                                                FrameworkPropertyMetadataOptions.AffectsArrange | 
                                                FrameworkPropertyMetadataOptions.AffectsMeasure |                                                            FrameworkPropertyMetadataOptions.AffectsParentArrange|
                                                FrameworkPropertyMetadataOptions.AffectsParentMeasure|
                                                FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,                                                       OnZOrderPropertyChanged));



       private static void OnZOrderPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            OrderingPanel pan = d as OrderingPanel;
            ReAddChild(pan);
           
        }

        private static void ReAddChild(OrderingPanel pan)
        {
            for (int i = 0; i < pan.InternalChildren.Count; i++)
            {
                pan.RemoveVisualChild(pan.InternalChildren[i]);
            }
            for (int i = 0; i < pan.InternalChildren.Count; i++)
            {
                pan.AddVisualChild(pan.InternalChildren[i]);
            }
        }

        private void ReAddChild()
        {
            for (int i = 0; i < InternalChildren.Count; i++)
            {
                RemoveVisualChild(InternalChildren[i]);
            }
            for (int i = 0; i < InternalChildren.Count; i++)
            {
                AddVisualChild(InternalChildren[i]);
            }
        }

        public OrderingPanel()
        {
             
        }

        public void ReOrderingChild()
        {
            _indexes.Clear();
            _zindexorder.Clear();

            for(int i=0;i<InternalChildren.Count;i++)
            {
                int outvalue;
                int.TryParse(InternalChildren[i].GetValue(ZIndexingProperty).ToString(),out outvalue);
                _zindexorder.Add(i,outvalue);
            }

           _indexes = _zindexorder.OrderBy(x=>x.Value).Select(x=>x.Key).ToList();
        }


        protected override System.Windows.Size MeasureOverride(System.Windows.Size availableSize)
        {
           
            if (availableSize.Height == double.PositiveInfinity && availableSize.Width ==                                                                             double.PositiveInfinity)
                return Size.Empty;

            for (int i = 0; i < InternalChildren.Count; i++)
            {
                InternalChildren[i].Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
            }

            return availableSize;
        }


        protected override System.Windows.Size ArrangeOverride(System.Windows.Size finalSize)
        {
            ReAddChild();

            for (int i = 0; i < InternalChildren.Count; i++)
            {
              InternalChildren[i].Arrange(new Rect(new Point(Margin.Left, Margin.Top),                                                                               InternalChildren[i].DesiredSize));
            }
            return finalSize;
        }


        protected override System.Windows.Media.Visual GetVisualChild(int index)
        {                                            
            if (IsZorder)
            {
                return InternalChildren[InternalChildren.Count - 1 - index];
            }
            else
            {
                if (InternalChildren.Count > _indexes.Count)
                    ReOrderingChild();

                return InternalChildren[_indexes[index]];
            }
        }


        protected override int VisualChildrenCount
        {
            get
            {
                return this.InternalChildren.Count;
            }
        }
   
    }
}



Xaml 

<Window x:Class="CustomPanel.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:CustomPanel"
        Title="MainWindow" Height="350" Width="525">
    <Grid>     
        <Grid.ColumnDefinitions>
            <ColumnDefinition />
            <ColumnDefinition />
        </Grid.ColumnDefinitions>
        <local:OrderingPanel IsZorder="False"  x:Name="zxpanel">
            <Border local:OrderingPanel.ZIndexing="3"  Margin="45,15,10,10"  Height="185" Width="160"                                    Background="Tomato"/>
            <Border local:OrderingPanel.ZIndexing="4" Margin="45,15,10,10"  Height="170" Width="170"                                    Background="Gray"/>
            <Border local:OrderingPanel.ZIndexing="7"  Margin="45,15,10,10"  Height="150" Width="180"                                    Background="Orange"/>
            <Border local:OrderingPanel.ZIndexing="6"  Margin="45,15,10,10"  Height="130" Width="190"                                    Background="SteelBlue"/>
        </local:OrderingPanel>       
        <Button Grid.Column="1" Width="100" Height="50" Click="Button_Click">Change order</Button>
        <CheckBox Content="CheckBox" Grid.Column="1" HorizontalAlignment="Left" Margin="86,246,0,0"                         VerticalAlignment="Top" Checked="CheckBox_Checked" Unchecked="CheckBox_Unchecked"/>
    </Grid>
</Window>


Class:
Code to change the value dynamically in main window

  public partial class MainWindow : Window
    {
        private int oldvalue = 0;
        public MainWindow()
        {
            InitializeComponent();
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            Random rand = new Random();
            int child = rand.Next(0, 3);
            while (oldvalue == child)
            {
                child = rand.Next(0, 3);
            }
            int valu = rand.Next(0, 100);

            int va =                                                                                                             int.Parse(zxpanel.Children[child].GetValue(OrderingPanel.ZIndexingProperty).ToString());
            OrderingPanel.SetZIndexing(zxpanel.Children[child], valu);

            oldvalue = child;
        }

        private void CheckBox_Checked(object sender, RoutedEventArgs e)
        {
            zxpanel.IsZorder = !zxpanel.IsZorder;
        }

        private void CheckBox_Unchecked(object sender, RoutedEventArgs e)
        {
            zxpanel.IsZorder = !zxpanel.IsZorder;
        }
    }


Output:
******

When click the Change button , the value of one of the controls zindex is changed



When click the check box enable the zorder.



From the article you can learn how to develop a control with Zindex propery and ordering the child elements inside a Control.