ForceColumnBoundary AttachedProperty
Aucun commentaire

J'ai récemment eu besoin d'un Datagrid avec lignes dépliables, c'est une demande classique de nos clients et des éditeurs comme Telerik propose cette fonctionnalité en standard. Toutefois les clients ne souhaite pas toujours dépenser 1000$ de licence. Du coup, je suis parti d'une ListBox que j'ai configuré pour qu'elle prenne la forme d'un Datagrid, déplier une ligne se révèle assez simple dans ce cas. Pourtant je suis tombé sur un bug du Panel Grid auquel je ne m'attendais pas vraiment. Chaque colonne de ma grille au sein de mon ItemTemplate possède une valeur relative, chacune d'entre elles possède un FrameworkElement dans le cas présent un TextBlock.
Toutefois lorsque le contenu d'un TextBlock (par exemple) au sein d'une colonne est trop important, cela décale chaque colonne, du coup on perd l'effet Datagrid. Ce comportement du Layout est illogique si l'on considère qu'en mode Stretch horizontal, chaque TextBlock ne devrait pas dépasser la largeur de la colonne dans laquelle il se trouve. Le résultat est affiché ci-dessous.
Pour remédier à cela, j'ai codé une propriété attachée qui force chaque élément au sein d'une colonne à respecter les limites de cette dernière. Le code est relativement simple :
class FrameworkElementExtension { private static Dictionary<frameworkelement , GridLength> FrameworkElementToGridLength = new Dictionary</frameworkelement><frameworkelement , GridLength>(); public static bool GetForceColumnBoundary(DependencyObject obj) { return (bool)obj.GetValue(ForceColumnBoundaryProperty); } public static void SetForceColumnBoundary(DependencyObject obj, bool value) { obj.SetValue(ForceColumnBoundaryProperty, value); } // Using a DependencyProperty as the backing store for EnsureColumnBoundary. This enables animation, styling, binding, etc... public static readonly DependencyProperty ForceColumnBoundaryProperty = DependencyProperty.RegisterAttached("ForceColumnBoundary", typeof(bool), typeof(FrameworkElement), new PropertyMetadata(new PropertyChangedCallback(OnEnsureColumnBoundary))); private static void OnEnsureColumnBoundary(DependencyObject d, DependencyPropertyChangedEventArgs e) { bool b = System.Convert.ToBoolean(e.NewValue); FrameworkElement fe = d as FrameworkElement; if (fe == null) { return; } Grid g = fe.Parent as Grid; ColumnDefinition cd = g.ColumnDefinitions[Grid.GetColumn(fe)]; if (g == null || cd == null || DesignerProperties.GetIsInDesignMode(fe)) { return; } if (b) { if (!FrameworkElementToGridLength.ContainsKey(fe)) { FrameworkElementToGridLength.Add(fe, cd.Width); g.SizeChanged -= new SizeChangedEventHandler(g_SizeChanged); g.SizeChanged += new SizeChangedEventHandler(g_SizeChanged); } } else { FrameworkElementToGridLength.Remove(fe); g.SizeChanged -= new SizeChangedEventHandler(g_SizeChanged); } } static void g_SizeChanged(object sender, SizeChangedEventArgs e) { Grid g = sender as Grid; foreach (FrameworkElement fe in g.Children) { bool b = GetForceColumnBoundary(fe); if (b) { GridLength gl = FrameworkElementToGridLength[fe]; ColumnDefinition cd = g.ColumnDefinitions[Grid.GetColumn(fe)]; cd.Width = gl; fe.Width = gl.Value*g.ActualWidth; } } } } </frameworkelement>
Le résultat est immédiat et fonctionne qu'elle soit le type de colonne dans votre DataGrid.

Il suffit d'appliquer la propriété attachée à l'objet que vous souhaitez contraindre dans votre grille de cette manière :
Text="{Binding Client}" TextWrapping="Wrap" Grid.Column="1"
local:FrameworkElementExtension.ForceColumnBoundary="True"

