简体   繁体   English

从 GridViewColumn 或 GridViewColumn.CellTemplate 的 GridView 获取项目

[英]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以将其传递到ListViewGridViewColumnGridView中:

    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方法,但GridViewGridViewColumn都不是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 ),因此您无法直接访问GridViewColumnGridViewColumn.CellTemplate The actual cell is placed inside a GridViewRowPresenter of the ListViewItem .实际单元格放置在ListViewItemGridViewRowPresenter内。

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路由事件注册了一个事件处理程序,该事件将仅处理源自名为CellPanelDockPanel 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);
}

Remarks评论

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.

相关问题 如何创建一个上下文菜单,其中包含Gridviewcolumn.celltemplate中的菜单项? 无法获取示例代码 - How to Create a context menu which contains menu items in a Gridviewcolumn.celltemplate? Unable to get sample code to work 具有TextBox的GridViewColumn.CellTemplate无法与ListView.ItemContainerStyle一起使用 - GridViewColumn.CellTemplate with TextBox not working with ListView.ItemContainerStyle MultiBinding a TextBlock in a StackPanel in a GridViewColumn.CellTemplate 并且文本不显示 - MultiBinding a TextBlock in a StackPanel in a GridViewColumn.CellTemplate and the Text does not show 有没有一种方法可以在运行时更改GridViewColumn的CellTemplate? - Is there a way to change CellTemplate of GridViewColumn at Runtime? 如何更改用于GridViewColumn的CellTemplate的数据模板 - How to change the Datatemplate which is used for CellTemplate of the GridViewColumn 使用celltemplate绑定集合以显示在gridview列中 - Binding a collection for display in a gridviewcolumn using celltemplate 从ListView获取GridViewColumn绑定路径名称? - Get GridViewColumn Binding Path name from ListView? WPF GridViewColumn CellTemplate c#的动态更改 - WPF Dynamic change of GridViewColumn CellTemplate c# GridViewColumn CellTemplate 文本运行的条件可见性 - GridViewColumn CellTemplate Conditional Visibility of Text Run 从TextBox开始获取GridViewColumn标头 - Get the GridViewColumn Header starting from a TextBox
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM