Basic Navigation with Caliburn.Micro

** There is an excellent blog on Navigation with  detailed information on each class and different styles @ devlicio.us for CM **

In the previous blog we look at how to do page navigation with Jounce. Today we will look at how to implement the same thing in Caliburn.Micro. The requirement is same.

I. Create a web page with two buttons and a container panel. The first buttons says ‘Hello World’ and second button says ‘Hello World1’.

II. Create two pages, with one page says ‘Hello world’ and the second page says ‘Hello World’.

III. Wire up the button events in such a way, when a user click the ‘Hello World’, it displays the page with ‘Hello World’ in the container panel. When the user presses ‘Hello World1’ button, it replaces the ‘Hello World’ page with ‘Hello World1’ page.

This is exactly the same requirement of our Jounce example. So how would we go about doing it? We start from what we learned from our ‘Hello World’ example.

I.Create web page with its contents:

Follow the steps specified in ‘Hello World’ example and create the simple Caliburn application. Make sure it compiles and runs. Lets add the button and content control to hold the panel to host the dynamic page by adding the following code to the ShellView.xaml

<Grid Background="White" d:DataContext="{d:DesignInstance sampleData:SampleShellViewModel, IsDesignTimeCreatable=True}">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="161*" />
            <ColumnDefinition Width="194*" />
            <ColumnDefinition Width="163*" />
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="34*" />
            <RowDefinition Height="253*" />
        </Grid.RowDefinitions>
        <Button Content="Hello World" Name="HelloWorldButton" Grid.Row="0" Grid.Column="0"/>
        <Button Content="Hello World1" Name="HelloWorld1Button" Grid.Row="0" Grid.Column="2"/>
        <ContentControl Grid.Row="1" Grid.ColumnSpan="3" Name="ActiveItem"/>
    </Grid>

 

We have added two buttons, in row 0, column 0 and 2. First button shows ‘Hello World’ and the second one shows ‘Hello World1’ as the requirement. The third control we added is ‘ContentControl to show a single content. It is named as ‘ActiveItem’. This name is important and we will come to that soon. Other than that, there is nothing special in here, the button have Name associated with it, which is nothing but caliburn convention to bind to a method for click event  in the view model. With XAML added your design time screen should look like the following

image

II. Create two pages to display ‘Hello World’ and ‘Hello Word1’:

Add two new Silverlight Pages (you can also use Silverlight User Controls if you want). Call it Page1View and Page2View. Remember, it is important to end all the view with ‘View’ and ViewModels with ‘ViewModels’. Caliburn uses convention to find and bind the View with ViewModels. Add a text block in each of the page to distinguish which page it is. So the page1view XAML would look like the following

<Grid x:Name="LayoutRoot">
        <TextBlock Text="Hello World"/>
    </Grid>

While the Page2view.xaml would look like this

<Grid x:Name="LayoutRoot">
        <TextBlock Text="Hello World1"/>
    </Grid>

Now that we have views, it is not over. Caliburn relies on ViewModel to do the routing, so create two classes with Page1ViewModel and Page2ViewModel. Both will be nothing but empty classes. You do not need to add any code. There is also different way to approach this same two screens, since I want to keep it simple, I am not going to discuss about them here yet.

III. Wireup the events to load pages:

Now that we have Shell to display the main page, two separate pages which display ‘Hello World’ and ‘Hello World1’. We need to wire up the button click events to activate appropriate page and display them in the content panel named ‘ActiveItem’.  As you know by now, when you click a button, Caliburn will look for a method name with exact name of the Button. So Lets look at the button name of ‘Hello World’, it is called ‘HelloWorld’ (with out space between hello and world). We need to write a method in the shell view model to activate the page1 and display it. To do that. we write the HelloWorld method as follows in the ShellViewModel.cs.

[Export(typeof(IShell))]
    public class ShellViewModel : Conductor<object>, IShell 
    {       

        public void HelloWorldButton()
        {
            ActivateItem(new Page1ViewModel());
        }

        public void HelloWorld1Button()
        {
            ActivateItem(new Page2ViewModel());
        }
    }

That’s it. Now if you would run the application, you will get to the main page

image

now if you would press ‘Hello World’ button, it displays the Page1View in the content panel

image

If you would press ‘Hello World1’ it displays

image

 

