Thursday, May 27, 2010

Dynamic Silverlight Theme Switcher

I was recently trying to determine how easy it would be to add theme changing functionality into my Silverlight apps. The Silverlight Toolkit provides 11 very nice looking themes, so it would be nice to give users the option to switch between themes.

After doing some research, I found that you can instantiate a theme, add content to the theme, and then add the theme as a child of another control. What I have done here is create a simple UserControl that has a StackPanel for showing the theme and a ComboBox for picking a theme. I have a separate page called Content that has some sample controls on it. This could be any page of your choosing and integrated with any project fairly easily. I have provided code summary, screenshots, and full code below.

Code Summary
When a theme is chosen, the following happens:

1. A Content control is instantiated.
Content c = new Content();
2. Based on what is chosen in a ComboBox, a Theme object is instantiated, and the Content object is added as the Content.
Theme t = new BubbleCremeTheme() { Content = c };
3. The Theme object is added as a child to a StackPanel on your main control.
ThemePanel.Children.Add(t);

Screenshots

Design View – MainPage.xaml
image
Design View – Content.xaml
image
Running - No Theme
image
Running - Picking a Theme
image
Running - Expression Dark Theme selected
image

Steps:

1. Create a Silverlight application in Visual Studio. I called mine “Theme Switcher”.
2. In the Web Application Project, add a folder to your ClientBin folder called “images”.
3. Add an image for each themes to this folder. You can make your own or get them from Silverlight Toolkit site. For simplicity, I renamed all of mine to the theme name. For example, “Rainier Orange.png”.
4. In the Silverlight application, add all of the assemblies as references. You can find these in the directory where you installed the Silverlight Toolkit. For example:
C:\Program Files\Microsoft SDKs\Silverlight\vX.X\Toolkit\MMMYY\Themes
5. Add a UserControl called Content.xaml and update the XAML and code behind as follows:
XAML
<UserControl x:Class="ThemeSwitcher.Content"
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:data="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data"
mc:Ignorable="d"
d:DesignHeight="265"d:DesignWidth="400">
<
Gridx:Name="LayoutRoot"Height="265"Background="Transparent"Margin="5,5">
<
Grid.RowDefinitions>
<
RowDefinition Height="Auto" />
<
RowDefinition Height="Auto" />
<
RowDefinition Height="Auto" />
<
RowDefinition Height="Auto" />
<
RowDefinition Height="Auto" />
<
RowDefinition Height="Auto" />
<
RowDefinition Height="Auto" />
<
RowDefinition Height="Auto" />
</
Grid.RowDefinitions>
<
TextBlock Text="TextBlock"Grid.Row="0"Margin="3,3" />
<
ComboBox Grid.Row="1"Margin="3,3">
<
ComboBoxItem Content="ComboBoxItem1"IsSelected="True" />
<
ComboBoxItem Content="ComboBoxItem2" />
<
ComboBoxItem Content="ComboBoxItem3" />
<
ComboBoxItem Content="ComboBoxItem4" />
<
ComboBoxItem Content="ComboBoxItem5" />
</
ComboBox>
<
StackPanel Orientation="Horizontal"Grid.Row="2"Margin="3,3"VerticalAlignment="Center">
<
RadioButton Content="RadioButton1"Margin="3,3" />
<
RadioButton Content="RadioButton2"Margin="3,3" />
<
RadioButton Content="RadioButton3"Margin="3,3" />
</
StackPanel>
<
StackPanel Orientation="Horizontal"Grid.Row="3"Margin="3,3"VerticalAlignment="Center">
<
CheckBox Content="CheckBox1"Margin="3,3" />
<
CheckBox Content="CheckBox2"Margin="3,3" />
<
CheckBox Content="CheckBox3"Margin="3,3" />
</
StackPanel>
<
ListBox Grid.Row="4"Grid.RowSpan="2"Margin="3,3">
<
ListBoxItem Content="ListBoxItem1" />
<
ListBoxItem Content="ListBoxItem2" />
<
ListBoxItem Content="ListBoxItem3" />
<
ListBoxItem Content="ListBoxItem4" />
</
ListBox>
<
SliderGrid.Row="6"Margin="3,3" />
<
ButtonContent="Button"Grid.Row="7"Margin="3,3" />
</
Grid>
</
UserControl>
6. Update the MainPage XAML and code behind like the following:
XAML
<UserControl x:Class="ThemeSwitcher.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:ThemeSwitcher"
mc:Ignorable="d"
d:DesignHeight="325" d:DesignWidth="400" Height="325" Width="400">
<
Border CornerRadius="5" BorderBrush="Gray" BorderThickness="1">
<
Grid x:Name="LayoutRoot" Margin="2,2">
<
Grid.RowDefinitions>
<
RowDefinition Height="270" />
<
RowDefinition Height="50*" />
</
Grid.RowDefinitions>
<
Grid.ColumnDefinitions>
<
ColumnDefinition Width="100" />
<
ColumnDefinition />
</
Grid.ColumnDefinitions>
<
Border CornerRadius="5" BorderBrush="Silver" BorderThickness="1"
Grid.Row="0" Grid.ColumnSpan="2" Margin="3">
<
StackPanel x:Name="ThemeDisplay" />
</
Border>
<
TextBlock Text="Select Theme:" FontWeight="Bold" Grid.Row="1" Grid.Column="0"
VerticalAlignment
="Center" Margin="3,3" />
<
ComboBox x:Name="ThemeSelector" Grid.Row="1" Grid.Column="1"
SelectionChanged
="ThemeSelector_SelectionChanged" Margin="3,3">
<
ComboBox.ItemTemplate>
<
DataTemplate>
<
StackPanel Orientation="Horizontal">
<
Image Source="{Binding ImageUrl}" />
<
TextBlock Text="{Binding ThemeTitle}" FontWeight="Bold" Margin="3,3,3,3"
VerticalAlignment
="Center" />
</
StackPanel>
</
DataTemplate>
</
ComboBox.ItemTemplate>
</
ComboBox>
</
Grid>
</
Border>
</
UserControl>
Code Behind
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Controls;
using System.Windows.Controls.Theming;
using System.Windows.Media;

