2016-05-30 20 views
1

WPF DataGridに適用されたときに、コンテキストメニュー(または既存の項目を追加)を追加するSystem.Windows.Interactivity.Behaviourを考え出しています。コンテキストメニュー)を使用して、列を表示または非表示にすることができます。WPF DataGridColumnHeader:列を非表示または再表示した後にContextMenuが消える

私は、ほぼがうまく機能するという解決策を考え出しました。 すべては、列を非表示にしてから再表示するまで、期待通りに機能します。もう一度目に見えるようになると、コンテクストメニューは消えるように見えますが、右クリックするだけでもう何もしません。私がやっているverbalyl

コードは、以下の通りです:

  1. 行動を取り付けるには、私はLoadedイベントには、「ロード」のDataGridイベント
  2. を聴き始める、私はすべてのDataGridColumnHeaderの子孫を見つけますDataGrid
  3. 個々のコンテキストメニューを生成し、DataGridColumnHeaderにアタッチします
  4. 各コンテキストメニューに対して、1つの列につき1つのメニュー項目を生成し、実行時に設定するコマンドを割り当てますDataGridColumnの可視性私は最も単純なケースのために最低限の例にコードをストリップダウンしました

表示または非表示に:これをテストするには、単にのContextMenuは、現在割り当てられていないのDataGridにその動作を適用します。

public class DgColumnBehavior : Behavior<DataGrid> 
{ 
    protected ICommand ToggleColumnVisibilityCmd; 
    protected DataGrid _AssociatedObject; 

    protected override void OnAttached() 
    { 
     this.ToggleColumnVisibilityCmd = new DelegateCommand<DataGridColumn>(ToggleColumnVisibilityCmdExecute); 
     this._AssociatedObject = (DataGrid)this.AssociatedObject; 

     Observable.FromEventPattern(this._AssociatedObject, "Loaded") 
      .Take(1) 
      .Subscribe(x => _AssociatedObject_Loaded()); 

     base.OnAttached(); 
    } 

    void _AssociatedObject_Loaded() 
    { 
     var columnHeaders = this._AssociatedObject.SafeFindDescendants<DataGridColumnHeader>(); // see second code piece for the SafeFindDescendants extension method 

     foreach (var columnHeader in columnHeaders) 
     { 
      EnsureSeparateContextMenuFor(columnHeader); 

      if (columnHeader.ContextMenu.ItemsSource != null) 
      { 
       // ContextMenu has an ItemsSource, so need to add items to that - 
       // ommitted though as irrelevant for example 
      } 
      else 
      { 
       // No ItemsSource assigned to the Menu, so we can just add directly 

       foreach (var item in CreateMenuItemsFor(columnHeader)) 
        columnHeader.ContextMenu.Items.Add(item); 
      } 
     } 
    } 

    /// Ensures that the columnHeader ... 
    /// A) has a ContextMenu, and 
    /// B) that is has an individual context menu, i.e. one that isn't shared with any other DataGridColumnHeaders. 
    /// 
    /// I'm doing that as in practice, I'm adding some further items that are specific to each column, so I can't have a shared context menu 
    private void EnsureSeparateContextMenuFor(DataGridColumnHeader columnHeader) 
    { 
     if (columnHeader.ContextMenu == null) 
     { 
      columnHeader.ContextMenu = new ContextMenu(); 
     } 
     else 
     { 
      // clone the existing menu 
      // ommitted as irrelevant for example 
     } 
    } 

    /// Creates one menu item for each column of the underlying DataGrid to toggle that column's visibility 
    private IEnumerable<FrameworkElement> CreateMenuItemsFor(DataGridColumnHeader columnHeader) 
    { 
     foreach (var column in _AssociatedObject.Columns) 
     { 
      var item = new MenuItem(); 
      item.Header = String.Format("Toggle visibility for {0}", column.Header); 
      item.Command = ToggleColumnVisibilityCmd; 
      item.CommandParameter = column; 

      yield return item; 
     } 
    } 

    // Gets executed when the user clicks on one of the ContextMenu items 
    protected void ToggleColumnVisibilityCmdExecute(DataGridColumn column) 
    { 
     bool isVisible = (column.Visibility == Visibility.Visible); 
     Visibility newVisibility = (isVisible) ? Visibility.Hidden : Visibility.Visible; 
     column.Visibility = newVisibility; 
    } 
} 

SafeFindDescendants伸長法は大きく、ここから1に基づいています。私は何が起こっているのを把握することはできませんDataGridColumnHeader ContextMenu programmatically

