Danish Khan
Danish Khan

Reputation: 1891

Enable TextWrap in dynamic DataGrid

I am generating a DataGrid dynamically and adding it to a StackPanel on my WPF application.

As the is dynamically generated, there is no mark up on XAML side for the same and I need to manage the binding and all properties programatically.

I want my DataGrid to have the values in the cell wrapped to the next line if the text is lengthy. I understand that I need to replace the DataGridCell with TextBlock and set the TextWrap property on it. All the examples that I have found suggest something on those lines itself. However, I couldn't find a way to do it completely from code behind, without XAML.

So far, I have tried to the following code, but it doesn't work.

DataGrid dg = new DataGrid();   

dg.ItemsSource = ((DataSet)data).Tables[0].DefaultView;
dg.DataContext = ((DataSet)data).Tables[0].DefaultView; 

DataTemplate ct = new DataTemplate(typeof(DataGridCell));
FrameworkElementFactory tb = new FrameworkElementFactory(typeof(TextBlock));
tb.SetValue(TextBlock.TextWrappingProperty, TextWrapping.Wrap);
ct.VisualTree = tb;

dg.ItemTemplate = ct;
dg.ColumnWidth = 300;

Can you please point me to the right direction here?

[Update]: Solution

On further researching I was able to get a solution to my issue. For Auto generated columns, we need to capture the AutoGeneratingColumn event and replace the default DataGridTextColumn by a DataGridTemplateColumn which would have a TextBlock in it. And we can then set the `TextWrappingProperty' to get the text wrapped.

Following is the updated code:

DataGrid dg = new DataGrid();   

dg.ItemsSource = ((DataSet)data).Tables[0].DefaultView;
dg.DataContext = ((DataSet)data).Tables[0].DefaultView; 

DataTemplate ct = new DataTemplate(typeof(DataGridCell));
FrameworkElementFactory tb = new FrameworkElementFactory(typeof(TextBlock));
tb.SetValue(TextBlock.TextWrappingProperty, TextWrapping.Wrap);
ct.VisualTree = tb;

dg.AutoGeneratingColumn += new EventHandler<DataGridAutoGeneratingColumnEventArgs>(dg_AutoGeneratingColumn);

dg.MaxColumnWidth = 300;

and then the Code under the Event Handler:

 private void dg_AutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e)
        {
        //cancel the auto generated column
        e.Cancel = true;

        //Get the existing column
        DataGridTextColumn dgTextC = (DataGridTextColumn)e.Column;

        //Create a new template column 
        DataGridTemplateColumn dgtc = new DataGridTemplateColumn();

        DataTemplate dataTemplate = new DataTemplate(typeof(DataGridCell));

        FrameworkElementFactory tb = new FrameworkElementFactory(typeof(TextBlock));
        tb.SetValue(TextBlock.TextWrappingProperty, TextWrapping.Wrap);
        dataTemplate.VisualTree = tb;

        dgtc.Header = dgTextC.Header;
        dgtc.CellTemplate = dataTemplate;

        tb.SetBinding(TextBlock.TextProperty, dgTextC.Binding);

        //add column back to data grid
        DataGrid dg = sender as DataGrid;
        dg.Columns.Add(dgtc);
    }

Upvotes: 2

Views: 3728

Answers (2)

HedgeHogFace
HedgeHogFace

Reputation: 106

An alternate approach is to use a behaviour like this.

  public class DataGridWrapTextBehaviour : Behavior<DataGrid>
  {
     private DataGrid DataGrid
     {
        get { return AssociatedObject as DataGrid; }
     }

     private Style ElementStyle { get; set; }
     private Style EditingElementStyle { get; set; }

     protected override void OnAttached()
     {
        base.OnAttached();

        this.ElementStyle = new Style( typeof( TextBlock ) );
        this.ElementStyle.Setters.Add( new Setter( TextBlock.TextWrappingProperty, TextWrapping.Wrap ) );

        this.EditingElementStyle = new Style( typeof( TextBox ) );
        this.EditingElementStyle.Setters.Add( new Setter( TextBox.TextWrappingProperty, TextWrapping.Wrap ) );

        this.DataGrid.Columns.CollectionChanged += Columns_CollectionChanged;
     }

     protected override void OnDetaching()
     {
        this.DataGrid.Columns.CollectionChanged -= Columns_CollectionChanged;
        base.OnDetaching();
     }

     private void Columns_CollectionChanged( object sender, NotifyCollectionChangedEventArgs e )
     {
        foreach ( var column in this.DataGrid.Columns.OfType<DataGridTextColumn>() )
        {
           column.ElementStyle = this.ElementStyle;
           column.EditingElementStyle = this.EditingElementStyle;
        }
     }
  }

You can then drap and drop the behaviour onto the DataGrid in Expression Blend.

Upvotes: 4

Vinit Sankhe
Vinit Sankhe

Reputation: 19885

First thing I must say is that you are using the datagrid in a wrong manner.

 dg.ItemTemplate = ct; 

is a wrong code!

WPF DataGrid does not entertain ItemTemplate property as other ItemsControls do. You would have to supply column templates in DataGrid for it to work correctly.

When DataGridBoundColumn \ DataGridTextColumn columns are supplied to the DataGrid , you could set their ElementStyle property like this...

        <toolkit:DataGrid
                 AutoGenerateColumns="False">                
            <toolkit:DataGrid.Columns>
                <toolkit:DataGridTextColumn 
                         Binding="{Binding SomeProperty}">
                    <toolkit:DataGridTextColumn.ElementStyle>
                        <Style TargetType="{x:Type TextBlock}">
                            <Setter Property="TextWrapping"
                                    Value="Wrap"/>
                        </Style>
                    </toolkit:DataGridTextColumn.ElementStyle>
                </toolkit:DataGridTextColumn>
            </toolkit:DataGrid.Columns>
        </toolkit:DataGrid>     

... where toolkit is namespace of the WPF toolkit in version .Net 3.5 or prior. In .Net 4.0, its part of the standard System.Windows.Controls namespace.

But that exact same solution is a little complicated in your case is because your grid has AutoGenerateColumns as true by default that generates the DataGrid.Columns automatically. So you have no scope to set this ElementStyle property.

So we have to take the XML and code behind approach ...

XAML:

       <toolkit:DataGrid x:Name="dg">
            <toolkit:DataGrid.Resources>
                <Style TargetType="{x:Type TextBlock}"
                       x:Key="WrappedTextBlockStyle">
                    <Setter Property="TextWrapping" Value="Wrap"/>
                </Style>
            </toolkit:DataGrid.Resources>                
        </toolkit:DataGrid>

Code Behind:

    dg.ItemsSource = ((DataSet)data).Table[0].DefaultView;
    dg.Columns.CollectionChanged
       += new NotifyCollectionChangedEventHandler(
              Columns_CollectionChanged);

    void Columns_CollectionChanged(
         object sender, NotifyCollectionChangedEventArgs e)
    {
        if (e.NewItems.Count > 0)
        {
            foreach(DataGridColumn col in e.NewItems)
            {
                if (col is DataGridTextColumn)
                {
                    ((DataGridTextColumn) col).ElementStyle
                         = dg.Resources["WrappedTextBlockStyle"] as Style;
                }
            }
        }
    }

Hope this helps...

Note: A word of caution that wrapping textblocks make the performance of WPF dataGrid slower.

Upvotes: 0

Related Questions