namespace ThemeSwitcher
{
public classThemeSelectorInfo
{
public ImageSource ImageUrl { get; set; }
public string ThemeTitle { get; set; }
}
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
string[] themes = { "Bubble Creme", "Expression Dark",
"Expression Light", "Rainier Orange",
"Rainier Purple", "Shiny Blue",
"Shiny Red", "Twilight Blue"};
List<ThemeSelectorInfo> themeInfo =
(from t inthemes
select newThemeSelectorInfo()
{
ImageUrl = new System.Windows.Media.Imaging.BitmapImage(
new Uri("images/" + t + ".png", UriKind.Relative)),
ThemeTitle = t
}).ToList();
themeInfo.Insert(0,
new ThemeSelectorInfo() { ThemeTitle = "No Theme" });
ThemeSelector.ItemsSource = themeInfo;
ThemeSelector.SelectedIndex = 0;
}

private void ThemeSelector_SelectionChanged(object sender,
SelectionChangedEventArgs e)
{
Content c = new Content();
Theme t = new ExpressionDarkTheme();
if (ThemeDisplay != null)
{
ThemeDisplay.Children.Clear();
switch (((ThemeSelectorInfo)ThemeSelector.SelectedItem).ThemeTitle)
{
case "Bubble Creme":
t = new BubbleCremeTheme() { Content = c };
break;
case "Expression Dark":
t = new ExpressionDarkTheme() { Content = c };
break;
case "Expression Light":
t = new ExpressionLightTheme() { Content = c };
break;
case "No Theme":
break;
case "Rainier Orange":
t = new RainierOrangeTheme() { Content = c };
break;
case "Rainier Purple":
t = new RainierPurpleTheme() { Content = c };
break;
case "Shiny Blue":
t = new ShinyBlueTheme() { Content = c };
break;
case "Shiny Red":
t = new ShinyRedTheme() { Content = c };
break;
case "Twilight Blue":
t = new TwilightBlueTheme() { Content = c };
break;
}
if (t.Content != null)
{
ThemeDisplay.Children.Add(t);
}
else
{
c.Foreground = new SolidColorBrush(Colors.Black);
c.LayoutRoot.Background =
new SolidColorBrush(Colors.White);
ThemeDisplay.Children.Add(c);
}
}
}
}
}