So if you notice, we did not write lot of code to achieve the simple page navigation. In fact, the pages we created to display the ‘Hello World’ and ‘Hello World1’ do not have any clue about this will be placed on a particular panel or the page by itself did not say anything about it is being used to display somewhere. All the navigation work is done at the Shell level and nothing at the page level.  Couple of important points here

1. If you notice, the ShellViewModel is derived from Conductor<object>, which is responsible for orchestrating the navigation.

2. To create and place an item on a panel, all you have to do is ActivateItem(viewmodel).

3. When ActivateItem is called, the screen instance is created and placed on ‘ActiveItem’ in the ShellView.

4. There is a whole a lot you can do during the navigation process and I barely touched the surface. We will look at more features in the navigation in later posts.

What makes XAP to download?

Every one knows when ever there is a new version of XAP file, Silverlight ActiveX control downloads the new version properly and all works. If you would Bing you will find out, people have trouble getting latest version of XAP file and there are lot of solution to it. So I am not going into that part. What I want to explain here is a scenario that we ran into, which is straight opposite. In our case XAP gets downloaded every time even when there is no changes the XAP file. It was a very interesting problem and I got great help from some of my colleagues to isolate and solve the problem. I thought I share it here for anyone looking for solution.

Let me explain the exact problem. In our QA environment when we open a brand new instance of IE, that is no other instance of IE is open and go to a Silverlight page for the first time, you can see that, XAP will be downloaded. Now navigate between the pages in the application and come back to the same page and you will notice, XAP will not be downloaded. So far is good. Now close all IE and launch IE again, go to Silverlight page, it will download XAP file again even though XAP file was downloaded a minute ago and we did not release any new XAP file either. Now, we though we could reproduce the problem in our development environment, but we couldn’t. In Development environment once you go the page, XAP get downloaded and close all IE and come back to the same page, it does not download XAP file again. It was a big puzzle.

I did some research on the internet and found out that, If I would set “HTTP Response header expiration’ for ClientBin folder in IIS to a specific time period, XAP will not download till the time period expires. Well it did not solve my real problem since I do not want to set the expiration time to too long. We knew there is still a problem that I am not finding. So I asked around and one of my colleague recommended me to check the HTTP header using Fiddler and he said it might give you an idea. He asked me to check for specifically

If-Modified-Since
Expires
ETag

Here is link in wiki which really helped understand the cache. With that information and I knew what I am after, I ran the same test as explained before against our QA servers with Fiddler running. First attempt I got http response 200 for the XAP file as I expected.

image

Now with in the same application on the same instance of IE, move between pages and come back to the same page and I get 304 as expected and it will not download the file.

image

I closed all the IE instances and open Silverlight page again I did get 200 and only difference in this case was my ETag was different. I was curious why ETag is different, it turned out, in QA we have multiple servers which are running under load balancer so this time, load balancer send me to another server. Since the ETag value is different from the previous server and even though the last modified date and time is same, it ended up downloading the file. So the culprit in this case is ETag. Different QA servers have different ETag set. One of my friend send me a mail with issue with ETag and its solution recommended in Microsoft site. 

There is two solution, one recommended in the Microsoft web site to bring all your QA servers to same ETag base number. The other is just add the ETag to ClientBin foler as empty. In our case, we added ETag as empty and performed the test, now it no longer downloads the XAP file.

Hopefully it helps someone.

Creating Silverlight Navigation using Jounce

Jounce has very good documentation on how to create Navigation and Region Management in the Codeplex. I always starts there. What I am doing to here to illustrate how to create Silverlight Navigation step by step. So before we go any further what are we trying to achieve? Here are the basic requirements;

I. Create two buttons on top of the screen as ‘Hello World’ and ‘Hello World1’ and bottom section will have a panel which display a page based on the button selection.

II. Create two separate pages which will display ‘Hello World’ and ‘Hello World1’. See the subtle difference in the text ‘World1’.

III. When user click the first button ‘Hello World’, the bottom section shows the ‘Hello World’ page and on ‘Hello World1’ click it shows ‘Hello World1’. At any point of time, we should see only one page.

Ok now we have the requirements done, lets step through the process of creating the application.

 

I. Create UI Part.

First lets create a simple Jounce application. I would strongly recommend you to go to Jounce page @ Codeplex and download the Jounce template for Visual Studio 2010. I always use it and it eliminates lot of code deletion and adding reference extra. So We start out by creating a Jounce Application

