Dynamically setting column names on an editable grid (Babar Ismail)

Dynamically setting column names on an editable grid (Babar Ismail)

Rate This
  • Comments 15

LightSwitch gives us the ability to import data from numerous sources. This data can then be shown to the end user in an editable grid by simply creating editable grid screens based off that data.

The column names of the grid will correspond to the field names of the table we import. This works for most cases but in certain situations we need these column names to be dynamic. For example, consider an application for the Top 5 cars of the year with database designed as follows:

CarRating

AttributeName

Car1

Car2

Car3

Car4

Car5

Braking

Good

Very Good

Good

Good

Good

Acceleration

Excellent

Fair

Very Good

Very Good

Good

Top Speed

Excellent

Excellent

Very Good

Good

Fair

Handling

Very Good

Excellent

Fair

Fair

Good

Car

Name

Ranking

BMW

5

Mercedes

4

Porsche

3

Aston Martin

2

Ferrari

1

Now we want to show the Car Rating table to the user but instead of “Car1”, Car2”, etc. in our column names we want to read the Cars table, get the top 5 cars and replace the column names with the names we get from the table.

In order to do that we start off with creating the 2 tables above: CarRating and Car. CarRating will have string fields called AttributeName, Car1, Car2, Car3, Car4, Car5 and the table “Car” will have a string field called Name and an Integer Field called Ranking.

Here is the snapshot of what the tables looks like in LightSwitch:

image

image

Next we create an editable grid screen for CarRating. So right click the screens node from the Solution Explorer and click Add Screen. Select the Editable Grid Template and choose CarRatings as your Screen Data and name your screen CarRatingsGridScreen.

Now from the Solution Explorer, right click the CarRatingsGridScreen and select View Screen Code.

image

This will bring you to the code editor. Replace all the code with the following (make sure the namespace name is consistent with the rest of the user code files):

VB

Imports System.Collections.Generic
Imports System.Linq
Imports Microsoft.LightSwitch
Imports Microsoft.LightSwitch.Presentation
Imports Microsoft.LightSwitch.Presentation.Extensions
Imports System.Windows.Controls
Imports Microsoft.LightSwitch.Threading
Namespace LightSwitchApplication
    Partial Public Class CarRatingsGridScreen

        Private Sub CarRatingsGridScreen_Created()
            AddHandler Me.FindControl("grid").ControlAvailable, AddressOf CarRatingsGrid_ControlAvailable
        End Sub

        Private Sub CarRatingsGrid_ControlAvailable(sender As Object, e As ControlAvailableEventArgs)
            UpdateColumnNames(DirectCast(e.Control, DataGrid))
        End Sub

        Private Sub UpdateColumnNames(carRatingsGrid As DataGrid)
            Me.Details.Dispatcher.BeginInvoke(
                Sub()

                    Dim top5Cars = Me.DataWorkspace.ApplicationData.Cars.OrderBy(Function(car) car.Ranking).Take(5).Execute()

                    Dim carNames As String() = top5Cars.[Select](Function(car) car.Name).ToArray()

                    Dispatchers.Main.BeginInvoke(
                        Sub()
                            Dim rowTemplate As IContentItem = DirectCast(carRatingsGrid.DataContext, IContentItem).ChildItems(0)

                            For index As Integer = 0 To carNames.Length - 1
                                'start with the second column of the grid
                                rowTemplate.ChildItems(index + 1).DisplayName = carNames(index)
                            Next

                        End Sub)

                End Sub)
        End Sub
    End Class
End Namespace

C#

using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.LightSwitch;
using Microsoft.LightSwitch.Presentation;
using Microsoft.LightSwitch.Presentation.Extensions;
using System.Windows.Controls;
using Microsoft.LightSwitch.Threading;
namespace LightSwitchApplication
{
    public partial class CarRatingsGridScreen
    {
        partial void CarRatingsGridScreen_Created()
        {
            this.FindControl("grid").ControlAvailable += CarRatingsGrid_ControlAvailable;
        }

        void CarRatingsGrid_ControlAvailable(object sender, ControlAvailableEventArgs e)
        {            
            UpdateColumnNames((DataGrid)e.Control);
        }

        private void UpdateColumnNames(DataGrid carRatingsGrid)
        {
            this.Details.Dispatcher.BeginInvoke(() =>
            {                

                var top5Cars = this.DataWorkspace.ApplicationData.Cars.OrderBy(car => car.Ranking).Take(5).Execute();
                
                string[] carNames = top5Cars.Select(car => car.Name).ToArray();

                Dispatchers.Main.BeginInvoke(() =>
                {
                    IContentItem rowTemplate = ((IContentItem)carRatingsGrid.DataContext).ChildItems[0];
                                        
                    for (int index = 0; index < carNames.Length; index++)
                    {
                        //start with the second column of the grid
                        rowTemplate.ChildItems[index + 1].DisplayName = carNames[index];
                    }
                });
            });
        }
    }
}
 