Notes:
1. I have not included BureauBlue, BureauBlack, and WhistlerBlue as there is currently a bug related to XamlParseExceptions. I am using the 11/09 release of the Silverlight 3.0 Toolkit in this example, so this may not be a problem in the newer versions.
2. The Background property of the LayoutRoot grid is White by default. Whenever you use these themes, you should always change this to Transparent.
3. If you do add theme changing functionality, remember that each reference make to a theme assembly increased the size of your XAP file. You can find more information here:
How to: Use Application Library Caching
image

Wednesday, May 19, 2010

Silverlight 4.0 – A Simple SharePoint 2010 User Profile Browser

I have just started to play around with SharePoint 2010 and have found the social networking capabilities pretty slick. Just for fun, I decided to create a very simple Silverlight application that allows you browse different SharePoint user profiles. (Since I’m on a DEV environment, I decided to create a few test users from American history and one Japanese samurai actor. This application allows you to pick from a custom ComboBox that includes the personalized picture configured within SharePoint and display a few profile details like: location, skills, school, birthday, status, and about me. I will provide a full posting of the code after the sample screenshots below.

Honest Abe’s Profile Info

image

Toshiro Mifune (三船敏郎): In one of his classic poses from the movie Yojimbo (用心棒).
You will also notice that I created a tooltip of the user status when you mouse over items in the ComboBox.
image

Thomas Jefferson – Still waiting on that picture Tommy!

image

Steps
Note: I am assuming SharePoint 2010, Silverlight 4.0, and Visual Studio 2010, but with a little tweaking I’m sure you could do something similar with previous versions. I am not going to provide details explanation of everything I am doing

1. Enable Cross Domain Communication - Create a clientaccesspolicy.xml file in your SharePoint IIS root directory. You will need this in order to access SharePoint web services via Silverlight. You can find more information here on MSDN:

Making a Service Available Across Domain Boundaries

Mine was stored at C:\inetpub\wwwroot\wss\VirtualDirectories\80 and looks like the following:

<?xml version="1.0" encoding="utf-8"?>
<
access-policy>
<
cross-domain-access>
<
policy>
<
allow-from http-request-headers="*">
<
domain uri="*"/>
</
allow-from>
<
grant-to>
<
resource path="/" include-subpaths="true"/>
</
grant-to>
</
policy>
</
cross-domain-access>
</
access-policy>



2. Create a non GIF SharePoint Profile Picture

SharePoint uses the PERSON.GIF file stored in the IMAGES directory. Unfortunately, Silverlight doesn’t support the GIF format, so you need to open this up in Paint and save it back as a png or other supported format. You can find the PERSON.GIF file here:

C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\TEMPLATE\IMAGES

3. Create a WCF Service Application project in Visual Studio.

Note: I recommend creating a Silverlight Application called SharePointUserBrowser first, and then adding the WCF Project to it.

1.) Add a web service reference to the SharePoint UserGroup.asmx web service.
(Note: To add a web service reference in Visual Studio 2010: right-click on your project in Solution Explorer –> Click Add Service Reference…-> Click Advanced… –> Click Add Web Reference…)
Your SharePoint UserGroup web service should look something like this. I named mine “SPUserGroupService”.
http://portalname/_vti_bin/usergroup.asmx
2.) Add another web service reference to the SharePoint UserProfileService. I named mine “SPUserProfileService”.
http://portalname/_vti_bin/userprofileservice.asmx
3.) Add a UserInfo class to project. This will be used to populate the SharePoint user personalization information mapped from the PropertyData objects. (Note: This is only a subset of the information available. I have provided a full list at the bottom of this post.)

namespace SPUserBrowserService
{
public class UserInfo
{
public string Name { get; set; }
public string Email { get; set; }
public string PictureUrl { get; set; }
public string Status { get; set; }
public string AboutMe { get; set; }
public string Location { get; set; }
public string PastProjects { get; set; }
public string Skills { get; set; }
public string School { get; set; }
public string Birthday { get; set; }
public string Interests { get; set; }
}
}

