8

ウィンドウの左側に WPF TreeView を持つ MVVM アプリケーションがあります。右側の詳細パネルは、選択したツリー ノードに応じて内容が変わります。

ユーザーがノードを選択すると、詳細パネルの内容が即座に変更されます。ユーザーがノードをクリックした場合は望ましいですが、ユーザーがキーダウン/アップを使用してツリーをナビゲートする場合は、コンテンツの変更を遅らせたいと考えています。(少なくとも Windows XP では、Windows エクスプローラーと同じ動作です)ノードがマウスまたはキーボードで選択されているかどうかを 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>

プロパティは、私の添付プロパティのパスです。

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

次に、ViewModel でHasMousefocusPropertyが true の場合、詳細パネル (GridView) をすぐに更新します。false の場合、単に DispatcherTimer を開始し、現在選択されている項目をタグとして適用します。500 ミリ秒の間隔の後、Tick-Event は詳細を適用しますが、選択された項目がまだタグと同じである場合のみです。

/// <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

あなたの(またはそれを持っているユーザーコントロール)を処理し、プログラムでViewModelにフラグを設定し、詳細パネルを更新しながらそれを考慮することができOnPreviewKeyDownます-TreeView

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 のコメントに応えて]

ビューにコード ビハインドを使用しても、MVVM が壊れることはないと思います。

モデル-ビュー-ビューモデル デザイン パターンを使用した WPF アプリの記事で、著者の Josh Smith は次のように述べています。

適切に設計された MVVM アーキテクチャでは、ほとんどのビューの分離コードは空にするか、せいぜいそのビューに含まれるコントロールとリソースを操作するコードのみを含める必要があります。 イベントをフックしたり、ViewModel 自体から呼び出すのが非常に困難なメソッドを呼び出したりするなど、ViewModel オブジェクトと対話するコードを View の分離コードに記述する必要がある場合もあります。

私の経験では、コード ビハインドなしで (かなりの規模の) エンタープライズ アプリケーションを構築することは不可能です。特に、TreeView、DataGrid、またはサード パーティ コントロールなどの複雑なコントロールを使用する必要がある場合はそうです。

于 2012-06-17T12:01:41.773 に答える