Showing posts with label Silverlight. Show all posts
Showing posts with label Silverlight. Show all posts

Friday, February 11, 2011

Custom Control with Connecting Lines using WPF / Silverlight

Recently I was developing a migration tool that incorporates two TreeView controls and allows a user to draw lines between controls. After a bit of a struggle, I finally came up with a solution that works pretty well. The control inherits from the Grid control and uses drag and drop event handlers to draw connecting lines between controls of the same type. Because it is a Grid control, it also gives you the ability to add multiple controls and style it as you wish. In this example, I simply embedded a border control with some rounded corners and line gradients. Within the Border control, I simply added a TextBlock with the necessary text. A random color algorithm (courtesy of Philosophil Blog) determines what color is used.

Custom Properties
ConnectedControl – When two controls are connected, they are able to reference each other through this property.
ConnectedLine – This contains a reference to the line that is added.
AllowMultipleConnections – This determines if the control to connect to multiple items.
ConnectionType – This is an enum that determines whether a control can connect to other controls based on their connection type. The values Both, Source, and Target. Both is the default and says that the control can connect to any other control. Source controls can only connect to Target controls and vice versa.
LineThickness – This determines how thick the connecting line is.

Steps
In order to use this control, you simply need to create a class called ConnectingControl.cs and copy and paste the code below your WPF project. (Of course, you will need to update your namespace.) In order to add the control to your page, simply add a reference to your local assembly and then add the control. At the very least, you will also need to nest another control like a TextBlock or Image. I have included the source code for my MainWindow.xaml below.

Screenshot

ConnectingLines

MainWindow.xaml

<Window x:Class="ConnectingLines.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:ConnectingLines"
Title="Connecting Lines" Height="350" Width="525">
<
Window.Resources>
<
Style x:Key="ConnectingGridBorderSource" TargetType="Border">
<
Setter Property="Background" Value="{DynamicResource BoxBrushSource}" />
<
Setter Property="CornerRadius" Value="0,35,0,35" />
<
Setter Property="Margin" Value="0" />
</
Style>
<
Style x:Key="ConnectingGridBorderTarget" TargetType="Border">
<
Setter Property="Background" Value="{DynamicResource BoxBrushTarget}" />
<
Setter Property="CornerRadius" Value="0,35,0,35" />
<
Setter Property="Margin" Value="0" />
</
Style>
<
Style x:Key="ConnectingGridSource" TargetType="local:ConnectingControl">
<
Setter Property="Margin" Value="10" />
<
Setter Property="AllowMultipleConnections" Value="False" />
<
Setter Property="ConnectionType" Value="Source" />
<
Setter Property="LineThickness" Value="3" />
</
Style>
<
Style x:Key="ConnectingGridTarget" TargetType="local:ConnectingControl">
<
Setter Property="Margin" Value="10" />
<
Setter Property="AllowMultipleConnections" Value="False" />
<
Setter Property="ConnectionType" Value="Target" />
<
Setter Property="LineThickness" Value="3" />
</
Style>
<
Style x:Key="ConnectingGridTextBlock" TargetType="TextBlock">
<
Setter Property="Foreground" Value="AliceBlue" />
<
Setter Property="FontSize" Value="24" />
<
Setter Property="FontWeight" Value="Bold" />
<
Setter Property="VerticalAlignment" Value="Center" />
<
Setter Property="HorizontalAlignment" Value="Center" />
</
Style>
<
LinearGradientBrush x:Key="BoxBrushSource" StartPoint="0,0" EndPoint="0,1">
<
LinearGradientBrush.GradientStops>
<
GradientStop Color="AliceBlue" Offset="0.0" />
<
GradientStop Color="Green" Offset="0.4" />
</
LinearGradientBrush.GradientStops>
</
LinearGradientBrush>
<
LinearGradientBrush x:Key="BoxBrushTarget" StartPoint="0,0" EndPoint="0,1">
<
LinearGradientBrush.GradientStops>
<
GradientStop Color="AliceBlue" Offset="0" />
<
GradientStop Color="DarkBlue" Offset=".4" />
</
LinearGradientBrush.GradientStops>
</
LinearGradientBrush>
</
Window.Resources>
<
Grid>
<
Grid.ColumnDefinitions>
<
ColumnDefinition />
<
ColumnDefinition />
<
ColumnDefinition />
</
Grid.ColumnDefinitions>
<
Grid.RowDefinitions>
<
RowDefinition />
<
RowDefinition />
<
RowDefinition />
</
Grid.RowDefinitions>
<
local:ConnectingControl Style="{StaticResource ConnectingGridSource}" Grid.Row="0" Grid.Column="0">
<
Border Style="{StaticResource ConnectingGridBorderSource}">
<
TextBlock Text="Banana" Style="{StaticResource ConnectingGridTextBlock}" />
</
Border>
</
local:ConnectingControl>
<
local:ConnectingControl Style="{StaticResource ConnectingGridSource}" Grid.Row="1" Grid.Column="0">
<
Border Style="{StaticResource ConnectingGridBorderSource}">
<
TextBlock Text="Spinach" Style="{StaticResource ConnectingGridTextBlock}" />
</
Border>
</
local:ConnectingControl>
<
local:ConnectingControl Style="{StaticResource ConnectingGridSource}" Grid.Row="2" Grid.Column="0">
<
Border Style="{StaticResource ConnectingGridBorderSource}">
<
TextBlock Text="Feta" Style="{StaticResource ConnectingGridTextBlock}" />
</
Border>
</
local:ConnectingControl>
<
local:ConnectingControl Style="{StaticResource ConnectingGridTarget}" Grid.Row="0" Grid.Column="2">
<
Border Style="{StaticResource ConnectingGridBorderTarget}">
<
TextBlock Text="Cheese" Style="{StaticResource ConnectingGridTextBlock}" />
</
Border>
</
local:ConnectingControl>
<
local:ConnectingControl Style="{StaticResource ConnectingGridTarget}" Grid.Row="1" Grid.Column="2">
<
Border Style="{StaticResource ConnectingGridBorderTarget}">
<
TextBlock Text="Fruit" Style="{StaticResource ConnectingGridTextBlock}" />
</
Border>
</
local:ConnectingControl>
<
local:ConnectingControl Style="{StaticResource ConnectingGridTarget}" Grid.Row="2" Grid.Column="2">
<
Border Style="{StaticResource ConnectingGridBorderTarget}">
<
TextBlock Text="Vegetable" Style="{StaticResource ConnectingGridTextBlock}" />
</
Border>
</
local:ConnectingControl>
</
Grid>
</
Window>