4.) Add a WCF Service item to your project. I called mine SharePointService.
5.) In the following service contract to the interface:

using System.Collections.Generic;
using System.ServiceModel;
namespace SPUserBrowserService
{
[ServiceContract]
public interface ISharePointService
{
[OperationContract]
List<UserInfo> GetUserInfo();
}
}

6.) Add the following code to the service code behind.

using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Xml;
using System.Text.RegularExpressions;
using System.Xml.Linq;
using SPUserBrowserService.SPUserGroupService;
using SPUserBrowserService.SPUserProfileService;

namespace SPUserBrowserService
{
public class SharePointService : ISharePointService
{
public List<UserInfo> GetUserInfo()
{
UserGroup userGroup = new UserGroup();
userGroup.UseDefaultCredentials = true;
XmlNode allUsers = userGroup.GetAllUserCollectionFromWeb();
XNode xNode = XDocument.Parse(allUsers.OuterXml);
return (from root in xNode.Document.Elements()
from users in root.Elements()
from user in users.Elements()
let loginName = (string)user.Attribute("LoginName")
let userInfo = GetUserInfoDetails(loginName)
//Filter out admin accounts.
//Strangely enough I couldn’t find a consistent
//correlating property.
where loginName != "SHAREPOINT\\system" &&
loginName != "NT AUTHORITY\\LOCAL SERVICE"
select new UserInfo()
{
Name = loginName,
Email = userInfo.Email,
PictureUrl = userInfo.PictureUrl,
Status = userInfo.Status,
//Filter out HTML tags doesn't strip out HTML
//character entities
AboutMe = Regex.Replace(userInfo.AboutMe,
@"<(.\n)*?>", string.Empty),
Location = userInfo.Location,
PastProjects = userInfo.PastProjects,
Skills = userInfo.Skills,
School = userInfo.School,
Birthday = userInfo.Birthday,
Interests = userInfo.Interests
}).ToList();
}

private UserInfo GetUserInfoDetails(string loginName)
{
//Set the default picture URL. Silverlight can't use the default GIF
//provided by SharePoint.
UserInfo userInfo = new UserInfo() {
PictureUrl = @"http://portalname/_layouts/images/person.png" };
try
{
UserProfileService userProfileService = new UserProfileService();
userProfileService.UseDefaultCredentials = true;
PropertyData[] data = userProfileService.GetUserProfileByName(loginName);
string pictureUrl = GetPropertyData(data, "PictureURL");
//This is empty be default until the user picks a picture.
if (!string.IsNullOrEmpty(pictureUrl))
{
userInfo.PictureUrl = pictureUrl;
}
userInfo.Status = GetPropertyData(data, "SPS-StatusNotes");
userInfo.AboutMe = GetPropertyData(data, "AboutMe");
userInfo.Email= GetPropertyData(data, "WorkEmail");
userInfo.Location = GetPropertyData(data, "SPS-Location");
userInfo.PastProjects = GetPropertyData(data, "SPS-PastProjects");
userInfo.Skills = GetPropertyData(data, "SPS-Skills");
userInfo.School = GetPropertyData(data, "SPS-School");
userInfo.Birthday = GetPropertyData(data, "SPS-Birthday");
userInfo.Interests = GetPropertyData(data, "SPS-Interests");
}
catch (System.Web.Services.Protocols.SoapException ex)
{
//It appears that an exception gets generated for users
//that have not logged into SharePoint yet.
//They must logon for a profile to be created.
if (!ex.Message.Contains("A user with the account name"))
{
throw;
}
}
return userInfo;
}

private string GetPropertyData(PropertyData[] data, string columnName)
{
//Delimit properties with a semi-colon or comma.
return string.Join("; ",
(from v in
((PropertyData)data.Where(x => x.Name == columnName).Single()).Values
select v.Value.ToString()).ToArray());
}
}
}

7.) VERY IMPORTANT: Add a clientaccesspolicy.xml file to your WCF Service application. If you don’t you will receive a CommunicationException exception. You can copy this from the one you created in Step 1.