image

 

Once the project created, I would recommend you build and run it. I always do that just to eliminate any missing references.  If all are good you are suppose to get the following screen with ‘Welcome to Jounce’ message. If you do not see it then, you have a problem with Jounce references. One reason you might not able to get it up and running if you do not have System.Interactivity.dll not in your GAC.

image 

Lets assume that, you got it working. Next we are going to implement the first requirement that to add two buttons and a panel. We modify the MainPage.XAML to achieve this.

1. First add two buttons for Hello World and Hello World 1

        <Button Content="Hello World" Command="{Binding HelloWorld}" Grid.Row="0" Grid.Column="1"/>
        <Button Content="Hello World1" Command="{Binding HelloWorld1}" Grid.Row="0" Grid.Column="2"/>

2. Next we need to create the content panel to host the page on button click

<ContentControl Grid.Row="1" Grid.ColumnSpan="4" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
                HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch">
            <ContentControl.Content>
                <StackPanel Orientation="Horizontal"/>
            </ContentControl.Content>
        </ContentControl>

This will create bottom panel to host the pages.

3. We are not done yet in the  main page.  Next we need to name the stack panel as a region to host the pages. This is achieved by adding Jounce Region namespace and then use the Region Name to name the panel.

<UserControl x:Class="NavigationTest.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:sampleData="clr-namespace:NavigationTest.SampleData"
    xmlns:Regions="clr-namespace:Jounce.Regions;assembly=Jounce"
    mc:Ignorable="d"
    d:DesignHeight="300" d:DesignWidth="400">

 

If you notice the 3rd line from the bottom, we are referencing the region namespace which we will use to name the Content Control. We are going to modify the step (2) and add name to content control. So with the change it will look like this.

<ContentControl Grid.Row="1" Grid.ColumnSpan="4" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
                HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch"
                Regions:ExportAsRegion.RegionName="AppRegion">
            <ContentControl.Content>
                <StackPanel Orientation="Horizontal"/>
            </ContentControl.Content>
        </ContentControl>

We named the content control as “AppRegion” that is the 3rd line from the top. That’s it. Now we completed the requirement (1). We created two buttons and a panel with a region name.

 

II. Create Two Pages.

Now lets look at the requirement (2), that is to create two separate pages with just a textblock which displays two different strings. In the solution, now add new Silverlight Page like the following

image

This will create a new page and we add a text block to display “Hello World” like the following

<Grid x:Name="LayoutRoot">
        <TextBlock Text="Hello World"/>
</Grid>

Repeat the same and create HelloWorld1Page.

III. Implement Navigation.

Ok now we have the main page and two addition pages as we wanted, now we need to tie all of them together. So lets start from main page view model. We need to implement the command to execute on button click happens.  Before we start, delete the interface definition for the welcome text since we no longer use that. Now the modified code of View Model look like the following

namespace NavigationTest.ViewModel
{
    using Jounce.Core.Command;
    using Jounce.Framework.Command;
    using Jounce.Core.View;

    [ExportAsViewModel("MainViewModel")]
    public class MainViewModel : BaseViewModel, IMainViewModel
    {
        public IActionCommand<string> HelloWorld { get; private set; }
        public IActionCommand<string> HelloWorld1 { get; private set; }

        public MainViewModel()
        {
            HelloWorld = new ActionCommand<string>(ShowHelloWorld);
            HelloWorld1 = new ActionCommand<string>(ShowHelloWorld1);
        }

        public void ShowHelloWorld(string parameter)
        {
            EventAggregator.Publish(new ViewNavigationArgs("HelloWorld"));
        }
        public void ShowHelloWorld1(string parameter)
        {
            EventAggregator.Publish(new ViewNavigationArgs("HelloWorld1"));
        }
    }
}

We defined two button implementation one for each button. If you see the implementation, all we are doing is that, we are publishing an event with ViewNavigationArgs with the name of the pages in other words, the name you use in ‘ExportAsView’ for each new page you created. If you remember we did not jouncify the two pages we created so it does not have ‘ExportAsView’. So lets do that. Open the code behind for HelloWorld.cs and make the code look like the following

namespace NavigationTest
{
    [ExportAsView("HelloWorld")]
    [ExportViewToRegion("HelloWorld", "AppRegion")]
    public partial class HelloWorldPage : Page
    {
        public HelloWorldPage()
        {
            InitializeComponent();
        }

