Fangliang Xue
Fangliang Xue

Reputation: 364

Where is WPF setting GridViewRowPresenter.Columns for GridView

I'm recently exploring a sample of using the GridViewRowPresenter inside a TreeView to make it like a "treegrid". To do this one generally need to define a GridViewColumnCollection, and set it to GridViewRowPresenter.Columns explicitly. The sample works for me very well.

However since GridViewRowPresenter is most often used with ListView and GridView. I begin to wonder how does the ListView and GridView setting the Columns property for each row. The xaml most likely like this:

<ListView>
    <ListView.View>
        <GridView>
            <GridViewColumn Header="Length" DisplayMemberBinding="{Binding}"/>
        </GridView>
    </ListView.View>
    <ListViewItem>Hello</ListViewItem>
    <ListViewItem>World</ListViewItem>
</ListView>

Note that though the GridViewColumnCollection is defined explicitly, it's not set anywhere for the ListViewItem. Eventually each ListViewItem will contain a GridViewRowPresenter, but in no where the Columns property is set. I checked the open source code for ListView.cs, ListView.xaml, ListViewItem.cs, ListViewItem.xaml, GridViewRowPresenter.cs GridViewRowPresenterBase.cs, in none of these the Columns property is set.

The closest thing I found is GridView.ColumnCollection property, I suspect it played some role in connecting the GridViewRowPresenter.Columns and GridView.Columns, but how?

Upvotes: 1

Views: 3177

Answers (3)

r3m0t
r3m0t

Reputation: 1870

It's done in FrameworkElementFactory.ApplyAutoAliasRules:

            // GridViewRowPresenter auto-aliases Content and Columns to Content
            // property GridView.ColumnCollection property on the templated parent.

https://referencesource.microsoft.com/#PresentationFramework/src/Framework/System/Windows/FrameworkElementFactory.cs,9ce27c5244be816c,references

So, GridViewRowPresenter.Columns defaults to {TemplateBinding GridView.ColumnCollection}.

Upvotes: 0

Glenn Slayden
Glenn Slayden

Reputation: 18829

I will show how to probe into the GridViewColumn and GridViewRowPresenter interactions at runtime. Consider the following simplified XAML:

<ListView x:Class="demo_listview"
    xmlns="http://schemas.microsoft.com/netfx/2009/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:wd="clr-namespace:System.Windows.Documents;assembly=PresentationFramework"
    VirtualizingStackPanel.IsVirtualizing="True" 
    VirtualizingStackPanel.VirtualizationMode="Recycling">

    <!-- ... -->

    <ListView.View>
        <GridView>

            <!-- ... -->

            <GridViewColumn x:Name="my_gv_column" Width="100">
                <GridViewColumn.CellTemplate>
                    <DataTemplate>
                        <TextBlock x:Name="my_textblock" Text="ABCD" />
                    </DataTemplate>
                </GridViewColumn.CellTemplate>
            </GridViewColumn>

            <!-- ... -->

        </GridView>
    </ListView.View>
</ListView>

Now in code, override a method such as ListView.ClearContainerForItemOverride as shown below. The code will modify the background color of the TextBlock to make it red:

enter image description here

Note that the red color persists after "Clearing" due to containers being recycled. I picked that function to keep the demo simple, since it ensures that the templates will be fully instantiated for examination. Otherwise, for example, if you get 'null' for gv_row, for example, you need to call lvi.ApplyTemplate() immediately prior.

I assume you are able to get the ListViewItem easily enough for the row you're interested in. So from any fully-instantiated ListViewItem at runtime, we can now see how to get to the contents of a particular instantiated CellTemplate at runtime. Additional remarks follow below the code.

public partial class demo_listview : ListView
{
    protected override void ClearContainerForItemOverride(DependencyObject el, object item)
    {
        base.ClearContainerForItemOverride(el, item);

        var lvi = (ListViewItem)el;

        var gv_row = (GridViewRowPresenter)lvi.Template.FindName("5_T", lvi);

        var ix = ((GridView)View).Columns.IndexOf(my_gv_column);
        var cp = (ContentPresenter)VisualTreeHelper.GetChild(gv_row, ix);

        var tb = (TextBlock)my_gv_column.CellTemplate.FindName("my_textblock", cp);
        tb.Background = Brushes.Red;
    }
};

The first step is to get the GridViewRowPresenter from the ListViewItem. There are a few ways to do this. Above, I use the magic constant "5_T" to get the instantiation from the template name. This constant is as defined by WPF, so it will work as long as you are using the standard ListViewItem its built-in control template.

Next we need the index of the column we're interested in. The column instance itself is easily accessed in C# by giving it a name (my_gv_column) in the XAML. As for its index, I'm not sure if the IndexOf technique shown here will continue to work if columns are reordered. If not, the correct result would be obtained by fetching, via reflection, the value of the private _actualIndex field (or ActualIndex property) from the GridViewColumn of interest.

Next, using VisualTreeHelper.GetChild, get the visual child of the GridViewRowPresenter which corresponds to the desired column index; this will be the ContentPresenter for the cell! With this, you can do another FindName search within the template to find the actual instantiated control you're looking for, by name. In the example, we successfully get the TextBlock instance and then change its background color to demonstrate the result.

Upvotes: 0

dev hedgehog
dev hedgehog

Reputation: 8791

GridView.ColumnCollection is the thing you seem to be looking for.

GridViewRowPresenter holds the same instance of ColumnCollection and once arranging its cells it runs through the list of columns for additional information such as desired width or desired DisplayMemberBinding.

There is also a GridViewHeaderPresenter and it does the same as RowPresenter just instead of creating cells per line it creates headers.

Edit:

You can see once you reflector the code following statement.

protected internal override void PrepareItem(ListViewItem item)
{
    base.PrepareItem(item);

    // attach GridViewColumnCollection to ListViewItem.
    SetColumnCollection(item, _columns);
}

That is how GridViewColumnCollection is being passed/attached to ListViewItem.

Its internal as you can see.

Upvotes: 1

Related Questions