Note: You will see that I am basically pulling everyone. You can do further filtering, by specifying users in a specific team site or SharePoint group. You will also see that I have to call the UserProfileService web service to return the details for each user. This could be potentially expensive with a ton of users, so you may need to provide other mechanisms for filtering. It would be nice if there were a way to retrieve a collection of UserProfile objects, but I haven’t found a way in the SharePoint web services or object model. I’m sure you could pull it from the database, but of course Microsoft doesn’t support this. If you’re feeling brave or rebellious, see my post on this:

SharePoint 2007 – FIVE very helpful (yet unsupported) stored procedures for querying membership data

At this point, you are ready to move on to the Silverlight implementation. However, I would highly recommend testing the service with something like a simple console application to make sure everything is property working.

4. Create a Silverlight Project
1.) If you haven’t already, create a Silverlight Project. I called mine SPUserBrowser.
2.) Add a service reference to the WCF Service you created in the previous step. I called mine SPUserBrowserService.
3.) Update the XAML in MainPage.xaml to the following:

<UserControl x:Class="SPUserBrowser.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:SPUserBrowser"
mc:Ignorable="d" d:DesignWidth="550" MaxWidth="475" MaxHeight="370" d:DesignHeight="370" FontFamily="Calibri" FontSize="13" Margin="10">
<
UserControl.Resources>
<
Style x:Name="Prompt" TargetType="TextBlock">
<
Setter Property="FontWeight" Value="Bold" />
<
Setter Property="Margin" Value="3,3" />
<
Setter Property="Width" Value="70" />
<
Setter Property="HorizontalAlignment" Value="Left"></Setter>
<
Setter Property="FontStyle" Value="Italic" />
</
Style>
<
Style x:Name="InfoContent" TargetType="TextBlock">
<
Setter Property="HorizontalAlignment" Value="Left"></Setter>
<
Setter Property="TextTrimming" Value="WordEllipsis" />
<
Setter Property="Foreground" Value="Black" />
</
Style>
<
Style x:Name="Status" TargetType="TextBlock">
<
Setter Property="FontWeight" Value="Normal" />
<
Setter Property="FontSize" Value="14" />
<
Setter Property="TextWrapping" Value="Wrap" />
<
Setter Property="FontStyle" Value="Italic" />
</
Style>
<
Style x:Name="BorderWhiteSmoke" TargetType="Border">
<
Setter Property="CornerRadius" Value="5" />
<
Setter Property="Background" Value="WhiteSmoke" />
<
Setter Property="Margin" Value="4,4" />
</
Style>
<
local:DateConverter x:Key="dateConverter" />
</
UserControl.Resources>
<
Grid>
<
Border CornerRadius="20">
<
Border.Background>
<
LinearGradientBrush StartPoint="0,0" EndPoint="0.3,1">
<
GradientStop Color="#FFC1B2B2" Offset="0"/>
<
GradientStop Color="#FF424949" Offset="0.5"/>
<
GradientStop Color="#FF2E1F1F" Offset="1"/>
<
GradientStop Color="#FF5F5050" Offset="0.246"/>
</
LinearGradientBrush>
</
Border.Background>
<
Grid x:Name="LayoutRoot" VerticalAlignment="Top" Background="Transparent" Margin="5,5">
<
Grid.RowDefinitions>
<
RowDefinition Height="25"></RowDefinition>
<
RowDefinition Height="*"></RowDefinition>
<
RowDefinition Height="50"></RowDefinition>
</
Grid.RowDefinitions>
<
TextBlock Text="SharePoint User Browser" FontSize="16" FontWeight="Bold" Foreground="WhiteSmoke" Margin="3,3"></TextBlock>
<
Grid x:Name="ProfileGrid" Background="Transparent" Grid.Row="1" Margin="8">
<
Grid.ColumnDefinitions>
<
ColumnDefinition Width="100"></ColumnDefinition>
<
ColumnDefinition></ColumnDefinition>
</
Grid.ColumnDefinitions>
<
Grid.RowDefinitions>
<
RowDefinition Height="32"></RowDefinition>
<
RowDefinition Height="32"></RowDefinition>
<
RowDefinition Height="32"></RowDefinition>
<
RowDefinition Height="32"></RowDefinition>
<
RowDefinition Height="32"></RowDefinition>
<
RowDefinition Height="95"></RowDefinition>
</
Grid.RowDefinitions>
<
Border Grid.Column="0" Grid.Row="0" Grid.RowSpan="4" CornerRadius="15" Background="AliceBlue" Margin="0">
<
Image x:Name="UserImage" Source="{Binding PictureUrl}" Margin="5,5" Height="110" ></Image>
</
Border>
<
Border Grid.Row="0" Grid.Column="1" Style="{StaticResource BorderWhiteSmoke}">
<
Grid>
<
Grid.ColumnDefinitions>
<
ColumnDefinition Width="60"></ColumnDefinition>
<
ColumnDefinition></ColumnDefinition>
</
Grid.ColumnDefinitions>
<
TextBlock x:Name="LocationPrompt" Text="Location:" Style="{StaticResource Prompt}" Grid.Column="0" Grid.Row="0" />
<
TextBlock x:Name="Location" Text="{Binding Location}" ToolTipService.ToolTip="{Binding Location}" Style="{StaticResource InfoContent}" Grid.Column="1" Grid.Row="0" />
</
Grid>
</
Border>
<
Border Grid.Row="1" Grid.Column="1" Style="{StaticResource BorderWhiteSmoke}">
<
Grid>
<
Grid.ColumnDefinitions>
<
ColumnDefinition Width="60"></ColumnDefinition>
<
ColumnDefinition></ColumnDefinition>
</
Grid.ColumnDefinitions>
<
TextBlock x:Name="SkillsPrompt" Text="Skills:" Style="{StaticResource Prompt}" Grid.Column="0" Grid.Row="0" />
<
TextBlock x:Name="Skills" Text="{Binding Skills}" ToolTipService.ToolTip="{Binding Skills}" Style="{StaticResource InfoContent}" Grid.Column="1" Grid.Row="0" />
</
Grid>
</
Border>
<
Border Grid.Row="2" Grid.Column="1" Style="{StaticResource BorderWhiteSmoke}">
<
Grid>
<
Grid.ColumnDefinitions>
<
ColumnDefinition Width="60"></ColumnDefinition>
<
ColumnDefinition></ColumnDefinition>
</
Grid.ColumnDefinitions>
<
TextBlock x:Name="SchoolPrompt" Text="School:" Style="{StaticResource Prompt}" Grid.Column="0" Grid.Row="0" />
<
TextBlock x:Name="School" Text="{Binding School}" ToolTipService.ToolTip="{Binding School}" Style="{StaticResource InfoContent}" Grid.Column="1" Grid.Row="0" />
</
Grid>
</
Border>
<
Border Grid.Row="3" Grid.Column="1" CornerRadius="5" Style="{StaticResource BorderWhiteSmoke}">
<
Grid>
<
Grid.ColumnDefinitions>
<
ColumnDefinition Width="60"></ColumnDefinition>
<
ColumnDefinition></ColumnDefinition>
</
Grid.ColumnDefinitions>
<
TextBlock x:Name="BirthdayPrompt" Text="Birthday:" Style="{StaticResource Prompt}" Grid.Column="0" Grid.Row="0" />
<
TextBlock x:Name="Birthday" Text="{Binding Birthday, Converter={StaticResource dateConverter}}" Style="{StaticResource InfoContent}" Grid.Column="1" Grid.Row="0" />
</
Grid>
</
Border>
<
TextBlock Text="{Binding Status}" Foreground="LightGreen" ToolTipService.ToolTip="{Binding Status}" VerticalAlignment="Center" Grid.Column="0" Grid.Row="4" Grid.ColumnSpan="3" FontSize="18" FontStyle="Italic" TextTrimming="WordEllipsis"></TextBlock>
<
Border Grid.Column="0" Grid.Row="5" Grid.ColumnSpan="3" Background="WhiteSmoke" CornerRadius="15" Margin="3">
<
StackPanel VerticalAlignment="Top" Orientation="Vertical" Margin="5">
<
TextBlock Text="About Me" Height="15" Style="{StaticResource Prompt}"></TextBlock>
<
TextBox x:Name="AboutMe" Height="55" Text="{Binding AboutMe}" TextWrapping="Wrap" IsReadOnly="True" VerticalScrollBarVisibility="Visible" HorizontalScrollBarVisibility="Hidden" BorderThickness="0" />
</
StackPanel>
</
Border>
</
Grid>
<
StackPanel Orientation="Horizontal" Grid.Row="2" Margin="10,0,10,10">
<
TextBlock Text="Pick a user:" Width="80" Foreground="LightBlue" Style="{StaticResource Prompt}" FontSize="16" VerticalAlignment="Center" Margin="3,3" />
<
ComboBox x:Name="PictureComboBox" Width="360" Height="40" SelectionChanged="PictureComboBox_SelectionChanged">
<
ComboBox.ItemTemplate>
<
DataTemplate>
<
StackPanel Orientation="Horizontal" VerticalAlignment="Center" ToolTipService.ToolTip="{Binding Status}">
<
Image Source="{Binding PictureUrl}" Width="40" />
<
TextBlock Text="{Binding Name}" Foreground="Black" FontSize="16" VerticalAlignment="Center" Margin="5,5" />
</
StackPanel>
</
DataTemplate>
</
ComboBox.ItemTemplate>
</
ComboBox>
</
StackPanel>
</
Grid>
</
Border>
</
Grid>
</
UserControl>