        // Executes when the user navigates to this page.
        protected override void OnNavigatedTo(NavigationEventArgs e)
        {
        }

    }
}

The third line is the standard Jounce attribute to name the page. The interesting part in this page is the fourth line. What we are saying here is that, when you get control, make sure, you identify the ‘AppRegion’ control and pace the view in there. Will do the same for HellowWorld1 code behind.

 

namespace NavigationTest
{
    [ExportAsView("HelloWorld1")]
    [ExportViewToRegion("HelloWorld1", "AppRegion")]
    public partial class HelloWorld1Page : Page
    {
        public HelloWorld1Page()
        {
            InitializeComponent();
        }

        // Executes when the user navigates to this page.
        protected override void OnNavigatedTo(NavigationEventArgs e)
        {
        }

    }
}

That’s it, now with these changes you would see a screen something like the following

image

Clicking Hello World button will show

image

now clicking Hello World 1 will replace hello world page with Hello World 1 as shown below

image

 

That’s about it. Now if you want to show both the controls then instead of using Content Control in the main page, use ItemsControl. So the bottom line is, have a region defined in the main page to host the pages. When you want to host a page, just publish the ‘ViewNavigationArgs’ with the page name. In the page, decorate the page with which region that page has to display by ‘ExportViewToRegion’. As simple as that. Hopefully it helps out. I will attempt the same in Caliburn next.

Step into Caliburn.Micro World

This is my first attempt at Caliburn so this might be little crude. After some very busy time, I thought I will give it a go on Caliburn.Micro. To my surprise ‘Hello World’ was not that complicated as I thought it would be. So in this blog, we are going to look at writing simple Hello World program to understand the following

I. What are all the things required to build an application.

II. MVVM.

III. Simple data binding.

IV. Design time data or Blendability.

V. Distribution file size.

To create a simple application, I followed this introduction link and got the Hello World working in no time. The steps are simple.

1. Create a brand new silverlight application.

2. If you do not have NuGet please go get it, with NuGet in place. Go to Tools->Library Package Manager –> Manage NuGet Packages and install Caliburn.Micro.

Once you install, not only it brings down the required dll, but also it creates the shell template with View, ViewModel and interface for the view model.

3. Follow the steps in the links mentioned to clean up App.XAML and make sure you delete the MainPage.XAML and run the program. You will see Hello World.

4. In this process we really did not use ViewModel. If you look at the ShellViewModel.cs, there is no code. Lets modify the code little bit to have small taste of MVVM.

5. Go to ShellView.XAML and change the textblock as follows;

<TextBlock Name="HelloWorld"
                   VerticalAlignment="Center"
                   HorizontalAlignment="Center"
                   FontSize="20" />

What we did here is that, instead of passing the constant now we named the TextBlock so that it can get some data from view model.

6. Go to IShell.cs and add the following

public interface IShell 
    {
        string HelloWorld { get; }
    }

Same as Jounce, I am creating the variable in here so that I can use it for design time data.

7. Modify the SheViewModel.cs as follows

[Export(typeof(IShell))]
    public class ShellViewModel : IShell 
    {
        #region IShell Members

        public string HelloWorld
        {
            get { return "Hello Unni"; }
        }

        #endregion
    }

 

8. Now if you run the code, it will show ‘Hello Unni’ instead of hello word from Caliburn. We will look at how binding happened in a second.

 

In this process you deleted lot of code and added few lines.

I. What are all the things required to build an application.

With our first program running, lets look at the components required to write Silverlight using Caliburn. You need two dlls, the first one, of course is Calburn.Micro.dll and the second one is System.Interactivity.dll. If you use blend then this dll will be available in GAC for you, you need to add the reference to your solution.

II. MVVM.

Caliurn uses simple convention to identify View and ViewModels, it is pretty cool. All your view have to end with ‘View’ and the view model as you guessed it, it should end with ‘ViewModel’, thats about it. So in the ’HelloWorld’, our main View is Shell and is called ‘ShellView’ and its corresponding view model is ‘ShellViewModel’. but if you would look at the app.xaml.cs we did not say which one is our starting page. It is done in the bootstrapper. If you look at the class definition of bootstrapper

public class AppBootstrapper : Bootstrapper<IShell>

 

