Wednesday, May 28, 2014

Column Panel

Hi,

today I wanted to arrange my elements in a grid in another way than stackpanel does. So I started to search for panel-implementations and found a simple implementation for a radial panel. Unfortunately a radial panel was not the solution for my problem so I had to build it myself. To tell the truth: it is even easier than I thought.

Panel
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;

namespace ColumnPanelProject
{
    public class ColumnPanel : Panel
    {
        #region ColumnsCount

        public int ColumnsCount
        {
            get { return (int)GetValue(ColumnsCountProperty); }
            set { SetValue(ColumnsCountProperty, value); }
        }

        // Using a DependencyProperty as the backing store for ColumnsCount.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty ColumnsCountProperty =
            DependencyProperty.Register("ColumnsCount", typeof(int), typeof(ColumnPanel), new PropertyMetadata(0));

        #endregion

        private readonly Size InfinitySize = new Size(double.PositiveInfinity, double.PositiveInfinity);
        
        protected override Size MeasureOverride(Size availableSize)
        {
            
            foreach (UIElement child in Children)
            {
                child.Measure(InfinitySize);
            }

            return base.MeasureOverride(availableSize);
        }

        protected override Size ArrangeOverride(Size finalSize)
        {
            if(this.Children.Count > 0)
            {            
                int columns = this.ColumnsCount != 0 ? 
                                this.ColumnsCount : 
                                (int)(finalSize.Width / Children[0].DesiredSize.Width) ;

                int x = 0;
                int y = 0;

                foreach (UIElement child in Children)
                {
                    child.Arrange(new Rect( x * child.DesiredSize.Width,
                                            y * child.DesiredSize.Height, 
                                            child.DesiredSize.Width, 
                                            child.DesiredSize.Height));

                    x = (x + 1) % columns;
                    y = y + (x == 0 ? 1 : 0);
                }
            }

            return finalSize;
        }
    }
}

Window
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<Window x:Class="ColumnPanelProject.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:ColumnPanelProject"
        xmlns:System="clr-namespace:System;assembly=mscorlib"
        Title="MainWindow" Height="456.579" Width="481.579"
        >
    <Window.Resources>
        <System:Int32 x:Key="columnCount">0</System:Int32>
        <ItemsPanelTemplate x:Key="columnTemplate">
            <local:ColumnPanel ColumnsCount="{DynamicResource columnCount}" />
        </ItemsPanelTemplate>

        <DataTemplate x:Key="dataTemplate">
            <Grid Margin="3" Width="100" Height="100" HorizontalAlignment="Center" VerticalAlignment="Center">
                <Border BorderThickness="5" BorderBrush="LightGray" CornerRadius="10">
                    <TextBlock TextWrapping="Wrap" Text="{Binding}" HorizontalAlignment="Center" VerticalAlignment="Center"></TextBlock>
                </Border>
            </Grid>
        </DataTemplate>
    </Window.Resources>
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="100"/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="*"/>
            <RowDefinition Height="30"/>
        </Grid.RowDefinitions>

        <ItemsControl
            Grid.Column="0"
            Grid.Row="0"
            Grid.ColumnSpan="2"
            ItemsSource="{x:Static Member=local:MainWindow.MyItems}"
            Name="itemsControl" 
            ItemTemplate="{DynamicResource dataTemplate}" 
            ItemsPanel="{DynamicResource columnTemplate}" Margin="0,2,0,-2" 
            />
        <Label Grid.Row="1" Grid.Column="1" Content="{DynamicResource columnCount}" HorizontalContentAlignment="Center" />
    </Grid>
</Window>

Code-Behind
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace ColumnPanelProject
{
    /// <summary>
    /// Interaktionslogik für MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        private static ObservableCollection<string> myItems = null;

        public static ObservableCollection<string> MyItems
        {
            get
            {
                if(myItems == null)
                {
                    myItems = new ObservableCollection<string>();
                    (new[]{
                        "Dr. Leonard Leakey Hofstadter",
                        "Dr. Dr. Sheldon Lee Cooper",
                        "Penny",
                        "Howard Joel Wolowitz",
                        "Dr. Rajesh „Raj“ Ramayan Koothrappali",
                        "Dr. Bernadette Maryann Rostenkowski-Wolowitz",
                        "Dr. Amy Farrah Fowler",
                        "Stuart Bloom"}).ToList().ForEach(x => myItems.Add(x));
                }

                return myItems;
            }
        }

        public MainWindow()
        {
            InitializeComponent();
        }
    }
}

This article should show how a grid arrangement can be set up using only MeasureOverride / ArrangeOverride.

referring to msdn MeasureOverride should implement:
  • Iterate your element's particular collection of children that are part of layout, call Measure on each child element.
  • Immediately get DesiredSize on the child (this is set as a property after Measure is called).
  • Compute the net desired size of the parent based upon the measurement of the child elements.
The return value of MeasureOverride should be the element's own desired size, which then becomes the measure input for the parent element of the current element. This same process continues through the layout system until the root element of the page is reached.


ArrangeOverride is obviously responsible for the arrangement of the items.


A good related article can be found here: http://drwpf.com/blog/2009/08/05/itemscontrol-n-is-for-natural-user-interface/


Kind regards,
Daniel

No comments: