Karine Bosch’s Blog

On SharePoint

Walkthrough 4 – Integrating Silverlight 3 in a Custom SharePoint Application Page


In previous parts of this series I already described how you can host Silverlight 3 applications from within web parts and custom list definitions. In this part I’m going to explain how you can host several Silverlight 3 applications on one SharePoint application page and how these Silverlight applications can communicate with each other.

When the human resource manager needs to interview the candidates, he/she can navigate to the Job Applications Manager page by clicking Site Actions and then choosing Job Application Manager.

The page will display a list box with all job opportunities. The human resource manager can then choose the job for which he/she needs to interview the candidate. A list with all applications will appear.

 

Click the right upper corner of the applicant to view the detailed information like the skills rating:

You can download the sample code here.

Step 1 – The Silverlight application for the Job Opportunities list box

The list box for this Silverlight application is a customized listbox with custom styles. When the page loads, the Job Opportunities are downloaded using HttpWebRequest. But what’s worth to mention is that when the Human Resources manager selects one of the Job Opportunities, a message is sent to the other Silverlight application on the same page. Messages can be sent between senders and consumers using the System.Windows.Messaging namespace.

The Job Opportunities application acts as a sender. Therefore a class-level variable of type LocalMessageSender is declared:

private LocalMessageSender messageSender;

The messageSender variable is initialized when the Silverlight application loads:

        public MainPage()
        {
            InitializeComponent();
            messageSender = new LocalMessageSender("JobMessaging");
            PopulateListBox();
        }

When the user clicks one of the Job Opportunities in the list box, a SelectionChanged event is triggered. The event handler sends a message out to eventual listeners:

        private void JobListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            JobData selectedJob = JobListBox.SelectedItem as JobData;
            if (selectedJob != null)
            {
                messageSender.SendAsync(selectedJob.ToString());
            }
        }

Step 2 – The Silverlight application for the Job Candidates

This Silverlight application makes use of the DragDockPanelHost control of the Blacklight controls. The DragDockPanelHost control can contain one or more DragDockPanel controls. Each DragDockPanel control represents an item in a collection, in this case job applications. The great thing is that you can design a minimal template and a maximal template for the DragDockPanel controls. When you click on the right upper corner of such a DockPanel control, it moves smoothly over the canvas and renders the maximal template.

The XAML for the x control looks as follows:

    <Canvas x:Name="LayoutRoot" Width="780" Height="780">
        <Border Margin="3,10,0,0" Grid.Column="1" Width="780" Height="780"
                HorizontalAlignment="Stretch" VerticalAlignment="Stretch" >
            <blacklight:DragDockPanelHost
                x:Name="ApplicantsPanel"
                MaxColumns="2"
                VerticalAlignment="Stretch"
                HorizontalAlignment="Stretch"
                MinimizedColumnWidth="320"
                MinimizedPosition="Left"
                Margin="5,0,0,0" Background="Black">
            </blacklight:DragDockPanelHost>
        </Border>
    </Canvas>

The minimal template looks as follows:

<UserControl x:Class="JobApplicationManager.MinApplicationDetails"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:toolkit="clr-namespace:System.Windows.Controls.DataVisualization.Charting;assembly=System.Windows.Controls.DataVisualization.Toolkit"
    xmlns:datavis="clr-namespace:System.Windows.Controls.DataVisualization;assembly=System.Windows.Controls.DataVisualization.Toolkit"
    Width="300" Height="100" VerticalAlignment="Top" >
    <Grid x:Name="LayoutRoot" Background="Transparent" Width="300" Height="100"  VerticalAlignment="Top" HorizontalAlignment="Left" >
        <Grid.RowDefinitions>
            <RowDefinition Height="22"/>
            <RowDefinition Height="22"/>
            <RowDefinition Height="22"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="65" />
            <ColumnDefinition Width="*" />
        </Grid.ColumnDefinitions>
        <Border HorizontalAlignment="Left" Margin="0,2,0,2" Grid.RowSpan="3" Height="60" Width="60" BorderBrush="#FF0A0A0A" BorderThickness="1" Background="#FF0A0A0A">
            <Image Height="60" Width="60"  HorizontalAlignment="Stretch" VerticalAlignment="Stretch" x:Name="ApplicantImage" Stretch="Uniform" />
        </Border>
        <TextBlock Grid.Column="1" Text="Kevin DeRudder" TextWrapping="Wrap" x:Name="NameTextBlock" VerticalAlignment="Center" Foreground="Blue" FontWeight="Bold" />
        <TextBlock Grid.Column="1" Grid.Row="1" TextWrapping="Wrap" x:Name="CityLabel" VerticalAlignment="Center" />
    </Grid>
