Deborah's Developer MindScape






         Tips and Techniques for Web and .NET developers.

Archive for Silverlight

January 29, 2011

Missing Chart Legend

Filed under: C#,Silverlight,VB.NET @ 7:18 pm

If you built a custom theme for your application (similar to this prior post) then you may find that your chart legend contents disappear.

NOTE: This post is part of a series that starts with this prior post. The example in this post uses the application that is built as part of that series.

The chart built in this prior post looks like this:

image

Notice that the legend (circled in red) is empty.

The problem appears to be a mismatch between the standard theme dll (which displays the legend correctly) and the theme source file provided for customization (which does not display the legend correctly).

To fix the legend:

1)  Find the visualizationToolkit:Legend Style in your theme.

2) Replace the StackPanel in the theme file with an ItemsPresenter.

3) Rebuild the solution.

The legend then magically appears:

image

You can then set other style properties, such as the size and font, to improve the look of the legend.

image

The resulting style is shown below with the modified lines shown in red.

<!–Legend–>
<Style TargetType="visualizationToolkit:Legend">
    <Setter Property="BorderBrush"
     Value="{StaticResource ControlBorderBrush}"/>
    <Setter Property="Background"
     Value="{StaticResource ControlBackgroundBrush}"/>
    <Setter Property="Foreground" Value="Black"/>
    <Setter Property="BorderThickness" Value="1"/>
    <Setter Property="IsTabStop" Value="False"/>
    <Setter Property="TitleStyle">
        <Setter.Value>
            <Style TargetType="visualizationToolkit:Title">
                <Setter Property="Margin" Value="0,5,0,10"/>
                <Setter Property="FontWeight" Value="Bold"/>
                <Setter Property="HorizontalAlignment" Value="Center"/>
            </Style>
        </Setter.Value>
    </Setter>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="visualizationToolkit:Legend">
                <Border
                    Background="{TemplateBinding Background}"
                    BorderBrush="{TemplateBinding BorderBrush}"
                    BorderThickness="{TemplateBinding BorderThickness}"
                    VerticalAlignment="Center"
                    Padding="2">
                    <Grid>
                        <Grid.RowDefinitions>
                            <RowDefinition Height="Auto"/>
                            <RowDefinition/>
                        </Grid.RowDefinitions>
                        <visualizationToolkit:Title
                            Grid.Row="0"
                            Content="{TemplateBinding Header}"
                            Style="{TemplateBinding TitleStyle}"/>
                        <ScrollViewer
                            Grid.Row="1"
                            VerticalScrollBarVisibility="Auto"
                            BorderThickness="0"
                            Padding="0"
                            IsTabStop="False">
                            <ItemsPresenter
                                x:Name="LegendItemsArea"
                                Margin="10,0,10,10" />
                        </ScrollViewer>
                    </Grid>
                </Border>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

If you use the same custom theme for your entire application, you will only need to do this one time. If you use multiple custom themes, you will need to repeat this process for each theme file.

Enjoy!

Charting in a Silverlight Application using MVVM

Filed under: C#,Silverlight,VB.NET @ 6:22 pm

Data visualization is often a requirement in today’s line of business (LOB) applications. Plus charts are just more fun to look at than data grids. Silverlight provides many different types of charts you can use to display the data in your LOB application.

NOTE: This post is part of a series that starts with this prior post. The example in this post uses the application that is built as part of that series.

As discussed in this prior post, using Model-View-View/Model (MVVM) comes with many benefits. One of those benefits is an easy way to define design-time data, which is a real plus when trying to design and style your charts.

Step 1: Drag and drop a Chart from the toolbox onto the desired page.

If you are working through the set of blog posts, add a new Page called StudentChart and add the chart to that page. (Be sure to add a link from the main page to this page using the technique from this prior post.) Otherwise, add a Chart to any page.

The chart immediately appears in the designer like this:

image

Two things to notice about the chart:

1) The chart has data. The designer automatically hard-codes data points into the xaml so you can see the chart. You will have to delete these points to hook up your actual data. But as soon as you delete the points, you no longer have design time data. More on this issue later in this post.

2) The legend may appear as a blank rectangle on the right instead of an actual legend. This will occur if you followed this prior post and defined a custom theme for your application. The custom theme has a bug which prevents it from correctly displaying the legend. More on this in a different post.

Step 2: Build/generate the business objects that contain the data for the chart. This step requires a fundamental decision on how you will get data into your application. The primary choices are:

  • Build an entity class in the Silverlight application that is populated from a WCF service.
  • Generate an entity class in the Silverlight application using WCF RIA services and Entity Framework.
  • Generate an entity class in the Silverlight application using WCF RIA services and your business objects (sometimes called Plain Old CLR Objects or POCOs). See this prior post for more information on using WCF RIA with your business objects.

The details of these approaches is beyond the scope of this post, so this example uses an entity class in the Silverlight application that is populated from code to keep things simple.

If you are working through the set of blog posts, you already built the model in this prior post. If not, the model code is shown here:

In C#:

using System;

namespace InStepSM.SL.Models
{
    public class Student
    {
        public String StudentName { get; set; }
        public int StudentId { get; set; }
        public decimal Project1Score { get; set; }
        public decimal Project2Score { get; set; }
        public decimal Project3Score { get; set; }
    }
}

In VB:

Namespace Models
    Public Class Student
        Public Property StudentName As String
        Public Property StudentId As Integer
        Public Property Project1Score As Decimal
        Public Property Project2Score As Decimal
        Public Property Project3Score As Decimal
    End Class
End Namespace

NOTE: The VB code Namespace includes only "Models" because VB automatically prepends the application namespace to any defined namespace in the application. The result is InStepSM.SL.Models in this case, just like the C# code.

This class includes a student’s name, Id, and three scores.

Step 3: Build the View/Model class, exposing properties to bind to the View.

Add a StudentChartViewModel class to your application. Be sure to implement INotifyPropertyChanged. This ensures that the UI is notified when properties of the View/Model are changed.

NOTE: INotifyPropertyChanged requires the System.ComponentModel namespace.

Add a property that contains the list of students. The View will bind to this property. You can define the property to be a List, but if you instead use an ObservableCollection, changes to the list will be reflected in the user interface.

NOTE: ObservableCollection requires the System.Collections.ObjectModel namespace.

Also add a property that contains the score titles. These will appear in the legend for each score.

In C#:

using System.Collections.ObjectModel;
using System.ComponentModel;
using InStepSM.SL.Models;

namespace InStepSM.SL.ViewModels
{
    public class StudentChartViewModel: INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        private ObservableCollection<Student> _studentList;
        public ObservableCollection<Student> StudentList
        {
            get
            {
                return _studentList;
            }
            set
            {
                if (_studentList != value)
                {
                    _studentList = value;
                    OnPropertyChanged("StudentList");
                }
            }
        }

  private List<string> _titleList;
  public List<string> TitleList
  {
      get
      {
        return _titleList;
      }
      set
      {
          if (_titleList != value)
          {
            _titleList = value;
            OnPropertyChanged("TitleList");
          }
      }
  }

        public StudentChartViewModel()
        {
            PopulateStudents();
        }

        private void OnPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)        
                PropertyChanged(this,
                    new PropertyChangedEventArgs(propertyName));
        }

        public void PopulateStudents()
        {
            var itemList = new ObservableCollection<Student>()
                    {new Student(){StudentName="Frodo Baggins",
                                    Project1Score = 89M,
                                    Project2Score=93M,
                                    Project3Score=88M},
                    new Student(){StudentName="Rosie Cotton",
                                    Project1Score = 97M,
                                    Project2Score=93M,
                                    Project3Score=94M},
                    new Student(){StudentName="Samwise Gamgee",
                                    Project1Score = 83M,
                                    Project2Score=90M,
                                    Project3Score=85M},
                    new Student(){StudentName="Peregrin Took",
                                    Project1Score = 69M,
                                    Project2Score=72M,
                                    Project3Score=75M}};
            StudentList = itemList; 

       List<string> itemNameList = new List<string>
                       {"PreTest", "Chp 1", "Test"};
       TitleList = itemNameList;
   }

    }
}

In VB:

Imports System.Collections.ObjectModel
Imports System.ComponentModel
Imports InStepSM.SL.Models

Namespace ViewModels
    Public Class StudentChartViewModel
        Implements INotifyPropertyChanged

        Public Event PropertyChanged(ByVal sender As Object,  
         ByVal e As System.ComponentModel.PropertyChangedEventArgs)  _
Implements System.ComponentModel.INotifyPropertyChanged.PropertyChanged

        Private _studentList As ObservableCollection(Of Student)
        Public Property StudentList As ObservableCollection(Of Student)
            Get
                Return _studentList
            End Get
            Set(ByVal value As ObservableCollection(Of Student))
                If _studentList IsNot value Then
                    _studentList = value
                    OnPropertyChanged("StudentList")
                End If
            End Set
        End Property

        Private _titleList As List(Of String)
        Public Property TitleList As List(Of String)
            Get
                Return _titleList
            End Get
            Set(ByVal value As List(Of String))
                If _titleList IsNot value Then
                    _titleList= value
                    OnPropertyChanged("TitleList")
                End If
            End Set
        End Property

        Public Sub New()
            PopulateStudents()
        End Sub

        Protected Sub OnPropertyChanged(ByVal propertyName As String)
            If Not String.IsNullOrEmpty(propertyName) Then
                RaiseEvent PropertyChanged(Me,
                          New PropertyChangedEventArgs(propertyName))
            End If
        End Sub

        Public Sub PopulateStudents()
            Dim itemList = New ObservableCollection(Of Student)() From
                 {New Student() With {.StudentName = "Frodo Baggins",
                                    .Project1Score = 89D,
                                    .Project2Score = 93D,
                                    .Project3Score = 88D},
                 New Student() With {.StudentName = "Rosie Cotton",
                                    .Project1Score = 97D,
                                    .Project2Score = 93D,
                                    .Project3Score = 94D},
                 New Student() With {.StudentName = "Samwise Gamgee",
                                    .Project1Score = 83D,
                                    .Project2Score = 90D,
                                    .Project3Score = 85D},
                 New Student() With {.StudentName = "Peregrin Took",
                                    .Project1Score = 69D,
                                    .Project2Score = 72D,
                                    .Project3Score = 75D}}
            StudentList = itemList

            Dim itemNameList = New List(Of String)(
                New String() {"PreTest", "Chp 1", "Test" })
            TitleList = itemNameList

        End Sub

    End Class
End Namespace

Step 4: Hook the View to the View/Model. This example defines the instance of the View/Model in the xaml so that the xaml has access to sample data. The code is shown in red below.

<navigation:Page x:Class="InStepSM.SL.Views.StudentChart"
    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:navigation=
       "clr-namespace:System.Windows.Controls;assembly=
       System.Windows.Controls.Navigation"
    xmlns:vms ="clr-namespace:InStepSM.SL.ViewModels"
    d:DesignWidth="640" d:DesignHeight="480"
    xmlns:toolkit=
  "
http://schemas.microsoft.com/winfx/2006/xaml/presentation/toolkit"
    Title="StudentChart Page">

    <!– Reference the View/Model –>
    <navigation:Page.DataContext>
        <vms:StudentChartViewModel/>
    </navigation:Page.DataContext>

</navigation:Page>

Step 5: Bind the Chart to View/Model properties.

Set the chart’s ItemSource to the StudentList property. Then set the IndependentValuePath to the student’s name and the DependentValuePath to the desired score.

To set the title of each bar in the chart in the legend, bind the Title property to the desired TitleList element. (The ability to bind to an element in a List or Array was added in Silverlight 3.)

Notice that as soon as you set the binding properties, you will again see sample data in the chart in design mode. This is the data from the View/Model.

To add all three scores, add multiple columnSeries elements to the xaml. The resulting xaml looks like this:

<navigation:Page x:Class="InStepSM.SL.Views.StudentChart"
    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:navigation="clr-namespace:System.Windows.Controls;assembly=
         System.Windows.Controls.Navigation"
    xmlns:vms ="clr-namespace:InStepSM.SL.ViewModels"
    d:DesignWidth="640" d:DesignHeight="480"
    xmlns:toolkit=       "
http://schemas.microsoft.com/winfx/2006/xaml/presentation/toolkit"
    Title="StudentChart Page">

    <!– Reference the View/Model –>
    <navigation:Page.DataContext>
        <vms:StudentChartViewModel/>
    </navigation:Page.DataContext>
   
    <Grid x:Name="LayoutRoot">
       
<toolkit:Chart Name="chart1"
               Title="Scores"
               VerticalAlignment="Stretch"
               HorizontalAlignment="Stretch">
        <toolkit:ColumnSeries DependentValuePath="Project1Score"
                    IndependentValuePath="StudentName"
                    ItemsSource="{Binding StudentList}"
                    Title="{Binding TitleList[0]}"/>
        <toolkit:ColumnSeries DependentValuePath="Project2Score" 
                    IndependentValuePath="StudentName"
                    ItemsSource="{Binding StudentList}"
                    Title="{Binding TitleList[1]}"/>
        <toolkit:ColumnSeries DependentValuePath="Project3Score"
                    IndependentValuePath="StudentName"
                    ItemsSource="{Binding StudentList}"
                    Title="{Binding TitleList[2]}"/>
        </toolkit:Chart>
    </Grid>
</navigation:Page>

And the chart looks like this:

image

Notice that the legend is still blank. That will be fixed in a later post.

Use the techniques described here any time you want to build a chart and still follow the MVVM design pattern.

Enjoy!

January 23, 2011

Populating a DataGrid with Dynamic Columns in a Silverlight Application using MVVM

Filed under: C#,Silverlight,VB.NET @ 7:52 pm

When binding to a DataGrid, in most cases you have a fixed number of columns and a variable number of rows. This post covers the case when you have a variable number of rows AND a variable number of columns.

NOTE: This post is part of a series that starts with this prior post. The example in this post uses the application that is built as part of that series.

For more general information on populating a DataGrid in Silverlight, see these prior posts:

The example is a Student Management application. Each student has an Id and name and a set of scores. When building the application, you don’t want to hard-code a predefined number of scores. Rather, you want to allow the users to define any number of scores. This makes it more challenging to then bind the data to a DataGrid.

Start by adding a DataGrid to a page. If you are working through the series of posts, you can use the DataGrid you added from the prior post in this series. (See the links above.)

Add a Models folder to your Silverlight project  and add a Student class to that folder. Or if you are working through the series, modify the existing Student class.

In C#:

using System;
using System.Collections.Generic;

namespace InStepSM.SL.Models
{
    public class Student
    {
        public String StudentName { get; set; }
        public int StudentId { get; set; }
        public List<decimal> ProjectScores { get; set; }
    }
}

In VB:

Namespace Models
    Public Class Student
        Public Property StudentName As String
        Public Property StudentId As Integer
        Public Property ProjectScores As List(Of Decimal)
    End Class
End Namespace

NOTE: The VB code Namespace includes only "Models" because VB automatically prepends the application namespace to any defined namespace in the application. The result is InStepSM.SL.Models in this case, just like the C# code.

This class includes a student’s name, Id, and any number of scores.

Since this example follows an MVVM approach, add a ViewModels folder to your Silverlight project and add a StudentViewModel class to that folder. Or if you are working through the series, modify the existing StudentViewModel class.

In C#:

using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using InStepSM.SL.Models;

namespace InStepSM.SL.ViewModels
{
    public class StudentViewModel: INotifyPropertyChanged

    {
        public event PropertyChangedEventHandler PropertyChanged;

        private ObservableCollection<Student> _studentList;
        public ObservableCollection<Student> StudentList
        {
            get
            {
                return _studentList;
            }
            set
            {
                if (_studentList != value)
                {
                    _studentList = value;
                    OnPropertyChanged("StudentList");
                }
            }
        }

        private List<string> _titleList;
        public List<string> TitleList
        {
            get
            {
                return _titleList;
            }
            set
            {
                if (_titleList != value)
                {
                    _titleList = value;
                    OnPropertyChanged("TitleList");
                }
            }
        }
       
        public StudentViewModel()
        {
            PopulateStudents();
        }

        private void OnPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)        
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }

        public void PopulateStudents()
        {
            var itemList = new ObservableCollection<Student>()
                    {new Student(){StudentName="Frodo Baggins",
                                    ProjectScores=new List<decimal>() {
                                        89M,93M,88M}},
                    new Student(){StudentName="Rosie Cotton",
                                    ProjectScores=new List<decimal>() {
                                        97M,93M,94M}},
                    new Student(){StudentName="Samwise Gamgee",
                                    ProjectScores=new List<decimal>() {
                                        83M,90M,85M}},
                    new Student(){StudentName="Peregrin Took",
                                    ProjectScores=new List<decimal>() {
                                        69M,72M,75M}}};
            StudentList = itemList;

            var itemNameList = new List<string>()
                      { "PreTest", "Chp 1", "Test" };
            TitleList = itemNameList;
        }

    }
}

In VB:

Imports System.Collections.ObjectModel
Imports System.ComponentModel
Imports InStepSM.SL.Models

Namespace ViewModels
    Public Class StudentViewModel
        Implements INotifyPropertyChanged

        Public Event PropertyChanged(ByVal sender As Object, 
          ByVal e As System.ComponentModel.PropertyChangedEventArgs)  _
