2016-05-30 28 views
1

我試圖想出一個System.Windows.Interactivity.Behaviour,當應用於WPF DataGrid,添加一個上下文菜單(或項目到現有的上下文菜單),允許用戶顯示或隱藏列。WPF DataGridColumnHeader:隱藏/重新顯示列後ContextMenu消失

我想出了一個解決方案,差不多工作得很好。 一切都按預期工作 - 直到您隱藏然後重新顯示一列。一旦再次變得可見,上下文菜單似乎消失,右鍵單擊該列不會再做任何事情。

代碼如下,verbalyl我在做什麼:

  1. 有關安裝的行爲,我開始聽DataGrid的「加載」事件
  2. 在Loaded事件,我覺得所有的DataGridColumnHeader後裔數據網格
  3. 對於每一種,我生成一個單獨的上下文菜單,並將它附加到DataGridColumnHeader
  4. 對於每個上下文菜單中,我生成每列一個菜單項,並指定一個命令到它的是,在執行時,套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擴展方法主要基於從這裏的一個: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的實際實例! 這是我的解決方案(我也聽LayoutUpdated事件):

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被稱爲LOT - 例如調整列的大小等在需要的時候我只開始收聽第一事件發生(=>列的可見性改變),然後從事件退訂再次(「取(1)」)。如果您發現解決方案存在任何性能問題,則可能需要執行類似的操作;實施取決於你的項目和要求 – Bogey

+0

謝謝你的提示!你是對的,它經常被稱爲!當可視性更改(menuItem.Click())時我正在監聽並在調用它之後註銷事件(之後立即進行)。 –