Tuesday, September 15, 2009

Silverlight 3 DataGrid : Beyond Databinding …

First of all, Thank You all for your wonderful response for my last series on Silverlight 3 RIA Services for your LOB applications. When Silverlight 3 RTW was in place, before that time when it was in Beta, I wrote a series of articles on various capabilities in Silverlight 3 which includes DataForm,DataGrid and much more like pulling and pushing real data via WCF services to Database and vice-versa in very effective way.I am happy to tell that those articles still getting massive hits which I came to know recently from analytics. Thanks once again for your feedback.

Well, Today I am going to discuss few things which you generally “may” not have found in books or other places but which are equally important in terms of day to day business apps. Recently while doing few POCs, I got lot of feedbacks and common questions from customers about DataGrid which I am trying my best to address those in this article. I am though not much going to talk on PageCollectionView, Sorting and Grouping, I might cover those things in upcoming articles.

1. How Do I change Color of DataGrid Header ?

Well, for Background color you need to very much depend upon VSM (Visual State Manager) since directly Background property to header is not available. However with Style you can set/override foreground color of Header Text like this : (I am giving quick solution where I am talking about applying styles in limited scope and not globally)

xmlns:primitives="clr-namespace:System.Windows.Controls.Primitives;assembly=System.Windows.Controls.Data"

First you need to incorporate above namespace. The you can declare resource like this :

<UserControl.Resources>                         
    <Style TargetType="primitives:DataGridColumnHeader" x:Name="Vikram">
            <Setter Property="Foreground" Value="Red"/>                
    </Style>
</UserControl.Resources>

Then next step is to apply this Style to actual column like this:

<data:DataGridTextColumn Binding="{Binding FirstName}" HeaderStyle="{StaticResource Vikram}" Header="FirstName" />

And output will look like this :

HeaderStyle

2. Adding “Hand” Cursor to Header via Styles and without any code :

Some of you dropped me mail asking this requirement, Well this is certainly interesting and out of the box, Since Hand cursor should get displayed on Header part only and not on Rows, If you write like :

private void dg_MouseMove(object sender, System.Windows.Input.MouseEventArgs e)
        {
            this.Cursor = Cursors.Hand;
        }

This line of code is not going to help us to get the desired result and hence this time also we are going to take help of Styles directly. Point to be note here is that, if you try to set Cursor property from XAML, your attempts will fail since it does not expose such property. So write style like this below :

<UserControl.Resources>                         
<Style TargetType="primitives:DataGridColumnHeader" x:Name="Vikram">
<Setter Property="Cursor" Value="Hand"/>                               
</Style>
</UserControl.Resources>

Then again apply that style to Column Header as shown above.Note that, once you apply this Cursor, It might take some time/small delay to actually show Hand Cursor only on column header. This will help to display cursor Hand only on Column and not on rest of grid or rows.

3. No Data Display :

Most of the Third-Party Grids like Telerik etc, do provide features like No Data Display Template( I saw such thing for ASP.NET Grids from Telerik), Well it make sense to have such templates especially when you have your ItemSource or e.Result (Output from Service) as null.By default no such template is though available, what I am doing here is explicitly building a class with single property like this :

public class nodata
{
        public string Display { get; set; }        
}

Take little more effort to build a method where we can set message like this :

public List<nodata> NoDataDisplay()

    List<nodata> ndt = new List<nodata>();
    ndt.Add(new nodata() { Display = "No Data to Display.." });            
    return ndt;
}

Then final logic goes like this :

if (dg.ItemsSource == null)
{
    dg.Columns.Clear();
    dg.ItemsSource = NoDataDisplay();

   DataGridTextColumn textColumn = new DataGridTextColumn();
   textColumn.Header = "FirstName|LastName|Age|Email|SIG|WebCastDate|WebCastTime";
   textColumn.Binding = new Binding("Display");
   dg.Columns.Add(textColumn);

   dg.Height -= 245;
   dg.Width -= 250;

}
else
{
   dg.ItemsSource = GetSIGHeads();
}

Output will look like this :

NormalGrid

And if there is no data then :

NoDataDisplay

