Archive

Archive for septembre 2009

[SL3] : TextBox UpdateSourceTrigger PropertyChanged connu sur WPF

Fréquemment les développeur WPF utilisent un attribut du Binding permettant d’appliquer le Binding sur la source de donnée au changement de la propriétée de la cible.


<TextBox x:Name="MyTextBox" Text="{Binding Path=LastName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>

Ainsi à chaque fois que la DP Text change on met à jour la source qui n’est autre que la propriétée LastName (de votre ViewModel par exemple). Une solution simple rapide et efficace !

Le problème est que celà n’existe pas encore au sein de Silverlight 3 (certainement dans la version 4 : croisons les doigts !)

Une première solution est de s’abonner à l’évènement TextChanged de la TextBox et de valider le Binding (comme nous devrions le faire si nous spécifions UpdateSourceTrigger=Explicit). Cela donnerai ceci :

Coté XAML


<Window x:Class="WpfApplication1.Window1" 
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<TextBox x:Name="MyTextBox" Text="{Binding Path=LastName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
</Window>

Coté Code-Behind


public partial class Window1 : Window
 {
 public Window1()
 {
 InitializeComponent();

 MyTextBox.TextChanged += new TextChangedEventHandler(OnTextChanged);
 }

 private void OnTextChanged(object sender, TextChangedEventArgs e)
 {
 BindingExpression be = MyTextBox.GetBindingExpression(TextBox.TextProperty);
 be.UpdateSource();
 }
 }

Ceci est fonctionnel mais celà peut devenir rapidement fastidieux si vous avez plusieurs TextBox dans votre écran… Une des solution étant d’utiliser les propriétées attachée pour réaliser ce traitement.

Nous allons donc réaliser une propriété attachée qui définira si l’on souhaite activer la validation du binding au changement de valeur de la textbox :


/// <summary>
 /// Classe fournissant une propriété attachée permettant d'ajouter la fonctionnalité d'UpdateSourceTrigger à PropertyChanged
 /// </summary>
 public class TextBoxTextUpdateSourceTrigger
 {
 #region DP
 /// <summary>
 /// Définit la dependency property attachée UpdateOnPropertyChanged
 /// </summary>
 public static readonly DependencyProperty UpdateOnPropertyChangedProperty =
 DependencyProperty.RegisterAttached(
 "UpdateOnPropertyChanged",
 typeof(Boolean),
 typeof(TextBoxTextUpdateSourceTrigger),
 new PropertyMetadata(OnUpdateOnPropertyChanged));

 /// <summary>
 /// Obtient la valeur de la propriété UpdateOnPropertyChanged
 /// </summary>
 /// <param name="sender">Objet attaché à la propriété</param>
 /// <returns>Valeur courante de la propriété</returns>
 public static Boolean GetUpdateOnPropertyChanged(DependencyObject sender)
 {
 return (Boolean)sender.GetValue(UpdateOnPropertyChangedProperty);
 }

 /// <summary>
 ///  Définit la valeur de la propriété UpdateOnPropertyChanged
 /// </summary>
 /// <param name="sender">Objet attaché à la propriété</param>
 /// <param name="value">Nouvelle valeur à inscrire</param>
 public static void SetUpdateOnPropertyChanged(DependencyObject sender, Boolean value)
 {
 sender.SetValue(UpdateOnPropertyChangedProperty, value);
 }
 #endregion

 #region Handlers
 private static void OnUpdateOnPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
 {
  TextBox textBox = null;
 textBox = sender as TextBox;

 if ((Boolean)e.OldValue) //ancienne valeur de PropertyChanged => true on se désabonne pour evider les effets de bord
 {
 textBox.TextChanged -= OnTextChanged;
 }

 if ((Boolean)e.NewValue)//nouvelle valeur de PropertyChanged => true on s'abonne à l'évènement TextChanged
 {
 textBox.TextChanged += OnTextChanged;
 }
 }

 private static void OnTextChanged(object sender, TextChangedEventArgs e)
 {
 TextBox textBox = sender as TextBox;

 if (textBox != null)
 {
 BindingExpression be = textBox.GetBindingExpression(TextBox.TextProperty);

 if (be != null)
 {
 be.UpdateSource();
 }
 }
}
 #endregion
 }

Enfin afin d’utiliser cette fonctionnalité, rien de plus simple :


<Window x:Class="WpfApplication1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ust="clr-namespace:WpfApplication1">