public static class Visual_ExtensionMethods 
{ 
    public static IEnumerable<T> SafeFindDescendants<T>(this Visual @this, Predicate<T> predicate = null) where T : Visual 
    { 
     if (@this != null) 
     { 
      int childrenCount = VisualTreeHelper.GetChildrenCount(@this); 
      for (int i = 0; i < childrenCount; i++) 
      { 
       var currentChild = VisualTreeHelper.GetChild(@this, i); 

       var typedChild = currentChild as T; 
       if (typedChild == null) 
       { 
        var result = ((Visual)currentChild).SafeFindDescendants<T>(predicate); 

        foreach (var r in result) 
         yield return r; 

       } 
       else 
       { 
        if (predicate == null || predicate(typedChild)) 
        { 
         yield return typedChild; 
        } 
       } 
      } 

     } 
    } 
} 

。列を隠したり再表示した後にコンテキストメニューが表示されないのはなぜですか?

アイデアを気に入ってください!ありがとう。

答えて

0

私は迅速かつ汚れた修正を考え出しました。それは動作しますが、それはかなりではありません。誰かがより良い解決策を考えるかもしれない。

DataGridColumnアイテムの可視性が非表示/折りたたみに変更されるたびに、DataGridColumnHeaderを取得して関連付けられたコンテキストメニューをキャッシュに格納します。 可視性が可視に変わるたびに、次のDataGrid LayoutUpdatedイベント(ビジュアルツリーが構築されていることを確認する)を聴いています。元のものとは異なるインスタンスであるDataGridColumnHeaderを再度取得し、そのコンテキストメニューをキャッシュされたものに設定します。

protected IDictionary<DataGridColumn, ContextMenu> _CachedContextMenues = new Dictionary<DataGridColumn, ContextMenu>(); 

protected void ToggleColumnVisibilityCmdExecute(DataGridColumn column) 
    { 
     bool isVisible = (column.Visibility == Visibility.Visible); 
     Visibility newVisibility = (isVisible) ? Visibility.Hidden : Visibility.Visible; 


     if(newVisibility != Visibility.Visible) 
     { 
      // We're hiding the column, so we'll cache its context menu so for re-use once the column 
      // becomes visible again 

      var contextMenu = _AssociatedObject.SafeFindDescendants<DataGridColumnHeader>(z => z.Column == column).Single().ContextMenu; 
      _CachedContextMenues.Add(column, contextMenu); 
     } 
     if(newVisibility == Visibility.Visible) 
     { 
      // The column just turned visible again, so we set its context menu to the 
      // previously cached one 

      Observable 
       .FromEventPattern(_AssociatedObject, "LayoutUpdated") 
       .Take(1) 
       .Select(x => _AssociatedObject.SafeFindDescendants<DataGridColumnHeader>(z => z.Column == column).Single()) 
       .Subscribe(x => 
        { 
         var c = x.Column; 
         var cachedMenu = _CachedContextMenues[c]; 
         _CachedContextMenues.Remove(c); 
         x.ContextMenu = cachedMenu; 
        }); 
     } 

     column.Visibility = newVisibility; 
    } 
0

あなたは既に優れたソリューションを見つけましたか?

私は新しいDataGridクラスを持っているので、 "this"はDataGridの実際のインスタンスです!まだunfortuantely

this.LayoutUpdated += (sender, args) => 
{ 
    foreach (DataGridColumnHeader columnHeader in GetVisualChildCollection<DataGridColumnHeader>(this)) 
    { 
     if(columnHeader.ContextMenu == null) 
      ContextMenuService.SetContextMenu(columnHeader, _ContextMenu); 
    } 
}; 



    public static List<T> GetVisualChildCollection<T>(object parent) where T : Visual 
    { 
     List<T> visualCollection = new List<T>(); 
     GetVisualChildCollection(parent as DependencyObject, visualCollection); 
     return visualCollection; 
    } 

    private static void GetVisualChildCollection<T>(DependencyObject parent, List<T> visualCollection) where T : Visual 
    { 
     int count = VisualTreeHelper.GetChildrenCount(parent); 
     for (int i = 0; i < count; i++) 
     { 
      DependencyObject child = VisualTreeHelper.GetChild(parent, i); 
      if (child is T) 
      { 
       visualCollection.Add(child as T); 
      } 
      else if (child != null) 
      { 
       GetVisualChildCollection(child, visualCollection); 
      } 
     } 
    } 
+0

こんにちは、ないよりよい解決策: は、これが私の解決策(私もLayoutUpdatedイベントをリッスンしています)です。私が上に投稿した修正はこれまでのところうまくいくようです。 コードを再入力してください:LayoutUpdatedはたくさんの名前で呼ばれています。カラムのサイズを変更しながら何回も必要なときに最初のイベントの発生を聞き始め(=>列の可視性が変更された)、イベントからの再登録を解除します( ".Take(1)")。ソリューションでパフォーマンスの問題が発生した場合は、同様のことを行うことをお勧めします。実装はあなたのプロジェクトと要件に少し依存する – Bogey

+0

あなたのヒントありがとう!あなたは正しかった、それは非常に頻繁に呼び出されます! Visibilityが変更されたとき(menuItem.Click())、呼び出された後にイベントの登録を解除する(直後に何が行われるのか)、今聞いています。 –

関連する問題