2016-06-29 28 views
0

首先簡要介紹一下我的問題的簡短摘要。 可能不需要討論一個解決方案,但是下面是我所瞭解的真正潛在問題的一些進一步的「可選」信息,只是爲了理解上下文。綁定到ContentPresenter的視覺元素/來自外部的子女

因此:我有一個ContentPresenter使用DataTemplate爲綁定項目生成其佈局。 現在,除了此contentpresenter之外,我正試圖在該內容展示器中按名稱綁定一個元素。

假設以下僞XAML(MainTextBlock的結合將在實踐中行不通):

<TextBlock Text="{Binding Text, ElementName=MyTextblock, Source = ???}" DataContext="{x:Reference TheContentPresenter}" x:Name="MainTextblock"/> 

    <ContentPresenter Content="{Binding SomeItem}" x:Name="TheContentPresenter"> 
     <ContentPresenter.ContentTemplate> 
      <DataTemplate> 
       <TextBlock x:Name="MyTextblock" Text="Test"/> 
      </DataTemplate> 
     </ContentPresenter.ContentTemplate> 
    </ContentPresenter> 

!請假定MainTextblock的DataContext必須是(參考)TheContentPresenter!

鑑於這種假設,我如何在MainTextblock上進行綁定工作?

我無法綁定到ContentPresenter的Content屬性,因爲它包含綁定元素(例如SomeItem),而不是其可視化表示。 不幸的是,ContentPresenter似乎沒有任何代表其可視化樹/視覺兒童的屬性。

有沒有辦法做到這一點?


現在我真的需要這樣做嗎?隨意跳過閱讀本文,不需要討論我相信上述問題的解決方案。

我正在寫一個行爲,增加了定製的過濾器到DataGrid:

<DataGrid AutoGenerateColumns="False"> 

     <i:Interaction.Behaviors> 
      <filter:FilterBehavior> 
       <filter:StringFilter Column="{x:Reference FirstCol}" Binding="{Binding DataContext.Value1}"/> 
       <filter:StringFilter Column="{x:Reference SecondCol}" Binding="{??????? bind to Content -> Visual Children -> Element With Name "MyTextBlock" -> Property "Text"}"/> 
      </filter:FilterBehavior> 
     </i:Interaction.Behaviors> 


     <DataGrid.Columns> 
      <DataGridTextColumn x:Name="FirstCol" Header="Test" Binding="{Binding Value1}"/> 
      <DataGridTemplateColumn x:Name="SecondCol" Header="Test 2"> 
       <DataGridTemplateColumn.CellTemplate> 
        <DataTemplate> 
         <TextBlock x:Name="MyTextblock" Text="{Binding Value2}"/> 
        </DataTemplate> 
       </DataGridTemplateColumn.CellTemplate> 
      </DataGridTemplateColumn> 
     </DataGrid.Columns> 
</DataGrid> 

「FilterBehavior」包含了各個過濾器對每一列,例如他們中的第一個將是一個過濾器,允許搜索其綁定到的任何列(本例中爲FirstCol)內的文本,並隱藏該文本未出現的列。

現在,綁定是一個有趣的部分。 Binding屬性的類型是BindingBase(所以綁定是「延遲」)。它旨在定義用於過濾的值。 當應該進行過濾時,每個過濾器都循環遍歷與其綁定的列的所有DataGridCells。對於每個DataGridCell,它將Binding的DataContext設置爲相應的DataGridCell,並評估綁定。

因此,StringFilter會遍歷FirstCol中的每個DataGridCell。 對於它們中的每一個,它都會檢索BindingBase「Binding」(即{Binding DataContext.Value1}),將其DataContext設置爲DataGridCell,並對其進行評估。 因此,在這種情況下,它將綁定到WpfGridCell.DataContext.Value1,或者換句話說,綁定到DataGridCell包含的項目的Value1屬性。 稍後,它將檢查這些評估項是否與用戶輸入的過濾字符串匹配。

這工作得很好。

