[WP7] : TextBox avec une indication visuelle (Watermark)

Nous allons voir aujourd’hui comment faire une TextBox contenant une indication visuelle pour l’utilisateur en Windows Phone 7, tout cela de la façon la plus simple possible.

Ce type de TextBox existe un peu partout dans le système mais n’est pas proposé nativement dans le kit de développement.

WP7-BingSearch

Nous verrons plus tard comment améliorer tout ça :)

Pour cela :

Création du composant

  • Créez un projet WP7
  • Ajoutez une classe “TextBoxInfo.cs” et ajoutez le squelette suivant :

/// <summary>
	/// TextBox permettant d'afficher une indication à l'utilisateur
	/// </summary>
	public class TextBoxInfo : TextBox
	{
		#region Constructeurs
		/// <summary>
		/// Initialise une nouvelle instance de la TextBox permettant d'afficher une indication à l'utilisateur
		/// </summary>
		public TextBoxInfo()
		{
			DefaultStyleKey = typeof(TextBoxInfo);
		}
		#endregion
	}

  • Ajoutez un TemplatePart correspondant à l’emplacement que nous utiliserons pour l’indication visuelle

/// <summary>
	/// TextBox permettant d'afficher une indication à l'utilisateur
	/// </summary>
	[TemplatePart(Name = "WatermarkContentElement", Type = typeof(FrameworkElement))]
	public class TextBoxInfo : TextBox
	{

  • Ajoutez une DP et son wrapper permettant de définir le contenu de l’indication visuelle

/// <summary>
	/// TextBox permettant d'afficher une indication à l'utilisateur
	/// </summary>
	[TemplatePart(Name = "WatermarkContentElement", Type = typeof(FrameworkElement))]
	public class TextBoxInfo : TextBox
	{
		#region DPs
		/// <summary>
		/// Définit la DP permettant de mentionner le contenu à afficher pour l'indication dans la TextBox
		/// </summary>
		public static readonly DependencyProperty WatermarkContentProperty = DependencyProperty.Register("WatermarkContent", typeof(Object), typeof(TextBoxInfo), new PropertyMetadata(OnWatermarkPropertyChanged));

		#endregion

		#region Constructeurs
		/// <summary>
		/// Initialise une nouvelle instance de la TextBox permettant d'afficher une indication à l'utilisateur
		/// </summary>
		public TextBoxInfo()
		{
			DefaultStyleKey = typeof(TextBoxInfo);
		}
		#endregion

		#region Propriétées
		/// <summary>
		/// Obtient ou définit l'indication visuelle à afficher
		/// </summary>
		public Object WatermarkContent
		{
			get
			{
				return GetValue(WatermarkContentProperty) as Object;
			}
			set
			{
				SetValue(WatermarkContentProperty, value);
			}
		}
		#endregion

	}

  • Maintenant redéfinissez la méthode OnApplyTemplate appelée avant l’affichage du composant

Nous récupérons le TemplatePart et le gardons en membre privé.

/// <summary>
	/// TextBox permettant d'afficher une indication à l'utilisateur
	/// </summary>
	[TemplatePart(Name = "WatermarkContentElement", Type = typeof(FrameworkElement))]
	public class TextBoxInfo : TextBox
	{
		#region Membres
		private ContentControl _watermarkContent;
		#endregion
		...
		/// <summary>
		/// Appelé juste avant d'être affiché à l'écran
		/// </summary>
		public override void OnApplyTemplate()
		{
			base.OnApplyTemplate();

			_watermarkContent = GetTemplateChild("WatermarkContentElement") as ContentControl;

			if (_watermarkContent != null)
				ComputeWatermarkContentVisibility();
		}
		...
}

  • Pour terminer redéfinissez les méthodes OnGotFocus et OnLostFocus

Nous faisons ici la logique d’affichage et masquage de l’indication visuelle en fonction du texte saisi par l’utilisateur et du Focus.

/// <summary>
	/// TextBox permettant d'afficher une indication à l'utilisateur
	/// </summary>
	[TemplatePart(Name = "WatermarkContentElement", Type = typeof(FrameworkElement))]
	public class TextBoxInfo : TextBox
	{
		#region Membres
		private ContentControl _watermarkContent;
		#endregion

		#region DPs
		/// <summary>
		/// Définit la DP permettant de mentionner le contenu à afficher pour l'indication dans la TextBox
		/// </summary>
		public static readonly DependencyProperty WatermarkContentProperty = DependencyProperty.Register("WatermarkContent", typeof(Object), typeof(TextBoxInfo), new PropertyMetadata(OnWatermarkPropertyChanged));

		#endregion

		#region Constructeurs
		/// <summary>
		/// Initialise une nouvelle instance de la TextBox permettant d'afficher une indication à l'utilisateur
		/// </summary>
		public TextBoxInfo()
		{
			DefaultStyleKey = typeof(TextBoxInfo);
		}
		#endregion

		#region Propriétées
		/// <summary>
		/// Obtient ou définit l'indication visuelle à afficher
		/// </summary>
		public Object WatermarkContent
		{
			get
			{
				return GetValue(WatermarkContentProperty) as Object;
			}
			set
			{
				SetValue(WatermarkContentProperty, value);
			}
		}
		#endregion

		#region Méthodes
		private void ComputeWatermarkContentVisibility()
		{
			if (_watermarkContent != null)
				_watermarkContent.Visibility = String.IsNullOrEmpty(this.Text) ? Visibility.Visible : Visibility.Collapsed;
		}
		#endregion

		#region Handlers
		/// <summary>
		/// Appelé juste avant d'être affiché à l'écran
		/// </summary>
		public override void OnApplyTemplate()
		{
			base.OnApplyTemplate();

			_watermarkContent = GetTemplateChild("WatermarkContentElement") as ContentControl;

			if (_watermarkContent != null)
				ComputeWatermarkContentVisibility();
		}

		/// <summary>
		/// Appelé avant que la TextBox récupère le focus
		/// </summary>
		/// <param name="e">Les données de l'évènement</param>
		protected override void OnGotFocus(RoutedEventArgs e)
		{
			if (_watermarkContent != null)
				_watermarkContent.Visibility = Visibility.Collapsed;

			base.OnGotFocus(e);
		}

		/// <summary>
		/// Appelé avant que la TextBox ne perde le focus
		/// </summary>
		/// <param name="e">Les données de l'évènement</param>
		protected override void OnLostFocus(RoutedEventArgs e)
		{
			if ((_watermarkContent != null) && (String.IsNullOrEmpty(Text)))
				_watermarkContent.Visibility = Visibility.Visible;

			base.OnLostFocus(e);
		}

		private static void OnWatermarkPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
		{
			TextBoxInfo self = sender as TextBoxInfo;

			if ((self != null) && (self._watermarkContent != null))
			{
				self.ComputeWatermarkContentVisibility();
			}
		}
		#endregion
	}

Création du style associé

Nous disposons dont un nouveau ContentControl que nous utiliserons pour afficher l’indication visuelle.

WP7-TextBoxInfo-Watermark

<ControlTemplate x:Key="PhoneDisabledTextBoxTemplate"
					 TargetType="TextBox">
		<ContentControl x:Name="ContentElement"
						BorderThickness="0"
						HorizontalContentAlignment="Stretch"
						Margin="{StaticResource PhoneTextBoxInnerMargin}"
						Padding="{TemplateBinding Padding}"
						VerticalContentAlignment="Stretch" />
	</ControlTemplate>

	<Style  TargetType="Controls:TextBoxInfo">
		<Setter Property="FontFamily"
				Value="{StaticResource PhoneFontFamilyNormal}" />
		<Setter Property="FontSize"
				Value="{StaticResource PhoneFontSizeMediumLarge}" />
		<Setter Property="Background"
				Value="{StaticResource PhoneTextBoxBrush}" />
		<Setter Property="Foreground"
				Value="{StaticResource PhoneTextBoxForegroundBrush}" />
		<Setter Property="BorderBrush"
				Value="{StaticResource PhoneTextBoxBrush}" />
		<Setter Property="SelectionBackground"
				Value="{StaticResource PhoneAccentBrush}" />
		<Setter Property="SelectionForeground"
				Value="{StaticResource PhoneTextBoxSelectionForegroundBrush}" />
		<Setter Property="BorderThickness"
				Value="{StaticResource PhoneBorderThickness}" />
		<Setter Property="Padding"
				Value="2" />
		<Setter Property="Template">
			<Setter.Value>
				<ControlTemplate TargetType="Controls:TextBoxInfo">
					<Grid Background="Transparent">
						<VisualStateManager.VisualStateGroups>
							<VisualStateGroup x:Name="CommonStates">
								<VisualState x:Name="Normal" />
								<VisualState x:Name="MouseOver" />
								<VisualState x:Name="Disabled">
									<Storyboard>
										<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Visibility"
																	   Storyboard.TargetName="EnabledBorder">
											<DiscreteObjectKeyFrame KeyTime="0">
												<DiscreteObjectKeyFrame.Value>
													<Visibility>Collapsed</Visibility>
												</DiscreteObjectKeyFrame.Value>
											</DiscreteObjectKeyFrame>
										</ObjectAnimationUsingKeyFrames>
										<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Visibility"
																	   Storyboard.TargetName="DisabledOrReadonlyBorder">
											<DiscreteObjectKeyFrame KeyTime="0">
												<DiscreteObjectKeyFrame.Value>
													<Visibility>Visible</Visibility>
												</DiscreteObjectKeyFrame.Value>
											</DiscreteObjectKeyFrame>
										</ObjectAnimationUsingKeyFrames>
									</Storyboard>
								</VisualState>
								<VisualState x:Name="ReadOnly">
									<Storyboard>
										<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Visibility"
																	   Storyboard.TargetName="EnabledBorder">
											<DiscreteObjectKeyFrame KeyTime="0">
												<DiscreteObjectKeyFrame.Value>
													<Visibility>Collapsed</Visibility>
												</DiscreteObjectKeyFrame.Value>
											</DiscreteObjectKeyFrame>
										</ObjectAnimationUsingKeyFrames>
										<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Visibility"
																	   Storyboard.TargetName="DisabledOrReadonlyBorder">
											<DiscreteObjectKeyFrame KeyTime="0">
												<DiscreteObjectKeyFrame.Value>
													<Visibility>Visible</Visibility>
												</DiscreteObjectKeyFrame.Value>
											</DiscreteObjectKeyFrame>
										</ObjectAnimationUsingKeyFrames>
										<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Background"
																	   Storyboard.TargetName="DisabledOrReadonlyBorder">
											<DiscreteObjectKeyFrame KeyTime="0"
																	Value="{StaticResource PhoneTextBoxBrush}" />
										</ObjectAnimationUsingKeyFrames>
										<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="BorderBrush"
																	   Storyboard.TargetName="DisabledOrReadonlyBorder">
											<DiscreteObjectKeyFrame KeyTime="0"
																	Value="{StaticResource PhoneTextBoxBrush}" />
										</ObjectAnimationUsingKeyFrames>
										<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Foreground"
																	   Storyboard.TargetName="DisabledOrReadonlyContent">
											<DiscreteObjectKeyFrame KeyTime="0"
																	Value="{StaticResource PhoneTextBoxReadOnlyBrush}" />
										</ObjectAnimationUsingKeyFrames>
									</Storyboard>
								</VisualState>
							</VisualStateGroup>
							<VisualStateGroup x:Name="FocusStates">
								<VisualState x:Name="Focused">
									<Storyboard>
										<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Background"
																	   Storyboard.TargetName="EnabledBorder">
											<DiscreteObjectKeyFrame KeyTime="0"
																	Value="{StaticResource PhoneTextBoxEditBackgroundBrush}" />
										</ObjectAnimationUsingKeyFrames>
										<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="BorderBrush"
																	   Storyboard.TargetName="EnabledBorder">
											<DiscreteObjectKeyFrame KeyTime="0"
																	Value="{StaticResource PhoneTextBoxEditBorderBrush}" />
										</ObjectAnimationUsingKeyFrames>
									</Storyboard>
								</VisualState>
								<VisualState x:Name="Unfocused" />

							</VisualStateGroup>
						</VisualStateManager.VisualStateGroups>
						<Border x:Name="EnabledBorder"
								BorderBrush="{TemplateBinding BorderBrush}"
								BorderThickness="{TemplateBinding BorderThickness}"
								Background="{TemplateBinding Background}"
								Margin="{StaticResource PhoneTouchTargetOverhang}">
							<Grid>

								<ContentControl x:Name="WatermarkContentElement"
												IsHitTestVisible="False"
												Background="Transparent"
												Padding="{TemplateBinding Padding}"
												Content="{TemplateBinding WatermarkContent}"
												HorizontalAlignment="Left"
												Opacity="0.5" />

								<ContentControl x:Name="ContentElement"
												BorderThickness="0"
												HorizontalContentAlignment="Stretch"
												Margin="{StaticResource PhoneTextBoxInnerMargin}"
												Padding="{TemplateBinding Padding}"
												VerticalContentAlignment="Stretch" />
							</Grid>
						</Border>
						<Border x:Name="DisabledOrReadonlyBorder"
								BorderBrush="{StaticResource PhoneDisabledBrush}"
								BorderThickness="{TemplateBinding BorderThickness}"
								Background="Transparent"
								Margin="{StaticResource PhoneTouchTargetOverhang}"
								Visibility="Collapsed">
							<TextBox x:Name="DisabledOrReadonlyContent"
									 Background="Transparent"
									 Foreground="{StaticResource PhoneDisabledBrush}"
									 FontWeight="{TemplateBinding FontWeight}"
									 FontStyle="{TemplateBinding FontStyle}"
									 FontSize="{TemplateBinding FontSize}"
									 FontFamily="{TemplateBinding FontFamily}"
									 IsReadOnly="True"
									 SelectionForeground="{TemplateBinding SelectionForeground}"
									 SelectionBackground="{TemplateBinding SelectionBackground}"
									 TextAlignment="{TemplateBinding TextAlignment}"
									 TextWrapping="{TemplateBinding TextWrapping}"
									 Text="{TemplateBinding Text}"
									 Template="{StaticResource PhoneDisabledTextBoxTemplate}" />
						</Border>
					</Grid>
				</ControlTemplate>
			</Setter.Value>
		</Setter>
	</Style>

Utilisation du composant

<Controls:TextBoxInfo WatermarkContent="Rechercher..." />

Rendu

WP7-TextBoxInfo-Watermark-Rendu

Sympa non ? :)

