mi-Août 2008, un nouveau contrôle a été mis à disposition sur CodePlex en même temps que le SP1 pour la .Net Framework 3.5 et Visual Studio 2008.
Pour le moment, il s'agit d'un CTP qui est téléchargeable séparément sur CodePlex, mais sera vraissemblablement inclus dans les prochaines versions de la framework. Les sources sont disponibles, ainsi qu'une librairie binaire. .Net 3.5 SP1 est obligatoire pour utiliser ce contrôle.
De nombreux articles proposent des tutoriels pour se familiariser avec ce contrôle, cet article n'a donc pas cette vocation.
Cependant, nous allons voir comment résoudre un problème précis, et en profiter pour découvrir un nouvel outil : Snoop.
DataGrid permet une chose fort sympathique : le style alternatif, histoire de changer une ligne sur deux le style d'une ligne (DataGridRow).
Manque de bol : On s'apperçoit, lorsqu'on définit un Background ou un AlternativeBackground, que les lignes sont colorées de bout en bout d'un contrôle, alors que la surbrillance (Highlighting) pour mettre en évidence les éléments sélectionnés s'arrêtent au niveau de la dernière colonne, provocant ainsi une inconsistence : une ligne avec une couleur de fond qui va jusqu'à la fin de la "colonne de padding" (La dernière colonne vide sans en-tête ni contenu) alors que la sélection s'arrête à la fin de la dernière colonne valide.
Notre but ici est de mettre en surbrillance toute la ligne.
Première tentative :
Ajouter la définition de propriété suivante dans le tag de DataGrid :
SelectionUnit="FullRow"
Malheureusement, ça n'a pas l'effet escompté : On sélectionne effectivement toute la ligne, au lieu d'une seule cellule, mais la sélection ne se propage pas jusque sur la colonne de padding.
Seconde tentative :
Le reflexe suivant que toute personne sensée aurait serait d'utiliser un Trigger pour changer le Background de la DataGridRow lorsqu'elle est sélectionnée. Là encore : chou blanc : en analysant le code de DataGrid on s'apperçoit qu'il y a un CoerceValue sur la couleur de Background appliquée sur notre DataGridRow : il faut donc trouver un auter endroit où changer cette couleur.
C'est ici que nous nous trouvons à cours de "réflexes". Il est l'heure de sortir la grosse artillerie : Snoop. ( ou Mole, pour ceux qui préfèrent. Personnellement, ma préférence va pour Snoop.
Troisième tentative
Snoop nous permet de voir l'arbre visuel (VisualTree) de notre application, et par conséquent, de noter contrôle DataGrid. En se balladant dans l'arborescence on peut voir où se trouve les éléments graphiques qui représentent notre sélection.
Il y a probablement une multitude de façons différentes de règler ce problème. Heureusement, nous n'en avons besoin que d'une seule. L'approche que j'ai choisie ici est de modifier l'élément qui est chargé de présenter les cellules pour une ligne donnée : DataGridCellsPresenter. Comme nous sommes très chanceux, et que le code de notre contrôle DataGrid est disponible sur CodePlex, nous pouvons sans autre visualiser le template défini pour ce type dans le fichier generic.xaml (ou le .xaml relatif au thème sur lequel on veut se baser) du projet de DataGrid. Comme nous sommes vraiment très chanceux : tout se trouve dans generic.xaml : Les templates sont les mêmes pour tous les thèmes, ce qui nous évitera de devoir surdéfinir plusieurs templates pour couvrir tous les thèmes.
Petite parenthèse : Si nous avions voulu faire ce travail sur un contrôle de la framework .Net il est possible de retrouver sur la MSDN ou, plus simple, dans le programme XamlHack les templates utilisés par les widgets créés par Microsoft. Si le contrôle est un contrôle tiers sans les sources : Il sera peut être possible de s'amuser avec Reflector, et de reconstruire le xaml à partir du baml se trouvant dans les resources de l'assembly du contrôle, mais ceci dépasse le cadre de cet article.
Résolution du problème
Maintenant que nous avons identifié le type dont nous voulons changer le Template, il nous suffit de copier coller la définition existante : Elle se trouve sous forme de ressources dans un ResourceDictionary, que nous pouvons incorporer dans notre Windows.Resources ou tout autre dictionnaire de ressources accessible par notre DataGrid... et de la modifier selon nos désirs, évidemment.
Après quelques essais à le tripoter, le template est passé de :
<Style x:Key="{x:Type dg:DataGridCellsPresenter}" TargetType="{x:Type dg:DataGridCellsPresenter}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type dg:DataGridCellsPresenter}">
<ItemsPresenter />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
à
<Style x:Key="{x:Type toolkit:DataGridCellsPresenter}" TargetType="{x:Type toolkit:DataGridCellsPresenter}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type toolkit:DataGridCellsPresenter}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<ItemsPresenter />
<Border Grid.Column="1">
<Border.Style>
<Style TargetType="{x:Type Border}">
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=toolkit:DataGridRow}, Path=IsSelected}" Value="True">
<DataTrigger.Setters>
<Setter Property="Background" Value="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}" />
</DataTrigger.Setters>
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
</Border>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
L'idée est d'avoir un élément qui prendra toute la place disponnible (Grid) , d'y placer tout à gauche ( Première colonne ) L'élément dont la taille est faite pour correspondre aux headers des colonnes (Ce qui nous permet d'avoir une largeur de colonne de Auto pour la première) et de placer dans la seconde colonne, un élément qui coloriera le "reste" de l'espace, ici un Border.
Pour le changement de couleurs, on utilise la même approche que lors de la seconde tentative, mais puisque notre Border se trouve au dessus des DataGridRow, et que son Background n'est pas forcé à l'aide d'un CoercevalueCallback, la ligne apparaîtra comme en surbrillance sur toute la largeur du contrôle.
Mission accomplie, et ce, sans créer un nouveau contrôle ! Merci WPF, Merci les Templates !
0df418e9-7735-48a1-964e-695b42f15034|0|.0