Thursday, April 12, 2012

Creating and Using Custom UniformGrid Control in Silverlight

Silverlight is a powerful development tool for creating Rich UI Application.

Silverlight delivers richest set of capabilities available to developer through a web browser plug-in.

Silverlight provides Animation, Styles, Media experience and control customization.

In this post we will go through how to customize Listbox and creating custom UniformGrid control to change the layout of Listbox control.



Above Image represents the output of my demo application contains customized Listbox control in HorizontalScrollViewer including UnformGrid for arrange controls in Rows and Columns.

Lets walk-Through scenario.

Step 1 : Create UniformGrid Class for Arrange controls in Rows/Columns.


    public class UnifromGrid : Panel
    {
        public UnifromGrid()
        {

        }
        private int ComputedRows { get; set; }
        private int ComputedColumns { get; set; }

        public int FirstColumn
        {
            get { return (int)GetValue(FirstColumnProperty); }
            set { SetValue(FirstColumnProperty, value); }
        }
        public static readonly DependencyProperty FirstColumnProperty = 
                DependencyProperty.Register("FirstColumn", typeof(int), typeof(UnifromGrid), 
                new PropertyMetadata(0, OnRowColumnChanged));

        public int Columns
        {
            get { return (int)GetValue(ColumnsProperty); }
            set { SetValue(ColumnsProperty, value); }
        }

        public static readonly DependencyProperty ColumnsProperty = 
               DependencyProperty.Register("Columns", typeof(int), typeof(UnifromGrid),
               new PropertyMetadata(0, OnRowColumnChanged));

        public int Rows
        {
            get { return (int)GetValue(RowsProperty); }
            set { SetValue(RowsProperty, value); }
        }

        public static readonly DependencyProperty RowsProperty = 
               DependencyProperty.Register("Rows", typeof(int), typeof(UnifromGrid), 
               new PropertyMetadata(0, OnRowColumnChanged));

        private static void OnRowColumnChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
        {
            if (!(e.NewValue is int) || (int)e.NewValue < 0)
            {
                o.SetValue(e.Property, e.OldValue);
            }
        }

       ..........

       ..........


Above class Inherits from Panel Control, so you can create properties that used to change layout of Panel Control base of UniformGrid.

I have created DependencyProperty for Rows/Columns, used to set Row/Column value so controls placed within UniformGrid are automatically arrange in rows/columns.


OnRowColumnChanged method invoked when any one of property( Rows/Column) value set or changed.

private void UpdateComputedValues()
        {
            ComputedColumns = Columns;
            ComputedRows = Rows;
           
            if (FirstColumn >= ComputedColumns)
            {
                FirstColumn = 0;
            }
            if ((ComputedRows == 0) || (ComputedColumns == 0))
            {
                int nonCollapsedCount = 0;
                for (int i = 0, count = Children.Count; i < count; ++i)
                {
                    UIElement child = Children[i];
                    if (child.Visibility != Visibility.Collapsed)
                    {
                        nonCollapsedCount++;
                    }
                }
                if (nonCollapsedCount == 0)
                {
                    nonCollapsedCount = 1;
                }
                if (ComputedRows == 0)
                {                    
                  ComputedRows = (nonCollapsedCount + FirstColumn + 
                                 (ComputedColumns - 1)) / ComputedColumns;                                      
                }
                else if (ComputedColumns == 0)
                {
                    ComputedColumns = (nonCollapsedCount + 
                                      (ComputedRows - 1)) / ComputedRows;
                }
            }
        }


This UpdateComputedValues method is used to calculate Rows based on Column value set vise a versa.


Step 2 : Create HoriZontalScrollViewer Custom Control

public class HorizontalScrollViewer : ContentControl
    {
        public HorizontalScrollViewer()
        {
            this.DefaultStyleKey = typeof(HorizontalScrollViewer);
        }

        public RepeatButton ScrollLeftButton { get; set; }
        public RepeatButton ScrollRightButton { get; set; }
        public ScrollViewer MainScrollViewer { get; set; }

        public override void OnApplyTemplate()
        {
            ScrollLeftButton = (RepeatButton)GetTemplateChild("ScrollLeftButton");
            ScrollRightButton = (RepeatButton)GetTemplateChild("ScrollRightButton");
            MainScrollViewer = (ScrollViewer)GetTemplateChild("MainScrollViewer");

            ScrollLeftButton.Click += new RoutedEventHandler(ScrollLeftButton_Click);
            ScrollRightButton.Click += new RoutedEventHandler(ScrollRightButton_Click);

            base.OnApplyTemplate();
        }

        void ScrollRightButton_Click(object sender, RoutedEventArgs e)
        {
            MainScrollViewer.ScrollToHorizontalOffset(MainScrollViewer.HorizontalOffset + 20);
        }

        void ScrollLeftButton_Click(object sender, RoutedEventArgs e)
        {
            MainScrollViewer.ScrollToHorizontalOffset(MainScrollViewer.HorizontalOffset -20);
        }
    }

If you want to change appearance of any content control, you can create your own control that inherits from ContentControl.

In Above snippet, i have created class for HorizontalScrollViewer.

I have created ScrollLeft and ScrollRight button properties to scroll content with offset into ScrollViewer.

Step 3 : Create Style for  HorizontalScrollViewer.

Style used for change the layout of control.
Style can be placed in tow locations either in  ResourceDictionary file or in App.xaml file.

If you want to create style for custom control, you have to create Generic.xaml file within Themes folder of root directory of your application.

   <Style TargetType="controls:HorizontalScrollViewer">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="controls:HorizontalScrollViewer">
                    <Grid>
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="Auto" />
                            <ColumnDefinition Width="*" />
                            <ColumnDefinition Width="Auto" />
                        </Grid.ColumnDefinitions>
                        <RepeatButton Name="ScrollLeftButton"
                                      Grid.Column="0">
                            <RepeatButton.Content>
                                <Polyline Points="8,0 0,8 8,15"
                                          Stroke="Black"
                                          StrokeThickness="2"
                                          HorizontalAlignment="Center"
                                          VerticalAlignment="Center" />
                            </RepeatButton.Content>
                        </RepeatButton>
                        <ScrollViewer Name="MainScrollViewer"
                                      Grid.Column="1"
                                      VerticalScrollBarVisibility="Disabled"
                                      HorizontalScrollBarVisibility="Hidden">
                            <ContentPresenter Content="{TemplateBinding Content}" />
                        </ScrollViewer>
                        <RepeatButton Name="ScrollRightButton"
                                      Grid.Column="2">
                            <RepeatButton.Content>
                                <Polyline Points="0,0 8,8 0,15"
                                          Stroke="Black"
                                          StrokeThickness="2"
                                          HorizontalAlignment="Center"
                                          VerticalAlignment="Center" />
                            </RepeatButton.Content>
                        </RepeatButton>
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style> 


As show in above code , i have created style for properties (ScrollLeftButton/ScrollRightButton and ScrollViewer) that was created in HorizontalScrollViewer class.

To set style for any control you have to set TargetType property which identify that for which control you are applying style.

Step 4 :Create Model that holds Data.

 public class NatureImages
    {
        public string ImageUri { get; set; }
        public string ImageName { get; set; }
        public NatureImages(string uri, string name)
        {
            ImageUri = uri;
            ImageName = name;
        }
    }

Step 5 : Create ViewModel for View

    public class MainPageViewModel : INotifyPropertyChanged
    {
        private ObservableCollection _imageCollection;
        public ObservableCollection ImageCollection
        {
            get { return _imageCollection; }
            set
            {
                _imageCollection = value;
                RaisePropertyChanged("ImageCollection");
            }
        }

        private NatureImages _selectedNature;
        public NatureImages SelectedNature
        {
            get { return _selectedNature; }
            set
            {
                _selectedNature = value;
                RaisePropertyChanged("SelectedNature");
            }
        }

        public MainPageViewModel()
        {
            ImageCollection = new ObservableCollection();
            //====Insert Data into ImageCollection======
              ......
              ......
              ......
           //===========================================   
           SelectedNature = ImageCollection.FirstOrDefault();

        }
        public event PropertyChangedEventHandler PropertyChanged;
        public void RaisePropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }

    }