[Promo] : Excellent blog Android / C# / Java

17 décembre 2011 1 commentaire

android_logoBonjour à tous, un petit article afin de vous présenter un blog très intéressant sur les technologies Android, Java et C#.

Vous y trouverez notamment une grande série d’articles sur le développement mobile Android ! Alors si vous souhaitez vous mettre à développer (ou vous perfectionner) sur la plateforme Android, je vous recommande ce blog réalisé par Pierre-Emmanuel Mercier :)

Bonne programmation !

Catégories:Android

[WP7] : Silverlight Control Toolkit pour WP7.1 – Novembre

Une nouvelle version du Silverlight Control Toolkit pour Windows Phone 7.1 vient de sortir.

WP7 - silverlight toolkit - november

C’est par ici :

What’s new

  • ListPicker once again works in a ScrollViewer
  • LongListSelector bug fixes around OutOfRange exceptions, wrong ordering of items, grouping issues, and scrolling events. ItemTuple is now refactored to be the public type LongListSelectorItem to provide users better access to the values in selection changed handlers.
  • PerformanceProgressBar binding fix for IsIndeterminate (item 9767 and others)
  • There is no longer a GestureListener dependency with the ContextMenu, WrapPanel, and ListPicker
  • The GestureListener should be considered deprecated for all Windows Phone 7.1 SDK development. There is no direct replacement at this time, though the platform now supports events such as Tap right on visual elements.

Other changes

  • Updates the ExpanderView sample so that each individual item has its own tilt effect.
  • Fixes PhoneTextBox bugs 9342 and 9345
  • PhoneTextBox hides actionitem when empty
  • ListPicker can be in horizontally scrollable views.
  • ListPicker now only supports Tap for activation (before it supported any touch that began in bounds and stayed in bounds)
  • The LongListSelector’s Background color now is used for the picker grid to enable better app/brand styling
  • LongListSelector state name correction
  • Comments added to clarify that ListPicker does not support UIElements directly by design since the framework only permits one instance of a UI Element in the tree at a time
  • ExpanderView visual glitch fixed (9525)
  • Additional null checks in ContextMenu visual state change methods
  • ListPicker SelectedItems is now settable
  • Small fixes to ToggleSwitch
  • A startup crash correction in the gesture listener.
  • The ExpanderView sample is documented better
  • LockablePivot’s IsLocked property is now a dependency property
  • RelativeTimerConverter fixes a UTC bug and no longer throws on future dates
  • Fixes some reported issues with VB.NET samples
  • Fixed a ToggleSwitchButton reanimation issue.
  • Owner is a public property now on ContextMenu
  • Handle listpicker selected item with a null items
  • Fixes sample issues when building.
  • Fixes build warnings.

[SL5] : Silverlight 5 Toolkit – September 2011

Une version du Silverlight Toolkit vient de sortir spécialement pour Silverlight 5 apportant pas mal de nouveautés au niveau de la gestion des éléments 3D.

  1. Seamless integration of 3D models and other assets with the Content Pipeline
  2. New Visual Studio templates for creating:
    1. Silverlight 3D Application
    2. Silverlight 3D Library
    3. Silverlight Effect
  3. New samples to demo these features

Pour la télécharger rendez-vous ici : http://silverlight.codeplex.com/releases/view/74436

Plus d’informations ici : http://blogs.msdn.com/b/eternalcoding/archive/2011/10/04/silverlight-toolkit-september-2011-for-silverlight-5-what-s-new.aspx

Bon tests :)

Catégories:Silverlight 5

[Promo] : MVVM : De la découverte à la maîtrise

Petite promotion d’un excellent livre technique écrit par Johnatan Antoine et Thomas Lebrun ayant pour vocation de vous apprendre les bonnes pratiques de développements MVVM pour Silverlight, WPF, WP7 et pourquoi pas WinRT !

MVVM : De la découverte à la maîtrise

mvvm

Dans cet ouvrage vous trouverez les points suivants :

  • Présentation du pattern MVVM
  • Les différents éléments et leurs rôles
  • Les différentes philosophies (view-first, model-first, view-model first)
  • Construire la partie modèle
  • Construire un ViewModel
  • Construire les vues
  • Edition de contrôles personnalisés
  • MVVM et Testabilité
  • Glossaire

Vous pouvez l’acheter :

  1. En version numérique ici :
  2. En version imprimée ici

Bonne lecture et encore bravo pour cette performance à Johnatan Antoine et son ami Thomas Lebrun :)

Catégories:Book, News

[WPF] : Custom RoutedEvents Tunnel-Bubble-Direct

Plusieurs personnes m’ont déjà contacté au sujet de soucis d’utilisation / compréhension des RoutedEvents présents dans WPF.

Voici un exemple complet d’utilisation simple des RoutedEvent Bubble et Tunnel !

Présentation

WPF à introduit un nouvelle notion au sein du mécanisme standard de gestion des évènements que l’on peut connaitre dans le framework .net, ce sont des évènements “intelligents” nommés RoutedEvent.
Pour que vous puissiez utiliser cette notion vous devez disposer d’un controle héritant au minimum de UIElement.

Les différents types de RoutedEvent

Il existe 3 types de RoutedEvents :

  • Bubble : L’évènement est déclenché sur la source et est ensuite déclenché à tous ses parents en remontant jusqu’à ce qu’il arrive à la racine.
  • Tunnel : C’est l’inverse de Bubble, dans ce cas l’évènement est déclanché depuis l’élément racine et parcours tous ses fils jusqu’à ce qu’il rencontre l’élément à l’origine de l’évènement.
  • Direct : Signifie que seul l’élément lui même peut déclencher l’évènement. C’est la notion d’évènement classique que l’on peut connaitre dans .net ou les évènements WinForm pour ce qui est des composants utilisateur.

Voici un graphique illustrant le fonctionnement des RoutedEvent Bubble et Tunnel :

WPF-RoutedEvent-Bubble-Tunnel

Comment définir un RoutedEvent Bubble ?

Rien de plus simple, à l’instar des DependencyProperty il suffit de déclarer l’évènement de la manière suivante :

public static readonly RoutedEvent FOOEvent = EventManager.RegisterRoutedEvent("FOO", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(MonCustomControl));

Ensuite vous devez écrire le Wrapper .net

public event RoutedEventHandler FOO
{
add
{
AddHandler(FOOEvent, value);
}
remove
{
RemoveHandler(FOOEvent, value);
}
}

Comment définir un RoutedEvent Tunnel ?

C’est exactement la même chose sauf qu’il faut mettre Tunnel et ne pas oublier de préfixer les évènement par “Preview” afin de respecter les règles de nommages instaurées dans le framework.

public static readonly RoutedEvent PreviewFOOEvent = EventManager.RegisterRoutedEvent("PreviewFOO", RoutingStrategy.Tunnel, typeof(RoutedEventHandler), typeof(MonCustomControl));

Ensuite vous devez écrire le Wrapper .net en n’oubliant pas “Preview”

public event RoutedEventHandler PreviewFOO
{
add
{
AddHandler(PreviewFOOEvent, value);
}
remove
{
RemoveHandler(PreviewFOOEvent, value);
}
}

