Reputation: 364
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
Reputation: 1870
It's done in FrameworkElementFactory.ApplyAutoAliasRules:
// GridViewRowPresenter auto-aliases Content and Columns to Content
// property GridView.ColumnCollection property on the templated parent.
So, GridViewRowPresenter.Columns
defaults to {TemplateBinding GridView.ColumnCollection}
.
Upvotes: 0
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:
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
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