ConnectingControl.cs

using System;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Shapes;

namespace ConnectingLines
{
public class ConnectingControl : Grid
{
#region PROPERTIES
public enum Connection { Both, Source, Target };
private ConnectingControl ConnectedControl { get; set; }
private Line ConnectedLine { get; set; }

new private bool AllowDrop { get; set; }

#region DEPENDENCY PROPERTIES
public event PropertyChangedEventHandler PropertyChanged;

private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}

public static readonly DependencyProperty AllowMultipleConnectionsProperty = DependencyProperty.Register("AllowMultipleConnections", typeof(bool), typeof(ConnectingControl));
public static readonly DependencyProperty ConnectionTypeProperty = DependencyProperty.Register("ConnectionType", typeof(Connection), typeof(ConnectingControl));
public static readonly DependencyProperty LineThicknessProperty = DependencyProperty.Register("LineThickness", typeof(double), typeof(ConnectingControl));

public bool AllowMultipleConnections
{
get
{
return (bool)GetValue(AllowMultipleConnectionsProperty); }
set
{
SetValue(AllowMultipleConnectionsProperty, value);
NotifyPropertyChanged("AllowMultipleConnections");
}
}

public Connection ConnectionType
{
get { return (Connection)GetValue(ConnectionTypeProperty); }
set
{
SetValue(ConnectionTypeProperty, value);
NotifyPropertyChanged("ConnectionType");
}
}

public double LineThickness
{
get
{
double thickness = (double)GetValue(LineThicknessProperty);
if (thickness == 0)
{
thickness = 3;
}
return thickness;
}
set
{
SetValue(LineThicknessProperty, value);
NotifyPropertyChanged("LineThickness");
}
}
#endregion
#endregion

#region
CONSTRUCTOR AND INITIALIZER
public ConnectingControl()
{
this.DragEnter += new DragEventHandler(CustomButton_DragEnter);
this.Drop += new DragEventHandler(CustomButton_Drop);
this.PreviewMouseLeftButtonDown += new MouseButtonEventHandler(CustomButton_PreviewMouseLeftButtonDown);
}

protected override void OnInitialized(System.EventArgs e)
{
base.AllowDrop = true;
base.OnInitialized(e);
PreviewMouseLeftButtonDown += new MouseButtonEventHandler(CustomButton_PreviewMouseLeftButtonDown);
Drop += new System.Windows.DragEventHandler(CustomButton_Drop);
DragEnter += new System.Windows.DragEventHandler(CustomButton_DragEnter);
}
#endregion

#region
EVENT HANDLERS AND METHODS
protected void CustomButton_PreviewMouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
ConnectingControl item = (ConnectingControl)sender;
DataObject dragData = new DataObject("source", (ConnectingControl)sender);
DragDrop.DoDragDrop((ConnectingControl)sender, dragData, DragDropEffects.Move);
}

