2012-09-14 100 views
1

當我涉及到WPF當前的綁定需求時,我處於綁定(無雙關語意圖)。我花了一天中最好的時間來研究我的問題,但我找不到解決我的問題的可靠解決方案。這裏是:在WPF中綁定到TabControl的ItemsSource

我想創建一個用戶控件,表示我正在調用工作區(Josh Smith的博客參考)。工作區將顯示在選項卡控件中。我打算使用標籤界面來管理我在非常酷的工作簿瀏覽器中打開的各種文檔。

每次用戶創建一個新的工作區時,該工作區都應顯示在選項卡控件中。每個工作區都採用用戶控件的形式,每個工作區都有自己的視圖模型。我希望Tab Header能夠從我的視圖模型中顯示一個屬性,我認爲它可能必須通過我的用戶控件作爲屬性公開。

到目前爲止,直到遇到很多問題時,我最喜歡的最乾淨的解決方案是使用數據模板。基本上我做了以下內容:

<DataTemplate x:Key="WorkspaceItem"> 
      <DockPanel Width="120"> 
       <ContentPresenter 
        Content="{Binding Title}" 
        VerticalAlignment="Center" 
        /> 
      </DockPanel> 
     </DataTemplate>  

<DataTemplate DataType="{x:Type CustomerViewModel}"> 
    <workspace:CustomerWorkspace /> 
</DataTemplate> 

<TabControl ItemsSource="{Binding Workspaces}" ItemTemplate="{StaticResource WorkspaceItem}"/> 

的TabControl.ItemsSource必然包含了所有我的工作空間的(對象)一個ObservableCollection。

這個工作,除了兩件事情很大:

  1. 如果我打開多個客戶,然後我有多個工作區中打開。由於DataTemplate Recycling,當我從一個標籤切換到另一個標籤時,我失去了狀態。所以所有不受約束的東西都會失去狀態。

  2. 不同工作空間(使用不同數據模式)之間交換的性能非常緩慢。

所以...我從SO上的另一個用戶發現了一個建議,將用戶控件添加到ObservableCOllection並丟棄數據模板。現在解決了失去國家的問題之一。但是,我現在面對的2個遺留問題:

  1. 我如何設置TabItem.Header屬性,而不使用一個DataTemplate
  2. 交換來回標籤之間的速度依然很慢,除非他們的相同的DataTemplate。

然後我開始實際添加一個TabItem到我的代碼隱藏的ObservableCollection中並將TabItem.Content屬性設置爲用戶控件的屬性。現在已經消除了速度問題,因爲我已經刪除了DataTemplates的使用,所以這是丟失狀態問題。但是,我現在堅持將TabItem.header綁定到應該在Tab標題中顯示的我的usercontrol的Custome「Title」屬性上。

所以在此之後非常長的帖子,我的問題是:

  1. 有沒有辦法使用的DataTemplates,迫使他們在集合中創建的每個項目一個新的實例,以防止回收和國家的損失。 1)。有沒有比我在上面的帖子中提到的更好的選擇?

  2. 有沒有辦法通過Xaml完成所有這些工作,而不是通過Tab項目的後端代碼構建?

回答

5

WPF的默認行爲是卸載它們是不可見的項目,其中包括卸載TabItems這是不可見的。這意味着當您回到選項卡時,TabItem會重新加載,並且任何未綁定的內容(例如滾動位置,控制狀態等)都將被重置。

有一個很好的網站here其中包含代碼來擴展TabControl並阻止它在切換標籤時破壞它的TabItems,但它現在不再存在。

下面是代碼的一個副本,儘管我對它做了一些更改。它在切換選項卡時保留TabItems的ContentPresenter,並在您返回頁面時使用它重畫TabItem。它佔用更多的內存,但是我發現它在性能上更好,因爲TabItem不再需要重新創建其上的所有控件。

// Extended TabControl which saves the displayed item so you don't get the performance hit of 
// unloading and reloading the VisualTree when switching tabs 

// Obtained from http://eric.burke.name/dotnetmania/2009/04/26/22.09.28 
// and made a some modifications so it reuses a TabItem's ContentPresenter when doing drag/drop operations 

[TemplatePart(Name = "PART_ItemsHolder", Type = typeof(Panel))] 
public class TabControlEx : System.Windows.Controls.TabControl 
{ 
    // Holds all items, but only marks the current tab's item as visible 
    private Panel _itemsHolder = null; 

