Summary row in DataGrid

February 1, 2010 lee 2 comments

SummaryRow in DataGrid is one of the frequently asked request. Here  is one way we can customize the GroupHeader in Datagrid to create one

By default when we do grouping in the datagrid we get something like this

more often we need something like this.


Here are the steps
1.Add a style for the groupheader, In the above snapshot we see 4 columns, so we assume we need the Groupheader also to have 4 columns

We start by changing the existing GroupHeader Style. In the style, we see a stackPanel. we dont want this

  <StackPanel Grid.Column=”3″ Grid.Row=”1″ Orientation=”Horizontal” VerticalAlignment=”Center” Margin=”0,1,0,1″>
                        <TextBlock x:Name=”PropertyNameElement” Margin=”4,0,0,0″ Visibility=”{TemplateBinding PropertyNameVisibility}”/>
                        <TextBlock Margin=”4,0,0,0″ Text=”{Binding Name}” />
                        <TextBlock x:Name=”ItemCountElement” Margin=”4,0,0,0″ Visibility=”{TemplateBinding ItemCountVisibility}”/>
                    </StackPanel>

we will replace that stackpanel with this, we have to name it something so we can get to this when we want the cells to resize. We will add 4 controls in this case as we have 4 columns in the datagrid. Any control would work here, I went with datagridcell to keep them consitent with colors.
 <StackPanel x:Name=”ghsp” Orientation=”Horizontal”  Grid.Column=”3″ Grid.Row=”1″  VerticalAlignment=”Center” Margin=”0,1,0,1″>                                        
                                        <my:DataGridCell  Content=”{Binding Name}”/>                                                                               
                                        <my:DataGridCell  Content=”"/>
                                        <my:DataGridCell HorizontalContentAlignment=”Right” Content=”{Binding Converter={StaticResource myConverter}, ConverterParameter=Orders}”/>
                                        <my:DataGridCell HorizontalContentAlignment=”Right” Content=”{Binding Converter={StaticResource myConverter}, ConverterParameter=Total,StringFormat=’{0:C}’}”/>                                       
                                    </StackPanel>
1st column is to display the name of the group, 2nd we dont want any summary in that, 3rd,4th will have the sum of orders and Totals
The rest of the xaml  is just styling the headers for right alignment, etc