<TextBox x:Name="MyTextBox"
Text="{Binding Path=LastName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
ust:TextBoxTextUpdateSourceTrigger.UpdateOnPropertyChanged="True"/>
</Window>

Et le tour est joué 🙂

Publicités
Catégories :Silverlight, Silverlight 3

[SL3] : RevealDecorator

Peut être avez vous déjà utilisé le contrôle RevealDecorator que vous pouvez trouver dans Kaxaml (http://kaxaml.codeplex.com/).

Celui-ci permet en quelques clics d’afficher ou de masquer un contrôle grâce à une propriété booléenne “IsExpanded”, mais aussi de définir la direction de révélation.

Malheureusement celui-ci n’est pas compatible en l’état avec Silverlight 3, j’ai donc décidé de le refaire pour qu’il soit utilisable.

Le controle ExpandableContentControl de la librairie Silverlight Toolkit

Cette librairie (que vous pouvez trouver à l’adresse suivante : http://www.codeplex.com/Silverlight) comporte un mine de petits contrôles qui ont pour but de simplifier la vie du développeur mais aussi de fournir une preview des contrôles qui seront implémentés dans les versions futures de Silverlight…

Pour réaliser notre contrôle nous allons dériver l’ “ExpandableContentControl” utilisé dans le Toolkit Silverlight pour réaliser le contrôle d’accordéon “Accordion”.

Celui ci dispose de deux propriétés clés pour nous :

  • une propriété “Percentage” permettant de déterminer le pourcentage d’affichage du contrôle fils (autrement dit si vous mettez 0.5 vous ne verrez que la moitié de celui-ci)
  • une propriété “RevealMode” de type “ExpandDirection” permettant de définir le sens de la révélation ou du masquage
    • Left : vers la gauche
    • Right : vers la droite
    • Up : vers le haut
    • Down : vers le bas

Réalisation du controle

Nous alors crée une classe RevealDecorator héritant de ExpandableContentControl


///
/// Content control affichant ou cachant le contenu suivant une direction et un temps donné
///
public class RevealDecorator : ExpandableContentControl
{
#region Ctors
///
/// Crée une instance d'un content control affichant ou cachant le contenu suivant une direction et un temps donné
///
public RevealDecorator()
{
this.DefaultStyleKey = typeof(ExpandableContentControl);
}
#endregion

}

Nous allons rajouter deux dependency property pour rendre ce contrôle facilement utilisable:

IsExpanded : valeur booléenne définissant si le contenu est affiché ou masqué.
Duration : durée en milliseconde déterminant la durée de l’animation réalisée lors du changement de valeur de la propriété “IsExpanded”

public class RevealDecorator : ExpandableContentControl
{
#region Membres
///
/// Identifie la dependency property IsExpanded
///
private static readonly DependencyProperty IsExpandedProperty =
DependencyProperty.Register(
"IsExpanded",
typeof(Boolean),
typeof(RevealDecorator),
new PropertyMetadata(false));

///
/// Identifie la dependency property Duration
///
public static readonly DependencyProperty DurationProperty =
DependencyProperty.Register(
"Duration",
typeof(Double),
typeof(RevealDecorator),
new PropertyMetadata(250.0));

#endregion

#region Ctors
///
/// Crée une instance d' une ListBox ayant la facultée de sélectionner les DataTemplates de façon personnalisée en fonction de la donnée
///
public RevealDecorator()
{
this.DefaultStyleKey = typeof(ExpandableContentControl);
}
#endregion

#region DP
///
/// Obtient ou définit si le contenu est révélé ou non
///
public Boolean IsExpanded
{
get
{
return (Boolean)GetValue(IsExpandedProperty);
}

set
{
SetValue(IsExpandedProperty, value);
}
}

///
/// Durée de l'animation de revelation
///
public double Duration
{
get
{
return (double)GetValue(DurationProperty);
}
set
{
SetValue(DurationProperty, value);
}
}

#endregion

}

A présent il ne nous reste plus qu’à animer la Dependency Property “Percentage” au changement de valeur de “IsExpanded” afin d’avoir un effet d’apparition et de masquage animé.


public class RevealDecorator : ExpandableContentControl
{
#region Membres
///
/// Identifie la dependency property IsExpanded
///
private static readonly DependencyProperty IsExpandedProperty =
DependencyProperty.Register(
"IsExpanded",
typeof(Boolean),
typeof(RevealDecorator),
new PropertyMetadata(false, new PropertyChangedCallback(OnIsExpandedChanged)));

///
/// Identifie la dependency property Duration
///
public static readonly DependencyProperty DurationProperty =
DependencyProperty.Register(
"Duration",
typeof(Double),
typeof(RevealDecorator),
new PropertyMetadata(250.0));

#endregion

#region Ctors
///
/// Crée une instance d' une ListBox ayant la facultée de sélectionner les DataTemplates de façon personnalisée en fonction de la donnée
///
public RevealDecorator()
{
this.DefaultStyleKey = typeof(ExpandableContentControl);
}
#endregion

#region DP
///
/// Obtient ou définit si le contenu est révélé ou non
///
public Boolean IsExpanded
{
get
{
return (Boolean)GetValue(IsExpandedProperty);
}

set
{
SetValue(IsExpandedProperty, value);
}
}

///
/// Durée de l'animation de revelation
///
public double Duration
{
get
{
return (double)GetValue(DurationProperty);
}
set
{
SetValue(DurationProperty, value);
}
}

#endregion

#region Méthodes
private static void OnIsExpandedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((RevealDecorator)d).ExpandCollapseAnimation((bool)e.NewValue);
}

private void ExpandCollapseAnimation(Boolean isExpanded)
{
double currentProgress = this.Percentage;

if (isExpanded)
{
currentProgress = 1.0 - currentProgress;
}

DoubleAnimation animation = new DoubleAnimation();
animation.To = isExpanded ? 1.0 : 0.0;
animation.Duration = TimeSpan.FromMilliseconds(Duration * currentProgress);
animation.EasingFunction = new PowerEase()
{
EasingMode = EasingMode.EaseInOut,
Power = 4
};

Storyboard.SetTarget(animation, this);
Storyboard.SetTargetProperty(animation, new PropertyPath(RevealDecorator.PercentageProperty));

Storyboard s = new Storyboard();

s.Children.Add(animation);

s.Begin();

}
#endregion

}

Utilisation du contrôle

<UserControl xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="SilverlightApplication3.MainPage"
xmlns:reveal="clr-namespace:SilverlightApplication3">

<Grid>

<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition />
</Grid.RowDefinitions>

<CheckBox Content="Afficher/Cacher"
x:Name="ExpandCollapse" />
<reveal:RevealDecorator VerticalAlignment="Top"
IsExpanded="{Binding Path=IsChecked, ElementName=ExpandCollapse}"
Duration="400"
Grid.Row="1">
<Rectangle Fill="#FFF20000"
Stroke="Black"
Height="100"
Width="300" />
</reveal:RevealDecorator>
</Grid>

</UserControl>

Et le tour est joué !

Catégories :Silverlight 3

[WPF] : First step into binding

5 septembre 2009 1 commentaire

Le binding est une notion qui existait déjà en WinForm mais était relativement limité. En WPF il en est tout autrement : nous disposons d’un outil extrêmement puissant… à condition de savoir l’utiliser mais aussi de savoir ce qu’il permet !

Si je devais résumer ce qu’est le DataBinding en quelque mots voici comment je le définirais : le DataBinding de WPF fournit un moyen simple et efficace d’interagir avec les données. c’est un terme anglais désignant l’action de lier des éléments entre eux. Les éléments peuvent être attachés à des données diverses et variées telles que du XML, la CLR mais aussi des autres éléments…

Au travers de ce post nous allons voir tout ce que nous propose le Binding de WPF.

Path

Définit concrètement dans quelle propriété on souhaite récupérer la valeur.


<TextBlock Text="{Binding Path=NomDeLaPropriete}"/>

Ainsi dans ce cas vous vous récupérez la valeurs de NomDeLaPropriete pour l’appliquer en tant que texte du contrôle TextBlock.

ElementName

Permet de définir que la source de données sur laquelle on souhaite effectuer le binding. Ici par exemple nous écoutons la valeur saisie dans la textbox via la propriété Text de la TextBox que l’on affecte à la propriété Text du TextBlock :


<StackPanel>
<TextBlock Text="{Binding Path=Text, ElementName=LaTextBox}"/>
<TextBox x:Name="LaTextBox"/>
</StackPanel>

Converter

Permet de définir un convertisseur de données à appliquer sur la donnée initiale pour effectuer une transformation et appliquer cette valeur et non pas celle initiale.

Vous devez déclarer une classe héritant de IValueConverter et redéfinir Convert et ConvertBack


public class VotreConverter : IValueConverter

{

public object Convert(object value,
		      Type targetType,
		      object parameter,
		      CultureInfo culture)

{

*/convertissez ici votre valeur initiale (ex un booléen que vous
 devriez convertir en texte du genre si true
 alors “Masculin”, si false ”Féminin”)  donnerait :*/

if(value is bool)

{

if((bool)value)

return “Masculin”;

else

return “Féminin”;

}

return “Valeur inconnue”;

}

public object ConvertBack(object value,
			  Type targetType,
			  object parameter,
			  CultureInfo culture)

{

/*ici si vous le pouvez reconvertissez votre valeur
convertie en valeur initiale, sinon lancer une exception par exemple.*/

}

}

FallbackValue

Définit une valeur à utiliser dans le cas où le binding ne peut renvoyer une valeur, ainsi par exemple si le binding ne trouve pas la propriétée « NomDeLaPropriete » alors il appliquera la valeur « valeur_par_defaut_ici» au texte du TextBlock.


<TextBlock Text="{Binding Path=NomDeLaPropriete,
			  FallbackValue='valeur_par_defaut_ici'}" />

