Accueil Nos publications Blog Visibilité des colonnes d’un Datagrid et MVVM

Visibilité des colonnes d’un Datagrid et MVVM

Afficher et masquer des colonnes sont souvent des opérations basiques demandées par les client et utilisateurs. En effet, un tableau, pour qu’il soit utile, ne doit pas contenir trop de données: le cerveau humain n’arrive pas à traiter un “mur” de chiffres!

Nous allons donc voir dans cet article comment gérer la visibilité des colonnes dans un scénario MVVM.

Le problème

Une colonne dans un datagrid n’est pas un élément visuel: elle n’est pas dans l’arbre visuel. De plus, l’élement Visibility n’est pas une DependencyProperty: pas de binding possible!
Ces deux problèmes empechent donc la gestion de visibilité des colonnes dans un scénario MVVM de manière simple.
Nous allons voir comment passer par les triggers et les triggeractions du SDK Blend pour y arriver.

La solution

Le SDK Blend nous apporte deux namespaces bien utiles:

xmlns:i="https://schemas.microsoft.com/expression/2010/interactivity"
xmlns:ei="https://schemas.microsoft.com/expression/2010/interactions"

Je me suis donc servi de la classe de base TargetedTriggerAction pour écrire ma propre action.
Une TargetedTriggerAction est une action déclenché par un trigger et qui s’appuie sur un objet (la target) pour faire son action.
Ici, nous allons faire une action dont la cible est un Datagrid. Cette action possède également une propriété avec le nom de la colonne devant changer de visibilité et une propriété indiquant la visibilité (j’ai volontairement choisi un booléen mais on peut utiliser l’énumération Visibility pour plus d’homogeneité avec le framework).

using System;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Interactivity;

namespace SilverlightApplication1
{
public class ToggleColumnVisibilityTriggerAction : TargetedTriggerAction
{
public static readonly DependencyProperty ColumnNameProperty = DependencyProperty.Register(
"ColumnName",
typeof(String),
typeof(ToggleColumnVisibilityTriggerAction),
new PropertyMetadata(String.Empty));

public static readonly DependencyProperty IsVisibleProperty = DependencyProperty.Register(
"IsVisible",
typeof(bool?),
typeof(ToggleColumnVisibilityTriggerAction),
new PropertyMetadata(null));

public String ColumnName
{
get
{
return (String)this.GetValue(ColumnNameProperty);
}

set
{
this.SetValue(ColumnNameProperty, value);
if (!String.IsNullOrWhiteSpace(value))
{
var column = Target.Columns.First(c => c.GetValue(FrameworkElement.NameProperty) as String == columnName);
IsVisible = column.Visibility == Visibility.Visible;
}
}
}

public bool? IsVisible
{
get
{
return GetValue(IsVisibleProperty) as bool?;
}
set
{
SetValue(IsVisibleProperty, value);
}
}

protected override void Invoke(object parameter)
{
var columnName = ColumnName;
if (!String.IsNullOrWhiteSpace(columnName))
{
var column = Target.Columns.First(c => c.GetValue(FrameworkElement.NameProperty) as String == columnName);
if (IsVisible.HasValue)
column.Visibility = IsVisible.Value ? Visibility.Visible : Visibility.Collapsed;
else
column.Visibility = column.Visibility == Visibility.Collapsed ? Visibility.Visible : Visibility.Collapsed;
}
}
}
}

Pas de grandes difficultés dans ce code, lors du déclenchement de l’action si le nom de colonne est un chaîne valable, on récupère la colonne et selon si la visibilité est précisée ou non, on applique la nouvelle valeur.

Utilisation

Deux utilisation sont possibles: la première est de binder la propriété IsVisible sur un booléen avec un trigger sur le changement de valeur, la seconde est d’utiliser un bouton pour déclencher l’évènement de bascule (sans préciser la visibilité).

Nous allons voir une application de test:

 xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:sys="clr-namespace:System;assembly=System"
xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="https://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="https://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:sdk="https://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk"
xmlns:i="https://schemas.microsoft.com/expression/2010/interactivity"
xmlns:ei="https://schemas.microsoft.com/expression/2010/interactions"
xmlns:app="clr-namespace:SilverlightApplication1"
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignWidth="400"
DataContext="{Binding RelativeSource={RelativeSource Self}}">

Background="White">

SelectedItem="{Binding Path=SelectedColumn, Mode=TwoWay}" />
<button> Content="toggle">

ColumnName="{Binding Path=SelectedColumn}" />

</button>
IsChecked="{Binding Path=ShowPrice, Mode=TwoWay}" />

Grid.Row="1"
ItemsSource="{Binding Cars}"
AutoGenerateColumns="False">

IsVisible="{Binding Path=ShowPrice}" />

Binding="{Binding Path=Brand}"
Header="Brand" />
Binding="{Binding Path=Color}"
Header="Color" />
Binding="{Binding Path=Price, StringFormat=C2}"
Header="Price (EUR)" />

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows.Controls;

namespace SilverlightApplication1
{
public partial class MainPage : UserControl, INotifyPropertyChanged
{
public ObservableCollection Cars { get; set; }

public List ColumnNames { get; set; }

private String _selectedColumn;
public String SelectedColumn
{
get
{
return _selectedColumn;
}
set
{
_selectedColumn = value;
RaisePropertyChanged("SelectedColumn");
}
}

private bool _showPrice;
public bool ShowPrice
{
get
{
return _showPrice;
}
set
{
_showPrice = value;
RaisePropertyChanged("ShowPrice");
}
}

public MainPage()
{
InitializeComponent();

ColumnNames = new List
{
"Brand",
"Color",
};

Cars = new ObservableCollection
{
new Car
{
Brand = "Aston-Martin",
Color = "Gray",
Price = 95000m
},
new Car
{
Brand = "Porsche",
Color = "Orange",
Price = 55000m
},
new Car
{
Brand = "Ferrari",
Color = "Red",
Price = 100000m
}
};
}

#region INotifyPropertyChanged Members

public event PropertyChangedEventHandler PropertyChanged;

private void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}

#endregion
}
}

On peut voir une Checkbox qui est bindé sur le booléen ShowPrice, ce booléen est “consommé” par la datagrid via un trigger PropertyChangedTrigger: à chaque changement de value du booléen, l’action est déclenchée.
On peut également voir un bouton couplé à une Combobox, ce couple permet de sélectionner la colonne à basculer puis déclencher l’action via un EventTrigger. On remarque que dans ce cas ci on est obligé de préciser que la target de l’action est la datagrid.

Attention dans l’utilisation de cette action: elle n’est pas bi-directionelle! Si la visibilité de la colonne change par un autre moyen, les changements ne seront pas répercutés sur le booléen!