Implements System.ComponentModel.INotifyPropertyChanged.PropertyChanged

        Private _studentList As ObservableCollection(Of Student)
        Public Property StudentList As ObservableCollection(Of Student)
            Get
                Return _studentList
            End Get
            Set(ByVal value As ObservableCollection(Of Student))
                If _studentList IsNot value Then
                    _studentList = value
                    OnPropertyChanged("StudentList")
                End If
            End Set
        End Property

        Private _titleList As List(Of String)
        Public Property TitleList As List(Of String)
            Get
                Return _titleList
            End Get
            Set(ByVal value As List(Of String))
                If _titleList IsNot value Then
                    _titleList = value
                    OnPropertyChanged("TitleList")
                End If
            End Set
        End Property

        Public Sub New()
            PopulateStudents()
        End Sub

        Protected Sub OnPropertyChanged(ByVal propertyName As String)
            If Not String.IsNullOrEmpty(propertyName) Then
                RaiseEvent PropertyChanged(Me,
                          New PropertyChangedEventArgs(propertyName))
            End If
        End Sub

        Public Sub PopulateStudents()
         Dim itemList = New ObservableCollection(Of Student)() From
            {New Student() With {.StudentName = "Frodo Baggins",
                            .ProjectScores = New List(Of Decimal)(
                            New Decimal() {89D, 93D, 88D})},
            New Student() With {.StudentName = "Rosie Cotton",
                        .ProjectScores = New List(Of Decimal)(
                            New Decimal() {97D, 93D, 94D})},
            New Student() With {.StudentName = "Samwise Gamgee",
                        .ProjectScores = New List(Of Decimal)(
                            New Decimal() {83D, 90D, 85D})},
            New Student() With {.StudentName = "Peregrin Took",
                        .ProjectScores = New List(Of Decimal)(
                            New Decimal() {69D, 72D, 75D})}}
           StudentList = itemList

           Dim itemNameList = New List(Of String)(
                New String() {"PreTest", "Chp 1", "Test" })
           TitleList = itemNameList

        End Sub
    End Class
End Namespace

This code defines two properties: one to hold the list of students, the other to hold the list of score titles. The list of score titles are used as the headers for the dynamic columns.

Hook the View to the View/Model in the xaml. Then modify the DataGrid xaml code to bind to both the list of score titles and the list of students.

<navigation:Page
  x:Class="InStepSM.SL.Views.Overview" 
  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:navigation="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Navigation"
  xmlns:vms ="clr-namespace:InStepSM.SL.ViewModels"
  xmlns:sdk="
http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk"
  xmlns:primitives="clr-namespace:System.Windows.Controls.Primitives;assembly=System.Windows.Controls.Data"
  d:DesignWidth="640" d:DesignHeight="480"
  Title="Overview Page" >

    <!– Reference the View/Model –>
    <navigation:Page.DataContext>
        <vms:StudentViewModel/>
    </navigation:Page.DataContext>
   
    <Grid x:Name="LayoutRoot">
        <ScrollViewer x:Name="PageScrollViewer"
                  Style="{StaticResource PageScrollViewerStyle}">

            <StackPanel x:Name="ContentStackPanel">

                <TextBlock x:Name="HeaderText"
                   Style="{StaticResource HeaderTextStyle}"
                                   Text="Student Overview"/>
                <sdk:DataGrid AutoGenerateColumns="False"
                      ItemsSource="{Binding StudentList}">
                    <sdk:DataGrid.Columns>
                        <sdk:DataGridTextColumn
                             Binding="{Binding StudentName}" 
                             Header="Name"/>

                        <sdk:DataGridTemplateColumn Width="*">
                            <sdk:DataGridTemplateColumn.HeaderStyle>
                                <Style
                          TargetType="primitives:DataGridColumnHeader">
                                    <Setter
                              Property="HorizontalContentAlignment"
                              Value="Stretch" />
                                    <Setter
                              Property="VerticalContentAlignment" 
                              Value="Stretch" />
                                    <Setter Property="Margin"
                                         Value="0" />
                                    <Setter Property="ContentTemplate">
                                        <Setter.Value>
                                            <DataTemplate>
                                  <ItemsControl 
                          ItemsSource="{Binding DataContext.TitleList,
                                     ElementName=LayoutRoot
}">
                                   <ItemsControl.ItemsPanel>
                                      <ItemsPanelTemplate>
                                        <StackPanel
                                         Orientation="Horizontal">
                                        </StackPanel>
                                      </ItemsPanelTemplate>
                                    </ItemsControl.ItemsPanel>
                                    <ItemsControl.ItemTemplate>
                                      <DataTemplate>
                                       <Border  Width="70" >
                                         <TextBlock Text="{Binding}"
                                              TextAlignment="Center"/>
                                       </Border>
                                      </DataTemplate>
                                    </ItemsControl.ItemTemplate>
                                                </ItemsControl>
                                            </DataTemplate>
                                        </Setter.Value>
                                    </Setter>
                                </Style>
                            </sdk:DataGridTemplateColumn.HeaderStyle>
                            <sdk:DataGridTemplateColumn.CellTemplate>
                                <DataTemplate>
                              <ItemsControl 
                                ItemsSource="{Binding ProjectScores}">
                                <ItemsControl.ItemsPanel>
                                 <ItemsPanelTemplate>
                                 <StackPanel Orientation="Horizontal"/>
                                 </ItemsPanelTemplate>
                                 </ItemsControl.ItemsPanel>
                                 <ItemsControl.ItemTemplate>
                                    <DataTemplate>
                                      <Border Width="70">
                                       <TextBlock Text="{Binding}"
                                            TextAlignment="Center"/>
                                      </Border>
                                     </DataTemplate>
                                   </ItemsControl.ItemTemplate>
                                    </ItemsControl>
                                </DataTemplate>
                            </sdk:DataGridTemplateColumn.CellTemplate>
                        </sdk:DataGridTemplateColumn>
                    </sdk:DataGrid.Columns>
                </sdk:DataGrid>
            </StackPanel>

        </ScrollViewer>
    </Grid>
</navigation:Page>

Yikes that is a lot of xaml!

The DataGrid element ItemSource property is bound to StudentList, the list of students defined in the View/Model. That is as it would be for a "normal" DataGrid (that is bound using MVVM).

The key technique here is that the DataGrid does not use automatically generated columns and instead explicitly defines the columns.

The first column is a DataGridTextColumn and binds to the student’s name.

The remaining columns use a DataGridTemplateColumn.

The HeaderStyle property of the DataGridTemplateColumn is bound to the list of score titles. The ContentTemplate property of the style defines the layout and content of each column title. The ItemsControl ItemsSource property defines the source of the data for the columns, which is the TitleList. Because the DataGrid is already bound to the StudentList, a simple binding here won’t work. The code must specify the original DataContext.TitleList from the root element.

The ItemsControl.ItemsPanel defines how the data is laid out. Since we want the titles spread across the top, the ItemsPanelTemplate uses a horizontal StatckPanel.

The ItemsControl.ItemTemplate defines how each title appears. The column width defined here must match the column width defined for the data in order for the header and the data to appear as a column.

The CellTemplate property of the DataGridTemplateColumn is bound to the list of scores. Since the scores are a part of the StudentList that is bound to the DataGrid, a simple binding works here. The ItemsSource is bound to the ProjectScores property.

Again, the ItemsControl.ItemsPanel defines how the scores are laid out and the ItemControl.ItemTemplate defines how each score appears.

[THANKS to Sally Xu for leading me in the right direction for setting up this binding.]

The final result:

image

Use this technique any time you need to bind data when the number of columns is not known.

Enjoy!

January 22, 2011

Populating a DataGrid in a Silverlight Application using MVVM

Filed under: C#,Silverlight,VB.NET @ 7:04 pm

Model View View/Model (MVVM) is an effective pattern for building Silverlight applications. It provides many useful benefits when building business applications with Silverlight.

NOTE: This post is part of a series that starts with this prior post. The example in this post uses the application that is built as part of that series.

MVVM requires breaking your Silverlight application into three pieces:

1) View: The user interface that the user sees and interacts with.

2) Model: The representation of the data used by the application.

3) View/Model: The code that provides the communication between the view and the model. This code massages the model into a format required by the view and handles events from the view to perform processing and interact with the data from the model.

There are many reasons to use MVVM. Bing "Silverlight MVVM" and you will find many good articles/posts on the merits of MVVM. The key reasons often sited are:

  • Provides a separate of concerns between the visual parts of the application (View), the processing (View/Model), and the data (Model).
  • Allows for testing of the View/Model logic without need to execute a user interface.
  • Provides a mechanism for design-time data.

And another reason that is often not mentioned is my teenager’s favorite: Everyone else is doing it. Why would that matter? Many Silverlight samples now use MVVM and many other developers posting on the forums or blogs now use MVVM. So the more you understand MVVM, the easier it may be to find help.