Mode

Définit la direction du binding. En WPF vous avez la possibilité d’affiner le comportement du binding en lui définissant un mode. Toutes les propriétés des éléments du framework n’ont pas forcement le même type de binding et donc il est conseillé de vérifier le sens par défaut des propriétés des éléments.

Default

Utilise le sens de binding par défaut de la propriété.

OneWay

De la source vers la destination, si la source change : la destination aussi !

Source >> Destination

OneWayToSource

N’est autre que l’inverse de OneWay, cette fois ci c’est la destination qui affecte la valeur à la source.

Destination >> Source

OneTime

Le binding de la source vers la destination s’effectue une seul fois puis, si la source change la valeur n’est pas appliquée à la destination. On utilise OneTime généralement quand vos objets sont uniquement en mode visualisation et afin d’améliorer les performances du fait que le binding n’écoute pas inutilement la source de données.

Source > 1 fois > Destination

TwoWay

Les changement de la source sont effectués sur la destination et vice et versa, si la destination change la valeur, la source est également modifiée !!

Source >><< Destination

RelativeSource

Utilisé pour définir une source de données en spécifiant un élément qui est positionné relativement par rapport à l’élément courant qui souhaite faire le binding.

Le propriétée RelativeSource du binding est utilisé pour binder une donnée provenant d’un élément par sa relation avec l’élément source du déclanchement du binding.