When I was scratching the Internet to get solutions on this, I hardly come across any for such requirement. So I went ahead and build my own logic. So I request you all to evaluate it and let me know if there is any better way to do it. Offcourse I will make your suggestion public giving full credit to you :)

4.Columns do get resized but not fully..How to reduce width to make them almost invisible.

Well, for some of my clients following was the situation :

DragCol1

This is the normal layout of grid, now in next situation, if user drag particular column, see what happens :DragCol2

Observe this carefully, User dragging LastName from left to right, As the default behavior it get shrink,But you can see it does not get shrink fully, so it becomes annoying a lot and looks like a stuck up column. So simple work around is to set MinWidth=0 and story ends ! For sample below I have set MinWidth of FirstName to 0 so here is the output :

<data:DataGridTextColumn Binding="{Binding FirstName}" HeaderStyle="{StaticResource Vikram}" Header="FirstName" MinWidth="0" />

DragCol3

Now you can see that there is not stuck up and you can drag any field and almost till respective on right or left will be overlapped.

5. Add Hyperlink to Cell..A Hyperlink and not Hyperlink Button !

Interesting ! a hyperlink and not hyperlink button, so idea is to just click on it and navigate, Any code behind event is not expected. Our job is to make it simple and workable in minimum workarounds and efforts, Solution :

<data:DataGridTemplateColumn>
  <data:DataGridTemplateColumn.CellTemplate>
      <DataTemplate>
          <HyperlinkButton Content="Click Me" TargetName="_blank" NavigateUri="http://www.silverlight.net">
           </HyperlinkButton>
       </DataTemplate>
   </data:DataGridTemplateColumn.CellTemplate>
</data:DataGridTemplateColumn>

and output will be like this :

Hyperlink

So you can redirect to any desired URL which you want and also you can specify location either same window or in new window by setting TargetName attribute. This is helpful to show Popup kind of window and render ASPX in it. But if you want to pass certain parameters to it, better watch out HTML DOM and limitations to do so before you directly take this as solution.

6. A Jazzy looking button is possible to be put in Grid?

Yes !, all you need to do it is to make use of Expression Design and Design a Jazzy button like this :

Delete

and then just add following code, Offcourse you will get event too for that button to write your code.

<data:DataGridTemplateColumn HeaderStyle="{StaticResource ColHead}">
  <data:DataGridTemplateColumn.CellTemplate>
     <DataTemplate>
        <Button x:Name="btnSave" Click="btnSave_Click">
           <Button.Template>
              <ControlTemplate>
                 <Image Source="Delete.png" Height="30" Width="80"/>
              </ControlTemplate>
           </Button.Template>
        </Button>
     </DataTemplate>
  </data:DataGridTemplateColumn.CellTemplate>
</data:DataGridTemplateColumn>

In such cases, be careful about Height and Width since they matter a lot in overall look and feel of the Grid.

7. How will you add Row Details ?

It can be simply added by following XAML Code :

<data:DataGrid.RowDetailsTemplate>
   <DataTemplate>
      <StackPanel Orientation="Horizontal" Background="AliceBlue">
          <TextBlock Width="200" Text="{Binding Email}" />
      </StackPanel>
   </DataTemplate>
</data:DataGrid.RowDetailsTemplate>

Since we have taken TextBlock, Advantage is that we can Wrap big amount of text and also apply various styles to the same. It is generally used to display the Description or Metadata of the particular row, It will be look like this :

RowDetails

Some Unsolved Mysteries for me for Silverlight DataGrid :

1. How to Merge two or more columns where we bind it with some collection?

2. Why there is always / most of the time a blank column look like dummy column getting bind to gird either on left or right, for most of the cases its on right side like this :

Unsolved Mystry

No Intellisense in CellTemplate ?? a Bug or Known Bug??

intellisense

Well, People address this issue as No Intellisense, actually you do get intellisense here but it is like almost not getting anything, ask me why? Simple answers is that this intellisense carries nothing interms of Standard Controls which we are looking ahead to embed it in the same. So work around is to whatever control you want to have inside it, declare it explicitly outside grid and do a copy-paste,what else can be done? :)