但是,在嘗試綁定到DataGridCell的可視內容時遇到了問題,例如Column =「{x:Reference SecondCol}」中的第二個StringFilter。 SecondCol是一個DataGridTemplateColumn。它的單元格內容將是ContentPresenter,其模板是DataGridTemplateColumn.CellTemplate,其內容是單元格包含的元素。

這就是我們從上面回到我的簡化版本的地方。我現在需要用DataContext = DataGridCell來評估「綁定」,並以某種方式想出一個綁定,讓我將它綁定到DataGridCell.Content中給出的ContentPresenter的可視元素。

謝謝!

+0

你可以嘗試定義'ContentPresenter'和外部使用你的'DataTemplate'它作爲StaticResource。請確保您在使用之前聲明DT – lokusking

+0

謝謝lokus - 我不太清楚這是否會影響TextBlock的綁定(看到我需要綁定與此模板的實例化視覺效果,而不是模板本身)。你會有一個這樣的工作例子嗎?謝謝! – Bogey

+0

是的你是對的。我在其他地方與我的想法。現在玩了半個小時,再現你的問題後,我找不到合適的解決方案。這讓我問你:你想開發一個自動過濾器嗎? – lokusking

回答

0

由於到目前爲止還沒有其他解決方案出現/這看起來不適用於XAML,這是我目前的解決方案。似乎有點混亂,但它起作用並允許相對普遍的使用。

本質上,我已經將第二個屬性引入了名爲「BindingContext」的過濾器,該類型也是BindingBase類型。消費者可以將它保留爲空,在這種情況下,它將默認爲相應的DataGridCell,或者它可以分配一個綁定(它本身將獲得DataContext = DataGridCell)。這種結合會被評估,其結果將作爲在DataContext的「綁定」屬性:

   <filter:StringFilter Column="{x:Reference SecondCol}" 
          BindingContext="{Binding Content, Converter={StaticResource ContentPresenterToVisualHelperConverter}, ConverterParameter='MyTextblock'}" 
          Binding="{Binding Visual.Text, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"           
         /> 

現在我已經創建其中ContentPresenter轉換成包裝類,其中itselfs暴露了的IValueConverter一個「視覺」屬性。 根據用例,此可視屬性或者暴露ContentPresenters的第一個也是唯一的直接可視子對象,或者它按名稱查找可視子對象。 我已經緩存了helper類的實例化,否則轉換器會創建相當多的這些,並且每次它至少會查詢一次Visual Tree。

它試圖保持此屬性同步到ContentPresenter;雖然我不覺得有任何直接的方法來監視它的可視化樹是否發生變化,但每當ContentPresenter的內容屬性發生變化時,我都會進行更新。 (另一種方法可能是更新每當其佈局的變化,但是這顯然被觸發在各種情況下很多,所以看起來像矯枉過正)

[ValueConversion(typeof(ContentPresenter), typeof(ContentPresenterVisualHelper))] 
public class ContentPresenterToVisualHelperConverter : IValueConverter 
{ 
    /// <param name="parameter"> 
    /// 1. Can be null/empty, in which case the first Visual Child of the ContentPresenter is returned by the Helper 
    /// 2. Can be a string, in which case the ContentPresenter's child with the given name is returned 
    /// </param> 
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) 
    { 
     if (value == null) 
      return null; 

     ContentPresenter cp = value as ContentPresenter; 

     if (cp == null) 
      throw new InvalidOperationException(String.Format("value must be of type ContentPresenter, but was {0}", value.GetType().FullName)); 

     return ContentPresenterVisualHelper.GetInstance(cp, parameter as string); 
    } 

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) 
    { 
     throw new NotImplementedException(); 
    } 
} 

/// <summary> 
/// Exposes either 
/// A) A ContentPresenter's only immediate visual child, or 
/// B) Any of the ContentPresenter's visual children by Name 
/// in the ContentPresenterVisualHelper's "Visual" property. Implements INotifyPropertyChanged to notify when this visual is replaced. 
/// </summary> 
public class ContentPresenterVisualHelper : BindableBase, IDisposable 
{ 
    private static object CacheLock = new object(); 
    private static MemoryCache Cache = new MemoryCache("ContentPresenterVisualHelperCache"); 