protected void CustomButton_DragEnter(object sender, System.Windows.DragEventArgs e)
{
if (!e.Data.GetDataPresent("source") || sender == e.Source)
{
e.Effects = DragDropEffects.None;
}
}

protected void CustomButton_Drop(object sender, System.Windows.DragEventArgs e)
{
if (e.Data.GetDataPresent("source"))
{
ConnectingControl source = e.Data.GetData("source") as ConnectingControl;
ConnectedControl = source;
ConnectControls();
}
}

private void ConnectControls()
{
bool connect = false;
if (this.ConnectionType == Connection.Both)
{
connect = true;
}
else
{
if (this.ConnectionType == ConnectedControl.ConnectionType)
{
connect = false;
}
else
{
connect = true;
}
}
if (connect)
{
Panel parent = GetRoot(this);
Line line = new Line() { StrokeThickness = LineThickness, Stroke = GetRandomBrush() };
GeneralTransform sourceTransform = ConnectedControl.TransformToVisual(parent);
Point sourcePoint = sourceTransform.Transform(new Point(0, 0));
GeneralTransform targetTransform = this.TransformToVisual(parent);
Point targetPoint = targetTransform.Transform(new Point(0, 0));
if (sourcePoint.X < targetPoint.X)
{
line.X1 = sourcePoint.X + ConnectedControl.ActualWidth;
line.Y1 = sourcePoint.Y + ConnectedControl.ActualHeight / 2;
line.X2 = targetPoint.X;
line.Y2 = targetPoint.Y + this.ActualHeight / 2;
}
else
{
line.X1 = targetPoint.X + this.ActualWidth;
line.Y1 = targetPoint.Y + this.ActualHeight / 2;
line.X2 = sourcePoint.X;
line.Y2 = sourcePoint.Y + ConnectedControl.ActualHeight / 2;
}
if (parent.GetType() == typeof(Grid))
{
line.SetValue(Grid.ColumnSpanProperty, ((Grid)parent).ColumnDefinitions.Count);
line.SetValue(Grid.RowSpanProperty, ((Grid)parent).RowDefinitions.Count);
}
if (!AllowMultipleConnections)
{
if (ConnectedLine != null)
{
parent.Children.Remove(ConnectedLine);
}
if (ConnectedControl != null)
{
if (ConnectedControl.ConnectedLine != null)
{
GetRoot(this).Children.Remove(ConnectedControl.ConnectedLine);
}
}
}
ConnectedLine = line;
ConnectedControl.ConnectedLine = line;
parent.Children.Add(line);
}
}
#endregion

#region
MISC METHODS
private Panel GetRoot(FrameworkElement child)
{
var parent = child.Parent as FrameworkElement;
if (parent == null)
{
if (child is Window)
{
if (((Window)child).Content.GetType().BaseType == typeof(Panel))
{
return ((Window)child).Content as Panel;
}
else
{
throw new Exception("The root content element is an unexpected type. It should be a Panel instead of a " +
((Window)child).Content.GetType().BaseType.ToString() + ".");
}
}
else
{
throw new Exception("The root element is an unexpected type. It should be a Window instead of a" +
child.GetType().ToString() + ".");
}
}
return GetRoot(parent);
}

//COLOR GENERATOR CODE Courtesy of: http://philosophil.spaces.live.com/blog/cns!7E55D8EFA2AEE5D6!201.entry
private Brush GetRandomBrush()
{
Random randomColor = new Random();
SolidColorBrush brush = new SolidColorBrush();
double r, g, b;
double lightness = randomColor.NextDouble() * 0.5 + 0.4; // not too dark nor too light
double hue = randomColor.NextDouble() * 360.0; // full hue spectrum
double saturation = randomColor.NextDouble() * 0.8 + 0.2; // not too grayish
HSLtoRGB(hue, saturation, lightness, out r, out g, out b);
brush.Color = System.Windows.Media.Color.FromRgb((byte)(r * 255.0), (byte)(g * 255.0), (byte)(b * 255.0));
return brush;
}