(And I don’t know about you, but I seem to spend a lot of my development time these days hunting down how-to’s for getting Silverlight to perform some challenging client-request feature. I hope some day soon I will get Silverlight to bow down to my every command … but today is not that day.)

So let’s start with one of the simplest MVVM applications that we can.

This example populates a simple DataGrid and produces the same results as this prior post that uses code-behind instead of MVVM.

The basic steps for populating a DataGrid in a Silverlight application using MVVM are:

1) Drag and drop a DataGrid control from the toolbox onto the desired page.

2) Build/generate the business objects that contains the data for the grid.

3) Build the View/Model class, exposing properties to bind to the View.

4) Hook the View to the View/Model.

5) Bind the DataGrid to a View/Model property.

Step 1 is self explanatory. If you are working through the set of blog posts, add the DataGrid to the Students page. Otherwise, add a DataGrid to any page.

Be sure to set the AutoGenerateColumns property to True so it will automatically show your data.

The Silverlight page is called a "View" because it is the user’s view of the application. It is the part of the application that the user sees and interacts with.

Step 2 requires a fundamental decision on how you will get data into your application. The primary choices are:

  • Build an entity class in the Silverlight application that is populated from a WCF service.
  • Generate an entity class in the Silverlight application using WCF RIA services and Entity Framework.
  • Generate an entity class in the Silverlight application using WCF RIA services and your business objects (sometimes called Plain Old CLR Objects or POCOs). See this prior post for more information on using WCF RIA with your business objects.

The details of these approaches is beyond the scope of this post, so this example builds an entity class in the Silverlight application that is populated from code to keep things simple.

Regardless of the approach, the entity class in your Silverlight application (built or generated) that defines the structure of the data is called the "Model" because it models, or represents, the application’s data.

Add a Models folder to your Silverlight project. In that folder, add a Student class.

In C#:

using System;

namespace InStepSM.SL.Models
{
    public class Student
    {
        public String StudentName { get; set; }
        public int StudentId { get; set; }
        public decimal Project1Score { get; set; }
        public decimal Project2Score { get; set; }
        public decimal Project3Score { get; set; }
    }
}

In VB:

Namespace Models
    Public Class Student
        Public Property StudentName As String
        Public Property StudentId As Integer
        Public Property Project1Score As Decimal
        Public Property Project2Score As Decimal
        Public Property Project3Score As Decimal
    End Class
End Namespace

NOTE: The VB code Namespace includes only "Models" because VB automatically prepends the application namespace to any defined namespace in the application. The result is InStepSM.SL.Models in this case, just like the C# code.

This class includes a student’s name, Id, and three scores.

Step 3 requires building a new class that is the View/Model.

Add a ViewModels folder to your Silverlight project. In that folder, add a StudentViewModel class.

Implement INotifyPropertyChanged in the ViewModel class. That ensures that the UI is notified when properties of the View/Model are changed.

NOTE: INotifyPropertyChanged requires the System.ComponentModel namespace.

Add a property that contains the list of students. The View will bind to this property. You can define the property to be a List, but if you instead use an ObservableCollection, changes to the list will be reflected in the user interface.

NOTE: ObservableCollection requires the System.Collections.ObjectModel namespace.

In C#:

using System.Collections.ObjectModel;
using System.ComponentModel;
using InStepSM.SL.Models;

namespace InStepSM.SL.ViewModels
{
    public class StudentViewModel: INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        private ObservableCollection<Student> _studentList;
        public ObservableCollection<Student> StudentList
        {
            get
            {
                return _studentList;
            }
            set
            {
                if (_studentList != value)
                {
                    _studentList = value;
                    OnPropertyChanged("StudentList");
                }
            }
        }

        public StudentViewModel()
        {
            PopulateStudents();
        }

        private void OnPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)        
                PropertyChanged(this,
                    new PropertyChangedEventArgs(propertyName));
        }

        public void PopulateStudents()
        {
            var itemList = new ObservableCollection<Student>()
                    {new Student(){StudentName="Frodo Baggins",
                                    Project1Score = 89M,
                                    Project2Score=93M,
                                    Project3Score=88M},
                    new Student(){StudentName="Rosie Cotton",
                                    Project1Score = 97M,
                                    Project2Score=93M,
                                    Project3Score=94M},
                    new Student(){StudentName="Samwise Gamgee",
                                    Project1Score = 83M,
                                    Project2Score=90M,
                                    Project3Score=85M},
                    new Student(){StudentName="Peregrin Took",
                                    Project1Score = 69M,
                                    Project2Score=72M,
                                    Project3Score=75M}};
            StudentList = itemList;
        }

    }
}

In VB:

Imports System.Collections.ObjectModel
Imports System.ComponentModel
Imports InStepSM.SL.Models

Namespace ViewModels
    Public Class StudentViewModel
        Implements INotifyPropertyChanged

        Public Event PropertyChanged(ByVal sender As Object,  
         ByVal e As System.ComponentModel.PropertyChangedEventArgs)  _
Implements System.ComponentModel.INotifyPropertyChanged.PropertyChanged

        Private _studentList As ObservableCollection(Of Student)
        Public Property StudentList As ObservableCollection(Of Student)
            Get
                Return _studentList
            End Get
            Set(ByVal value As ObservableCollection(Of Student))
                If _studentList IsNot value Then
                    _studentList = value
                    OnPropertyChanged("StudentList")
                End If
            End Set
        End Property

        Public Sub New()
            PopulateStudents()
        End Sub

        Protected Sub OnPropertyChanged(ByVal propertyName As String)
            If Not String.IsNullOrEmpty(propertyName) Then
                RaiseEvent PropertyChanged(Me,
                          New PropertyChangedEventArgs(propertyName))
            End If
        End Sub

        Public Sub PopulateStudents()
            Dim itemList = New ObservableCollection(Of Student)() From
                 {New Student() With {.StudentName = "Frodo Baggins",
                                    .Project1Score = 89D,
                                    .Project2Score = 93D,
                                    .Project3Score = 88D},
                 New Student() With {.StudentName = "Rosie Cotton",
                                    .Project1Score = 97D,
                                    .Project2Score = 93D,
                                    .Project3Score = 94D},
                 New Student() With {.StudentName = "Samwise Gamgee",
                                    .Project1Score = 83D,
                                    .Project2Score = 90D,
                                    .Project3Score = 85D},
                 New Student() With {.StudentName = "Peregrin Took",
                                    .Project1Score = 69D,
                                    .Project2Score = 72D,
                                    .Project3Score = 75D}}
            StudentList = itemList
        End Sub

    End Class
End Namespace

Step 4 hooks the View to the View/Model. There are several choices for hooking them together.

  • Option 1: Use the code behind.
    You can use the code behind to create an instance of the View/Model and assign it as the DataContext of the page.

    The plus side of this option is that there is then an easy way to handle events. You can create a standard event procedure in your code behind and use the View/Model instance to call a method in the View/Model to perform the event processing.

    The down side of this approach is that you won’t get the design-time data because the designer does not execute the code behind’s constructor.

    The View’s code behind would look something like this:

In C#:

using System.Windows.Controls;
using System.Windows.Navigation;
using InStepSM.SL.ViewModels;

namespace InStepSM.SL.Views
{
    public partial class Overview : Page
    {
        public StudentViewModel vm { get; set; }

        public Overview()
        {
            InitializeComponent();
            vm = new StudentViewModel();
            this.DataContext = vm;
        }

        private void dataGrid1_SelectionChanged(object sender,
                                SelectionChangedEventArgs e)
        {
            vm.SomeMethod();
        }

    }
}

In VB:

Imports InStepSM.SL.ViewModels

Partial Public Class Overview
    Inherits Page

    Public Property vm As StudentViewModel

    Public Sub New()
        InitializeComponent()
        vm = New StudentViewModel()
        Me.DataContext = vm
    End Sub

    Private Sub dataGrid1_SelectionChanged(ByVal sender As Object, 
       ByVal e As System.Windows.Controls.SelectionChangedEventArgs) _
       Handles dataGrid1.SelectionChanged
        vm.SomeMethod()
    End Sub
End Class

And the xaml for the DataGrid would look something like this:

<sdk:DataGrid AutoGenerateColumns="true"
      ItemsSource="{Binding StudentList}"
      Height="200"
      Name="dataGrid1"
      Width="500"
      SelectionChanged="dataGrid1_SelectionChanged" />

Where sdk is:

xmlns:sdk=http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk

  • Option 2: Define the instance of the View/Model in the xaml.
    This is the most common approach because it minimizes the amount of code required in the code behind and provides for design-time data.

    In this option, the View’s code behind is basically empty:

In C#:

using System.Windows.Controls;
using System.Windows.Navigation;

namespace InStepSM.SL.Views
{
    public partial class Overview : Page
    {
        public Overview()
        {
            InitializeComponent();
        }
    }
}