In the code above, the first thing we are doing is registering CarRatingsGrid_ControlAvailable event handler with the ControlAvailable event of our DataGrid. This is happening in the CarRatingsGridScreen_Created method.

When the DataGrid on the screen shows up, our CarRatingsGrid_ControlAvailable method will fire. This method has an argument of type ControlAvailableEventArgs from which we can get the actual Silverlight control.

Next we pass in the control to the UpdateColumnNames method from CarRatingsGrid_ControlAvailable. Since the method will be executed on the Main thread, we go to the logical thread to get the top 5 car names and store them in an array (we cannot access data from the DataWorkspace on the Main thread). Then we go back to the Main thread and set the column names on the grid.

Now in order to verify that it works, you will need to add a few “Car” records. So create an editable grid screen for the Car table and add a few records. Now open a new instance of the CarRatingsGridScreen. It should have the updated column names.

Here is a screenshot of how the screens should look like at runtime:

image

 

image

Although this post is about setting the column names dynamically, it can be extended into any sort of UI manipulation. For example, we can get a handle to a textbox control in a list and details screen and set its background to yellow based on a computation or we can change the font color of a data grid cell. The same concept applies – get a handle to the underlying Silverlight control and change any property you want on the UI thread.

Happy Coding!!!

Leave a Comment
  • Please add 8 and 1 and type the answer here:
  • Post
  • This was really helpfull... thank you :)

  • Hi Babar

    Nice post again.

    But I have one doubt here. In LS V1 if we use FindControl within InitializeDataWorkspace method then it's randomly showing an error called "Screen <ScreenName> is not loaded yet. Is this bug resolve in LS V2 ?

  • Hi

    Here is the forum thread for the error

    social.msdn.microsoft.com/.../3b7fba8f-5b5d-4106-9d35-9290d3adfd9d

  • thanks !!!

    www.blogomegamartvn.blogspot.com

    http://www.omegamart.vn

  • In the CarRatingsGridScreen_InitializeDataWorkspace method, you use FindControl to subscribe to the ControlAvailable event, and then immediately call UpdateColumnNames() from the same method.

    If the grid isn't available when the CarRatingsGridScreen_InitializeDataWorkspace method fires (which is quite possible), then you'll get the InvalidOperationException thrown.

    Surely you should call UpdateColumnNames from CarRatingsGrid_ControlAvailable, because then you know the grid will be available (that's the point of the method after all). That way you're pretty much guaranteed not to get the exception.

    Don't know if this helps you Babloo1436?

  • @Yossu

    Thank you for your answer.

    It helps me to understand correctly.

    Thanks

  • I haven't encountered this issue in V2 so far although I have used code quite similar to this extensively in an app I have been working on.

    If you are encountering such issues then you can move it Screen_Created() method.

    Thanks,

    Babar

  • i have updated the blog post with new code. Our findcontrol method is now being called in Screen_Created method and UpdateColumnNames also has a little different implementation.

    Babloo... let me know if the new code works for you.

    Mr Yossu.. I have also moved the call to UpdateColumnNames method into the ControlAvailable event handler. Hope this solves the problem.

    Thanks,
    Babar

  • Hi Babar,

    Thanks for the blog post, but I really wish that such explanations were based on something a little more "real world" than a table with properties like Car1, Car2, Car3 etc. That's an example of an really badly designed table. If someone who's new to programming sees properties like this in an example, they're likely to think it's ok to create their tables the same way, because they see a member of the LS team doing it.

    I spent years trying to "fix" tables like that in Access for clients, because the people who created them didn't know any better.

    I'm also glad to see that you ammended your code to not be trying to access a control before the screen has been "created". Yes, you may get away with it some of the time (or even most of the time), but it's the times when it doesn't work that will have people baffled.

    This article would be much more useful if it described a "real" scenario, with well-designed tables.

    I'm sorry if I sound negative, I do appreciate members of the team taking the time to write these posts, but I also hope you understand what I'm getting at.

  • @Babar

    I really appreciate that you updated the code sample.

    Yes. The Updated code is working fine. And ofcource this is really a very useful post.

    Thank You Babar

    Thanks LightSwitch Team

  • Thanks Yann for your comment.. The intention of this post was to explain how we can achieve a certain functionality in LightSwitch. I used this example to simplify things and not to suggest it is ok to design databases like that. But I understand where you're coming from and is definitely something to keep in mind.

    Thanks,

    Babar

  • Thanks Babar!

  • This was really awesome... thank you :)

  • It would be great if we were able to do this in javascript.

  • I'm not sure what is happening here but I needed t o add a ';' after .Take and one after .ChildItems[0] to get it to compile and even then I got the message:

    'Cannot assign method group to an implicitly-typed local variable' refering to top5Cars

    Couldn't get it to work!

Page 1 of 1 (15 items)