Il existe plusieurs façons d’utiliser cette propriété donc les plus communes sont las suivantes :

* Quand l’élément source est égal à la cible (utile pour se binder sois même) et éviter d’avoir à donner un nom à l’élément et utiliser ElementName :


<CheckBox Content="{Binding Path=IsChecked,
			    RelativeSource={RelativeSource Self}}" />

* Quand l’élément source est égal au Template utilisé pour l’élément. Utilisé fréquemment lors dans les styles :


{Binding Path=Proprietee,
	 RelativeSource={RelativeSource TemplatedParent}}

* Quand la l’élément source est égal au plus proche des parents d’un type donné (ici renvoie Grille1)


<Grid x:Name="Grille1">
<StackPanel>
<TextBlock Text="{Binding Path=Name,
        RelativeSource={RelativeSource Mode=FindAncestor,
                                       AncestorType={x:Type Grid}}}" />
</StackPanel>
</Grid>

* Quand vous souhaitez le nième parent d’un type donné ex le deuxième parent du type Grid (ayant le nom Grille1 et non Grille2 comme dans l’exemple précédent)


<Grid x:Name="Grille1">
<Grid x:Name="Grille2">
<StackPanel>
<TextBlock Text="{Binding Path=Name,
			  RelativeSource={RelativeSource Mode=FindAncestor,
					  AncestorType={x:Type Grid},
					  AncestorLevel=2}}" />
</StackPanel>
</Grid>
</Grid>

Source

Utilisé quand ou veut utiliser un objet source à utiliser pour effectuer le binding (et non pas un élément)

StringFormat

Nouveauté apportée dans le .Net 3.5 SP1 qui nous permet de formater la donnée bindée. Avant le SP1 il fallait créer son propre Converter se chargeant d’effectuer un String.Format…


