Why can't I style a DataGridTextColumn?
Asked Answered
C

6

42

I tried to create a Style for DataGridTextColumn with the following code

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

However, Visual Studio 2010 highlights {x:Type DataGridTextColumn} with a blue line and elaborates: Exception has been thrown by the target of an invocation.

Why does this happen and how do I fix it?

Collapse answered 13/4, 2010 at 14:25 Comment(3)
Take a look at this link, its like a cheat sheet for styling datagrids: blogs.msdn.microsoft.com/jaimer/2009/01/20/…Islamize
Did Ray's solution work for you? If it did I would love to see some sample code. The part I don't get here is that the TargetType for the style is FrameworkElement, so how could you set a property like CanUserSort=False, for example? CheersDermatome
here is another way... #21983026Industrious
S
29

You can't style the DataGridTextColumn because DataGridTextColumn does not derive from FrameworkElement (or FrameworkContentElement). Only FrameworkElement, etc supports styling.

When you attempt to create a style in XAML for any type that is not a FrameworkElement or FrameworkContentElement you get that error message.

How do you solve this? As with any problem, where there is a will there is a way. In this case I think the easiest solution is to create an attached property for DataGrid to assign a DataGridColumn style:

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

The implementation would be something along these lines:

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);
  }
}

The way this works is, any time the attached property is changed, a handler is added for the Columns.CollectionChanged event on the grid. When the CollectionChanged event fires, all columns are updated with the style that was set.

Note that the above code does not handle the situation where a style is removed and re-added gracefully: Two event handlers are registered. For a really robust solution you would want to fix this by adding another attached property containing the event handler so the event handler could be unregistered, but for your purpose I think this is unimportant.

Another caveat here is that the direct use of SetBinding and SetValue will cause the DependencyProperty to have a BaseValueSource of Local instead of DefaultStyle. This will probably make no difference in your case but I thought I should mention it.

Singlet answered 14/4, 2010 at 20:38 Comment(2)
What about triggers in this case, will they work as usual?Precessional
To make it work had to remove TargetType and instead specify Property="DataGridColumn.Visibility" inside SetterFlimsy
G
11

The style tag has to go in the right place. Your datagrid may look something like this right now:

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

You might initially try to add the style tag directly within the DataGridTextColumn element which will not work. You can however create elements for "DataGridTextColumn.ElementStyle" and or "DataGridTextColumn.EditingElementStyle" just within the "DataGridTextColumn" element. Each of those element tags can then have style tags within them:

    <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>

One style will be applied to viewing and the other will be applied when the cell is in edit mode. Note that it changes from a TextBlock when viewing to a TextBox when editing (This got me at first!).

Grunter answered 7/6, 2016 at 15:38 Comment(0)
H
1

This is more an addition to Ray Burns answer. I first wasn't able to implement it on my own, but with help of mm8 (https://mcmap.net/q/392136/-style-all-datagridtextcolumns-via-attachedproperty) I got it running. Works really fine. For other people who have problems following this attached property approach maybe a full code snippet is helpful.

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: In first approach I did overwrite the whole style. In new version it is still possible to maintain other styles modifications like this one

<DataGridTextColumn.ElementStyle>
    <Style TargetType="{x:Type TextBlock}">
        <Setter Property="Foreground" Value="Red"/>
    </Style>
</DataGridTextColumn.ElementStyle>
Habitat answered 16/10, 2017 at 11:53 Comment(0)
J
0

A small addition to pedrito answer. Everything works fine, but if origStyle has base style, base style's setters get discarded.

To fix this, we need get all setters:

 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;

            var baseSetters = GetBaseSetters(origStyle);
            var allSetters = baseSetters.Concat(origStyle.Setters.OfType<Setter>());

            foreach (var setter in allSetters)
            {
                newStyle.Setters.Add(setter);
            }

            column.ElementStyle = newStyle;
        }
    }

    private static IEnumerable<Setter> GetBaseSetters(Style style)
    {
        return style.BasedOn?.Setters.OfType<Setter>().Concat(GetBaseSetters(style.BasedOn)??new Setter[0]);
    }
Journalist answered 5/6, 2020 at 11:14 Comment(0)
R
-1

More simple:

<FontFamily x:Key="DefaultFont">Snap ITC</FontFamily>
<Style x:Key="ControlStyle" TargetType="Control">
    <Setter Property="FontFamily" Value="{StaticResource DefaultFont}"/>
</Style>
<Style TargetType="{x:Type DataGridCellsPresenter}" BasedOn="{StaticResource ControlStyle}">
</Style>
Reisman answered 3/12, 2015 at 11:38 Comment(1)
This is by no means a general solution to the posed question. Also it would be more helpful if you explained what the intention of your proposed solution is instead of just dropping code.Impish
W
-5

A DataGridTextColumn is nothing but a column with a TextBlock in it. Write a style with the TargetType as TextBlock and bind the ElementStyle property of the DataGridTextColumn to it. Hope that helps!

Width answered 23/4, 2013 at 9:17 Comment(1)
TextBlock isn't a System.Windows.Controls.Control, so this doesn't work.Harber

© 2022 - 2024 — McMap. All rights reserved.