4.) Add the following code to your MainPage.xaml code behind:

using System;
using System.Globalization;
using System.Windows.Controls;
using System.Windows.Data;
using SPUserBrowser.SPUserBrowserService;
namespace SPUserBrowser
{
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
SharePointServiceClient c = new SharePointServiceClient();
c.GetUserInfoCompleted += new EventHandler<GetUserInfoCompletedEventArgs>(c_GetUserInfoCompleted);
c.GetUserInfoAsync();
}

void c_GetUserInfoCompleted(object sender, GetUserInfoCompletedEventArgs e)
{
PictureComboBox.ItemsSource = e.Result;
PictureComboBox.SelectedIndex = 0;
ProfileGrid.DataContext = e.Result[0];
}

private void PictureComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
UserInfo userInfo = (UserInfo)PictureComboBox.SelectedItem;
ProfileGrid.DataContext = userInfo;
}
}

public class DateConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
string returnValue = string.Empty;
if (!string.IsNullOrEmpty(value.ToString()))
{
DateTime date = DateTime.Parse(value.ToString());
returnValue = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(date.Month) + " " + date.Day.ToString();
}
return returnValue;
}

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

That’s basically it! You should be good to go.

Other Information

PropertyData List
Here is a list of PropertyData names that are used in the SharePoint UserProfileService web service:

