Getting reference to the named elements within data template is a commonly requested feature of WPF. In the current version of WPF, there is no intuitive and straightforward way to do it, you need to either traverse the visual tree to compare the Name property of each FrameworkElements within the tree, or alternatively you can first off grab the reference to the ContentPresenter within the ControlTemplate, and call DataTemplate.FindName() passing in the ContentPresenter reference as the "templatedParent" argument. Accessing named elements within CellTemplate complicates the things a bit since each Cell will have different instantiation of CellTemplate, the following helper class gives a possible solution to this problem (I also posted it in this WPF MSDN forum thread):
using System;
using System.Windows;
using System.Windows.Media;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
namespace Sheva.Windows.Component
{
public static class ListViewHelper
{
/// <summary>
///Finds an element that has the provided identifier name within the specified cell template identified by the cellColumn and cellRow
/// </summary>
/// <param name="listView">the referenced ListView control</param>
/// <param name="cellColumn">the index of column from which the CellTemplate will be retrieved</param>
/// <param name="cellRow">the index of the row from which the specified ListViewItem will be retrieved</param>
/// <param name="name">The name of the requested element.</param>
/// <returns>The requested element. This can be null reference if no matching element was found.</returns>
public static FrameworkElement FindNameFromCellTemplate(ListView listView, Int32 cellColumn, Int32 cellRow, Stringname)
{
if(listView == null)
{
throw newArgumentNullException("listView");
}
if(!listView.IsLoaded)
{
throw new InvalidOperationException("ListView is not yet loaded");
}
if (cellRow >= listView.Items.Count || cellRow < 0)
{
throw new ArgumentOutOfRangeException("row");
}
GridView gridView = listView.View as GridView;
if (gridView == null)
{
return null;
}
if (cellColumn >= gridView.Columns.Count || cellColumn < 0)
{
throw new ArgumentOutOfRangeException("column");
}
ListViewItem item = listView.ItemContainerGenerator.ContainerFromItem(listView.Items[cellRow]) as ListViewItem;
if (item != null)
{
if (!item.IsLoaded)
{
item.ApplyTemplate();
}
GridViewRowPresenter rowPresenter = GetFrameworkElementByName<GridViewRowPresenter>(item);
if(rowPresenter != null)
{
ContentPresenter templatedParent = VisualTreeHelper.GetChild(rowPresenter, cellColumn) as ContentPresenter;
DataTemplate dataTemplate = gridView.Columns[cellColumn].CellTemplate;
if(dataTemplate != null && templatedParent != null)
{
return dataTemplate.FindName(name, templatedParent) as FrameworkElement;
}
}
}
return null;
}
private static T GetFrameworkElementByName<T>(FrameworkElement referenceElement) where T : FrameworkElement
{
FrameworkElementchild = null;
for(Int32 i = 0; i < VisualTreeHelper.GetChildrenCount(referenceElement); i++)
{
child = VisualTreeHelper.GetChild(referenceElement, i) as FrameworkElement;
if(child != null && child.GetType() == typeof(T))
{
break;
}
else if(child != null)
{
child = GetFrameworkElementByName<T>(child);
if(child != null && child.GetType() == typeof(T))
{
break;
}
}
}
returnchild asT;
}
}
}
This actually requires a bit of coding, hopefully future version of WPF in particular the ListView control could has a much better content model to make it more flexible and extensible than it is right now.