In VB:

Partial Public Class Overview
    Inherits Page

    Public Sub New()
        InitializeComponent()
    End Sub

End Class

The xaml contains the reference to the View/Model, shown in Red:

<navigation:Page x:Class="InStepSM.SL.Views.Overview"        

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:navigation="clr-namespace:System.Windows.Controls;assembly=
                                  System.Windows.Controls.Navigation"
xmlns:vms ="clr-namespace:InStepSM.SL.ViewModels"
xmlns:sdk="
http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk"
d:DesignWidth="640" d:DesignHeight="480"
Title="Overview Page" >

    <!– Reference the View/Model –>
    <navigation:Page.DataContext>
        <vms:StudentViewModel/>
    </navigation:Page.DataContext>

</navigation:Page>

The above vms namespace line references the namespace for our View/Model. The DataContext lines set the DataContext of the page to the StudentViewModel. When the designer executes this xaml, it creates an instance of the StudentViewModel class, which in turn executes the constructor. So any code in the View/Model’s constructor is executed by the designer.

Step 5 binds the property in the View/Model to the DataGrid in the xaml.

<sdk:DataGrid AutoGenerateColumns="true"
      ItemsSource="{Binding StudentList}"
      Height="200"
      Name="dataGrid1"
      Width="500" />

VoilĂ ! The grid data now appears in the designer:

image

And the resulting application appears as follows:

image

This post provides the very basics of using MVVM in a Silverlight application.

There are several frameworks available for working with MVVM, including MVVMLight. But trying MVVM manually first may help you better understand and leverage MVVM frameworks.

Enjoy!

EDIT 1/23/11: Inserted missing code example.

EDIT 1/25/11: Corrected a few lines that were cut off. Added information on setting the AutoGenerateColumns property of the DataGrid to True.

Populating a DataGrid in a Silverlight Application

Filed under: C#,Silverlight,VB.NET @ 4:36 pm

One of the common requirements of a line of business application is to present data. This post demonstrates the basics of how to populate a DataGrid from a business object. This post is a "stepping stone" to the ultimate goal of populating the DataGrid using MVVM, which is a later post in this series.

NOTE: This post is part of a series that starts with this prior post. The example in this post uses the application that is built as part of that series.

The basic steps for populating a DataGrid in a Silverlight application are:

1) Drag and drop a DataGrid control from the toolbox onto the desired page.

2) Build/generate the business objects that contains the data for the grid.

3) Bind the business objects to the data grid using the DataGrid’s ItemsSource.

Step 1 is self explanatory. If you are working through the set of blog posts, add the grid to the Students page. Otherwise, add a grid to any page.

Be sure to set the AutoGenerateColumns property to True so it will automatically show your data.

The Silverlight page is called a "View" because it is the user’s view of the application. It is the part of the application that the user sees and interacts with.

Step 2 requires a fundamental decision on how you will get data into your application. The primary choices are:

  • Build an entity class in the Silverlight application that is populated from a WCF service.
  • Generate an entity class in the Silverlight application using WCF RIA services and Entity Framework.
  • Generate an entity class in the Silverlight application using WCF RIA services and your business objects (sometimes called Plain Old CLR Objects or POCOs). See this prior post for more information on using WCF RIA with your business objects.

The details of these approaches is beyond the scope of this post, so this example builds an entity class in the Silverlight application that is populated from code to keep things simple.

Regardless of the approach, the entity class that defines the structure of the data is called the "Model" because it models, or represents, the application’s data.

Add a Models folder to your Silverlight project. In that folder, add a Student class.

In C#:

using System;

namespace InStepSM.SL.Models
{
    public class Student
    {
        public String StudentName { get; set; }
        public int StudentId { get; set; }
        public decimal Project1Score { get; set; }
        public decimal Project2Score { get; set; }
        public decimal Project3Score { get; set; }
    }
}

In VB:

Namespace Models
    Public Class Student
        Public Property StudentName As String
        Public Property StudentId As Integer
        Public Property Project1Score As Decimal
        Public Property Project2Score As Decimal
        Public Property Project3Score As Decimal
    End Class
End Namespace

This class includes a student’s name, Id, and three scores. To keep things simple, the data to populate the class is in the code behind for the xaml file.

Step 3 requires setting the DataGrid’s ItemsSource property to a list of students from the class.

The code behind file for the xaml containing the data grid is shown below:

In C#:

using System.Collections.ObjectModel;
using System.Windows.Controls;
using System.Windows.Navigation;
using InStepSM.SL.Models;

namespace InStepSM.SL.Views
{
    public partial class Overview : Page
    {
        {
            InitializeComponent();

            dataGrid1.ItemsSource = GetStudents();
        }

        public ObservableCollection<Student> GetStudents()
        {
            var studentList = new ObservableCollection<Student>()
                    {new Student(){StudentName="Frodo Baggins",
                                    Project1Score = 89M,
                                    Project2Score=93M,
                                    Project3Score=88M},
                    new Student(){StudentName="Rosie Cotton",
                                    Project1Score = 97M,
                                    Project2Score=93M,
                                    Project3Score=94M},
                    new Student(){StudentName="Samwise Gamgee",
                                    Project1Score = 83M,
                                    Project2Score=90M,
                                    Project3Score=85M},
                    new Student(){StudentName="Peregrin Took",
                                    Project1Score = 69M,
                                    Project2Score=72M,
                                    Project3Score=75M}};
            return studentList;
        }
    }
}

In VB:

Imports System.Collections.ObjectModel
Imports InStepSM.SL.Models

Partial Public Class Overview
    Inherits Page

    Public Sub New()
        InitializeComponent()

        dataGrid1.ItemsSource = GetStudents()
    End Sub

    Public Function GetStudents() As ObservableCollection(Of Student)
        Dim studentList = New ObservableCollection(Of Student)() From
                {New Student() With {.StudentName = "Frodo Baggins",
                                .Project1Score = 89D,
                                .Project2Score = 93D,
                                .Project3Score = 88D},
                New Student() With {.StudentName = "Rosie Cotton",
                                .Project1Score = 97D,
                                .Project2Score = 93D,
                                .Project3Score = 94D},
                New Student() With {.StudentName = "Samwise Gamgee",
                                .Project1Score = 83D,
                                .Project2Score = 90D,
                                .Project3Score = 85D},
                New Student() With {.StudentName = "Peregrin Took",
                                .Project1Score = 69D,
                                .Project2Score = 72D,
                                .Project3Score = 75D}}
        Return StudentList
    End Function

End Class

The Silverlight designer does not execute the constructor of the code behind, so it does not populate the data or set the ItemsSource. That means that the designer looks like this:

image

Not very helpful. The lack of data in the designer is especially problematic as you try to style or theme the DataGrid. (This issue is handled as the application is moved to an MVVM architecture.)

If you run the application, you will see the results:

image

Use the techniques presented here if you need to bind a set of data to a DataGrid. (Or use the techniques presented in this later post to bind using MVVM.)

Enjoy!

UPDATED 1/25/11: Added information on setting AutoGenerateColumns to True to automatically display the data.

January 16, 2011

Adding a New Page to a Multi-Page Silverlight Application

Filed under: C#,Silverlight,VB.NET @ 7:48 pm

So, you’ve started building a Silverlight Line of Business (LOB) application using the Silverlight Navigation Application template (as shown in this prior post). This post covers how to add pages to it.

NOTE: This post is part of a series that starts with this prior post.

1) Add a new Page by right-clicking on the Views folder and selecting Add | New Item. Then pick the Silverlight Page template under the desired language: VB or C#.

image

2) Add any desired controls to the page.

3) Modify MainPage.xaml to include an additional HyperlinkButton to display the new page. The changes are shown in red:

<UserControl
    x:Class="InStepSM.SL.MainPage"
    xmlns="