UserProfile_GUID
AccountName
FirstName
SPS-PhoneticFirstName
LastName
SPS-PhoneticLastName
PreferredName
SPS-PhoneticDisplayName
WorkPhone
Department
Title
SPS-JobTitle
Manager
AboutMe
PersonalSpace
PictureURL
UserName
QuickLinks
WebSite
PublicSiteRedirect
SPS-Dotted-line
SPS-Peers
SPS-Responsibility
SPS-SipAddress
SPS-MySiteUpgrade
SPS-ProxyAddresses
SPS-HireDate
SPS-DisplayOrder
SPS-ClaimID
SPS-ClaimProviderID
SPS-ClaimProviderType
SPS-SavedAccountName
SPS-ResourceAccountName
SPS-ObjectExists
SPS-MasterAccountName
SPS-DistinguishedName
SPS-SourceObjectDN
WorkEmail
CellPhone
Fax
Office
SPS-Location
SPS-TimeZone
Assistant
SPS-PastProjects
SPS-Skills
SPS-School
SPS-Birthday
SPS-StatusNotes
SPS-Interests
SPS-EmailOptin

SharePoint 2010 Web Services List
You can browse them easily by adding _vti_bin/webservicename.asmx to any portal site. These are found in the following directory:
C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\ISAPI

image