Normal Validations of Data in TextBox :

Before saying good bye for today, I would like to also drop a code snippet which many of you have ask me over mail, It is how one can validate certain fields especially Data entered in TextBox. In my old DataGrid article and DataForm article I have address this issue, but what if the TextBox is not part of those control and is independent?

Solution goes like this : I assume one layout where user suppose to enter his Birthdate, Logic to validate the Date part is what I am going to discuss here, the date is real birthdate or not is not part of our discussion.

if (!Regex.IsMatch(txtdob.Text, @"^([1-9]|0[1-9]|1[012])[- /.]([1-9]|0[1-9]|[12][0-9]|3[01])[- /.](19|20)\d\d$"))
{
      // An Invalid Date format was entered
      registerStatus.Text = "Invalid Date.Follow mm/dd/yyyy";
      txtdob.Select(0, txtdob.Text.Length);
      txtdob.Focus();
}

Do I need to tell more about Regex? I am sure you will be able to do it with multiple types of expressions like for Email, SSN etc. I am using RegisterStatus as error label to set-reset error messages and catching focus on the same control after error and highlighting it with Select( ), This is how it is simple to validate normal textbox controls,which may be part of your Data Entry or Registration of Users kind of applications.

We a talk a lot about small things in DataGrid which we never explore here before and we spend ample of time to bind data by various way. I hope information above will be useful for you and as usual your feedback is most welcome ! I am soon dropping few most post here which will address day to day problems which developers normally face especially if they don’t have any knowledge on Silverlight. So keep checking this place for lots of new things coming with much more interesting topics.

Vikram.

8 comments:

R Pooran Prasad said...

Good article!
Can you post the code..

Prasanna said...

I was looking at your input on "How Do I change Color of DataGrid Header ?". You have shown the implementation example for one column, does that mena we will need to repeat the code for each of the column or there is an easy-way to replicate it for all the columns?

Vikram Pendse said...

@Pooran : Thanks for your comment :)

@Prasanna : Try skinning this one,may help.

Toast said...

Hi Vikram. Quick question: Do you know if it is possible to use the DataForm inside the RowDetails template of a DataGrid? Thanks.

herzmeister der welten said...

Helloes, that's a funny joke with the (3) no data display classes and the single column header that goes like "FirstName|LastName|Age|Email|SIG|..." ;-)

There's a much easier solution. We can always let a TextBlock float above a DataGrid and make it visible when no items are found.

This simple example assumes the control is bound to an appropriate view model:


<Grid x:Name="ResultPanel">

<ctl:DataGrid ItemsSource="{Binding ResultList}" />

<TextBlock HorizontalAlignment="Center"
VerticalAlignment="Center"
FontWeight="Bold"
Text="No items found"
Visibility="{Binding NoItemsFoundMessageVisibility}" />

</Grid>


That's it.

Vikram Pendse said...

@herzmeister der welten Nothing funny if it works ! ;-) ..well thanks a lot for another solutions,seems to be unique.

Pravesh Singh said...

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 Data binding with datagrid control in silverlight. Check this links too its also having nice post with wonderful explanation on data binding with datagrid control in silverlight....
http://www.c-sharpcorner.com/uploadfile/mahesh/working-with-datagrid-in-silverlight/

http://blogs.msdn.com/b/scmorris/archive/2008/03/27/defining-columns-for-a-silverlight-datagrid.aspx

http://mindstick.com/Articles/f5a53bd0-ab3f-47d3-b559-1a1a64d28b1e/?Data%20Binding%20with%20DataGrid%20control

Thank Everyone!!

Annie Calvert said...

Nice blog,Data binding links a data layer with graphical controls and enables data independency of its presentation. Data binding is broadly used in WinForms applications, while in WPF applications it is practically the only data presentation method. Correct organization of data binding is the foundation of well-built applications.
All grids use binding to data collections of (IList, IBindingList, IListSource) type as the main and often the only method of working with data. Besides, connection to IBindingList, Dapfor NetGrid significantly expands data binding features enabling operations in unbound mode.more help visit http://www.dapfor.com/en