http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="
http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:navigation="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Navigation"
    xmlns:uriMapper="clr-namespace:System.Windows.Navigation;assembly=System.Windows.Controls.Navigation"
    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">

        <Grid x:Name="LayoutRoot">
            <Border x:Name="ContentBorder" 
                 Style="{StaticResource ContentBorderStyle}">

                <navigation:Frame x:Name="ContentFrame"
                    Style="{StaticResource ContentFrameStyle}" 
                    Source="/Home" Navigated="ContentFrame_Navigated"
                    NavigationFailed="ContentFrame_NavigationFailed">
                    <navigation:Frame.UriMapper>
                        <uriMapper:UriMapper>
                            <uriMapper:UriMapping Uri=""
                                 MappedUri="/Views/Home.xaml"/>
                            <uriMapper:UriMapping Uri="/{pageName}"
                                 MappedUri="/Views/{pageName}.xaml"/>
                        </uriMapper:UriMapper>
                    </navigation:Frame.UriMapper>
                </navigation:Frame>
            </Border>

            <Grid x:Name="NavigationGrid"
                     Style="{StaticResource NavigationGridStyle}">

                <Border x:Name="BrandingBorder"
                         Style="{StaticResource BrandingBorderStyle}">
                    <StackPanel x:Name="BrandingStackPanel" 
                     Style="{StaticResource BrandingStackPanelStyle}">
                        <ContentControl
                           Style="{StaticResource LogoIcon}"/>
                        <TextBlock x:Name="ApplicationNameTextBlock" 
                          Style="{StaticResource ApplicationNameStyle}"
                          Text="InStep Student Management"/>
                    </StackPanel>
                </Border>

                <Border x:Name="LinksBorder"
                             Style="{StaticResource LinksBorderStyle}">
                    <StackPanel x:Name="LinksStackPanel" 
                        Style="{StaticResource LinksStackPanelStyle}">
                        <HyperlinkButton x:Name="Link1"  
                           NavigateUri="/Home" 
                           TargetName="ContentFrame" Content="home"/>

                        <Rectangle x:Name="Divider1" 
                           Style="{StaticResource DividerStyle}"/>

                        <HyperlinkButton x:Name="Link2" 
                         NavigateUri="/Overview" 
                         TargetName="ContentFrame" Content="overview"/>
                        
                        <Rectangle x:Name="Divider2" 
                          Style="{StaticResource DividerStyle}"/>

                        <HyperlinkButton x:Name="Link3" 
                          NavigateUri="/About" 
                          TargetName="ContentFrame" Content="about"/>

                    </StackPanel>
                </Border>
            </Grid>
        </Grid>
</UserControl>

Ensure that the name of the page is the same name as that defined in the NavigateUri property. In this case, the page is named "Overview" so the NavigateUri property is set to "/Overview.

When you click on the hyperlink, the navigation framework uses the NavigateUri property and the UriMapper code defined in the MainPage.xaml to display the new page.

When you run the application, you should see the following. Notice the new hyperlink in the upper right corner.

image

NOTE: This page is themed and styled as defined in this prior post. Your page may look different if you used other styles/themes.

Click on the new Overview hyperlink and you will see your page. (The look of the page will differ based on the controls you added to the page.)

image

Use this technique to add all of the desired pages to your Silverlight application.

Enjoy!

UPDATE 1/17/2011: Changed the screen shots to reflect the theme/styles used in the prior post.

Theming a Silverlight Application using Custom Themes

Filed under: C#,Silverlight,VB.NET @ 2:06 am

If you want to give your line of business application a nice color theme and some basic interactive styles without a lot of work, you can use the built-in themes as described in this prior post. However, as shown in that prior post you may want to adjust or customize the theme.

NOTE: This post is part of a series that starts with this prior post.

For example, adding the BureauBlack theme to our sample application resulted in this look:

image

The hyperlinks in the upper left corner are now blue on black, making them hard to see. And the Welcome text under the header on the page is next to impossible to see.

But when the user clicks on a link, the link automatically changes its color as shown below:

image

That looks a little nicer. So we just need to make a little adjustment to the default color of the hyperlinks and they should be fine.

But no. There is no way to make any "adjustments" to the built-in themes. You can’t just modify one color or add another style, or make a font a little bigger. You use the style from the theme OR you override the theme for a specific control with an explicit style.

OR, you can dive into the deep end and "edit" the theme. That sounds simple enough, but it is not. It involves making a copy of the theme’s xaml file,  adding the resulting xaml file to your project, editing the copy, and redoing your theme code to bring the theme in as a resource dictionary instead of an external dll/control.

But, starting with an existing theme and manually editing it is still significantly easier than building your own themes from scratch. So start with the Silverlight Toolkit theme (or any third party theme) that most closely provides the look and feel you want, and follow the steps below to customize it.

1) Find the theme xaml file.

I found the Silverlight Toolkit themes here: C:\Program Files (x86)\Microsoft SDKs\Silverlight\v4.0\Toolkit\Apr10\Themes\Xaml

2) Copy the desired theme xaml file into the Assets folder of your project.

Rename it so you can ensure your customized file is not confused with the file from the toolkit.

3) If you added the theme by referencing it’s dll, remove the reference and add instead a reference to Theming. Then change the theme control (BureauBlackTheme in my example) with theming:Theme.

In the sample application, the MainPage.xaml file then looks like this:

<UserControl
    x:Class="InStepSM.SL.MainPage"
    xmlns="
http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="
http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:navigation="clr-namespace:System.Windows.Controls;assembly=
                                   System.Windows.Controls.Navigation" 
    xmlns:uriMapper="clr-namespace:System.Windows.Navigation;assembly=
                                   System.Windows.Controls.Navigation"
    xmlns:d="
http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="
http://schemas.openxmlformats.org/markup-compatibility/2006"
   xmlns:theming="clr-
               namespace:System.Windows.Controls.Theming;assembly=
                            System.Windows.Controls.Theming.Toolkit"
    mc:Ignorable="d" d:DesignWidth="640" d:DesignHeight="480">

    <theming:Theme>
        <Grid x:Name="LayoutRoot">

        </Grid>

    </theming:Theme>
</UserControl>

4) Add the theme into the app.xaml file of your application as a ResourceDictionary as follows:

<Application.Resources>
  <ResourceDictionary>
    <ResourceDictionary.MergedDictionaries>
      <ResourceDictionary Source="Assets/Styles.xaml"/>
      <!– Define the theme for the application –>
      <ResourceDictionary Source="/Assets/InStepBlackTheme.xaml" />
     </ResourceDictionary.MergedDictionaries>
  </ResourceDictionary>
</Application.Resources>

5) Fix the namespace issues.

As soon as you finish step #4 and recompile, you will get a large number of namespace errors. (I got 19.) These are because the theme file you just copied into your application references many more namespaces that you probably have referenced.

You have two choices at this point: Add the missing references OR Remove the controls are you not using (and the associated namespaces) from the theme xaml file. If you are just starting your application, you may not want to remove the other controls from the theme because you may find you need  them later.

For now, we will add references to all of the missing assemblies. Open the Theme xaml file to see the list of namespaces that have missing references. Add the necessary references to your project. You may also find references in the theme to the theme’s dll. You can remove these.

About 10 minutes and several builds later, you should be at a point where you can start to actually edit the theme file. It is just a set of Style elements, so you can edit them as you do any explicit style. Just don’t give these styles a name (or key).

When I finish the above steps and run the application, it again appears as follows:

image

Your project now owns your theme file, so you can change it however you need. Just remember that whatever styles you define in the theme will be applied to every instance of the target control (unless you override it with an explicit style).

In scrolling through the theme file, it may be a bit intimidating. I find it is easiest to deal with if I use the "-" controls on the left side of the code editor to close each style. This leaves just the target type in view making it much quicker to find the style I want to change. Here is a screen shot:

image

Now that we have the theme file. Let’s make a small edit. Find the HyperlinkButton in the file. Click the "+" icon on the left side of the code editor to open the style and change the Foreground color. In the template it is set to #FF042271, which is the blue.

image

Change it to another color, such as orange: #FFF28926.

The page then appears as shown below. Notice the colors of the links in the upper right corner.

image

Use the technique defined here to set up your own customized theme file as a copy of an existing theme.

NOTE: There is may not be a style in the theme for the TextBlock control. So to change the color of the page text, update the styles in the Styles.xaml file instead. In this example, I modified the BodyTextColorBrush in the Styles.xaml file as follows:

<SolidColorBrush x:Key="BodyTextColorBrush" Color="WhiteSmoke"/>

The page then appears as follows:

image

Enjoy!

Update 1/17/2011: Corrected Step #3 and added a note at the bottom of the post to make the page text more readable.

Update 1/25/2011: Some of the namespace lines had been cut off. Corrected it.

Theming a Silverlight Application using Existing Themes

Filed under: C#,Silverlight,VB.NET @ 12:20 am

There are two ways to apply styles in a Silverlight application: explicit styles and implicit styles. The primary difference between them is that explicit styles have names (x:Key) and are applied to controls by their name. Implicit styles are not named and are applied to controls that don’t have an explicit style.

A theme is basically a set of implicit styles that are used to apply a consistent look and interactivity across controls.

NOTE: This post is part of a series that starts with this prior post.

An example of explicit styles is shown below:

<!– Page ScrollViewer Style –>
<Style x:Key="PageScrollViewerStyle" TargetType="ScrollViewer">
  <Setter Property="BorderBrush" Value="Transparent"/>
  <Setter Property="BorderThickness" Value="0,1,0,1"/>
  <Setter Property="Margin" Value="-58,-15,-58,-15"/>
  <Setter Property="Padding" Value="58,0,58,0"/>
  <Setter Property="VerticalScrollBarVisibility" Value="Auto"/>
  <Setter Property="HorizontalScrollBarVisibility" Value="Auto"/>