    // Temporaily holds deleted item in case this was a drag/drop operation 
    private object _deletedObject = null; 

    public TabControlEx() 
     : base() 
    { 
     // this is necessary so that we get the initial databound selected item 
     this.ItemContainerGenerator.StatusChanged += ItemContainerGenerator_StatusChanged; 
    } 

    /// <summary> 
    /// if containers are done, generate the selected item 
    /// </summary> 
    /// <param name="sender"></param> 
    /// <param name="e"></param> 
    void ItemContainerGenerator_StatusChanged(object sender, EventArgs e) 
    { 
     if (this.ItemContainerGenerator.Status == GeneratorStatus.ContainersGenerated) 
     { 
      this.ItemContainerGenerator.StatusChanged -= ItemContainerGenerator_StatusChanged; 
      UpdateSelectedItem(); 
     } 
    } 

    /// <summary> 
    /// get the ItemsHolder and generate any children 
    /// </summary> 
    public override void OnApplyTemplate() 
    { 
     base.OnApplyTemplate(); 
     _itemsHolder = GetTemplateChild("PART_ItemsHolder") as Panel; 
     UpdateSelectedItem(); 
    } 

    /// <summary> 
    /// when the items change we remove any generated panel children and add any new ones as necessary 
    /// </summary> 
    /// <param name="e"></param> 
    protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e) 
    { 
     base.OnItemsChanged(e); 

     if (_itemsHolder == null) 
     { 
      return; 
     } 

     switch (e.Action) 
     { 
      case NotifyCollectionChangedAction.Reset: 
       _itemsHolder.Children.Clear(); 

       if (base.Items.Count > 0) 
       { 
        base.SelectedItem = base.Items[0]; 
        UpdateSelectedItem(); 
       } 

       break; 

      case NotifyCollectionChangedAction.Add: 
      case NotifyCollectionChangedAction.Remove: 

       // Search for recently deleted items caused by a Drag/Drop operation 
       if (e.NewItems != null && _deletedObject != null) 
       { 
        foreach (var item in e.NewItems) 
        { 
         if (_deletedObject == item) 
         { 
          // If the new item is the same as the recently deleted one (i.e. a drag/drop event) 
          // then cancel the deletion and reuse the ContentPresenter so it doesn't have to be 
          // redrawn. We do need to link the presenter to the new item though (using the Tag) 
          ContentPresenter cp = FindChildContentPresenter(_deletedObject); 
          if (cp != null) 
          { 
           int index = _itemsHolder.Children.IndexOf(cp); 

           (_itemsHolder.Children[index] as ContentPresenter).Tag = 
            (item is TabItem) ? item : (this.ItemContainerGenerator.ContainerFromItem(item)); 
          } 
          _deletedObject = null; 
         } 
        } 
       } 

       if (e.OldItems != null) 
       { 
        foreach (var item in e.OldItems) 
        { 

         _deletedObject = item; 

         // We want to run this at a slightly later priority in case this 
         // is a drag/drop operation so that we can reuse the template 
         this.Dispatcher.BeginInvoke(DispatcherPriority.DataBind, 
          new Action(delegate() 
         { 
          if (_deletedObject != null) 
          { 
           ContentPresenter cp = FindChildContentPresenter(_deletedObject); 
           if (cp != null) 
           { 
            this._itemsHolder.Children.Remove(cp); 
           } 
          } 
         } 
         )); 
        } 
       } 

       UpdateSelectedItem(); 
       break; 

      case NotifyCollectionChangedAction.Replace: 
       throw new NotImplementedException("Replace not implemented yet"); 
     } 
    } 

    /// <summary> 
    /// update the visible child in the ItemsHolder 
    /// </summary> 
    /// <param name="e"></param> 
    protected override void OnSelectionChanged(SelectionChangedEventArgs e) 
    { 
     base.OnSelectionChanged(e); 
     UpdateSelectedItem(); 
    } 

    /// <summary> 
    /// generate a ContentPresenter for the selected item 
    /// </summary> 
    void UpdateSelectedItem() 
    { 
     if (_itemsHolder == null) 
     { 
      return; 
     } 

     // generate a ContentPresenter if necessary 
     TabItem item = GetSelectedTabItem(); 
     if (item != null) 
     { 
      CreateChildContentPresenter(item); 
     } 

     // show the right child 
     foreach (ContentPresenter child in _itemsHolder.Children) 
     { 
      child.Visibility = ((child.Tag as TabItem).IsSelected) ? Visibility.Visible : Visibility.Collapsed; 
     } 
    } 

    /// <summary> 
    /// create the child ContentPresenter for the given item (could be data or a TabItem) 
    /// </summary> 
    /// <param name="item"></param> 
    /// <returns></returns> 
    ContentPresenter CreateChildContentPresenter(object item) 
    { 
     if (item == null) 
     { 
      return null; 
     } 

     ContentPresenter cp = FindChildContentPresenter(item); 

     if (cp != null) 
     { 
      return cp; 
     } 

     // the actual child to be added. cp.Tag is a reference to the TabItem 
     cp = new ContentPresenter(); 
     cp.Content = (item is TabItem) ? (item as TabItem).Content : item; 
     cp.ContentTemplate = this.SelectedContentTemplate; 
     cp.ContentTemplateSelector = this.SelectedContentTemplateSelector; 
     cp.ContentStringFormat = this.SelectedContentStringFormat; 
     cp.Visibility = Visibility.Collapsed; 
     cp.Tag = (item is TabItem) ? item : (this.ItemContainerGenerator.ContainerFromItem(item)); 
     _itemsHolder.Children.Add(cp); 
     return cp; 
    } 

    /// <summary> 
    /// Find the CP for the given object. data could be a TabItem or a piece of data 
    /// </summary> 
    /// <param name="data"></param> 
    /// <returns></returns> 
    ContentPresenter FindChildContentPresenter(object data) 
    { 
     if (data is TabItem) 
     { 
      data = (data as TabItem).Content; 
     } 

     if (data == null) 
     { 
      return null; 
     } 

     if (_itemsHolder == null) 
     { 
      return null; 
     } 

     foreach (ContentPresenter cp in _itemsHolder.Children) 
     { 
      if (cp.Content == data) 
      { 
       return cp; 
      } 
     } 

     return null; 
    } 

    /// <summary> 
    /// copied from TabControl; wish it were protected in that class instead of private 
    /// </summary> 
    /// <returns></returns> 
    protected TabItem GetSelectedTabItem() 
    { 
     object selectedItem = base.SelectedItem; 
     if (selectedItem == null) 
     { 
      return null; 
     } 

     if (_deletedObject == selectedItem) 
     { 

     } 

     TabItem item = selectedItem as TabItem; 
     if (item == null) 
     { 
      item = base.ItemContainerGenerator.ContainerFromIndex(base.SelectedIndex) as TabItem; 
     } 
     return item; 
    } 
} 