Comment définir un RoutedEvent Direct?

Il faut faire comme Bubble mais mettre Direct pour la stratégie de routage.

public static readonly RoutedEvent FOOEvent = EventManager.RegisterRoutedEvent("FOO", RoutingStrategy.Direct, typeof(RoutedEventHandler), typeof(MonCustomControl));

Comment déclencher un RoutedEvent ?

Il suffit d’appeler la séquence de code suivante

RaiseEvent(new RoutedEventArgs(MonCustomControl.FOOEvent));

Exemple complet d’un CustomControl avec un RoutedEvent Bubble

 public class CustomButton : Button
    {
        public static readonly RoutedEvent HelloWorldEvent = EventManager.RegisterRoutedEvent("HelloWorld", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(CustomButton));

        public event RoutedEventHandler HelloWorld
        {
            add
            {
                AddHandler(HelloWorldEvent, value);
            }
            remove
            {
                RemoveHandler(HelloWorldEvent, value);
            }
        }

        protected override void OnClick()
        {
            RaiseEvent(new RoutedEventArgs(CustomButton.HelloWorldEvent));
        }
    }

Exemple complet d’un CustomControl avec un RoutedEvent Tunnel

 public class CustomButton : Button
    {
        public static readonly RoutedEvent PreviewHelloWorldEvent = EventManager.RegisterRoutedEvent("PreviewHelloWorld", RoutingStrategy.Tunnel, typeof(RoutedEventHandler), typeof(CustomButton));

        public event RoutedEventHandler PreviewHelloWorld
        {
            add
            {
                AddHandler(PreviewHelloWorldEvent, value);
            }
            remove
            {
                RemoveHandler(PreviewHelloWorldEvent, value);
            }
        }

        protected override void OnClick()
        {
            RaiseEvent(new RoutedEventArgs(CustomButton.PreviewHelloWorldEvent));
        }
    }

Comment utiliser les RoutedEvent en XAML ?

Il suffit de déclarer les évènements en tant que propriétés attachées.

<Grid x:Name="root" local:CustomButton.HelloWorld="HelloWorldEventHandler"></Grid>

Exemple complet permettant de voir le fonctionnement du mode Bubble

XAML

<Window x:Class="RoutedEventSample.MainWindow"
		xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
		xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
		Title="MainWindow"
		xmlns:local="clr-namespace:RoutedEventSample">

	<Grid x:Name="root"
		  local:CustomButton.HelloWorld="EventCatchedHandler">
		<Grid.RowDefinitions>
			<RowDefinition Height="450" />
			<RowDefinition />
		</Grid.RowDefinitions>
		<Grid x:Name="GrilleJaune"
			  local:CustomButton.HelloWorld="EventCatchedHandler"
			  Background="#FFFFF36B">
			<Grid.ColumnDefinitions>
				<ColumnDefinition />
				<ColumnDefinition />
			</Grid.ColumnDefinitions>

			<Border Background="#FF91FF6B"
					local:CustomButton.HelloWorld="EventCatchedHandler"
					x:Name="BorderVert"
					Margin="40,40,20,40">
				<Border Background="#FFFD7171"
						Margin="40"
						x:Name="BorderRouge"
						local:CustomButton.HelloWorld="EventCatchedHandler">
					<local:CustomButton x:Name="BoutonGauche"
										Content="Button"
										HorizontalAlignment="Center"
										VerticalAlignment="Center"
										local:CustomButton.HelloWorld="EventCatchedHandler" />
				</Border>
			</Border>

			<Border Background="#FF92A7FF"
					Grid.Column="1"
					x:Name="BorderBleu"
					local:CustomButton.HelloWorld="EventCatchedHandler"
					Margin="20,40,40,40">
				<Border Background="#FFBC71FD"
						Margin="40"
						Grid.Column="1"
						local:CustomButton.HelloWorld="EventCatchedHandler"
						x:Name="BorderViolet">
					<local:CustomButton x:Name="BoutonDroite"
										Content="Button"
										HorizontalAlignment="Center"
										VerticalAlignment="Center"
										local:CustomButton.HelloWorld="EventCatchedHandler" />
				</Border>
			</Border>

		</Grid>

		<GridSplitter Grid.Row="1"
					  HorizontalAlignment="Stretch" VerticalAlignment="Top" Height="5" Panel.ZIndex="1"/>

		<ListBox x:Name="EventList"
				 Grid.Row="1"
				 Grid.ColumnSpan="2" />
		<Button Content="Effacer"
				Click="Button_Click"
				HorizontalAlignment="Right"
				VerticalAlignment="Top"
				Margin="0,5,5,0" Grid.Row="1"/>

	</Grid>
</Window>

CS

public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private void EventCatchedHandler(object sender, RoutedEventArgs e)
        {
            EventList.Items.Add(String.Format("{0:T} - Evenement {1} reçu sur {2}", DateTime.Now, e.RoutedEvent.Name, ((FrameworkElement)sender).Name));
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            EventList.Items.Clear();
        }
    }

Aperçu

On s’aperçoit bien que les évènements remontent du bouton jusqu’à la racine :)

RoutedEventSample

Exemple complet permettant de voir le fonctionnement du mode Tunnel

XAML

<Window x:Class="RoutedEventSample.MainWindow"
		xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
		xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
		Title="MainWindow"
		xmlns:local="clr-namespace:RoutedEventSample">

	<Grid x:Name="root"
		  local:CustomButton.PreviewHelloWorld="EventCatchedHandler">
		<Grid.RowDefinitions>
			<RowDefinition Height="450" />
			<RowDefinition />
		</Grid.RowDefinitions>
		<Grid x:Name="GrilleJaune"
			  local:CustomButton.PreviewHelloWorld="EventCatchedHandler"
			  Background="#FFFFF36B">
			<Grid.ColumnDefinitions>
				<ColumnDefinition />
				<ColumnDefinition />
			</Grid.ColumnDefinitions>

			<Border Background="#FF91FF6B"
					local:CustomButton.PreviewHelloWorld="EventCatchedHandler"
					x:Name="BorderVert"
					Margin="40,40,20,40">
				<Border Background="#FFFD7171"
						Margin="40"
						x:Name="BorderRouge"
						local:CustomButton.PreviewHelloWorld="EventCatchedHandler">
					<local:CustomButton x:Name="BoutonGauche"
										Content="Button"
										HorizontalAlignment="Center"
										VerticalAlignment="Center"
										local:CustomButton.PreviewHelloWorld="EventCatchedHandler" />
				</Border>
			</Border>

			<Border Background="#FF92A7FF"
					Grid.Column="1"
					x:Name="BorderBleu"
					local:CustomButton.PreviewHelloWorld="EventCatchedHandler"
					Margin="20,40,40,40">
				<Border Background="#FFBC71FD"
						Margin="40"
						Grid.Column="1"
						local:CustomButton.PreviewHelloWorld="EventCatchedHandler"
						x:Name="BorderViolet">
					<local:CustomButton x:Name="BoutonDroite"
										Content="Button"
										HorizontalAlignment="Center"
										VerticalAlignment="Center"
										local:CustomButton.PreviewHelloWorld="EventCatchedHandler" />
				</Border>
			</Border>

		</Grid>

		<GridSplitter Grid.Row="1"
					  HorizontalAlignment="Stretch" VerticalAlignment="Top" Height="5" Panel.ZIndex="1"/>

		<ListBox x:Name="EventList"
				 Grid.Row="1"
				 Grid.ColumnSpan="2" />
		<Button Content="Effacer"
				Click="Button_Click"
				HorizontalAlignment="Right"
				VerticalAlignment="Top"
				Margin="0,5,5,0" Grid.Row="1"/>

	</Grid>