So if you want to specify a different page, all you have to do is to bootstrap off of its view mode.

III. Simple data binding.

Data Binding is super simple. In our Hello word program the TextBlock name was ‘HelloWorld’ and if you look at the view model, we have a property called ‘HelloWord’. So binding is done through Name in XAML and associated property in the view model.

IV. Design time data or Blendability.

Unfortunately, so far I am not successful in enabling design time data. Here is what I attempted. Created a new class called SampleShellViewModel.cs as follows

namespace HelloWorld
{
    public class SampleShellViewModel:IShell
    {

        #region IShell Members

        public string HelloWorld
        {
            get { return "Hello World"; }
        }

        #endregion
    }
}

Modified the ShellView.XAML as follows

<UserControl x:Class="HelloWorld.ShellView"
             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"
             mc:Ignorable="d"
             xmlns:sampleData="clr-namespace:HelloWorld"
             d:DesignHeight="287" d:DesignWidth="518"
             >

    <Grid Background="White" d:DataContext="{d:DesignInstance sampleData:SampleShellViewModel, IsDesignTimeCreatable=True}">
        <TextBlock Name="HelloWorld"
                   VerticalAlignment="Center"
                   HorizontalAlignment="Center"
                   FontSize="20" />
    </Grid>

</UserControl>

But view does not recognizes the design time data object. I will continue work on it and see what am I doing wrong here and update the post once I find the answer.

