8

我在窗口左侧有一个带有 WPF TreeView 的 MVVM 应用程序。右侧的详细信息面板会根据所选的树节点更改内容。

如果用户选择一个节点,详细信息面板的内容会立即更改。如果用户单击节点,这是需要的,但是如果用户使用向下/向上键导航树,我想延迟更改内容。(与 Windows 资源管理器的行为相同,至少在 Win XP 下)我假设我必须在我的 ViewModel 中知道节点是否已通过鼠标或键盘选择。

我怎样才能做到这一点?

更新:

这是我的第一篇文章,因此我不确定这是否是正确的地方,但我想让社区知道我在此期间做了什么。这是我自己的解决方案。我不是专家,因此我不知道这是否是一个好的解决方案。但它对我有用,如果它帮助别人,我会很高兴。高度赞赏错误修复、改进或更好的解决方案。

我在附加属性HasMouseFocus下方创建...
(首先我使用了 MouseEnterEvent,但如果用户使用向上/向下键导航树并且鼠标指针随机位于任何导航的树项目上,这将无法正常工作,因为在这种情况下详细信息立即更新。)

public static bool GetHasMouseFocus(TreeViewItem treeViewItem)
{
  return (bool)treeViewItem.GetValue(HasMouseFocusProperty);
}

public static void SetHasMouseFocus(TreeViewItem treeViewItem, bool value)
{
  treeViewItem.SetValue(HasMouseFocusProperty, value);
}

public static readonly DependencyProperty HasMouseFocusProperty =
  DependencyProperty.RegisterAttached(
    "HasMouseFocus",
    typeof(bool),
    typeof(TreeViewItemProperties),
    new UIPropertyMetadata(false, OnHasMouseFocusChanged)
  );

static void OnHasMouseFocusChanged(
  DependencyObject depObj, DependencyPropertyChangedEventArgs e)
{
  TreeViewItem item = depObj as TreeViewItem;
  if (item == null)
    return;

  if (e.NewValue is bool == false)
    return;

  if ((bool)e.NewValue)
  {
    item.MouseDown += OnMouseDown;
    item.MouseLeave += OnMouseLeave;
  }
  else
  {
    item.MouseDown -= OnMouseDown;
    item.MouseLeave -= OnMouseLeave;
  }
}

/// <summary>
/// Set HasMouseFocusProperty on model of associated element.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
static void OnMouseDown(object sender, MouseEventArgs e)
{
  if (sender != e.OriginalSource)
    return;

  TreeViewItem item = sender as TreeViewItem;
  if ((item != null) & (item.HasHeader))
  {
    // get the underlying model of current tree item
    TreeItemViewModel header = item.Header as TreeItemViewModel;
    if (header != null)
    {
      header.HasMouseFocus = true;
    }
  }
}

/// <summary>
/// Clear HasMouseFocusProperty on model of associated element.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
static void OnMouseLeave(object sender, MouseEventArgs e)
{
  if (sender != e.OriginalSource)
    return;

  TreeViewItem item = sender as TreeViewItem;
  if ((item != null) & (item.HasHeader))
  {
    // get the underlying model of current tree item
    TreeItemViewModel header = item.Header as TreeItemViewModel;
    if (header != null)
    {
      header.HasMouseFocus = false;
    }
  }
}

...并将其应用于TreeView.ItemContainerStyle

    <TreeView.ItemContainerStyle>
      <Style TargetType="{x:Type TreeViewItem}" >
        <!-- These Setters binds some properties of a TreeViewItem to the TreeViewItemViewModel. -->
        <Setter Property="IsExpanded" Value="{Binding Path=IsExpanded, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
        <Setter Property="IsSelected" Value="{Binding Path=IsSelected, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
        <Setter Property="ToolTip" Value="{Binding Path=CognosBaseClass.ToolTip}"/>
        <!-- These Setters applies attached behaviors to all TreeViewItems. -->
        <Setter Property="properties:TreeViewItemProperties.PreviewMouseRightButtonDown" Value="True" />
        <Setter Property="properties:TreeViewItemProperties.BringIntoViewWhenSelected" Value="True" />
        <Setter Property="properties:TreeViewItemProperties.HasMouseFocus" Value="True" />
      </Style>
    </TreeView.ItemContainerStyle>

其中properties是我的附加属性的路径。

    xmlns:properties="clr-namespace:WPF.MVVM.AttachedProperties;assembly=WPF.MVVM"

然后在我的 ViewModel 中,如果HasMousefocusProperty为 true,我会立即更新详细信息面板 (GridView)。如果为 false,我只需启动 DispatcherTimer 并将当前选定的项目应用为标记。在 500 毫秒的间隔后,滴答事件应用详细信息,但前提是所选项目仍与标记相同。

/// <summary>
/// This property is beeing set when the selected item of the tree has changed.
/// </summary>
public TreeItemViewModel SelectedTreeItem
{
  get { return Property(() => SelectedTreeItem); }
  set
  {
    Property(() => SelectedTreeItem, value);
    if (this.SelectedTreeItem.HasMouseFocus)
    {
      // show details for selected node immediately
      ShowGridItems(value);
    }
    else
    {
      // delay showing details
      this._selctedNodeChangedTimer.Stop();
      this._selctedNodeChangedTimer.Tag = value;
      this._selctedNodeChangedTimer.Start();
    }
  }
}
4

1 回答 1

2

您可以OnPreviewKeyDown为您的TreeView(或拥有它的用户控件)处理,并以编程方式在您的 ViewModel 中设置一个标志,并在刷新详细信息面板时考虑它 -

protected override void OnPreviewKeyDown(System.Windows.Input.KeyEventArgs e)
{
    switch(e.Key)
    {
        case Key.Up:
        case Key.Down:
           MyViewModel.IsUserNavigating = true;
           break;
    }
}

这个SO问题中提到了类似的方法和其他解决方案 -

如何以编程方式导航(不是选择,而是导航)WPF TreeView?

更新: [回应 AalanY 的评论]

我认为在 Views 中有一些代码隐藏没有任何问题,这不会破坏 MVVM。

在文章中,具有模型-视图-视图模型设计模式的 WPF 应用程序,作者 Josh Smith 说:

在设计良好的 MVVM 架构中,大多数视图的代码隐藏应该是空的,或者最多只包含操作该视图中包含的控件和资源的代码。有时还需要在 View 的代码隐藏中编写与 ViewModel 对象交互的代码,例如挂钩事件或调用 原本很难从 ViewModel 本身调用的方法。

根据我的经验,如果没有任何代码隐藏,就不可能构建一个企业(相当大的)应用程序,特别是当您必须使用复杂的控件,如 TreeView、DataGrid 或 3'rd 方控件时。

于 2012-06-17T12:01:41.773 回答