web-dev-qa-db-fra.com

Pourquoi ne puis-je pas styliser un DataGridTextColumn?

J'ai essayé de créer un style pour DataGridTextColumn avec le code suivant

<Style TargetType="{x:Type DataGridTextColumn}">
           ...
</Style>

Cependant, Visual Studio 2010 met en évidence {x:Type DataGridTextColumn} avec une ligne bleue et élabore: Exception has been thrown by the target of an invocation.

Pourquoi cela se produit-il et comment le corriger?

38
Nike

Vous ne pouvez pas styliser le DataGridTextColumn car DataGridTextColumn ne dérive pas de FrameworkElement (ou FrameworkContentElement). Seul FrameworkElement, etc. prend en charge le style.

Lorsque vous essayez de créer un style en XAML pour tout type qui n'est pas un FrameworkElement ou FrameworkContentElement, vous obtenez ce message d'erreur.

Comment résolvez-vous cela? Comme pour tout problème, là où il y a une volonté, il y a un moyen. Dans ce cas, je pense que la solution la plus simple consiste à créer une propriété attachée pour DataGrid pour attribuer un style DataGridColumn:

<DataGrid ...>
  <local:MyDataGridHelper.TextColumnStyle>
    <Style TargetType="FrameworkElement">
      ... setters here ...
    </Style>
  </local:MyDataGridHelper.TextColumnStyle>
  ...

La mise en œuvre serait quelque chose dans ce sens:

public class MyDataGridHelper : DependencyObject
{
  // Use propa snipped to create attached TextColumnStyle with metadata:
  ... RegisterAttached("TextColumnStyle", typeof(Style), typeof(MyDataGridHelper), new PropertyMetadata
  {
    PropertyChangedCallback = (obj, e) =>
    {
      var grid = (DataGrid)obj;
      if(e.OldValue==null && e.NewValue!=null)
        grid.Columns.CollectionChanged += (obj2, e2) =>
        {
          UpdateColumnStyles(grid);
        }
    }
  }
  private void UpdateStyles(DataGrid grid)
  {
    var style = GetTextColumnStyle(grid);
    foreach(var column in grid.Columns.OfType<DataGridTextColumn>())
      foreach(var setter in style.Setters.OfType<Setter>())
        if(setter.Value is BindingBase)
          BindingOperations.SetBinding(column, setter.Property, setter.Value);
        else
          column.SetValue(setter.Property, setter.Value);
  }
}

La façon dont cela fonctionne est, chaque fois que la propriété attachée est modifiée, un gestionnaire est ajouté pour l'événement Columns.CollectionChanged sur la grille. Lorsque l'événement CollectionChanged se déclenche, toutes les colonnes sont mises à jour avec le style qui a été défini.

Notez que le code ci-dessus ne gère pas la situation où un style est supprimé et rajouté avec élégance: Deux gestionnaires d'événements sont enregistrés. Pour une solution vraiment robuste, vous voudriez résoudre ce problème en ajoutant une autre propriété attachée contenant le gestionnaire d'événements afin que le gestionnaire d'événements puisse être non enregistré, mais pour votre objectif, je pense que cela n'a pas d'importance.

Une autre mise en garde ici est que l'utilisation directe de SetBinding et SetValue entraînera la DependencyProperty d'avoir une BaseValueSource de Local au lieu de DefaultStyle. Cela ne fera probablement aucune différence dans votre cas, mais j'ai pensé que je devrais le mentionner.

26
Ray Burns

La balise de style doit aller au bon endroit. Votre grille de données peut ressembler à ceci en ce moment:

    <DataGrid>
        <DataGrid.Columns>
            <DataGridTextColumn />
        </DataGrid.Columns>
    </DataGrid>

Vous pourriez initialement essayer d'ajouter la balise de style directement dans l'élément DataGridTextColumn qui ne fonctionnera pas. Vous pouvez cependant créer des éléments pour "DataGridTextColumn.ElementStyle" et ou "DataGridTextColumn.EditingElementStyle" uniquement dans l'élément "DataGridTextColumn". Chacune de ces balises d'élément peut alors contenir des balises de style:

    <DataGrid>
        <DataGrid.Columns>
            <DataGridTextColumn>
                <DataGridTextColumn.ElementStyle>
                    <Style TargetType="TextBlock">
                        <Setter Property="Background" Value="Green"></Setter>
                    </Style>
                </DataGridTextColumn.ElementStyle>
                <DataGridTextColumn.EditingElementStyle>
                    <Style TargetType="TextBox">
                        <Setter Property="Background" Value="Orange"></Setter>
                    </Style>
                </DataGridTextColumn.EditingElementStyle>
            </DataGridTextColumn>
        </DataGrid.Columns>
    </DataGrid>

Un style sera appliqué à l'affichage et l'autre sera appliqué lorsque la cellule sera en mode édition. Notez qu'il change d'un TextBlock lors de l'affichage à un TextBox lors de l'édition (Cela m'a d'abord donné!).

6
Patrick Graham

C'est plus un ajout à la réponse de Ray Burns. Je n'ai d'abord pas pu l'implémenter par moi-même, mais avec l'aide de mm8 ( https://stackoverflow.com/a/46690951/538162 ) je l'ai fait fonctionner. Fonctionne très bien. Pour d'autres personnes qui ont des problèmes à suivre cette approche de propriété jointe, un extrait de code complet peut être utile.

public class MyDataGridHelper : DependencyObject
{
    private static readonly DependencyProperty TextColumnStyleProperty = DependencyProperty.RegisterAttached("TextColumnStyle", typeof(Style), typeof(MyDataGridHelper), new PropertyMetadata
    {
        PropertyChangedCallback = (obj, e) =>
        {
            var grid = (DataGrid)obj;
            if (e.OldValue == null && e.NewValue != null)
                grid.Columns.CollectionChanged += (obj2, e2) =>
                {
                    UpdateColumnStyles(grid);
                };
        }
    });

    public static void SetTextColumnStyle(DependencyObject element, Style value)
    {
        element.SetValue(TextColumnStyleProperty, value);
    }
    public static Style GetTextColumnStyle(DependencyObject element)
    {
        return (Style)element.GetValue(TextColumnStyleProperty);
    }

    private static void UpdateColumnStyles(DataGrid grid)
    {
        var origStyle = GetTextColumnStyle(grid);
        foreach (var column in grid.Columns.OfType<DataGridTextColumn>())
        {
            //may not add setters to a style which is already in use
            //therefore we need to create a new style merging
            //original style with setters from attached property
            var newStyle = new Style();
            newStyle.BasedOn = column.ElementStyle;
            newStyle.TargetType = origStyle.TargetType;

            foreach (var setter in origStyle.Setters.OfType<Setter>())
            {
                newStyle.Setters.Add(setter);
            }

            column.ElementStyle = newStyle;
        }
    }
}

xaml

<Grid>
    <DataGrid Name="MyDataGrid" ItemsSource="{Binding Lines}" AutoGenerateColumns="False" >
        <local:MyDataGridHelper.TextColumnStyle>
            <Style TargetType="TextBlock">
                <Setter Property="TextWrapping" Value="Wrap"/>
            </Style>
        </local:MyDataGridHelper.TextColumnStyle>
        <DataGrid.Columns>
            <DataGridTextColumn Header="ProductId1" Binding="{Binding Path=Result1}" />
            <DataGridTextColumn Header="ProductId2" Binding="{Binding Path=Result2}" />
        </DataGrid.Columns>
    </DataGrid>
</Grid>

Edit : Dans la première approche, j'ai écrasé tout le style. Dans la nouvelle version, il est toujours possible de conserver d'autres modifications de styles comme celle-ci

<DataGridTextColumn.ElementStyle>
    <Style TargetType="{x:Type TextBlock}">
        <Setter Property="Foreground" Value="Red"/>
    </Style>
</DataGridTextColumn.ElementStyle>
1
pedrito