</Window>

CS

public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private void EventCatchedHandler(object sender, RoutedEventArgs e)
        {
            EventList.Items.Add(String.Format("{0:T} - Evenement {1} reçu sur {2}", DateTime.Now, e.RoutedEvent.Name, ((FrameworkElement)sender).Name));
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            EventList.Items.Clear();
        }
    }

Aperçu

On s’aperçoit bien que les évènements sont déclanchés de la racine vers le bouton ! :)

RoutedEventSampleTunnel

Avec ces exemples tout devrait être beaucoup plus clair à présent :D

Catégories:WPF

[SL5] : Sortie de Silverlight 5 RC

Vous pouvez dès à présent télécharger la version RC de Silverlight 5 !

http://www.silverlight.net/learn/overview/what%27s-new-in-silverlight-5

Voici les liens utiles pour utiliser cette RC:

A programme les nouveautés suivantes :

  •      Support du P/Invoke pour appeler des méthodes natives
  •      Le support du 64 bits !
  •      Impression post script
  •      Contrôle à distance dans les médias
  •      Trusted App dans le navigateur possible et non plus uniquement en OOB
  •      PivotViewer inclut dans le plugin

Attention toutefois ceci n’est pas une version GoLive et il est donc possible que des changements soient réalisés d’ici à la sortie définitive. Ne l’utilisez pas dans un environnement de production, mais uniquement dans le but de vous faire la main et de tester d’éventuelles migrations.

Bon tests :)

Catégories:Silverlight, Silverlight 5

[WP7] : Silverlight Toolkit Août 2011

Silverlight-Toolkit-Windows-Phone-7

Microsoft vient de sortir une nouvelle version du projet Silverlight Toolkit spécialement mis à jour pour Windows Phone Mango.
C’est une excellent nouvelle pour les développeur d’application souhaitant publier les applications reposant sur des fonctionnalités Mango.

Silverlight-Toolkit-August-2011

Voici la liste des nouveautés :

  • LongListSelector avec le scroll fluide
  • MultiselectList
  • LockablePivot
  • HubTile
  • ExpanderView
  • LocalizedResources
  • DateTimeConverters
  • DateTimePickers
  • HeaderedItemsControl
  • PhoneTextBox
  • Transitions (meilleurs performances)
  • ContextMenu (meilleurs performances)
  • ListPicker avec multi sélection
  • Bug Fix

 

Toutefois il ne vous reste que quelques jours pour tester votre application avec cette nouvelle version du Toolkit car la soumission d’applications fonctionnant sur Mango débute le 22 août prochain (voir ici) ! vite le temps presse :)

Vous pouvez télécharger le Toolkit ici

Bonne chance à tous pour être présent lors de la disponibilité de Mango au grand public :)

Catégories:Windows Phone 7

[SL-SPCOM] : SP2010 Client Object Model – Part 1

Une nouveauté intéressante pour la communauté Silverlight est apparue dans SharePoint 2010. Il est désormais possible, grâce au Client Objet Model, de réaliser des applications interagissant avec SharePoint 2010 sans avoir à réaliser du code personnalisé sur l’environnement SharePoint.

Grâce au Client Object Model nous pouvons à présent réaliser simplement des applications Silverlight ou ECMAScript (JavaScript) accédant à SharePoint sans avoir à développer quoi que ce soit sur l’environnement SharePoint 2010.

Dans SharePoint 2007, nous devions utiliser des WebServices pour réaliser ces opérations. Ce qui avait donc un impact sur les délais de développement d’une application cliente.

Les références nécessaires

Pour un projet Silverlight

Vous devez référencer l’assembly “Microsoft.SharePoint.Client.Silverlight.dll” et “Microsoft.SharePoint.Client.Silverlight.Runtime.dll” qui se trouve dans l’installation SharePoint 2010 à l’emplacement suivant : “C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\TEMPLATE\LAYOUTS\ClientBin\”

Pour un projet .net classique

Vous devez référencer l’assembly “Microsoft.SharePoint.Client.dll” et “Microsoft.SharePoint.Client.Runtime.dll” qui se trouve dans l’installation SharePoint 2010 à l’emplacement suivant : “C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\ISAPI\”

L’API

Le Client Object Model est un “mapping” des objets présents sur le modèle objet Sharepoint ainsi vous retrouverez des noms familiers pour un développeur SharePoint :

  • Site -> SPSite
  • Web -> SPWeb
  • ClientContext -> SPContext
  • User -> SPUser
  • List -> SPList
  • ListItem -> SPListItem
  • Field  -> SPField
  • File -> SPFile
  • Folder -> SPFolder

Utilisation

Afin de voir comment utiliser cette librairie nous allons en guise d’exemple tenter de récupérer la liste des “Listes SharePoint” disponibles.

Récupération du contexte / création d’une connexion

 using (ClientContext clientContext = new ClientContext("http://yoursharepointweburl"))
            {
               //actions TODO
            }

Initialisation de la requête

Pour cela il faut tout d’abord utiliser “clientContext.LoadQuery” qui va se charger d’empiler une action à réaliser.

ListCollection listCollections = clientContext.Web.Lists;
clientContext.LoadQuery(listCollections);

A ce stade vous ne pouvez toujours pas utiliser la variable listCollections car elle n’est toujours pas initialisée !

Exécution de la requête

Pour cela il faut appeler “clientContext.ExecuteQuery();” qui va réaliser l’appel au serveur. Il est donc possible d’empiler plusieurs actions et de faire qu’un seul appel. Limitant ainsi les appel couteux Client Serveur…

    clientContext.ExecuteQuery();

