How to generate my columns via code

Jan 22, 2009 at 8:36 PM

Hello,

   I have the current layouting problem. The general datagrid layout is a "fixed" list of column displaying a variable count of row items. For presentation purposes, I would need to have a "fixed" list of rows displaying a variable count of columns. The general principle, databind, row event handling doesn't change. Rows are just oriented vertically.

  Would there be a styling trick to re-layout the datagrid this way ?

SBS

Jan 23, 2009 at 10:52 AM
Sounds like fun ... I like a challenge!

The following XAML uses a layout transform to rotate the grid by 90 degrees. However, this causes a problem, all the cell contents are rotate by 90 degrees as well!

To overcome this, I have modified the control templates for both the cell and column header to rotate by -90 degrees 'canceling' out the DataGrid transformation.

If you want to know a bit more about render and layout transformations, have a look at this blog post:
http://www.scottlogic.co.uk/blog/wpf/2008/12/layouttransform-vs-rendertransform-whats-the-difference/

<Window x:Class="WPFDataGridStyling.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:dg="http://schemas.microsoft.com/wpf/2008/toolkit"
    Title="Window1" Height="300" Width="400">
    
    <Window.Resources>
        <XmlDataProvider x:Key="NewsRSS"
                     Source="http://newsrss.bbc.co.uk/rss/newsonline_uk_edition/front_page/rss.xml"  
                     XPath="//item" />

        <CollectionViewSource  x:Key="NewsRSSSource" Source="{Binding Source={StaticResource NewsRSS}}">
            <CollectionViewSource.GroupDescriptions>
                <PropertyGroupDescription PropertyName="author"/>
            </CollectionViewSource.GroupDescriptions>
        </CollectionViewSource>     
    </Window.Resources>
    
    <Grid Binding.XmlNamespaceManager="{StaticResource map}">       
        <dg:DataGrid Name="dataGrid" AutoGenerateColumns="False" ItemsSource="{Binding Source={StaticResource NewsRSSSource}}">
            
            <!-- rotate the DataGrid by 90 degrees -->
            <dg:DataGrid.LayoutTransform>
                <RotateTransform CenterX="200" CenterY="150" Angle="90"/>
            </dg:DataGrid.LayoutTransform>
            
            <dg:DataGrid.CellStyle>
                <Style TargetType="{x:Type dg:DataGridCell}">
                    <Setter Property="Template">
                        <Setter.Value>
                            <ControlTemplate TargetType="{x:Type dg:DataGridCell}">
                                <Border Background="{TemplateBinding Background}"
                                          BorderBrush="{TemplateBinding BorderBrush}"  
                                          BorderThickness="{TemplateBinding BorderThickness}"
                                          SnapsToDevicePixels="True">
                                <ContentPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}">
                                    <!-- rotate the cell contents by -90 degrees -->
                                    <ContentPresenter.LayoutTransform>
                                        <RotateTransform Angle="-90"/>
                                    </ContentPresenter.LayoutTransform>
                                </ContentPresenter>
                            </Border>
                            </ControlTemplate>
                        </Setter.Value>
                    </Setter>                                    
                </Style>
            </dg:DataGrid.CellStyle>
            
            <dg:DataGrid.ColumnHeaderStyle>
                <Style TargetType="{x:Type dg:DataGridColumnHeader}">
                    <Setter Property="Template">
                        <Setter.Value>
                            <ControlTemplate TargetType="{x:Type dg:DataGridColumnHeader}">
                                <Grid>
                                    <dg:DataGridHeaderBorder SortDirection="{TemplateBinding SortDirection}"
                                     IsHovered="{TemplateBinding IsMouseOver}" IsPressed="{TemplateBinding IsPressed}"
                                     IsClickable="{TemplateBinding CanUserSort}" Background="{TemplateBinding Background}"
                                     BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}"
                                     Padding ="{TemplateBinding Padding}" SeparatorVisibility="{TemplateBinding SeparatorVisibility}"
                                     SeparatorBrush="{TemplateBinding SeparatorBrush}">
                                        <ContentPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
                                            VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
                                            HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}">
                                            <!-- rotate the column header contents by -90 degrees -->
                                            <ContentPresenter.LayoutTransform>
                                                <RotateTransform Angle="-90"/>
                                            </ContentPresenter.LayoutTransform>
                                        </ContentPresenter>
                                    </dg:DataGridHeaderBorder>                                    
                                </Grid>
                            </ControlTemplate>
                        </Setter.Value>
                    </Setter>
                </Style>
            </dg:DataGrid.ColumnHeaderStyle>
            
            <dg:DataGrid.Columns>
                <dg:DataGridTextColumn Header="Title" Binding="{Binding XPath=title}" Width="*"/>                
                <dg:DataGridHyperlinkColumn  Header="Url" Binding="{Binding XPath=link}" Width="*"/>
                <dg:DataGridTextColumn Header="Date" Binding="{Binding XPath=pubDate}" Width="*"/>
            </dg:DataGrid.Columns>
        </dg:DataGrid>       
    </Grid>
</Window>

Regards,
Colin E.
--
http://www.scottlogic.co.uk/blog/wpf/ - my WPF blog
http://www.codeproject.com/KB/WPF/WPFDataGridExamples.aspx - WPF DataGrid Practical Examples