</UserControl>

The maximum template is defined as follows:

<UserControl x:Class="JobApplicationManager.MaxApplicationDetails"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:local="clr-namespace:JobApplicationManager"
    xmlns:chartingToolkit="clr-namespace:System.Windows.Controls.DataVisualization.Charting;assembly=System.Windows.Controls.DataVisualization.Toolkit"
    Width="410" Background="Black">
    <UserControl.Resources>
        <local:DateConverter x:Key="dateconverter" />
    </UserControl.Resources>
    <StackPanel x:Name="ApplicationPanel"
                    Width="410"
                    Height="350"
                    Background="White"
                    Orientation="Vertical">
        <Grid Margin="5,5,5,0" VerticalAlignment="Top" HorizontalAlignment="Left" >
            <Grid.RowDefinitions>
                <RowDefinition Height="24"/>
                <RowDefinition Height="24"/>
                <RowDefinition Height="24"/>
                <RowDefinition Height="24"/>
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="100" />
                <ColumnDefinition Width="100" />
                <ColumnDefinition Width="*" />
            </Grid.ColumnDefinitions>
            <Border Margin="2,2,2,8" Grid.RowSpan="4" BorderBrush="#FF0A0A0A" BorderThickness="2,2,2,2" Background="#FF0A0A0A">
                <Image HorizontalAlignment="Stretch" VerticalAlignment="Stretch" x:Name="ApplicantImage" Stretch="Uniform" Source="{Binding ApplicantPhoto}" />
            </Border>
            <TextBlock Width="76" Height="24" Text="First name:" Grid.Row="0" Grid.Column="1" Foreground="Black" />
            <TextBlock Width="Auto" Height="24" Text="{Binding FirstName}" Grid.Row="0" Grid.Column="2" Foreground="Black" />
            <TextBlock Width="76" Height="24" Text="Last name:" Grid.Row="1" Grid.Column="1" Foreground="Black" />
            <TextBlock Width="Auto" Height="24" Text="{Binding LastName}" Grid.Column="2" Grid.Row="1" Foreground="Black" />
            <TextBlock Width="76" Height="24" Text="City:" Grid.Row="2" Grid.Column="1" Foreground="Black" />
            <TextBlock Width="Auto" Height="24" Text="{Binding City}" Grid.Row="2" Grid.Column="2" Foreground="Black" />
            <TextBlock Width="76" Height="24" Text="Birthdate:" Grid.Row="3" Grid.Column="1" Foreground="Black" />
            <TextBlock Width="Auto" Height="24" Text="{Binding BirthDate, Converter={StaticResource dateconverter}}" Grid.Row="3" Grid.Column="2" Foreground="Black" />
        </Grid>
        <Border Width="300" Height="230" BorderBrush="#D3DBEF" BorderThickness="0" HorizontalAlignment="Left" VerticalAlignment="Top" >
            <chartingToolkit:Chart x:Name="chart" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" >
                <chartingToolkit:Chart.Series>
                    <chartingToolkit:ColumnSeries ItemsSource="{Binding}"
                            DependentValuePath="Rate"
                            IndependentValuePath="Name"
                            Title="Score"
                            IsSelectionEnabled="True"
                            x:Name="scoreseries"
                            />
                </chartingToolkit:Chart.Series>
            </chartingToolkit:Chart>
        </Border>
    </StackPanel>
</UserControl>

The maximum template contains a chart to render the skills and the ratings graphically. This chart is a control of th Silverlight toolkit. The graph can also be bound to data, in this case to a collection of skill objects that consist of a skill and a rate. The ItemsSource property is set to the data collection. You have to set also the properties DependentValuePath and IndependentValuePath.