public static void HSLtoRGB(double hue, double saturation, double luminance, out double red, out double green, out double blue)
{
double q;
double p;
if (luminance < 0.5)
{
q = luminance * (1.0 + saturation);
}
else
{
q = luminance + saturation - (luminance * saturation);
}
p = 2 * luminance - q;
double hk = hue / 360.0;
double tr = hk + 1.0 / 3.0;
double tg = hk;
double tb = hk - 1.0 / 3.0;
tr = Normalize(tr);
tg = Normalize(tg);
tb = Normalize(tb);
red = ComputeColor(q, p, tr);
green = ComputeColor(q, p, tg);
blue = ComputeColor(q, p, tb);
}

private static double ComputeColor(double q, double p, double tc)
{
if (tc < 1.0 / 6.0)
{
return p + ((q - p) * 6.0 * tc);
}
if (tc < 0.5)
{
return q;
}
if (tc < 2.0 / 3.0)
{
return p + ((q - p) * 6.0 * (2.0 / 3.0 - tc));
}
return p;
}

private static double Normalize(double tr)
{
if (tr < 0)
{
return tr + 1.0;
}
if (tr > 1.0)
{
return tr - 1.0;
}
return tr;
}
#endregion
}
}

Tuesday, January 4, 2011

SharePoint 2007 / 2010 - Silverlight Slide Show Web Part for Images and Videos

For those familiar with SharePoint 2007, you know that a slide show web part is nonexistent and in order to get this functionality you would either have to resort to a 3rd party web part or do some custom web part development. SharePoint 2010 does provide some out of the box web parts like the Picture Library Slide Show Web Part and the Media Web Part, however these do have limitations. Neither provide the ability for albums, the ability to mix video and images, or full screen functionality. The Media Web Part also only allows a single video to be embedded. Furthermore, getting the web part’s width and heights properties to render properly can be challenging. This web part mitigates all of these issues and provides the following functionality:

    • Web Part Configuration – Using out of the box SharePoint functionality, you can simply add this web part to a web part zone. The web part properties are context sensitive and will display appropriate SharePoint webs and Picture Libraries and Asset Libraries that can be used for the slide show.
    • Mixed Media – This slide show web part allows video and/or images within slide shows.
    • Album View– An album view allows you to create multiple albums within the slide show. Albums are based on the SharePoint library’s root level folders.
    • SharePoint 2007 and 2010 Compatible – I have created two projects since each platform has different deployment requirements and other subtle differences.
    • Embed Slide Show – If you have non SharePoint web pages, the slide show has a feature that allows you to easily copy and paste HTML code to be used for embedding in other web sites.
    • Full Screen Mode – Any slide show can be viewed in full screen mode simply by clicking a button on the web part.
    • Other – For a full list of slide show features, see the Slide.Show link in References below.

Acknowledgements

This web part was based on the popular and open source Vertigo Slide.Show project in CodePlex, which also has an XML and Flickr data provider. All images used within the screenshots below can be found within the CodePlex source code. For the SharePoint data provider code, I made just a few changes to the code provided in Mathew McDermott’s blog post on the subject. See References below for more details.

Installation Overview

I am not going to provide complete instructions here as both projects will use standard deployment steps, but I have outlined a few things that you should probably know.

Visual Studio Solution and Projects

When you open either solution, you will see that there are three projects: Samples, SlideShow, and SharePoint20xx.CustomControls. The Samples and SlideShow projects are a customized versions of Vertigo’s Slide.Show CodePlex source code. The SlideShow project is the Silverlight project and has been changed to add the SPDataProvider code, remove some of the out of the box branding, and a few other minor changes. I have also created post build events that copy the XAP file to the SharePoint20xx.CustomControls project since this is used by the web part. In this project, the most important file is the SPDataProvider.cs file. If Vertigo provides an update to the CodePlex project, you could copy this over and rebuild the project. Just make sure that the XAP file makes it way into the Web Part project. The SharePoint20xx.CustomControls contains all of the Web Part specific information and deploys the XAP and a png file to the SharePoint hive within CONTROLTEMPLATES\SharePoint20xx.CustomControls directory. The XAP file contains all of the Silverlight code and the png file is used for video thumbnails. (SharePoint actually has one, but since it is a GIF file and thus unsupported by Silverlight.) You may also want to change things like the elements.xml file as I kept the name and group of the web part generic.

Debugging

The best way to debug is to set the Samples project as your StartUp project and the default.html page in the SharePoint folder as your Start Page in Visual Studio. Be sure to open the default.html file first and change the Web and List parameters appropriately. In order to play with the XML data providers, you are also going to need to download the original CodePlex project and add the Samples project’s Assets folder. This has an excellent collection of Silverlight compatible images and videos, but I have removed them from the download as they took up too much space.

