Шаблон не загружен в момент создания. Он будет загружен после загрузки ListViewItems
.
Поскольку GridViewColumn
- это просто модель фактического ListViewItem
, а не часть визуального дерева (оно даже не расширяется FrameworkElement
), вы не можете получить доступ к GridViewColumn
или GridViewColumn.CellTemplate
напрямую. Фактическая ячейка находится внутри GridViewRowPresenter
из ListViewItem
.
Решение состоит в том, чтобы перебрать все элементы, когда ListView
полностью загружен и отображаются все его элементы :
private void FillListView(DataTable table)
{
GridView grid = (GridView)lvMain.View;
foreach (DataColumn col in table.Columns)
{
DataTemplate cellTemplate = CreateTemplate(col.ColumnName);
var gridColumn = new GridViewColumn()
{
Header = col.ColumnName,
CellTemplate = cellTemplate
};
grid.Columns.Add(gridColumn);
}
lvMain.HorizontalContentAlignment = HorizontalAlignment.Stretch;
lvMain.ItemsSource = ((IListSource)table).GetList();
HandleGridViewColumns(lvMain);
}
private void HandleGridViewColumns(ListView listView)
{
foreach (var item in listView.Items)
{
DependencyObject itemContainer = listView.ItemContainerGenerator.ContainerFromItem(item);
// Each item corresponds to one row, which contains multiple cells
foreach (ContentPresenter cellContent in FindVisualChildElements<ContentPresenter>(itemContainer))
{
if (!cellContent.IsLoaded)
{
cellContent.Loaded += OnCellContentLoaded;
continue;
}
SubscribeToDockPanel(cellContent);
}
}
}
private void OnCellContentLoaded(object sender, RoutedEventArgs e)
{
SubscribeToDockPanel(sender as DependencyObject);
}
private void SubscribeToDockPanel(DependencyObject visualParent)
{
if (TryFindVisualChildElementByName(visualParent, "cellPanel", out FrameworkElement element))
{
var dockPanel = element as DockPanel;
// Handle DockPanel
}
}
private IEnumerable<TChild> FindVisualChildElements<TChild>(DependencyObject parent)
where TChild : DependencyObject
{
if (parent is Popup popup)
{
parent = popup.Child;
if (parent == null)
{
yield break;
}
}
for (var childIndex = 0; childIndex < VisualTreeHelper.GetChildrenCount(parent); childIndex++)
{
DependencyObject childElement = VisualTreeHelper.GetChild(parent, childIndex);
if (childElement is TChild child)
{
yield return child;
}
foreach (TChild childOfChildElement in FindVisualChildElement<TChild>(childElement))
{
yield return childOfChildElement;
}
}
}
private bool TryFindVisualChildElementByName(DependencyObject parent, string childElementName, out FrameworkElement resultElement)
{
resultElement = null;
if (parent is Popup popup)
{
parent = popup.Child;
if (parent == null)
{
return false;
}
}
for (var childIndex = 0; childIndex < VisualTreeHelper.GetChildrenCount(parent); childIndex++)
{
DependencyObject childElement = VisualTreeHelper.GetChild(parent, childIndex);
if (childElement is FrameworkElement uiElement && uiElement.Name.Equals(
childElementName,
StringComparison.OrdinalIgnoreCase))
{
resultElement = uiElement;
return true;
}
if (TryFindVisualChildElementByName(childElement, childElementName, out resultElement))
{
return true;
}
}
return false;
}
Приведенное выше решение, как правило, будет работать, но не будет работать в сценарии, в котором включена виртуализация пользовательского интерфейса (по умолчанию для ListView
). Результатом виртуализации пользовательского интерфейса является то, что не реализованные элементы не будут генерировать контейнер элементов. ItemGenerator.ContainerFromItem
вернет null
в этом случае, что означает, что шаблон не применяется, и поэтому его визуальное дерево не загружено и является частью дерева приложения.
Я могу обновить ответ позже, чтобы показать, как получить доступ к элементу. шаблон контейнера в контексте виртуализации пользовательского интерфейса.
Но поскольку вашей основной целью было присоединить обработчики событий мыши к DockPanel
, я рекомендую другое решение.
UIElement
события являются маршрутизируемыми событиями (которые, согласно лучшей практике, идут в тандеме с оберткой событий экземпляра).
Преимущество маршрутизируемых событий состоит в том, что они не требуют от подписчика подписки к экземпляру, который вызывает событие. Это устраняет сложность кода. События экземпляров вводят необходимость обработки жизненных циклов экземпляров и связанных с ними обработчиков событий, поскольку экземпляры создаются и удаляются динамически из / в визуальное дерево (например, TabControl
, виртуализация пользовательского интерфейса). Кроме того, маршрутизируемые события не требуют каких-либо знаний о визуальном дереве для их обработки.
Маршрутизируемые события работают по-разному. Система свойств зависимостей будет проходить по визуальному дереву и вызывать зарегистрированных RoutedEventHandler
делегатов для определенных c событий.
Поэтому рекомендуется подписаться на необходимое маршрутизируемое событие, например UIElement.PreviewLeftMouseButtonUp
.
Поскольку маршрутизируемые события реализуются системой свойств зависимостей платформы, слушатель должен быть DependencyObject
.
В следующем примере регистрируется обработчик события для маршрутизируемого события UIElement.PreviewLeftMouseButtonUp
, который будет обрабатывать только события, которые происходят от любого дочернего элемента DockPanel
с именем CellPanel
:
C#
public MainWindow()
{
AddHandler(UIElement.MouseLeftButtonUpEvent, new MouseButtonEventHandler(OnUIElementClicked));
}
XAML
<MainWindow MouseLeftButtonUp="OnUIElementClicked" />
MainWindow.xaml.cs
private void OnUIElementClicked(object sender, MouseButtonEventArgs e)
{
// Check if the event source is a child of the DockPanel named CellPanel
if (TryFindVisualParentElementByName(e.OriginalSource as DependencyObject, "CellPanel", out FrameworkElement element))
{
// Handle DockPanel clicked
}
}
private bool TryFindVisualParentElementByName(DependencyObject child, string elementName, out FrameworkElement resultElement)
{
resultElement = null;
if (child == null)
{
return false;
}
var parentElement = VisualTreeHelper.GetParent(child);
if (parentElement is FrameworkElement frameworkElement
&& frameworkElement.Name.Equals(elementName, StringComparison.OrdinalIgnoreCase))
{
resultElement = frameworkElement;
return true;
}
return TryFindVisualParentElementByName(parentElement, elementName, out resultElement);
}
Замечания
Так как вы обычно хотите сделать что-то связанное с пользовательским интерфейсом, когда пользовательский интерфейс событие было возбуждено, я рекомендую обрабатывать маршрутизируемые события, используя EventTrigger
. Они могут быть определены в XAML, что делает код более легким для чтения (поскольку их синтаксис разметки проще, чем синтаксис C#) и устраняет необходимость обхода визуального дерева вручную. Если вам нужен доступ к элементам шаблона, вы должны переместить триггеры внутри него.
Из области видимости шаблона вы можете легко настроить таргетинг на любой именованный элемент:
<DataTemplate>
<DataTemplate.Triggers>
<EventTrigger RoutedEvent="UIElement.MouseEnter"
SourceName="CellPanel">
<BeginStoryBoard>
<StoryBoard>
<DoubleAnimation Storyboard.TargetName="CellText"
Storyboard.TargetProperty="Opacity" ... />
</StoryBoard>
</BeginStoryBoard>
</EventTrigger>
</DataTemplate.Triggers>
<DockPanel x:Name="CellPanel">
<TextBlock x:Name="CellText" />
...
</DockPanel>
</DataTemplate>
Если вам нужно сделать больше сложные операции, такие как выделение всего текста TextBox
на MouseEnter
, вы должны написать Attached Behavior , используя присоединенные свойства.