This MainPageViewModel contains properties for ItemsSource and SelectedItem of Listbox.

INotifyPropertyChanged Interface is used to notify view when any property value changed.

Step 6 : Create UI page.

  <controls:HorizontalScrollViewer Grid.Row="1">
                <ListBox    Padding="0"
                            Height="Auto"
                            ItemsSource="{Binding ImageCollection}"
                            SelectedItem="{Binding SelectedNature,Mode=TwoWay}"
                            HorizontalAlignment="Stretch"
                            VerticalAlignment="Stretch">
                    <ListBox.ItemContainerStyle>
                        <Style TargetType="ListBoxItem">
                            <Setter Property="HorizontalAlignment"
                                    Value="Stretch"></Setter>
                            <Setter Property="VerticalAlignment"
                                    Value="Stretch"></Setter>
                            <Setter Property="HorizontalContentAlignment"
                                    Value="Stretch"></Setter>
                            <Setter Property="VerticalContentAlignment"
                                    Value="Stretch"></Setter>
                            <Setter Property="Template">
                                <Setter.Value>
                                    <ControlTemplate TargetType="ListBoxItem">
                                        <Grid>                                            
                                            <Border x:Name="colorBorder">
                                                <ContentPresenter Content="{TemplateBinding Content}" />
                                            </Border>
                                            <Border x:Name="selectedBorder"
                                                    Opacity="0"
                                                    BorderBrush="Black"
                                                    BorderThickness="2"></Border>
                                        </Grid>
                                    </ControlTemplate>
                                </Setter.Value>
                            </Setter>
                        </Style>
                    </ListBox.ItemContainerStyle>
                    <ListBox.ItemsPanel>
                        <ItemsPanelTemplate>
                            <controls:UnifromGrid Rows="1" />
                        </ItemsPanelTemplate>
                    </ListBox.ItemsPanel>
                    <ListBox.ItemTemplate>
                        <DataTemplate>
                            <Border Width="100"                                   
                                    BorderBrush="Black"
                                    BorderThickness="1,1,0,0"
                                    RenderTransformOrigin="0.5, 0.5">
                                <Image Source="{Binding ImageUri}"
                                       Stretch="Fill" />
                            </Border>
                        </DataTemplate>
                    </ListBox.ItemTemplate>
                </ListBox>
            </controls:HorizontalScrollViewer>
 
 
As show in above snippet, i have placed Listbox in HorizontalScrollViewer custom control that was created previously.

so when any large amount of data is there in list, you can Scroll content using Previous and Next button.

I have set UniformGrid as ItemPanelTemplate in Listbox and set Rows=1, so it will arrange content in 1 Rows and Multiple columns based on ItemsSource records.

Conclusion 

This way you can customize layout of your control with styling and template based on requirements.

Click CustomUniformGridControl To Download Source.

No comments:

Post a Comment