Jan 29, 2009 at 4:52 PM

Hello,

   Thank you for your answer, however, I have found a couple issues with doing the layout transform (columnheaders cannot be relayouted, the row header fall at the bottom of the control, instead of being on top.

So I changed my mind to populating the grid columns collection on the fly using a constant templategrid as model for the new columns. All columns have the same layout, the difference being the datacontext.

I end up with this code to create my columns as needed. However the template do not bind to its datacontext. My code is : 

For Each O In SelectedOptionList
Dim NewCol As New DataGridTemplateColumn

With NewCol
.Header = _OptionList.GetOptionByID(O).Description
'FE = New FrameworkElementFactory(CT.VisualTree.Text.Replace("Values[0]", "Values[" & Index & "]"))

UI = CType(CloneUsingXaml(CT), DataTemplate).LoadContent
fe = New FrameworkElementFactory(UI.GetType)
fe.SetValue(System.Windows.Controls.Grid.DataContextProperty, New Binding("Values[" & Index & "]"))
'fe.SetBinding(CT.d
.CellTemplate = New DataTemplate
.CellTemplate.VisualTree = fe
'.CellTemplate.VisualTree = FE

End With

C.Add(NewCol)

Index += 1
Next

At run time the exception is :

System.Windows.Data Error: 3 : Cannot find element that provides DataContext. BindingExpression:Path=Values[0]; DataItem=null; target element is 'Grid' (Name=''); target property is 'DataContext' (type 'Object')

The static column that is defined in the xaml (snippet below shows properly and is databound as expected):

 <DataTemplate x:Key="PnValueTemplate">
<Grid DataContext="{Binding Path=Values[0], Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<TextBox x:Name="TextValue" Text="{Binding Path=Value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" TextWrapping="Wrap" TextAlignment="Right" Background="#FFC0C0C0" Foreground="#FF000000" IsReadOnly="{Binding Path=IsReadOnly, Mode=Default}"/>
</Grid>
</DataTemplate>

SBS

Feb 2, 2009 at 11:37 PM
Edited Feb 2, 2009 at 11:39 PM

Hi All,

I have eventually found the solution to my problem of adding new instances of templated columns, using a template column but changing the binding from on column to another-one

The principle is pretty simple (even if I pulled my hair to get it to work...). Load a datatemplate from the resources. Clone It and during cloning replace the template original binding with the binding relevant for this instance of the column.

Below is the associated code:

 Dim NewCol As New DataGridTemplateColumn

With NewCol
.Header = _OptionList.GetOptionByID(O).Description

CT = CType(G.FindResource("PNValueTemplate"), DataTemplate)
UI = CT.LoadContent()

fe = New FrameworkElementFactory(GetType(Grid), "ContextRoot")
CloneObject(fe, UI, "Values[0]", "Values[" & Index & "]")

.CellTemplate = New DataTemplate()
.CellTemplate.VisualTree = fe
.CellEditingTemplate = New DataTemplate
.CellEditingTemplate.VisualTree = fe

C.Add(NewCol)

End With

Below are the helper function for cloning the template. I could not get the binding to be instanciated in a working manner after renaming the binding path, so I reused this cloneusingxaml function found on google.

Private Shared Function CloneUsingXaml(ByVal o As Object) As Object


Dim xaml = System.Windows.Markup.XamlWriter.Save(o)

Return System.Windows.Markup.XamlReader.Load(New System.Xml.XmlTextReader(New System.IO.StringReader(xaml)))

End Function

Private Shared Sub CloneObjectLocalProperties(ByVal O As FrameworkElementFactory, ByVal Source As DependencyObject, ByVal SourcePath As String, ByVal DestPath As String)
Dim E = Source.GetLocalValueEnumerator
While E.MoveNext

If TypeOf E.Current.Value Is BindingExpression Then
Dim B As Binding

B = CType(E.Current.Value, BindingExpression).ParentBinding

If B.Path.Path = SourcePath Then
B = CType(CloneUsingXaml(CType(E.Current.Value, BindingExpression).ParentBinding), Binding)
With B
.Path.Path = DestPath
End With
End If

O.SetBinding(E.Current.Property, B)
Else
O.SetValue(E.Current.Property, Source.GetValue(E.Current.Property))

End If
End While
End Sub
Private Sub CloneObject(ByVal O As FrameworkElementFactory, ByVal Source As DependencyObject, ByVal SourcePath As String, ByVal DestPath As String)

Dim Count As Integer = VisualTreeHelper.GetChildrenCount(Source)
Dim i As Integer

CloneObjectLocalProperties(O, Source, SourcePath, DestPath)

For i = 0 To Count - 1

Dim fe As New FrameworkElementFactory(VisualTreeHelper.GetChild(Source, i).GetType)

CloneObjectLocalProperties(fe, VisualTreeHelper.GetChild(Source, i), SourcePath, DestPath)

CloneObject(fe, VisualTreeHelper.GetChild(Source, i), SourcePath, DestPath)
O.AppendChild(fe)

Next

End Sub

May 30, 2009 at 3:14 PM

A horizontal or transposed DataGrid is an essential feature we're missing, I think.

I've added this to the Issue Tracker, so if you are interested in seeing this implemented, vote for it.

Thanks for the above workarounds ColinEber and bsod!

Adriano