When the user selects a job opportunity in the list box on the left, this Silverlight application receives a message containing the selected job opportunity. The job applications related to this job opportunity are then downloaded from the SharePoint site and displayed in DragDockPanel controls. From the code below you can see that the header of the panel is set to the job title and that he job application object is bound to the DragDockPanel control using the DataContext property. At the same time a minimal control is initialized and bound to the Content property. Also event handlers are associated with the Minimized and Maximized events.

        private DragDockPanel AddDockPanel(ApplicationData jobApplication)
        {
            DragDockPanel contentPanel = new DragDockPanel();
            contentPanel.Background = new SolidColorBrush(Colors.White);
            contentPanel.MaxWidth = maxminimizedpanelwidth;
            contentPanel.VerticalAlignment = VerticalAlignment.Top;
            contentPanel.HorizontalAlignment = HorizontalAlignment.Left;
            contentPanel.BorderBrush = new SolidColorBrush(Colors.Blue);
            contentPanel.Header = jobApplication.JobTitle;
            contentPanel.DataContext = jobApplication;
            MinApplicationDetails detailsControl = new MinApplicationDetails(jobApplication);
            contentPanel.Height = 100;
            contentPanel.MaxHeight = maxminimizedpanelheight;
            contentPanel.HorizontalAlignment = HorizontalAlignment.Left;
            contentPanel.HorizontalContentAlignment = HorizontalAlignment.Left;
            contentPanel.Content = detailsControl;
            contentPanel.Maximized += new EventHandler(contentPanel_Maximized);
            contentPanel.Minimized += new EventHandler(contentPanel_Minimized);
            return contentPanel;
        }

 When the job application controls are loaded, they are all displayed in minimum mode. When the user clicks the right upper corner of a DragDockPanel control, the Maximized event is triggered. The event handler will minimize the current maximized control and maximize the newly selected job application.

        private void contentPanel_Minimized(object sender, EventArgs e)
        {
            if (((DragDockPanel)sender).DataContext is ApplicationData)
            {
                ApplicationData jobApplication = ((DragDockPanel)sender).DataContext as ApplicationData;
                MinApplicationDetails detailsControl = new MinApplicationDetails(jobApplication);
                ((DragDockPanel)sender).MaxWidth = maxminimizedpanelwidth;
                ((DragDockPanel)sender).MaxHeight = maxminimizedpanelheight;
                ((DragDockPanel)sender).Content = detailsControl;
            }
        }
        private void contentPanel_Maximized(object sender, EventArgs e)
        {
            if (((DragDockPanel)sender).DataContext is ApplicationData)
            {
                selectedJobApplication = ((DragDockPanel)sender).DataContext as ApplicationData;
                MaxApplicationDetails detailsControl = new MaxApplicationDetails(selectedJobApplication);
                detailsControl.MaxHeight = 400;
                detailsControl.MaxWidth = 420;
                detailsControl.ButtonClicked += new ValueEventHandler(detailsControl_ButtonClicked);
                ((DragDockPanel)sender).MaxWidth = 306;
                ((DragDockPanel)sender).MaxHeight = 376;
                ((DragDockPanel)sender).Content = detailsControl;
            }
        }

The DragDockPanelHost control is only one of the whole set of Blacklight controls. You can see a showecase of these fantasic Blacklight controls here and you can download these controls here.

This Silverlight application acts as a message consumer. Therefore a class-level variable of type LocalMessageReceiver is declared:

private LocalMessageReceiver messageReceiver;

During the OnLoad event of the Silverlight application the message receiver is initialized to listen to messages of category JobMessaging. When a message is received the selected job opportunity is derived from the incoming data and the PopulateCanvas method is called. This method will retrieve the related job applications from the SharePoint site and populate the DragDockPanelHost control.

        public MainPage()
        {
            InitializeComponent();
            this.Loaded += new RoutedEventHandler(JobApplicationManager_Loaded);
        }
        private void JobApplicationManager_Loaded(object sender, RoutedEventArgs e)
        {
            messageReceiver = new LocalMessageReceiver("JobMessaging");
            messageReceiver.MessageReceived +=
                 new EventHandler<MessageReceivedEventArgs>((o, ev) =>
                 {
                     string selectedJob = ev.Message;
                     string jobTitle = null;
                     int jobId = 0;
                     if (!string.IsNullOrEmpty(selectedJob))
                     {
                         string[] data = selectedJob.Split('|');
                         jobTitle = data[0];
                         int.TryParse(data[1], out jobId);
                     }
                     ev.Response = string.Empty;
                     Dispatcher.BeginInvoke(new Action(() =>
                     {
                         requestType = RequestType.Retrieve;
                         this.PopulateCanvas(jobTitle, jobId);
                     }));
                 });
            messageReceiver.Listen();
        }

