2012-09-14 95 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> 
相关问题