A ce moment le serveur SharePoint est appelée et une réponse JSon est reçue contenant la collection des Listes SharePoint demandées :
[
{
"SchemaVersion":"14.0.0.0","LibraryVersion":"14.0.4762.1000","ErrorInfo":null
},16,{
"IsNull":false
},18,{
"IsNull":false
},20,{
"IsNull":false
},21,{
"_ObjectType_":"SP.ListCollection","_Child_Items_":[
{
"_ObjectType_":"SP.List","_ObjectIdentity_":"740c6a0b-85e2-48a0-a494-e0f1759d4aa7:web:5db0d1d5-c41a-43ec-82c3-b694288a3ef9:list:7461b3be-a298-477d-970b-74e07c053a32","_ObjectVersion_":"0","ParentWebUrl":"\u002f","HasExternalDataSource":false,"Created":"\/Date(1303826931000)\/","LastItemModifiedDate":"\/Date(1303995918000)\/","LastItemDeletedDate":"\/Date(1303826931000)\/","Id":"\/Guid(7461b3be-a298-477d-970b-74e07c053a32)\/","Description":"Cette liste vous permet de prendre connaissance des \u00e9v\u00e9nements \u00e0 venir, des changements d\u2019\u00e9tat ou d\u2019autres informations concernant votre \u00e9quipe.","Title":"Annonces","Direction":"none","BaseType":0,"ImageUrl":"\u002f_layouts\u002fimages\u002fitann.png","ItemCount":2,"BaseTemplate":104,"DefaultContentApprovalWorkflowId":"\/Guid(00000000-0000-0000-0000-000000000000)\/","TemplateFeatureId":"\/Guid(00bfea71-d1ce-42de-9c63-a44004ce0104)\/","DefaultViewUrl":"\u002fLists\u002fAnnonces\u002fAllItems.aspx","DefaultEditFormUrl":"\u002fLists\u002fAnnonces\u002fEditForm.aspx","DefaultNewFormUrl":"\u002fLists\u002fAnnonces\u002fNewForm.aspx","DefaultDisplayFormUrl":"\u002fLists\u002fAnnonces\u002fDispForm.aspx","EnableAttachments":true,"ServerTemplateCanCreateFolders":true,"EnableFolderCreation":false,"EnableModeration":false,"EnableVersioning":false,"ForceCheckout":false,"EnableMinorVersions":false,"DraftVersionVisibility":0,"Hidden":false,"IsApplicationList":false,"IsCatalog":false,"AllowContentTypes":true,"DocumentTemplateUrl":null,"ContentTypesEnabled":false,"MultipleDataList":false,"NoCrawl":false
},{
"_ObjectType_":"SP.List","_ObjectIdentity_":"740c6a0b-85e2-48a0-a494-e0f1759d4aa7:web:5db0d1d5-c41a-43ec-82c3-b694288a3ef9:list:4a1affe4-2721-4321-bf20-eab8ca008942","_ObjectVersion_":"0","ParentWebUrl":"\u002f","HasExternalDataSource":false,"Created":"\/Date(1303826923000)\/","LastItemModifiedDate":"\/Date(1303826923000)\/","LastItemDeletedDate":"\/Date(1303826923000)\/","Id":"\/Guid(4a1affe4-2721-4321-bf20-eab8ca008942)\/","Description":"Utilisez la biblioth\u00e8que de style pour stocker des feuilles de style telles que des fichiers CSS ou XSL. Les feuilles de style de cette galerie peuvent \u00eatre utilis\u00e9es par ce site et tous ses sous-sites.","Title":"Biblioth\u00e8que de styles","Direction":"none","BaseType":1,"ImageUrl":"\u002f_layouts\u002fimages\u002fitdl.png","ItemCount":0,"BaseTemplate":101,"DefaultContentApprovalWorkflowId":"\/Guid(00000000-0000-0000-0000-000000000000)\/","TemplateFeatureId":"\/Guid(00bfea71-e717-4e80-aa17-d0c71b360101)\/","DefaultViewUrl":"\u002fStyle Library\u002fForms\u002fAllItems.aspx","DefaultEditFormUrl":"\u002fStyle Library\u002fForms\u002fEditForm.aspx","DefaultNewFormUrl":"\u002fStyle Library\u002fForms\u002fUpload.aspx","DefaultDisplayFormUrl":"\u002fStyle Library\u002fForms\u002fDispForm.aspx","EnableAttachments":false,"ServerTemplateCanCreateFolders":true,"EnableFolderCreation":true,"EnableModeration":false,"EnableVersioning":false,"ForceCheckout":false,"EnableMinorVersions":false,"DraftVersionVisibility":0,"Hidden":false,"IsApplicationList":false,"IsCatalog":true,"AllowContentTypes":true,"DocumentTemplateUrl":null,"ContentTypesEnabled":false,"MultipleDataList":false,"NoCrawl":false
},

...

{
"_ObjectType_":"SP.List","_ObjectIdentity_":"740c6a0b-85e2-48a0-a494-e0f1759d4aa7:web:5db0d1d5-c41a-43ec-82c3-b694288a3ef9:list:08a34d3e-9336-4124-b6a0-d8e7ce442192","_ObjectVersion_":"6","ParentWebUrl":"\u002f","HasExternalDataSource":false,"Created":"\/Date(1303828530000)\/","LastItemModifiedDate":"\/Date(1303828853000)\/","LastItemDeletedDate":"\/Date(1303828530000)\/","Id":"\/Guid(08a34d3e-9336-4124-b6a0-d8e7ce442192)\/","Description":"","Title":"Utilisateurs","Direction":"none","BaseType":0,"ImageUrl":"\u002f_layouts\u002fimages\u002fitgen.png","ItemCount":2,"BaseTemplate":100,"DefaultContentApprovalWorkflowId":"\/Guid(00000000-0000-0000-0000-000000000000)\/","TemplateFeatureId":"\/Guid(00bfea71-de22-43b2-a848-c05709900100)\/","DefaultViewUrl":"\u002fLists\u002fUtilisateurs\u002fAllItems.aspx","DefaultEditFormUrl":"\u002fLists\u002fUtilisateurs\u002fEditForm.aspx","DefaultNewFormUrl":"\u002fLists\u002fUtilisateurs\u002fNewForm.aspx","DefaultDisplayFormUrl":"\u002fLists\u002fUtilisateurs\u002fDispForm.aspx","EnableAttachments":true,"ServerTemplateCanCreateFolders":true,"EnableFolderCreation":false,"EnableModeration":false,"EnableVersioning":false,"ForceCheckout":false,"EnableMinorVersions":false,"DraftVersionVisibility":0,"Hidden":false,"IsApplicationList":false,"IsCatalog":false,"AllowContentTypes":true,"DocumentTemplateUrl":null,"ContentTypesEnabled":false,"MultipleDataList":false,"NoCrawl":false
},{
"_ObjectType_":"SP.List","_ObjectIdentity_":"740c6a0b-85e2-48a0-a494-e0f1759d4aa7:web:5db0d1d5-c41a-43ec-82c3-b694288a3ef9:list:e278088c-0c52-40dd-9b32-1719cef6c0f3","_ObjectVersion_":"0","ParentWebUrl":"\u002f","HasExternalDataSource":false,"Created":"\/Date(1304005536000)\/","LastItemModifiedDate":"\/Date(1304005536000)\/","LastItemDeletedDate":"\/Date(1304005536000)\/","Id":"\/Guid(e278088c-0c52-40dd-9b32-1719cef6c0f3)\/","Description":"description de la voiture","Title":"voiture","Direction":"none","BaseType":1,"ImageUrl":"\u002f_layouts\u002fimages\u002fitwp.png","ItemCount":0,"BaseTemplate":119,"DefaultContentApprovalWorkflowId":"\/Guid(00000000-0000-0000-0000-000000000000)\/","TemplateFeatureId":"\/Guid(00bfea71-c796-4402-9f2f-0eb9a6e71b18)\/","DefaultViewUrl":"\u002fvoiture\u002fForms\u002fAllPages.aspx","DefaultEditFormUrl":"\u002fvoiture\u002fForms\u002fEditForm.aspx","DefaultNewFormUrl":"\u002fvoiture\u002fForms\u002fUpload.aspx","DefaultDisplayFormUrl":"\u002fvoiture\u002fForms\u002fDispForm.aspx","EnableAttachments":false,"ServerTemplateCanCreateFolders":true,"EnableFolderCreation":false,"EnableModeration":false,"EnableVersioning":true,"ForceCheckout":false,"EnableMinorVersions":false,"DraftVersionVisibility":0,"Hidden":false,"IsApplicationList":true,"IsCatalog":false,"AllowContentTypes":true,"DocumentTemplateUrl":null,"ContentTypesEnabled":false,"MultipleDataList":false,"NoCrawl":false
}
]
}
]