Step 3 – The Custom SharePoint Application Page

The custom application page is very simple and has no code behind. It contains a table with 2 columns. The left column will contain the Job Opportunities list box and the right column will contain the Silverlight applications for the Job Applicants. There are also 3 hidden fields:

  • one for the URL of the SharePoint site
  • one for the name of the Job Opportunities list
  • one for the name of the Job Applications list

The ids of these hidden fields are passed to the initParams argument of the Silverlight applications.

<%@ Assembly Name="Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Assembly Name="System.Web.Silverlight, Version=2.0.5.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" %>
<%@ Page Language="C#" MasterPageFile="~/_layouts/application.master" Inherits="Microsoft.SharePoint.WebControls.LayoutsPageBase" %>
<%@ Import Namespace="Microsoft.SharePoint" %>
<asp:Content ID="Main" ContentPlaceHolderID="PlaceHolderMain" runat="server">
    <script language="c#" runat="server">
        public string WebUrl
        {
            get { return Microsoft.SharePoint.SPContext.Current.Web.Url; }
        }
        public string JobListName
        {
            get { return "SL Job Opportunities"; }
        }
        public string ListName
        {
            get { return "SL Job Applications"; }
        }
    </script>
    <input type="hidden" id="HiddenUrlField" value="<%= WebUrl %>" />
    <input type="hidden" id="HiddenJobListField" value="<%= JobListName %>" />
    <input type="hidden" id="HiddenListField" value="<%= ListName %>" />

    <table style="width: 100%">
        <tr>
            <td style="width: 260px">
                <object data="data:application/x-silverlight-2," type="application/x-silverlight-2"
                    width="260" height="800">
                    <param name="source" value="/_layouts/SL Job Application Manager/JobOpportunitiesListBox.xap" />
                    <param name="initParams" value="uctlid=HiddenUrlField,lctlid=HiddenJobListField" />
                    <a href="http://go.microsoft.com/fwlink/?LinkID=149156&v=3.0.40624.0" style="text-decoration: none">
                        <img src="http://go.microsoft.com/fwlink/?LinkId=108181" alt="Get Microsoft Silverlight"
                            style="border-style: none" />
                    </a>
                </object>
            </td>
            <td style="width: 800px">
                <object data="data:application/x-silverlight-2," type="application/x-silverlight-2"
                    width="800" height="800">
                    <param name="source" value="/_layouts/SL Job Application Manager/JobApplicationManager.xap" />
                    <param name="initParams" value="uctlid=HiddenUrlField,lctlid=HiddenListField" />
                    <a href="http://go.microsoft.com/fwlink/?LinkID=149156&v=3.0.40624.0" style="text-decoration: none">
                        <img src="http://go.microsoft.com/fwlink/?LinkId=108181" alt="Get Microsoft Silverlight"
                            style="border-style: none" />
                    </a>
                </object>
            </td>
        </tr>
    </table>
</asp:Content>
<asp:Content ID="PageTitle" ContentPlaceHolderID="PlaceHolderPageTitle" runat="server">
    Job Application Manager
</asp:Content>
<asp:Content ID="PageTitleInTitleArea" runat="server" ContentPlaceHolderID="PlaceHolderPageTitleInTitleArea">
    Job Application Manager
</asp:Content>

Step 4 – Deploy and Test

The project structure looks as follows:

The application page and the Silverlight applications are deployed to a subfolder of the 12\TEMPLATE\LAYOUTS folder.

The elements.xml contains a custom action to make the application page available from the Site Actions menu:

<?xml version="1.0" encoding="utf-8" ?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
 <!-- Add Command to Site Actions Dropdown -->
 <CustomAction Id="SLJobApplicationPage"
    GroupId="SiteActions"
    Location="Microsoft.SharePoint.StandardMenu"
    Sequence="2000"
    Title="Job Application Manager"
    ImageUrl="~site/_layouts/Images/Besug/besug.png"
    Description="This Silverlight-enabled application page lets you manage job applications.">
  <UrlAction Url="~site/_layouts/SL Job Application Manager/SLJobApplicationManager.aspx"/>
 </CustomAction>
</Elements>

The project comes with an install.bat that you can use to deploy the solution to SharePoint.

I hope you like the sample!

You can download the sample code here.

1 Comment »

  1. Its really good article and worthable plz keep posting nice articles

    Comment by Sarvn | December 9, 2010 | Reply


Leave a comment