Deployment

For either 2008 or 2010 projects, you can simply use STSADM and the WSP file. If you are a development machine, you can use WSPBuilder for 2008 or for 2010, Visual Studio 2010. The SharePoint 2008 source code utilizes Visual Studio 2008, WSPBuilder extensions, and Silverlight 2.0 development tools. You are welcome to use Silverlight 3.0 or 4.0 development tools as well, but will be prompted to upgrade the project. The SharePoint 2010 source code was developed in Visual Studio 2010 and Silverlight 4.0 development tools. Once you have deployed the feature, you will need to make sure it is activated on the site it has been deployed at. Once the feature has been activated, it can be added as a web part, by default under Custom Controls –> Silverlight Slide Show.

Silverlight / SharePoint Cross Domain Requirements – IMPORTANT!

In order for Silverlight to access SharePoint web services (“Lists.asmx” in this case), you will need to create a file called clientaccesspolicy.xml and save it to the SharePoint wwwroot directory (C:\inetpub\wwwroot\wss\VirtualDirectories\80). You can find a copy of this in the Slide.Show Sample project or use the text below. See References below for more details.

<?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>


Configuration

In order to use the web part, you will first need to create a SharePoint Picture Library where you will be storing your images and/or videos. If you are using SharePoint 2010, you can alternatively create an Asset Library. You can then add images and videos (wmv format) to the library. In order to create albums, you can create folders at the root level. The name of the folder will be the name of the web album respectively. You can also set the Title and Description as these will be used when you mouse over a slide. If you do not provide a Title and Description, the image path will be used. One other minor thing I noticed is that the Title and Description change as soon as the transition starts, so depending on how you set up your Wait Times and Transition durations, it may appear that the these values are wrong. Also, keep in mind that the web part will resize pictures based on the height and width you provide in the web part properties, so it is recommended that you use images of equal size and aspect ratio. In order to configure the web part, open the page in edit mode, click on Modify Shared Web Part from the Web Part Edit drop down.

Supported Formats


Silverlight only accepts JPG and PNG for image formats and WMV for video formats. See MSDN References below for more details.

Web Part Configuration Properties

  • Web – This is used to filter the lists.
  • List – This is the Picture Library that will be used in the slide show.
  • Width – Image / Video width. Pictures and videos will be resized to fit this.
  • Height – Image / video height. Pictures and videos will be resized to fit this.
  • Transition Duration – Time in milliseconds that it takes to transition between slides.
  • Wait Time – Time in milliseconds that the slide stays in one place.
  • Theme – Theme to be used. Dark and Light are the only two themes.
  • Transition Type – These are the types of transitions that can be used between slides.
  • Start In Album View – This indicates whether or not you want the web part to default to album view.

 

Web Part Properties

Untitled
Based on the web you pick, the list will be populated with Picture Libraries and/or Asset Libraries.

Untitled

Screenshots (SharePoint 2010)

Slide Show View
Untitled
Slide Show View – On Mouse Over
Slide Show View – Mouse Hover

Untitled

Album View

Untitled

Source Code


SkyDrive – SharePoint 2008 / SharePoint 2010
http://cid-a2379f7b2ea6f936.office.live.com/browse.aspx/Public/Projects/SharePoint/SharePoint%20SlideShow

 

References

CodePlex – Vertigo Slide.Show – Original Source Code for Samples and SlideShow projects. This is a great place for understanding the features and troubleshooting issues.
http://slideshow.codeplex.com

Building Blocks #1: Silverlight Slideshow Viewer for SharePoint  - Matthew provided the original SPDataProvider class that I borrowed from heavily.
http://www.ableblue.com/blog/archive/2010/03/08/building-blocks-1-silverlight-slideshow-viewer-for-sharepoint.aspx

Supported Media Formats, Protocols, and Log Fields – Silverlight compatible video formats
http://msdn.microsoft.com/en-us/library/cc189080(VS.95).aspx

BitMap Image Class – Silverlight compatible image formats
http://msdn.microsoft.com/en-us/library/system.windows.media.imaging.bitmapimage(v=vs.95).aspx

Making a Service Available Across Domain Boundaries – Information on clientaccesspolicy.xml. I also recommend Jesse Liberty and Tim Heuer’s blogs.
http://msdn.microsoft.com/en-us/library/cc197955(v=vs.95).aspx

WSPBuilder – Visual Studio Extension required by the SharePoint 2007 project.
http://wspbuilder.codeplex.com

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