[英]Get items from GridViewColumn or GridViewColumn.CellTemplate of GridView
I generate DataTemplate
in code to pass it into a GridViewColumn
of a GridView
of a ListView
:我在代码中生成
DataTemplate
以将其传递到ListView
的GridViewColumn
的GridView
中:
private static DataTemplate CreateTemplate(string sourceProperty)
{
string Xaml = "<DataTemplate xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\">" +
" <DataTemplate.Resources>" +
" <Style TargetType=\"DockPanel\">" +
" <Setter Property=\"HorizontalAlignment\" Value=\"Stretch\" />" +
" <Style.Triggers>" +
" <Trigger Property=\"IsMouseOver\" Value=\"True\">" +
" <Setter Property=\"Background\" Value=\"Black\"/>" +
" </Trigger>" +
" <Trigger Property=\"IsMouseOver\" Value=\"False\">" +
" <Setter Property=\"Background\" Value=\"White\"/>" +
" </Trigger>" +
" </Style.Triggers>" +
" </Style>" +
" </DataTemplate.Resources>" +
" <DockPanel x:Name=\"cellPanel\">" +
" <TextBlock Text=\"{Binding " + sourceProperty + "}\"/>" +
" </DockPanel>" +
" </DataTemplate>";
return XamlReader.Parse(Xaml) as DataTemplate;
}
I need to subscribe to mouse events of the DockPanel
and cannot do it via parser, since it doesn't work.我需要订阅
DockPanel
鼠标事件并且不能通过解析器来完成,因为它不起作用。 A workaround I found is to find the DockPanel
by name (which is "cellPanel") and subscribe manually.我发现的一种解决方法是按名称(即“cellPanel”)查找
DockPanel
并手动订阅。 How can I do it?我该怎么做? Here is my method for fillinga
ListView
with columns and templates:这是我用列和模板填充
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();
}
I could surely use TemplateFramework.FindName
method, but both GridView
and GridViewColumn
are not FrameworkElement
s.我当然可以使用
TemplateFramework.FindName
方法,但GridView
和GridViewColumn
都不是FrameworkElement
s。
The template is not loaded at the point of creation.模板在创建时未加载。 It will be loaded once the
ListViewItems
are loaded.一旦加载了
ListViewItems
它就会被加载。
Since the GridViewColumn
is just the model for the actual ListViewItem
and not part of the visual tree (it doesn't even extend FrameworkElement
), you can't access the GridViewColumn
or GridViewColumn.CellTemplate
directly.由于
GridViewColumn
只是实际ListViewItem
的模型,而不是可视化树的一部分(它甚至不扩展FrameworkElement
),因此您无法直接访问GridViewColumn
或GridViewColumn.CellTemplate
。 The actual cell is placed inside a GridViewRowPresenter
of the ListViewItem
.实际单元格放置在
ListViewItem
的GridViewRowPresenter
内。
The solution is to iterate over all items, once the ListView
is completely loaded and all its items are displayed :解决方案是遍历所有项目,一旦
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;
}
The above solution will generally work, but will fail in a scenario where UI virtualization is enabled (which is the default for the ListView
).上述解决方案通常可以工作,但在启用 UI 虚拟化(这是
ListView
的默认设置)的情况下会失败。 The result of UI virtualization is, that items that are not realized, won't have a item container generated. UI 虚拟化的结果是,未实现的项目不会生成项目容器。
ItemGenerator.ContainerFromItem
would return null
in this case, which means the template is not applied and its visual tree therefore not loaded and part of the application tree.在这种情况下,
ItemGenerator.ContainerFromItem
将返回null
,这意味着未应用模板,因此它的可视化树未加载并且是应用程序树的一部分。
I may update the answer later, to show how to access the item's container template in an UI virtualization context.我稍后可能会更新答案,以展示如何在 UI 虚拟化上下文中访问项目的容器模板。
But as your primary goal was to attach mouse event handlers to the DockPanel
, I recommend a different solution.但由于您的主要目标是将鼠标事件处理程序附加到
DockPanel
,我建议使用不同的解决方案。
UIElement
events are Routed Events (which, according to best practice, come in tandem with an instance event wrapper). UIElement
事件是路由事件(根据最佳实践,它与实例事件包装器一起出现)。
Routed Events have the advantage, that they don't require the observer to subscribe to the instance which raises the event.路由事件的优点是它们不需要观察者订阅引发事件的实例。 This eliminates code complexity.
这消除了代码复杂性。 Instance events introduce the necessity to handle instance life-cycles and their attached event handlers, as instances are created and removed dynamically from/to the visual tree (eg
TabControl
, UI virtualization).实例事件引入了处理实例生命周期及其附加事件处理程序的必要性,因为实例是在可视化树中动态创建和删除的(例如
TabControl
、UI 虚拟化)。 Additionally Routed Events don't require any knowledge of the visual tree in order to handle them.此外,路由事件不需要任何可视化树的知识来处理它们。
Routed Events work differently.路由事件的工作方式不同。 The dependency property system will traverse the visual tree and invoke registered
RoutedEventHandler
delegates for specific events.依赖属性系统将遍历可视化树并为特定事件调用已注册的
RoutedEventHandler
委托。
The recommended solution is therefore to subscribe to the required Routed Event eg UIElement.PreviewLeftMouseButtonUp
.因此,推荐的解决方案是订阅所需的路由事件,例如
UIElement.PreviewLeftMouseButtonUp
。
Because Routed Events are realized by the framework's dependency property system, the listener must be a DependencyObject
.因为路由事件是由框架的依赖属性系统实现的,所以监听器必须是一个
DependencyObject
。
The following example registers an event handler for the UIElement.PreviewLeftMouseButtonUp
Routed Event, which will only handle events, that originate from any child element of a DockPanel
named CellPanel
:以下示例为
UIElement.PreviewLeftMouseButtonUp
路由事件注册了一个事件处理程序,该事件将仅处理源自名为CellPanel
的DockPanel
CellPanel
元素的CellPanel
:
C# C#
public MainWindow()
{
AddHandler(UIElement.MouseLeftButtonUpEvent, new MouseButtonEventHandler(OnUIElementClicked));
}
XAML XAML
<MainWindow MouseLeftButtonUp="OnUIElementClicked" />
MainWindow.xaml.cs主窗口.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);
}
Since you usually want to do something UI related, when a UI event was raised, I recommend to handle Routed Events using an EventTrigger
.由于您通常想做一些与 UI 相关的事情,因此在引发 UI 事件时,我建议使用
EventTrigger
处理路由事件。 They can be defined in XAML, which makes the code more easier to read (as their markup syntax is simpler than the C# syntax) and eliminates the need to manually traverse the visual tree.它们可以在 XAML 中定义,这使得代码更易于阅读(因为它们的标记语法比 C# 语法更简单)并且无需手动遍历可视化树。 If you need access to elements of a template, you should move the triggers inside it.
如果您需要访问模板的元素,您应该移动其中的触发器。
From within the template's scope, you can easily target any named element:在模板的范围内,您可以轻松定位任何命名元素:
<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>
If you need to do more complex operations, like selecting all text of a TextBox
on MouseEnter
, you should write an Attached Behavior using attached properties.如果您需要执行更复杂的操作,例如在
MouseEnter
上选择TextBox
所有文本,您应该使用附加属性编写一个附加行为。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.