** By default Caliburn Micro naming convention will not work with design time data. So instead of using Name=”Hello World”, do Content=”{Binding HelloWorld"}” will do run time binding and design time data binding.”

V. Distribution file size.

The distribution dll for Caliburn is only 41K compressed.

 

This is my baby steps into Caliburn and so far what I found is that it is easy to use.

Comparing to Jounce, here are some of my observations;

1. Compare to Jounce the distribution dll, Caliburn is only 6K more, it is not a big deal.

2. Caliburn uses convention to do data binding and View, View Model binding while in Jounce, for every view, you have to write the ViewModelRoute binding.

3. Jounce is attribute based development, while Caliburn is convention based development.

4. Since I do not have definite answer on Blendability, I am not going to comment on it yet.

4. By default the convention binding will not work on Blendabilty. If you want design time data, add or replace Name with normal Binding.

5. You have to have a bootstrapper for Caliburn, while Jounce it is build in.

6. Caliburn can use any IoC container, while Jounce is build on top of MEF.

I will go more about binding, MEF and other features in a later blog.

Design time data – Second look

In my previous blog I went through how Jounce template for Silverlight application creates default application and generates plumbing in place to use Design Time data. All the projects I have worked on, we completely ignore it. For the last couple of months, I have been consciously taking extra steps to use Design Time data. As I mentioned in my multiple posts, it is simply of the best feature most of every one ignores. Without doubt, in my opinion it is one of the easiest one to achieve without lot of learning curve and available out of the box. Jounce Silverlight Application template already creates all interfaces and implementation in place to use it. In my previous post I went though what is available out of the box. In this blog, I am going to take it one extra step.

I need to emphasis here, this would be lot more easier if we would use Expression Blend. Since we are not designers, we are going to look at solving the problem traditional way as a coder.

Here are the requirements for the application.

1. It has to display the data in grid format.

2. The column order has to be Name, Age, Salary and Sex.

3. Salary has to currency formatted and should be right aligned.

4. In case the record allows ‘IsEnable’ then change the background color of the salary field to be RED.

First we are going to create a Silverlight application using Jounce Silverlight Application framework. What I am going to do is to crate a grid, which will show our favorite Person class information. So here is our person class.

public class Person : INotifyPropertyChanged
    {
        public string Name { get; set; }
        public int Salary { get; set; }
        public char Sex { get; set; }
        public int Age { get; set; }
        public bool IsEnabled { get; set; }

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

Normally the way, I go about doing is, to crank up the XAML and run the application and then see if the grid shows my data properly if not tweak the XAML, rinse and repeat the process till you get what you want. In my opinion we are wasting time on this, where all the requirement in this case can be verified in the IDE itself. Lets see how it should have been done (IMHO).

Since we have created the Silverlight application using Jounce Template, it already created the interface for data layer. I removed the default string variable to List of Person, like the following in the IMainViewModel.cs

using System.Collections.Generic;
namespace FilteringIssueOnCellTemplate.Common
{
    public interface IMainViewModel
    {
        List<Person> People { get; }
    }
}

Just a refresher here, if you look at the solution, we are after ‘DesignMainViewModel.cs’ under SampleData. This particular class will be used for design time data. We will see later how it is hooked up for design time data. For now, lets change this class to return bunch of dummy data which covers our requirement.

using System;
using FilteringIssueOnCellTemplate.Common;
using System.Collections.Generic;

namespace FilteringIssueOnCellTemplate.SampleData
{
    public class DesignMainViewModel : IMainViewModel
    {
        public List<Person> People
        {
            get
            {
                List<Person> _ppl = new List<Person>();
                _ppl.Add(new Person() { Age = 20, Name = "A", Salary = 10000, Sex = 'F', IsEnabled = true });
                _ppl.Add(new Person() { Age = 20, Name = "B", Salary = 10000, Sex = 'M', IsEnabled = false });
                _ppl.Add(new Person() { Age = 20, Name = "C", Salary = 10000, Sex = 'F', IsEnabled = false });
                _ppl.Add(new Person() { Age = 20, Name = "D", Salary = 10000, Sex = 'F', IsEnabled = true });
                _ppl.Add(new Person() { Age = 20, Name = "E", Salary = 10000, Sex = 'M', IsEnabled = false });
                _ppl.Add(new Person() { Age = 20, Name = "F", Salary = 10000, Sex = 'M', IsEnabled = true });
                return _ppl;
            }
        }
    }
}

As you can see, I have few of the records set to IsEnabled to True so we can see if the XAML I wrote really works.

<Grid x:Name="LayoutRoot" Background="White"
          d:DataContext="{d:DesignInstance sampleData:DesignMainViewModel, IsDesignTimeCreatable=True}">
</Grid>

By default the layout grid looks like this. If you notice, the second line, where d:DataContext is set to our sample view model. So Lets put our data grid control in there and see how it looks. So I am going to add my grid and bind it to Person collection ‘People’ like the following

<c1:C1FlexGrid ItemsSource="{Binding People}"/>

Now if you see the design screen after adding the line would like the following

image 

So we need to change the column order and display only required fields. So I modify the XAML like the following

<c1:C1FlexGrid ItemsSource="{Binding People}">
            <c1:C1FlexGrid.Columns>
                <c1:Column Binding="{Binding Name, Mode=TwoWay}"/>
                <c1:Column Binding="{Binding Age, Mode=TwoWay}"/>
                <c1:Column Binding="{Binding Salary, Mode=TwoWay}"/>
                <c1:Column Binding="{Binding Sex, Mode=TwoWay}"/>
            </c1:C1FlexGrid.Columns>
        </c1:C1FlexGrid>

If you are like the normal developer here you would compile and run to see how it looks, I say ‘stop’ and look at the design panel

image 

You might have noticed, Name field appears twice so we have a bug in the XAML.  It is easy, by default, data grid AutoGenerateColumn is True so we change it to False. Now after adding and look at the design Panel, the duplicate column is gone.

image

Now we are going to tweak the XAML to add additional requirements. lets look at the simple ones, Need to change the formatting of the column to currency and right aligned. Modify the salary column to the following

<c1:Column Binding="{Binding Salary, Mode=TwoWay, StringFormat=c}" HorizontalAlignment="Right"/>

 

The interesting part here is that, as you change you can see design panel changes. With the above changes, now you got the gird to look almost close to what you want except the back ground color.

image

Now we need to set the back ground color of the salary, you can not do dynamic binding to back ground for a column, only way you can do that is through the cell template.

Before I write cell template, I am going to write a converter which will check and see if IsEnabled is set to true or false like the following

public class ColorConverterForRowType : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            bool item = (bool)value;

            if (item == false)
                return new SolidColorBrush(Color.FromArgb(255, 0xde, 0xe2, 0xe6));

            return new SolidColorBrush(Colors.Red);
        }

        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            return null;
        }
    }

Now add the cell template in the XAML as follows

 

<c1:Column Binding="{Binding Salary, Mode=TwoWay, StringFormat=c}" HorizontalAlignment="Right">
                    <c1:Column.CellTemplate>
                        <DataTemplate>
                            <Grid>
                                <Border Background="{Binding IsEnabled, Converter={StaticResource ColorConverterForRowType}}"/>
                                <TextBlock Text="{Binding Salary, Mode=TwoWay, StringFormat=c}" HorizontalAlignment="Right"/>
                            </Grid>
                        </DataTemplate>
                    </c1:Column.CellTemplate>
                </c1:Column>