</Style>

<!– Content Panel Style –>
<Style x:Key="ContentPanelStyle" TargetType="StackPanel"/>

<!– Header Text Style –>
<Style x:Key="HeaderTextStyle" TargetType="TextBlock">
  <Setter Property="Foreground"
           Value="{StaticResource BodyTextColorBrush}"/>
  <Setter Property="FontSize" Value="15"/>
  <Setter Property="FontWeight" Value="Bold"/>
  <Setter Property="TextWrapping" Value="Wrap"/>
  <Setter Property="Margin" Value="0,15,0,4"/>
  <Setter Property="HorizontalAlignment" Value="Left"/>
</Style>

The above code defines three explicit styles named PageScrollViewerStyle (for a ScrollViewer), ContentPanelStyle (for a StackPanel), and HeaderTextSTyle (for a TextBlock).

These styles are applied to the appropriate controls using the Style property as shown below.

    <Grid x:Name="LayoutRoot">
        <ScrollViewer x:Name="PageScrollViewer"
           Style="{StaticResource PageScrollViewerStyle}">
            <StackPanel x:Name="ContentStackPanel">
                <TextBlock x:Name="HeaderText"
                   Style="{StaticResource HeaderTextStyle}" 
                   Text="Welcome to InStep’s Student Management"/>
                <TextBlock x:Name="ContentText" 
                   Style="{StaticResource ContentTextStyle}" 
                   Text="InStep’s Student Management package keeps you in step with your students and their progress."/>
                <Image Height="150" Margin="10" Name="image1"  
                   Width="200" 
            Source="/InStepSM.SL;component/Assets/Images/Welcome.jpg"/>
            </StackPanel>
        </ScrollViewer>
    </Grid>

If you are building a line of business application, you may want all of the controls in the application styled consistently. One way to accomplish that consistency is to repeat the Style properties on every control. Alternatively, you can apply a theme and the controls will automatically be styled  the same.

This prior post covered an introduction to the Silverlight themes available in the Silverlight Toolkit. You can try out each of these toolkit themes by visiting this link and clicking on Theme Browser in the left menu.

This current post applies one of these themes to the sample application that was started here.

NOTE: Before you try the following code, ensure that you have the Silverlight 4 Toolkit installed. You can find it here.

1) Add a reference to the theme in your project references.

image

[On my system the file was located in: Program Files (x86)\Microsoft SDKs\Silverlight\v4.0\Toolkit\Apr 10\Themes]

2) Add the following xmlns statement to the top of a xaml file you wish to theme:

xmlns:bureauBlack="clr-
       namespace:System.Windows.Controls.Theming;assembly=
       System.Windows.Controls.Theming.BureauBlack"

Be sure to change "BureauBlack" to the name of the theme you want to use.

3) Define the theme in the desired location in the xaml. You can theme the entire page, or just a portion of the page. Surround the controls with the following tags:

<bureauBlack:BureauBlackTheme>

</bureauBlack:BureauBlackTheme>

NOTE: If you apply a theme to an application that was started with the Silverlight Navigation Application, like the sample associated with this post, you may not see much difference when you run the application. That is because all of the explicit styles defined within the controls override any implicit styles defined in the themes. So you may need to remove some of the explicit Style properties before you will see the theme.

For the sample application, the theme is applied to the MainPage xaml file and several of the explicit styles are removed. The resulting sample code is shown below:

<UserControl
    x:Class="InStepSM.SL.MainPage"
    xmlns="
http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="
http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:navigation="clr-namespace:System.Windows.Controls;
                  assembly=System.Windows.Controls.Navigation"
    xmlns:uriMapper="clr-namespace:System.Windows.Navigation;
                  assembly=System.Windows.Controls.Navigation"
    xmlns:d="
http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="
http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:bureauBlack=
           "clr-namespace:System.Windows.Controls.Theming;
            assembly=System.Windows.Controls.Theming.BureauBlack"
 
    mc:Ignorable="d" d:DesignWidth="640" d:DesignHeight="480">

    <bureauBlack:BureauBlackTheme>

        <Grid x:Name="LayoutRoot">
            <Border x:Name="ContentBorder"
                  Style="{StaticResource ContentBorderStyle}">

                <navigation:Frame x:Name="ContentFrame"
                   Style="{StaticResource ContentFrameStyle}" 
                   Source="/Home" Navigated="ContentFrame_Navigated" 
                   NavigationFailed="ContentFrame_NavigationFailed">
                    <navigation:Frame.UriMapper>
                        <uriMapper:UriMapper>
                            <uriMapper:UriMapping Uri=""
                               MappedUri="/Views/Home.xaml"/>
                            <uriMapper:UriMapping Uri="/{pageName}"
                               MappedUri="/Views/{pageName}.xaml"/>
                        </uriMapper:UriMapper>
                    </navigation:Frame.UriMapper>
                </navigation:Frame>
            </Border>

            <Grid x:Name="NavigationGrid"
                Style="{StaticResource NavigationGridStyle}">

                <Border x:Name="BrandingBorder" 
                     Style="{StaticResource BrandingBorderStyle}">
                    <StackPanel x:Name="BrandingStackPanel" 
                     Style="{StaticResource BrandingStackPanelStyle}"> 
                       <ContentControl
                             Style="{StaticResource LogoIcon}"/> 
                       <TextBlock x:Name="ApplicationNameTextBlock"  
                        Style="{StaticResource ApplicationNameStyle}"
                        Text="InStep Student Management"/>
                    </StackPanel>
                </Border>

                <Border x:Name="LinksBorder" 
                   Style="{StaticResource LinksBorderStyle}">
                    <StackPanel x:Name="LinksStackPanel" 
                       Style="{StaticResource LinksStackPanelStyle}">
                        <HyperlinkButton x:Name="Link1"  
                           NavigateUri="/Home"
                           TargetName="ContentFrame" Content="home"/>
                        <Rectangle x:Name="Divider1"
                            Style="{StaticResource DividerStyle}"/>
                        <HyperlinkButton x:Name="Link2"  
                          NavigateUri="/About" 
                          TargetName="ContentFrame" Content="about"/>
                    </StackPanel>
                </Border>
            </Grid>
        </Grid>
    </bureauBlack:BureauBlackTheme>
</UserControl>

When you run the code, the screen now looks like this:

image

The theme makes it very difficult to see the links or the page text. So at this point you need to either override the theme’s implicit styles with explicit styles, or modify the theme. But that is for another post.

Use the techniques described in this post any time you want to theme your application using one of the built-in themes.

Enjoy!

January 15, 2011

Building a Multi-Page Silverlight LOB Application

Filed under: C#,Silverlight,VB.NET @ 7:29 pm

Many Line of Business (LOB) applications need to accomplish more than one objective. For example, a Student Management application may need to enter students, enter grades, and show student progress.

NOTE: This post is part of a series that starts with this prior post.

Though you could try putting all of this functionality on one page, it may be easier for the users if the application provides multiple pages. Each page would allow the user to accomplish one key objective. So the application could have a student page, grade entry page, progress page, and so on.

Building a multi-page Silverlight application is easy if you use the Silverlight Navigation Application Template that is provided in Visual Studio.

Let’s get started.

1) In Visual Studio, select File | New Project.

2) From the New Project dialog, select the Silverlight Navigation Application Template under the Visual Basic or Visual C# node, as appropriate.

image

3) Accept the  defaults in the New Silverlight Application dialog.

image

Visual Studio then creates the Silverlight project and the associated Web project. The Web project provides a test page to launch your Silverlight application. By default, it is defined as the startup project.

The Silverlight Navigation Application template creates a series of files and folders in your Silverlight project.

image

The Assets folder contains your application assets, such as styles and images. If you want to keep your files organized, you can add two folders under the Assets folder: Images and Resources. The Images folder contains any images used by your application. The Resources folder contains any application resources, such as string resources for localization.

The Views folder contains the pages of the application. Visual Studio automatically creates About, Home, and ErrorWindow pages. You can add all of the other pages that your application requires under this folder.

App.xaml contains the application-level resources.

The MainPage.xaml is the start page  for your application. It provides a background for your application and a location to display all of the other pages. It also contains the code for the page navigation. The generated code, modified to display the desired application name and for better formatting, is shown below.

MainPage code:

<UserControl
    x:Class="InStepSM.SL.MainPage"
    xmlns="