的TabControl的模板,我通常使用看起來像這樣:

<Style x:Key="TabControlEx_NoHeadersStyle" TargetType="{x:Type local:TabControlEx}"> 
    <Setter Property="SnapsToDevicePixels" Value="true"/> 
    <Setter Property="Template"> 
     <Setter.Value> 
      <ControlTemplate TargetType="{x:Type localControls:TabControlEx}"> 
       <DockPanel> 
        <!-- This is needed to draw TabControls with Bound items --> 
        <StackPanel IsItemsHost="True" Height="0" Width="0" /> 
        <Grid x:Name="PART_ItemsHolder" /> 
       </DockPanel> 
      </ControlTemplate> 
     </Setter.Value> 
    </Setter> 
</Style> 

您還可以通過使用隱含DataTemplate,而不是因爲你的視圖模型的ItemTemplate簡化您的XAML將被放置在您的TabItem.Content。我也不太確定你對標題提出的問題,但是如果我正確地理解了你的意思,你可以將標題設置爲另一種隱含的樣式,以用於TabItem

<Window.Resources> 
    <DataTemplate DataType="{x:Type CustomerViewModel}"> 
     <workspace:CustomerWorkspace /> 
    </DataTemplate> 
</Window.Resources> 

<TabControl ItemsSource="{Binding Workspaces}"> 
    <TabControl.Resources> 
     <Style TargetType="{x:Type TabItem}"> 
      <Setter Property="Header" Value="{Binding HeaderProperty}" /> 
     </Style> 
    </TabControl.Resources> 
</TabControl> 
相關問題