    protected readonly ContentPresenter ContentPresenter; 
    protected readonly CompositeDisposable Subscriptions = new CompositeDisposable(); 
    protected readonly string ChildName; 

    private FrameworkElement _Visual; 
    public FrameworkElement Visual 
    { 
     get { return _Visual; } 
     private set { this.SetProperty(ref _Visual, value); } 
    } 

    /// <summary> 
    /// Creates a unique Cache key for a Combination of ContentPresenter + ChildName 
    /// </summary> 
    private static string CreateKey(ContentPresenter ContentPresenter, string ChildName) 
    { 
     var hash = 17; 
     hash = hash * 23 + ContentPresenter.GetHashCode(); 

     if (ChildName != null) 
      hash = hash * 23 + ChildName.GetHashCode(); 

     var result = hash.ToString(); 
     return result; 
    } 

    /// <summary> 
    /// Creates an instance of ContentPresenterVisualHelper for the given ContentPresenter and ChildName, if necessary. 
    /// Or returns an existing one from cache, if available. 
    /// </summary> 
    public static ContentPresenterVisualHelper GetInstance(ContentPresenter ContentPresenter, string ChildName) 
    { 
     string key = CreateKey(ContentPresenter, ChildName); 
     var cachedObj = Cache.Get(key) as ContentPresenterVisualHelper; 

     if (cachedObj != null) 
      return cachedObj; 

     lock (CacheLock) 
     { 
      cachedObj = Cache.Get(key) as ContentPresenterVisualHelper; 

      if (cachedObj != null) 
       return cachedObj; 

      var obj = new ContentPresenterVisualHelper(ContentPresenter, ChildName); 

      var cacheItem = new CacheItem(key, obj); 
      var expiration = DateTimeOffset.Now + TimeSpan.FromSeconds(60); 
      var policy = new CacheItemPolicy { AbsoluteExpiration = expiration }; 
      Cache.Set(cacheItem, policy); 

      return obj; 
     } 
    } 

    private ContentPresenterVisualHelper(ContentPresenter ContentPresenter, string ChildName) 
    { 
     this.ContentPresenter = ContentPresenter; 
     this.ChildName = ChildName; 

     this 
      .ContentPresenter 
      .ObserveDp(x => x.Content) // extension method that creates an IObservable<object>, pushing values initially and then whenever the "ContentProperty"-dependency property changes 
      .DistinctUntilChanged() 
      .Subscribe(x => ContentPresenter_LayoutUpdated()) 
      .MakeDisposable(this.Subscriptions); // extension method which just adds the IDisposable to this.Subscriptions 

     /* 
     * Alternative way? But probably not as good 
     * 
     Observable.FromEventPattern(ContentPresenter, "LayoutUpdated") 
      .Throttle(TimeSpan.FromMilliseconds(50)) 
      .Subscribe(x => ContentPresenter_LayoutUpdated()) 
      .MakeDisposable(this.Subscriptions);*/ 

    } 

    public void Dispose() 
    { 
     this.Subscriptions.Dispose(); 
    } 

    void ContentPresenter_LayoutUpdated() 
    { 
     Trace.WriteLine(String.Format("{0:hh.mm.ss:ffff} Content presenter updated: {1}", DateTime.Now, ContentPresenter.Content)); 

     if(!String.IsNullOrWhiteSpace(this.ChildName)) 
     { 
      // Get Visual Child by name 
      var child = this.ContentPresenter.FindChild<FrameworkElement>(this.ChildName); // extension method, readily available on StackOverflow etc. 
      this.Visual = child; 
     } 
     else 
     { 
      // Don't get child by name, but rather 
      // Get the first - and only - immediate Visual Child of the ContentPresenter 

      var N = VisualTreeHelper.GetChildrenCount(this.ContentPresenter); 

      if (N == 0) 
      { 
       this.Visual = null; 
       return; 
      } 

      if (N > 1) 
       throw new InvalidOperationException("ContentPresenter had more than 1 Visual Children"); 

      var child = VisualTreeHelper.GetChild(this.ContentPresenter, 0); 
      var _child = (FrameworkElement)child; 

      this.Visual = _child; 
     } 
    } 
} 
相關問題