http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="
http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:navigation="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Navigation"
    xmlns:uriMapper="clr-namespace:System.Windows.Navigation;assembly=System.Windows.Controls.Navigation"
    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">

    <Grid x:Name="LayoutRoot"
              Style="{StaticResource LayoutRootGridStyle}">

        <Border x:Name="ContentBorder"
               Style="{StaticResource ContentBorderStyle}">

            <navigation:Frame x:Name="ContentFrame"
                Style="{StaticResource ContentFrameStyle}" 
                Source="/Home" Navigated="ContentFrame_Navigated"
                NavigationFailed="ContentFrame_NavigationFailed">
                <navigation:Frame.UriMapper>
                  <uriMapper:UriMapper>
                    <uriMapper:UriMapping
                        Uri="" 
                        MappedUri="/Views/Home.xaml"/>
                    <uriMapper:UriMapping
                         Uri="/{pageName}"
                         MappedUri="/Views/{pageName}.xaml"/>
                  </uriMapper:UriMapper>
                </navigation:Frame.UriMapper>
            </navigation:Frame>
        </Border>

        <Grid x:Name="NavigationGrid"
              Style="{StaticResource NavigationGridStyle}">

            <Border x:Name="BrandingBorder"
              Style="{StaticResource BrandingBorderStyle}">
                <StackPanel x:Name="BrandingStackPanel"
                  Style="{StaticResource BrandingStackPanelStyle}">

                    <ContentControl Style="{StaticResource LogoIcon}"/>
                    <TextBlock x:Name="ApplicationNameTextBlock"
                         Style="{StaticResource ApplicationNameStyle}" 
                         Text="InStep Student Management"/>
                </StackPanel>
            </Border>

            <Border x:Name="LinksBorder"
              Style="{StaticResource LinksBorderStyle}">
                <StackPanel x:Name="LinksStackPanel"
                     Style="{StaticResource LinksStackPanelStyle}">
                    <HyperlinkButton x:Name="Link1"
                      Style="{StaticResource LinkStyle}" 
                      NavigateUri="/Home"
                      TargetName="ContentFrame" Content="home"/>
                                    
                    <Rectangle x:Name="Divider1"
                      Style="{StaticResource DividerStyle}"/>
                   
                    <HyperlinkButton x:Name="Link2"
                      Style="{StaticResource LinkStyle}" 
                      NavigateUri="/About"
                      TargetName="ContentFrame" Content="about"/>

                </StackPanel>
            </Border>
        </Grid>
    </Grid>
</UserControl>

Notice that this generated code includes a Style definition for many of the controls, such as the Grid, Border, StackPanel and so on. These resources are defined in the Styles.xaml file.

The key part of this code is the navigation:Frame control. It defines the area where all of the other navigation pages will appear.

The Source property of the Frame control defines the first page that the MainPage.xaml navigates to. Navigated and NavigationFailed define events that you can use upon navigation to the page or when there is a navigation error.

The UriMapper allows you to map uniform resource identifier (URI) strings with pages in your application. In this example, the code is:

                <navigation:Frame.UriMapper>
                  <uriMapper:UriMapper>
                    <uriMapper:UriMapping
                        Uri="" 
                        MappedUri="/Views/Home.xaml"/>
                    <uriMapper:UriMapping
                         Uri="/{pageName}"
                         MappedUri="/Views/{pageName}.xaml"/>
                  </uriMapper:UriMapper>
                </navigation:Frame.UriMapper>

This specifies that if there is no Uri defined (Uri="") , then display the Home page that is in the Views subdirectory.

If there is a Uri defined and it matches the format of "/{pageName}, use the pageName as the name of the page that is in the Views subdirectory. This is a shortcut for explicitly defined Uri’s and prevents the need to list each page individually. So you do NOT have to do this:

Uri = "/Students" MappedUri="/Views/Students.xaml"
Uri = "/Grades"  MappedUri="/Views/Grades.xaml"

The HyperlinkButton controls at the top of the page define the navigation to the Home and About pages by setting their NavigationUri properties. For example, in the code above, the About HyperlinkButton code is as follows:

                     <HyperlinkButton x:Name="Link2"
                      Style="{StaticResource LinkStyle}" 
                      NavigateUri="/About"
                      TargetName="ContentFrame" Content="about"/>

The UriMapper then maps the Uri (/About) to the page defined by /Views/About.xaml. The navigation then displays that Page in the Frame named "ContentFrame".

Now let’s add some content to the other pages before we run the application.

Adding content to the Home page:

1) Select the Home.xaml file under Views in Solution Explorer to display the  page in the designer.

2) Add controls to build a welcome page using the Toolbox controls.

3) If desired, add an image to your project and then add it to your page.

The sample code for the Home page looks like this:

<navigation:Page x:Class="InStepSM.SL.Home"
    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:navigation="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Navigation"
    mc:Ignorable="d" d:DesignWidth="640" d:DesignHeight="480"
    Title="Home"
    Style="{StaticResource PageStyle}">

    <Grid x:Name="LayoutRoot">
        <ScrollViewer x:Name="PageScrollViewer"
           Style="{StaticResource PageScrollViewerStyle}">

            <StackPanel x:Name="ContentStackPanel">
                <TextBlock x:Name="HeaderText"
                   Style="{StaticResource HeaderTextStyle}" 
                   Text="Welcome to InStep’s Student Management"/>
                <TextBlock x:Name="ContentText" 
                   Style="{StaticResource ContentTextStyle}" 
                   Text="InStep’s Student Management package keeps you in step with your students and their progress."/>
                <Image Height="150" Margin="10" Name="image1"  
                   Width="200" 
            Source="/InStepSM.SL;component/Assets/Images/Welcome.jpg"/>
            </StackPanel>

        </ScrollViewer>
    </Grid>
</navigation:Page>

Notice that the topmost element for the page is a "Page". Any Page can be displayed in the MainPage using the navigation feature.

Adding content to the About page:

1) Select the About.xaml file under Views in Solution Explorer to display the  page in the designer.

2) Add controls to build an about page.

The sample code for the About Page looks like this:

<navigation:Page x:Class="InStepSM.SL.About"
    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:navigation="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Navigation"
    mc:Ignorable="d" d:DesignWidth="640" d:DesignHeight="480"
    Title="About"
    Style="{StaticResource PageStyle}">

    <Grid x:Name="LayoutRoot">
        <ScrollViewer x:Name="PageScrollViewer"
               Style="{StaticResource PageScrollViewerStyle}">
            <StackPanel x:Name="ContentStackPanel">
                <TextBlock x:Name="HeaderText"
                   Style="{StaticResource HeaderTextStyle}" 
                   Text="About InStep Student Management"/>
                <TextBlock x:Name="ContentText"
                    Style="{StaticResource ContentTextStyle}" 
                    Text="Application Name: InStep.SL"/>
                <TextBlock Style="{StaticResource ContentTextStyle}" 
                    Text="Version: 1.0"/>
            </StackPanel>
        </ScrollViewer>
    </Grid>
</navigation:Page>

Now you are ready to run the application.

The page initially displays the Home page.

image

Notice that the Internet Explorer Navigation bar shows the page after the "/". This has the added benefit of allowing a user to bookmark a specific page of your application.

If you click on About in the upper right corner, the About page displays.

image

Use the techniques described here any time you want to build a Silverlight application that navigates to multiple pages.

Enjoy!

Building a Silverlight LOB Application

Filed under: C#,Silverlight,VB.NET @ 7:27 pm

This is the first in a series of posts that will build a Silverlight Line of Business (LOB) application. This application builds a Student Management application for my company, InStep Technologies, Inc.

One of the goals for these posts is to find the right level of sample code that is complex enough to be useful, yet simple enough to understand and short enough to paste relevant code. So this example tries to keep the code as simple as possible and yet accomplish the application’s requirements.

These posts use Silverlight 4.0 and the Silverlight Toolkit. So if you are going to work through these posts, here are the prerequisites:

1) Download and install Silverlight 4.

Visual Studio 2010 comes with Silverlight 3. You need to download and install the Silverlight 4 Tools for Visual Studio 2010 from here.

NOTE: Visual Studio 2010 SP1 comes with Silverlight 4, so you don’t need to download and install it separately.

2) Download the Silverlight Toolkit.

This download provides many additional controls and themes. You can download it from here.

That’s it. Now you are ready to dive into building a Silverlight LOB application following these posts:

Click on a link to navigate to the desired post.

NOTE: The Silverlight XAML code requires that namespaces be defined with an xmlns statement, similar to the Import statement (VB) and Using statement (C#). These xmlns lines are very long and do not wrap well went pasted into blog posts. For this reason, carriage returns were added to the xmlns statements in the posts in this series. However, THERE MUST BE NO EXCESS SPACES OR RETURNS in the xmlns quoted strings in the code. Otherwise you will get "does not exist in xxx namespace" errors.

Enjoy!

EDIT 2/19/11: Added the note regarding the namespaces as per reader feedback. Thanks for the suggestion!

EDIT 2/25/11: Corrected a broken link.

EDIT 5/1/11: Added note regarding Visual Studio 2010 SP1.

« Previous PageNext Page »

© 2022 Deborah's Developer MindScape   Provided by WPMU DEV -The WordPress Experts   Hosted by Microsoft MVPs