![](/img/trans.png)
[英]How to Create a context menu which contains menu items in a Gridviewcolumn.celltemplate? Unable to get sample code to work
[英]Get items from GridViewColumn or GridViewColumn.CellTemplate of GridView
我在代碼中生成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;
}
我需要訂閱DockPanel
鼠標事件並且不能通過解析器來完成,因為它不起作用。 我發現的一種解決方法是按名稱(即“cellPanel”)查找DockPanel
並手動訂閱。 我該怎么做? 這是我用列和模板填充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();
}
我當然可以使用TemplateFramework.FindName
方法,但GridView
和GridViewColumn
都不是FrameworkElement
s。
模板在創建時未加載。 一旦加載了ListViewItems
它就會被加載。
由於GridViewColumn
只是實際ListViewItem
的模型,而不是可視化樹的一部分(它甚至不擴展FrameworkElement
),因此您無法直接訪問GridViewColumn
或GridViewColumn.CellTemplate
。 實際單元格放置在ListViewItem
的GridViewRowPresenter
內。
解決方案是遍歷所有項目,一旦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;
}
上述解決方案通常可以工作,但在啟用 UI 虛擬化(這是ListView
的默認設置)的情況下會失敗。 UI 虛擬化的結果是,未實現的項目不會生成項目容器。 在這種情況下, ItemGenerator.ContainerFromItem
將返回null
,這意味着未應用模板,因此它的可視化樹未加載並且是應用程序樹的一部分。
我稍后可能會更新答案,以展示如何在 UI 虛擬化上下文中訪問項目的容器模板。
但由於您的主要目標是將鼠標事件處理程序附加到DockPanel
,我建議使用不同的解決方案。
UIElement
事件是路由事件(根據最佳實踐,它與實例事件包裝器一起出現)。
路由事件的優點是它們不需要觀察者訂閱引發事件的實例。 這消除了代碼復雜性。 實例事件引入了處理實例生命周期及其附加事件處理程序的必要性,因為實例是在可視化樹中動態創建和刪除的(例如TabControl
、UI 虛擬化)。 此外,路由事件不需要任何可視化樹的知識來處理它們。
路由事件的工作方式不同。 依賴屬性系統將遍歷可視化樹並為特定事件調用已注冊的RoutedEventHandler
委托。
因此,推薦的解決方案是訂閱所需的路由事件,例如UIElement.PreviewLeftMouseButtonUp
。
因為路由事件是由框架的依賴屬性系統實現的,所以監聽器必須是一個DependencyObject
。
以下示例為UIElement.PreviewLeftMouseButtonUp
路由事件注冊了一個事件處理程序,該事件將僅處理源自名為CellPanel
的DockPanel
CellPanel
元素的CellPanel
:
C#
public MainWindow()
{
AddHandler(UIElement.MouseLeftButtonUpEvent, new MouseButtonEventHandler(OnUIElementClicked));
}
XAML
<MainWindow MouseLeftButtonUp="OnUIElementClicked" />
主窗口.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);
}
由於您通常想做一些與 UI 相關的事情,因此在引發 UI 事件時,我建議使用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>
如果您需要執行更復雜的操作,例如在MouseEnter
上選擇TextBox
所有文本,您應該使用附加屬性編寫一個附加行為。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.