Now just compile the code and see what is in the panel

image

Look at it, we did not run once and we got the look and feel the initial requirement. It is very easy and avoid all those multi runs to see if the screen looks like we need.

Playing with Read Only in Data Grid

Once in a while you run into a situation where you have a DataGird, where you need make either the whole Grid read only, or a row based on some specific condition or a column based on some specific condition or a specific cell read only. So in this blog, I thought I document it here;

1. Read Only Data Grid:

This is very easy. There is a property in the grid, ‘IsReadOnly’ that you can use to set it to true or false. By setting true you are telling that the grid is read only. Following code shows how to make a C1FlexGrid read only.

<c1:C1FlexGrid Name="_grid" IsReadOnly="True"

 

2. Columns Read only:

Couple of ways you can go about to set columns read only. If you know before hand which columns are read only, you can either set ‘IsReadOnly’ property of the column to true. You can also set the Binding Mode to One Way or leave out the attribute from the column definition then the column is not editable.

<c1:C1FlexGrid.Columns>
                <c1:Column Binding="{Binding Name, Mode=OneWay}" x:Name="Name1"/>
                <c1:Column Binding="{Binding Age}" x:Name="Age1"/>
            </c1:C1FlexGrid.Columns>

 

3. Read only rows:

This is also simple, in most of the cases you want to make a particular row as read only based on a particular condition. Make sure you have columns set properly to two ways if you want some rows editable and some rows not editable.

<c1:C1FlexGrid Name="_grid" ItemsSource="{Binding People}" AutoGenerateColumns="False" BeginningEdit="C1FlexGrid_BeginningEdit" Style="{StaticResource teststyle}">
            <c1:C1FlexGrid.Columns>
                <c1:Column Binding="{Binding Name, Mode=TwoWay}" x:Name="Name1"/>
                <c1:Column Binding="{Binding Age, Mode=TwoWay}" x:Name="Age1"/>
            </c1:C1FlexGrid.Columns>

In the above code, if you would see there are two columns, name and age. Both the fields are set to be editable, because we have the mode set to two way. In the grid definition, if you would see there is an even for ‘BeginingEdit’. This event will be fired when you enter into edit mode regardless of any row or cell. Lets look at the code behind to see the implementation

private void C1FlexGrid_BeginningEdit(object sender, C1.Silverlight.FlexGrid.CellEditEventArgs e)
        {
            Person person = _grid.SelectedItem as Person;
            e.Cancel = person.IsReadOnly;
        }

What I am doing in the first line is to identify person object based on the row selected. you enable edit or disable edit by setting e.Cancel.  That’s about it.

 

4. Read only cells:

In some situation you may want to set a particular cell read only. This is little tricky. If you are using Telerik then you have a property called ‘IsReadOnlyBinding’ where you can use your binding and converter. On the other hand, normal data grid and in Component One, you have to use cell template to achieve this. So you will have cell template to show the data and cell edit template to make cell available to edit. In the cell edit template, when using ‘TextBox’, it has read only property that can be bound to a variable or set using converter.

<c1:C1FlexGrid.Columns>
                <c1:Column Binding="{Binding Name, Mode=TwoWay}" x:Name="Name1">                    
                    <c1:Column.CellEditingTemplate>
                        <DataTemplate>
                            <Grid>
                                <TextBox IsReadOnly="{Binding IsReadOnly}" Text="{Binding Name, Mode=TwoWay}"/>
                            </Grid>
                        </DataTemplate>
                    </c1:Column.CellEditingTemplate>
                </c1:Column>
                <c1:Column Binding="{Binding Age, Mode=TwoWay}" x:Name="Age1"/>
            </c1:C1FlexGrid.Columns>

In the above example, I am directly binding the read only property. But if want to do complex logic, you could use Converter in the binding. I really hope, Component One will give us some property like Telerik does without us having to write cell editing template. One thing I do not like in cell edit template is that, even though we mark the cell as read only, when you double click on the cell, it will enter in edit mode but will not allow you to edit. That is not the same behavior us, marking a column read only. When you set a column read only, it will not let you double click on that column period.