<CheckBox Content="{Binding Path=IsChecked,
			    RelativeSource={RelativeSource Self},
			    StringFormat='La valeur de IsChecked est : {0} !'}" />

Ainsi dans le texte de la CheckBox on verra apparaitre la phrase : “La valeur de IsChecked est : True !!!” ou False suivant la valeur de IsChecked.

Si l’on souhaite utiliser plusieurs valeurs dans le StringFormat il faudra utiliser un MultiBinding comme ici par exemple ou l’on souhaite faire une phrase avec la valeur saisie dans deux textbox :


<StackPanel>
   <TextBlock>
      <TextBlock.Text>
         <MultiBinding StringFormat="Textbox1 : ({0}), Textbox2 : ({1})">
            <Binding ElementName="LaTextBox1"
                     Path="Text" />
            <Binding ElementName="LaTextBox2"
                     Path="Text" />
            </MultiBinding>
       </TextBlock.Text>
   </TextBlock>
   <TextBox x:Name="LaTextBox1" />
   <TextBox x:Name="LaTextBox2" />
</StackPanel>

UpdateSourceTrigger

Définit les evénements sur lequels le binding va s’effectuer. Généralement le binding TwoWay et OneWayToSource propagent les nouvelles valeurs à chaque fois que la propriété sur laquelle le binding est appliqué sur la propriété cible. Toutefois dans certains cas (performances, annulations, actions, etc.) nous avons besoin de déterminer la façon dont va s’appliquer le binding…

Prenons l’exemple d’une TextBox dont voici le XAML de base


<TextBox Text="{Binding Path=NomDeLaPropriete,
			Mode=TwoWay}"/>

Mode=TwoWay n’est pas obligatoire car il s’agit du mode par défaut de la Dependency Property TextProperty de la TextBox.

LostFocus


<TextBox Text="{Binding Path=NomDeLaPropriete,
			Mode=TwoWay,
			UpdateSourceTrigger=LostFocus}"/>

Dans ce cas la valeur entrée dans la TextBox sera propagée dans la propriétée NomDeLaPropriete une fois que la TextBox perd le focus et uniquement dans ce cas !

PropertyChanged


<TextBox Text="{Binding Path=NomDeLaPropriete,
			Mode=TwoWay,
			UpdateSourceTrigger=PropertyChanged}"/>

Dans ce cas la valeur entrée dans la TextBox sera propagée dans la propriété NomDeLaPropriete à chaque fois que vous tapez un caractère dans la TextBox ! Attention donc si vous devez faire des traitements long à chaque fois que la valeur change…

Explicit


<TextBox x:Name="LaTextBox"
	 Text="{Binding Path=NomDeLaPropriete,
			Mode=TwoWay,
			UpdateSourceTrigger=Explicit}"/>

Dans ce cas la valeur entrée dans la TextBox sera propagée dans la propriété NomDeLaPropriete quand le développeur le souhaitera ! Pour cela vous devez utiliser la syntaxe suivante :


BindingExpression binding = null;
binding = LaTextBox.GetBindingExpression(TextBox.TextProperty);
binding.UpdateSource();

Et là la valeur que l’utilisateur aura saisi dans la textbox sera propagée dans la propriété NomDeLaPropriete.

ValidationRules

Définit une collection de règles de validation appliquées sur la valeur du binding. Nous en reparlerons dans un autre article.

IsAsync

Cette propriétée est interessante dans le cas de propriétées qui sont couteuse en temps

Il faut utiliser cette propriété lorsque la propriétée source du binding prend un peu de temps. L’exemple le plus commun est une propriété image avec un “accesseur get” qui télécharge une image depuis le web… Mettre IsAsync=True empèche que la fenêtre soit bloquée pendant que le téléchargement s’effectue en réalisant cette tâche de façon asynchrone.


<Image Source="{Binding Path=ProprieteeImageLente,
			IsAsync=True" />

Catégories :WPF

Bienvenue sur mon blog !

Bonjour !

Bienvenue sur mon blog professionnel, je publierai ici quelques articles relatifs aux technologies que j’utilise à l’ongueur de journée dans mon travail.

Il sera question des technologies WPF (Windows Presentation Fundation) ainsi que son petit frère connu sous le nom de Silverlight.

A bientôt !

Catégories :Uncategorized