The code in the converter looks like this

 public class MyConverter : IValueConverter
    {
        // This converts the DateTime object to the string to display.
        public object Convert(object value, Type targetType, object parameter,
            System.Globalization.CultureInfo culture)
        {
            CollectionViewGroup cvg = value as CollectionViewGroup;
            string param = parameter as string;
            if (param == “Orders”)
                return cvg.Items.Sum(x => (x as Product).Orders);
            else
                return cvg.Items.Sum(x => (x as Product).Total);
        }

        // No need to implement converting back on a one-way binding
        public object ConvertBack(object value, Type targetType,
            object parameter, System.Globalization.CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }

2.Binding the data

 products = new List<Product>();
 products.Add(new Product { Category = “Beverages”, Name = “Chai”, Orders = 110, Total = 648.00M });
 products.Add(new Product { Category = “Beverages”, Name = “Chai”, Orders = 120, Total = 259.20M });
 products.Add(new Product { Category = “Condiments”, Name = “Northwoods Cranberry Sauce”, Orders = 120, Total = 216.00M });
 products.Add(new Product { Category = “Condiments”, Name = “Grandma’s Boysenberry Spread”, Orders = 130, Total = 1200.00M });
 products.Add(new Product { Category = “Dairy Products”, Name = “Queso Cabrales”, Orders = 150, Total = 1400.00M });
 products.Add(new Product { Category = “Dairy Products”, Name = “Queso Manchego La Pastora”, Orders = 160, Total = 1280.00M });
 products.Add(new Product { Category = “Confections”, Name = “Pavlova”, Orders = 210, Total = 1500.00M });
 products.Add(new Product { Category = “Confections”, Name = “Teatime Chocolate Biscuits”, Orders = 114, Total = 800.00M });

 PagedCollectionView collectionViewSource = new PagedCollectionView(products);
 SortDescription sortDescription = new SortDescription(“Category”, ListSortDirection.Ascending);
 collectionViewSource.SortDescriptions.Add(sortDescription);
 GroupDescription groupDescription = new PropertyGroupDescription(“Category”);
 collectionViewSource.GroupDescriptions.Add(groupDescription);
 datagrid1.ItemsSource = collectionViewSource;

The above code is pretty standard, we need to add handlers so that the groupheader cells are resized when header cells of the datagrid

 datagrid1.LayoutUpdated += new EventHandler(datagrid1_LayoutUpdated);

 datagrid1.UnloadingRowGroup += (s, e) => { Resize(); };
 datagrid1.LoadingRowGroup += (s, e) => { refreshUI = true; };

 void datagrid1_LayoutUpdated(object sender, EventArgs e)
        {
            if (refreshUI && headers == null)
            {
                dghc = datagrid1.GetChildrenByType<DataGridColumnHeadersPresenter>().FirstOrDefault();
                foreach (DataGridColumnHeader dgch in dghc.Children)
                {
                    dgch.SizeChanged += (s, args) => { Resize(); };
                }
            }
            if (refreshUI)
                Resize();

            refreshUI = false;
        }

 void Resize()
 {
   headers = datagrid1.GetChildrenByType<StackPanel>().Where(x => x.Name == “ghsp”).ToList();
   headers.ForEach(x =>
   {               
      for (int i = 1; i < dghc.Children.Count – 2; i++)                                                      
         (x.Children[i - 1] as DataGridCell).Width = dghc.Children[i].RenderSize.Width;                                                                                  
   });
 }

what we are doing in the event handlers is, when the groups are loading/unloading or the datagrid is scrolled we adjust the width of the cells in the groupheader

3.If you look at the how the columns are defined in the datagrid

<my:DataGrid.Columns>
                <my:DataGridTextColumn Header=”Category” Binding=”{Binding Category}” Width=”Auto”/>
                <my:DataGridTextColumn  Header=”Name” Binding=”{Binding Name}” Width=”*”/>
                <my:DataGridTextColumn HeaderStyle=”{StaticResource headerStyle}” CellStyle=”{StaticResource cellStyle}” Header=”Orders” Binding=”{Binding Orders}” Width=”Auto” />
                <my:DataGridTextColumn  HeaderStyle=”{StaticResource headerStyle}” CellStyle=”{StaticResource cellStyle}” Header=”Total” Binding=”{Binding Total}” Width=”150″/>
                <my:DataGridTemplateColumn Header=”"  Width=”Auto” Visibility=”Collapsed”>
                    <my:DataGridTemplateColumn.CellTemplate>
                        <DataTemplate>
                            <TextBlock  />
                        </DataTemplate>
                    </my:DataGridTemplateColumn.CellTemplate>
                </my:DataGridTemplateColumn>
            </my:DataGrid.Columns>

There is an extra column at the end and Visibility=”Collapsed” is set. This is a hack as I could not get the last column to align right. If you dont have a summary column at the end. you dont need that.

you can download the code here

Categories: Silverlight 4 Tags: ,

Quick sample using EF, WCF RIA Services

January 11, 2010 lee 4 comments

Here is a quick sample, I did using EF, RIA services which involves CRUD operations when there is many to many relationship between tables

you can download the sample database here and the sample here

you will be able to do

1. See all the playlists available, click on details to see the tracks in the playlist

2. Remove the tracks from the playlist and when save changes is clicked, save the changes

3. Add a new playlist

Binding DataField to a collection

December 10, 2009 lee Leave a comment

Here is something I found interesting browsing the silverlight forums. Assuming we are editing a object and we are in editmode in the DataForm

If we want to bind a property to a datafield we do something like

<dataFormToolkit:DataField Label=”FirstName”>
                            <TextBox Text=”{Binding FirstName, Mode=TwoWay}”/>
                        </dataFormToolkit:DataField>

buf if the property is a collection doing something similar wont work( the control will not be enabled)

<dataFormToolkit:DataField Label=”Skills”  >
                            <ListBox  ItemsSource=”{Binding Skills }”></ListBox>
                        </dataFormToolkit:DataField>

looks like we need to do something like this, specify the DataContext on the DataField

<dataFormToolkit:DataField Label=”Skills”  DataContext=”{Binding Skills}” >
                            <ListBox  ItemsSource=”{Binding }”></ListBox>
                        </dataFormToolkit:DataField>

Categories: Sivlerlight 3

Customizing datafield in SL3

December 10, 2009 lee Leave a comment

I was trying to answer a forum post regarding customizing  datafield and thought It might be better to post the sample here.

below is all the xaml

<UserControl xmlns:dataFormToolkit=”clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data.DataForm.Toolkit” 
             x:Class=”SilverlightApplication3.MainPage”
    xmlns=”http://schemas.microsoft.com/winfx/2006/xaml/presentation
    xmlns:x=”http://schemas.microsoft.com/winfx/2006/xaml
             xmlns:ctl=”clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data.Input”
 xmlns:sys=”clr-namespace:System;assembly=mscorlib”
 xmlns:vsm=”clr-namespace:System.Windows;assembly=System.Windows”
    xmlns:d=”http://schemas.microsoft.com/expression/blend/2008” xmlns:mc=”http://schemas.openxmlformats.org/markup-compatibility/2006
    mc:Ignorable=”d” d:DesignWidth=”640″ d:DesignHeight=”480″>
    <UserControl.Resources>
        <Style x:Key=”requiredStyle” TargetType=”ctl:Label”>
            <Setter Property=”IsTabStop” Value=”False”/>
            <Setter Property=”Template”>
                <Setter.Value>
                    <ControlTemplate TargetType=”ctl:Label”>
                        <StackPanel Orientation=”Horizontal”>
                            <vsm:VisualStateManager.VisualStateGroups>
                                <vsm:VisualStateGroup x:Name=”CommonStates”>
                                    <vsm:VisualState x:Name=”Normal”/>
                                    <vsm:VisualState x:Name=”Disabled”/>
                                </vsm:VisualStateGroup>
                                <vsm:VisualStateGroup x:Name=”ValidationStates”>
                                    <vsm:VisualState x:Name=”Valid”/>
                                    <vsm:VisualState x:Name=”Invalid”>
                                        <Storyboard>
                                            <ObjectAnimationUsingKeyFrames Storyboard.TargetName=”ContentControl” Storyboard.TargetProperty=”Foreground” Duration=”0:0:1.5″>
                                                <DiscreteObjectKeyFrame KeyTime=”0″>
                                                    <DiscreteObjectKeyFrame.Value>
                                                        <SolidColorBrush Color=”Red” />
                                                    </DiscreteObjectKeyFrame.Value>
                                                </DiscreteObjectKeyFrame>
                                            </ObjectAnimationUsingKeyFrames>
                                        </Storyboard>
                                    </vsm:VisualState>
                                </vsm:VisualStateGroup>
                                <vsm:VisualStateGroup x:Name=”RequiredStates”>
                                    <vsm:VisualState x:Name=”NotRequired”>
                                        <Storyboard>
                                            <ObjectAnimationUsingKeyFrames Storyboard.TargetName=”requiredControl” Storyboard.TargetProperty=”Opacity” Duration=”0:0:1.5″>
                                                <DiscreteObjectKeyFrame KeyTime=”0″ Value=”0″>                                               
                                                </DiscreteObjectKeyFrame>
                                            </ObjectAnimationUsingKeyFrames>
                                        </Storyboard>
                                    </vsm:VisualState>
                                    <vsm:VisualState x:Name=”Required”>
                                        <Storyboard>
                                            <ObjectAnimationUsingKeyFrames Storyboard.TargetName=”requiredControl” Storyboard.TargetProperty=”Opacity” Duration=”0:0:1.5″>
                                                <DiscreteObjectKeyFrame KeyTime=”0″ Value=”1″>
                                                </DiscreteObjectKeyFrame>
                                            </ObjectAnimationUsingKeyFrames>
                                        </Storyboard>
                                    </vsm:VisualState>                                                                      
                                </vsm:VisualStateGroup>
                            </vsm:VisualStateManager.VisualStateGroups>
                            <Border Background=”{TemplateBinding Background}” BorderBrush=”{TemplateBinding BorderBrush}” BorderThickness=”{TemplateBinding BorderThickness}” Padding=”{TemplateBinding Padding}” CornerRadius=”2″>
                                <StackPanel Orientation=”Horizontal”  HorizontalAlignment=”Left”>
                                    <TextBlock x:Name=”requiredControl” Text=”*” FontSize=”10″/>
                                    <ContentControl x:Name=”ContentControl” Foreground=”{TemplateBinding Foreground}” Content=”{TemplateBinding Content}” ContentTemplate=”{TemplateBinding ContentTemplate}” FontWeight=”{TemplateBinding FontWeight}” Cursor=”{TemplateBinding Cursor}” HorizontalAlignment=”{TemplateBinding HorizontalAlignment}” HorizontalContentAlignment=”{TemplateBinding HorizontalContentAlignment}” FontFamily=”{TemplateBinding FontFamily}” FontSize=”{TemplateBinding FontSize}” FontStretch=”{TemplateBinding FontStretch}” VerticalAlignment=”{TemplateBinding VerticalAlignment}” VerticalContentAlignment=”{TemplateBinding VerticalContentAlignment}” IsTabStop=”False” />
                                </StackPanel>
                            </Border>
                        </StackPanel>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
        <Style x:Key=”datafieldStyle” TargetType=”dataFormToolkit:DataField”>
            <Setter Property=”LabelStyle”  Value=”{StaticResource requiredStyle}”/>
        </Style>
    </UserControl.Resources>
  <Grid x:Name=”LayoutRoot”>
       <dataFormToolkit:DataForm Width=”400″ Margin=”30″ Height=”200″  DataFieldStyle=”{StaticResource datafieldStyle}”   x:Name=”dataform1″></dataFormToolkit:DataForm>
    </Grid>
</UserControl>

you can download the code sample here

Categories: Silverlight, Sivlerlight 3

Sorting nested collections

December 8, 2009 lee 2 comments

Lets say we retrieve data which has nested property which is a collection(customer entity having bunch of orders). there are many ways to sort the returned collection of orders.

here is one way

we will have some code like below to get the list of customers, where we are including the Orders also, instead of changing the methods we could you CollectionViewSource to do the sorting

public IQueryable GetCustomers()
{
return this.ObjectContext.Customers.Include(“Orders”);
}

In the following sample, we are binding the collectionviewsource to the selected customers orders collection and sorting by orderdate, and binding ordersDatagrid to the CollectionViewSource

 we get the customers using

ctx.Load<Customer>(ctx.GetCustomersQuery(), (lo) => { customers = lo.Entities.ToList(); datagrid1.ItemsSource = customers; }, null);

  <UserControl.Resources>
        <CollectionViewSource x:Name=”cvs” Source=”{Binding ElementName=datagrid1, Path=SelectedItem.Orders}”>
            <CollectionViewSource.SortDescriptions>
                <scm:SortDescription PropertyName=”OrderDate” Direction=”Descending”/>
            </CollectionViewSource.SortDescriptions>
        </CollectionViewSource>
       
    </UserControl.Resources>

    <Grid x:Name=”LayoutRoot” Background=”White”>
        <Grid.RowDefinitions>
            <RowDefinition Height=”*”/>         
            <RowDefinition Height=”Auto”/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        <my:DataGrid x:Name=”datagrid1″ IsReadOnly=”True” Grid.Row=”0″ Grid.Column=”0″></my:DataGrid>                
        <my:DataGrid x:Name=”ordersDatagrid” ItemsSource=”{Binding Source={StaticResource cvs}}” Grid.Column=”1″/>
     
    </Grid>

SL4: DataGrid and contextmenu

November 19, 2009 lee 2 comments

There is a nice implementation of contextmenu by Jesse Bishop in SL4. It supports commands as well as click handlers
we could use that control and add context sensitive contextmenu to datagrid

I created a basic application, Here is the xaml for the mainpage. we have a datagrid which is bound to a list of persons

<UserControl xmlns:my=”clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data”  x:Class=”SilverlightApplication1.MainPage”
    xmlns=”http://schemas.microsoft.com/winfx/2006/xaml/presentation
    xmlns:x=”http://schemas.microsoft.com/winfx/2006/xaml
    xmlns:d=”http://schemas.microsoft.com/expression/blend/2008
    xmlns:mc=”http://schemas.openxmlformats.org/markup-compatibility/2006
             xmlns:menu=”clr-namespace:ContextMenuControls;assembly=ContextMenuControls”   
    mc:Ignorable=”d”
    d:DesignHeight=”300″ d:DesignWidth=”400″>

    <Grid x:Name=”LayoutRoot” Background=”White”>
<my:DataGrid x:Name=”datagrid1″ ItemsSource=”{Binding Persons}”></my:DataGrid>
    </Grid>
</UserControl>

when the rightmouse button is down on the grid. we do a hit test to get the row( the person item that was clicked), create a menuitem with person’s name and add to the Items of the contextmenu
when the item is clicked we remove the person from the list

void datagrid1_MouseRightButtonUp(object sender, MouseButtonEventArgs e)
{
IEnumerable elements = VisualTreeHelper.FindElementsInHostCoordinates(e.GetPosition(LayoutRoot), datagrid1);
DataGridRow row = null;
foreach (UIElement ele in elements)
{
if (ele is DataGridRow)
{
row = ele as DataGridRow;
break;
}
}

ContextMenu cm = new ContextMenu();
Person p = row.DataContext as Person;
MenuItem mi = new MenuItem { Text = “Delete ” + p.FirstName, Tag=p };
mi.Click += new MenuItem.ClickHandler(mi_Click);
cm.Items.Add(mi);

datagrid1.SetValue(ContextMenu.ContextMenuProperty, cm);
cm.IsOpen = true;

}

void mi_Click(object sender, EventArgs e)
{
Persons.Remove((sender as MenuItem).Tag as Person);
}

you can download the sample here

Categories: Silverlight 4

Same XAML Silverlight and WPF – different result

October 14, 2009 lee 8 comments

I did not know the following would produce different results in WPF and Silverlight

<Grid>
        <TextBlock Text=”{Binding RelativeSource={RelativeSource Self}, Path=Tag}” Tag=”hello”/>
    </Grid>

In WPF, you see “Hello”. In silverlight you dont see anything

The following XAML produces same result in WPF and Silverlight

<Grid>
        <TextBlock Tag=”hello” Text=”{Binding RelativeSource={RelativeSource Self}, Path=Tag}” />
    </Grid>

Categories: General

ListBox and scrolling

October 8, 2009 lee 1 comment

When the listbox is bound to some data and when the data changes we may want to make the first item in the list visible.  That is not the default behaviour and ScrollIntoView(Item[0]) wont work. here is a way to reset the scrollposition

In the sample, I had the listbox’s  are bound to 200 items and when the button is clicked I set the items to 20. the listbox on the left shows the default behaviour , the listbox on the right  has the ScrollViewer position changed to show the first item  listbox scrolling
update: Looks like this method still has issues when the number of items changes from a very low number to  high number where the scrollbar appears and disappears as pointed out in this post. May be a bug?

update 2: Mog Liang (Microsoft Online Community Support) says “This is an known issue caused by ListBox virtualization. Currently, an workaround is replace VirtualizingStackPanel with StackPanel, however, this will drop the listbox performance.”

Here is the XAML and codebehind used in the sample

<Grid x:Name=”LayoutRoot” Margin=”5″>
        <Grid.ColumnDefinitions>
            <ColumnDefinition/>
            <ColumnDefinition/>
            <ColumnDefinition/>
            </Grid.ColumnDefinitions>
      <Button Content=”set to 20 items” Height=”28″ Width=”100″  HorizontalAlignment=”Right” Click=”Button_Click”/>
            <ListBox Grid.Column=”1″  Height=”200″ x:Name=”list1″ ItemsSource=”{Binding MyList}”/>
        <ListBox Grid.Column=”2″  Height=”200″ x:Name=”list2″ ItemsSource=”{Binding MyList}”/>
    </Grid>

 

public partial class MainPage : UserControl, INotifyPropertyChanged
    {
        public IEnumerable<string> MyList { get; set; }
        public MainPage()
        {
            InitializeComponent();
            MyList = Enumerable.Range(0, 200).Select(i => “Item..” + i.ToString()).ToList();
            this.DataContext = this;
           
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            MyList = Enumerable.Range(0, 20).Select(i => “Item..” + i.ToString()).ToList();
            NotifyPropertyChanged(“MyList”);
            ScrollViewer sv = GetChild<ScrollViewer>(list2);
            sv.ScrollToVerticalOffset(0);
        }

        public static T GetChild<T>(DependencyObject obj) where T : DependencyObject
        {
            DependencyObject child = null; for (Int32 i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
            {
                child = VisualTreeHelper.GetChild(obj, i);
                if (child != null && child.GetType() == typeof(T))
                {
                    break;
                }
                else if (child != null)
                {
                    child = GetChild<T>(child);
                    if (child != null && child.GetType() == typeof(T))
                    {
                        break;
                    }
                }
            }
            return child as T;
        }
        #region INotifyPropertyChanged Members

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

       
        #endregion
    }

Categories: Sivlerlight 3