Comme on peut le voir nous recevons beaucoup d’informations, ce qui n’est peut être pas utilise tout le temps. Parfois nous n’avons besoin que de 2 ou 3 informations, il serait donc judicieux de ne renvoyer que ce dont on a vraiment besoin pour améliorer les performances de l’application.

Comment récupérer les listes avec uniquement certaines données ?

Pour faire cela rien de plus simple car la librairie est très modulable.
Il suffit de changer l’appel à la méthode Load vu ci-dessus par l’expression suivante utilisant des lambda Linq.
Nous allons par exemple demander à ne récupérer que les propriétées Title, TemplateFeatureId et ImageUrl de chaques listes

 clientContext.Load(listCollections, (lists => lists.Include(list => list.Title, list => list.TemplateFeatureId, list => list.ImageUrl)));

Nous obtenons une réponse JSon beaucoup plus “light” et donc beaucoup plus petite à transférer sur un réseau informatique.

[
{
"SchemaVersion":"14.0.0.0","LibraryVersion":"14.0.4762.1000","ErrorInfo":null
},90,{
"IsNull":false
},92,{
"IsNull":false
},94,{
"IsNull":false
},95,{
"_ObjectType_":"SP.ListCollection","_Child_Items_":[
{
"_ObjectType_":"SP.List","_ObjectIdentity_":"740c6a0b-85e2-48a0-a494-e0f1759d4aa7:web:5db0d1d5-c41a-43ec-82c3-b694288a3ef9:list:7461b3be-a298-477d-970b-74e07c053a32","_ObjectVersion_":"0","Title":"Annonces","TemplateFeatureId":"\/Guid(00bfea71-d1ce-42de-9c63-a44004ce0104)\/","ImageUrl":"\u002f_layouts\u002fimages\u002fitann.png"
},{
"_ObjectType_":"SP.List","_ObjectIdentity_":"740c6a0b-85e2-48a0-a494-e0f1759d4aa7:web:5db0d1d5-c41a-43ec-82c3-b694288a3ef9:list:4a1affe4-2721-4321-bf20-eab8ca008942","_ObjectVersion_":"0","Title":"Biblioth\u00e8que de styles","TemplateFeatureId":"\/Guid(00bfea71-e717-4e80-aa17-d0c71b360101)\/","ImageUrl":"\u002f_layouts\u002fimages\u002fitdl.png"
},

...

{
"_ObjectType_":"SP.List","_ObjectIdentity_":"740c6a0b-85e2-48a0-a494-e0f1759d4aa7:web:5db0d1d5-c41a-43ec-82c3-b694288a3ef9:list:08a34d3e-9336-4124-b6a0-d8e7ce442192","_ObjectVersion_":"6","Title":"Utilisateurs","TemplateFeatureId":"\/Guid(00bfea71-de22-43b2-a848-c05709900100)\/","ImageUrl":"\u002f_layouts\u002fimages\u002fitgen.png"
},{
"_ObjectType_":"SP.List","_ObjectIdentity_":"740c6a0b-85e2-48a0-a494-e0f1759d4aa7:web:5db0d1d5-c41a-43ec-82c3-b694288a3ef9:list:e278088c-0c52-40dd-9b32-1719cef6c0f3","_ObjectVersion_":"0","Title":"voiture","TemplateFeatureId":"\/Guid(00bfea71-c796-4402-9f2f-0eb9a6e71b18)\/","ImageUrl":"\u002f_layouts\u002fimages\u002fitwp.png"
}
]
}
]

Soit une réponse de 8 109 bytes contre de 35 524 bytes pour la requête initiale !

Comment factoriser cela sous forme de méthodes ?

Méthodes pour récupérer les listes

using System;
using Microsoft.SharePoint.Client;
using File = Microsoft.SharePoint.Client.File;
using System.Linq.Expressions;
using System.Linq;
using System.Collections.Generic;
using System.Windows.Threading;
using System.Threading;
using System.Text;

namespace SliverlightSPTests
{
    ///
<summary> /// Classe permettant de simplifier les accès à SharePoint via Silverlight
 /// </summary>
    public class SharePointTools
    {
        #region Membres
          private String _webFullUrl = String.Empty;
        #endregion

        #region Ctors
        ///
<summary> /// Créé une instance de la classe permettant de simplifier les accès à SharePoint via Silverlight
 /// </summary>
        ///Url du site sur lequel les requetes vont s'effectuer
        public SharePointTools(String webUrl)
        {
            _webFullUrl = webUrl;
           }
        #endregion

        #region Méthodes

 ///
<summary> /// Permet de récupérer les listes (avec toutes les propriétés) du site actuel
 /// </summary>
        /// Collection des listes du site
        public ListCollection GetAllList()
        {
            ListCollection listCollections = null;

            using (ClientContext clientContext = new ClientContext(_webFullUrl))
            {
                listCollections = clientContext.Web.Lists;

                clientContext.Load(listCollections);
                clientContext.ExecuteQuery();
            }

            return listCollections;
        }

        ///
<summary> /// Permet de récupérer les listes (avec uniquement les propriétés spécifiées) du site actuel
 /// </summary>
        ///Filtres d'inclusion à appliquer sur la requete
        /// Collection des listes du site
        public ListCollection GetAllListWithSpecificProperties(Expression> retrievals)
        {
            ListCollection listCollections = null;

            using (ClientContext clientContext = new ClientContext(_webFullUrl))
            {
                listCollections = clientContext.Web.Lists;

                clientContext.Load(listCollections, retrievals);
                clientContext.ExecuteQuery();
            }

            return listCollections;
        }
#endregion
}

Comment l’appeler ?

Le Client Object Model Silverlight n’accepte pas les appels synchrones autrement dit non exécutés dans une autre Thread que celui de l’UI.

SharePointTools sptools = new SharePointTools("http://yoursharepointweburl");
				ThreadPool.QueueUserWorkItem(new WaitCallback((o) =>
				{
					ListCollection lists = sptools.GetAllList();
                    //ou : IEnumerable lists = sptools.GetAllListWithSpecificProperties((l => l.Include(list => list.Title, list => list.TemplateFeatureId, list => list.ImageUrl)));

					Dispatcher.BeginInvoke(() =>
					{
						Lists.ItemsSource = lists;
					});
				}));

Résultat

Il est donc facile de réaliser des WebPart dynamiques en Silverlight avec un minimum d’effort !

SP2010-COM-GetList

Prochainement

Nous verrons plus en détail comment intéragir avec les Listes SharePoint et comment simplifier les appels vers la Client Object Library

[SL5] : Silverlight WPF’s {x:Static} MarkupExtension

Silverlight 5 Beta fournit à présent la classe de base MarkupExtension qui lorsqu’on implémente la méthode ProvideValue(IServiceProvider), permet de définir une MarkupExtension customisée, qui peut être interprétée par le parseur de XAML de Silverlight 5.

Ceci permet de réaliser des tâches qui n’étaient pas possible, plutot complexes ou verbeuse à mettre en place. Par exemple il est à présent possible de réaliser un comportement similaire à ce que l’on connait en WPF tel que {x:Type}, {x:Static}, etc.

Dans ce post nous allons voir comment réaliser l’équivalent de x:Static connu en WPF utilisé dans certains cas comme le référencement de constantes dans le XAML. Les adeptes de PRISM l’utilisent généralement pour nommer les régions suivant des constantes définies dans l’infrastructure du projet.

Généralités

Pour faire une MarkupExtension vous devez implémenter une classe dérivant de l’interface IMarkupExtension<T> ou la classe de base MarkupExtension.


    public class YourMarkupExtension: IMarkupExtension<Object>
    {
        public Object ProvideValue(IServiceProvider serviceProvider)
        {
                throw new NotImplementedException();
        }
    }

Implémentation du comportement de {x:Static}


using System;
using System.Windows.Markup;
using System.Reflection;
using System.Xaml;

namespace SL5MarkupExtension.MarkupExtensions
{
    /// <summary>
    ///  Classe permettant de faire une MarkupExtension XAML pour récupérer les valeurs de champs ou propriétés.
    /// </summary>
    public class Static : IMarkupExtension<Object>
    {
        #region Data
        private String _member;
        private Type _memberType;
        #endregion

        #region Ctors
        /// <summary>
        ///  Crée une instance de la classe permettant de faire une MarkupExtension XAML pour récupérer les valeurs de champs ou propriétés.
        /// </summary>
        public Static()
        {
        }
        #endregion

        #region MarkupExtension implementation
        /// <summary>
        ///  Obtient un objet correspondant au type fournit en paramètre. Permet de récupérer un champ ou une propriété
        /// </summary>
        /// <param name="serviceProvider">Service permettant de récuperer le service utilisé pour les MarkupExtension.</param>
        /// <returns>
        ///  Un objet correspondant à la chaine fournie en paramettre (Member)
        /// </returns>
        public Object ProvideValue(IServiceProvider serviceProvider)
        {
            Object ret = null;
            Boolean typeResolveFailed = true;
            Type type = MemberType;
            String fieldMemberName = null;
            String fullFieldMemberName = null;

            if (Member != null)
            {
                if (MemberType != null)//on a le type et le membre
                {
                    fieldMemberName = Member;
                    fullFieldMemberName = String.Format("{0}.{1}", type.FullName, Member);
                }
                else //on a pas le type, on regarde si la chaine est bien formatée ex : local:MyEnum.MyEnumeValue et on essaie de résoudre le type
                {
                    Int32 index = Member.IndexOf('.');

                    if (index >= 0)
                    {
                        string typeName = Member.Substring(0, index);

                        if (!String.IsNullOrEmpty(typeName))
                        {
                            IXamlTypeResolver xamlTypeResolver = serviceProvider.GetService(typeof(IXamlTypeResolver)) as IXamlTypeResolver;

                            if (xamlTypeResolver != null)
                            {
                                type = xamlTypeResolver.Resolve(typeName);
                                fieldMemberName = Member.Substring(index + 1); //, Member.Length - index - 1
                                typeResolveFailed = String.IsNullOrEmpty(fieldMemberName);
                            }
                        }
                    }
                }

                if (typeResolveFailed)
                {
                    throw new InvalidOperationException("Member");
                }
                else
                {
                    if (type.IsEnum) //si c'est un enum alors on essaie de résoudre le membre
                    {
                        ret = Enum.Parse(type, fieldMemberName, true);
                    }
                    else //ce n'est pas un enum : probablement un champ ou une propriété
                    {
                        Boolean fail = true;

                        FieldInfo field = type.GetField(fieldMemberName, BindingFlags.Public | BindingFlags.FlattenHierarchy | BindingFlags.Static);

                        if (field != null)
                        {
                            fail = false;
                            ret = field.GetValue(null);
                        }
                        else//ce n'est pas un champ, on regarde si c'est une propriété
                        {
                            //on regarde si c'est une propriété
                            PropertyInfo property = type.GetProperty(fieldMemberName, BindingFlags.Public | BindingFlags.FlattenHierarchy | BindingFlags.Static);

                            if (property != null)//c'est une propriété
                            {
                                fail = false;
                                ret = property.GetValue(null, null);
                            }
                        }

                        if (fail)
                        {
                            throw new ArgumentException(fullFieldMemberName);
                        }
                    }
                }
            }
            else
            {
                throw new InvalidOperationException();
            }

            return ret;
        }
        #endregion

        #region Membres
        /// <summary>
        /// Obtient ou définit le membre statique à résoudre ex : local:MyEnum.MyEnumValue si pas de MemberType sinon MyEnumValue
        /// </summary>
        public string Member
        {
            get
            {
                return _member;
            }
            set
            {
                if (value == null)
                {
                    throw new ArgumentNullException("Member value");
                }

                _member = value;
            }
        }

        /// <summary>
        /// Obtient ou définit le type du membre à résoudre
        /// </summary>
        public Type MemberType
        {
            get
            {
                return _memberType;
            }
            set
            {
                if (value == null)
                {
                    throw new ArgumentNullException("MemberType value");
                }

                _memberType = value;
            }
        }
        #endregion
    }
}

Action !

Sample objects

Nous définissons ici une classe Module contenant 3 constantes


public class Modules
    {
        public static readonly string ModuleA = "NewsModule";
        public static readonly string ModuleB = "MediasModule";
        public static readonly string ModuleC = "CalendarModule";
    }

Puis un enum contenant 3 valeurs


    public enum Gender
    {
        Homme,
        Femme,
        Indefini
    }

Mise en pratique

Tout d’abord vous devez référencer votre MarkupExtension et les objets que vous souhaitez référencer.

xmlns:markup="clr-namespace:SL5MarkupExtension.MarkupExtensions"
xmlns:local="clr-namespace:SL5MarkupExtension"

Puis vous n’avez plus qu’à l’utiliser dans votre XAML avec la syntaxe suivante :

{markup:Static Member=local:Modules.ModuleA}

Exemple complet

<UserControl x:Class="SL5MarkupExtension.MainPage"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:markup="clr-namespace:SL5MarkupExtension.MarkupExtensions"
             xmlns:local="clr-namespace:SL5MarkupExtension"
             xmlns:app="clr-namespace:System.Windows;assembly=System.Windows">

    <Grid x:Name="LayoutRoot"
          Background="White">
        <StackPanel VerticalAlignment="Center"
                    HorizontalAlignment="Center">
            <!--CONST-->
            <TextBlock Text="{markup:Static Member=local:Modules.ModuleA}" />
            <TextBlock Text="{markup:Static Member=local:Modules.ModuleB}" />

            <!--CLASS-->
            <TextBlock Text="{Binding Path=InstallState, Source={markup:Static Member=app:Application.Current}}" />

            <!--ENUM-->
            <TextBlock Text="{Binding Source={markup:Static Member=local:Gender.Homme}}" />
        </StackPanel>
    </Grid>
</UserControl>

Résultat

silverlight-x-static-markupextension-like-wpf

Le résultat est très intéressant et ouvre de nouvelles possibilités dans le développement Silverlight :)

Catégories:Silverlight, Silverlight 5, WPF
Suivre

Get every new post delivered to your Inbox.