绑定到的TabControl的ItemsSource的WPF中 [英] Binding to ItemsSource of TabControl in WPF

查看:1173
本文介绍了绑定到的TabControl的ItemsSource的WPF中的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我在一个绑定(没有双关语意),当谈到在我的WPF当前绑定的需求。我已经花了一整天试图研究我的问题的更好的一部分,我不能找到一个坚实的解决我的问题。这里是:

我想创建一个用户控件,再presents什么我打电话一个工作区(由乔什 - 史密斯从博客的引用)。工作区将显示在一个标签控件。我的目标使用一个标签式界面来管理,我有开放很像在excal工作簿的浏览器的各种文件。

每个用户ooens一个新的工作区时,该工作区应显示在标签控件。每个工作区需要用户控件的形式,每个工作区都有自己的视图模型。我想为选项卡标题来显示我的视图模型的属性,我认为将有可能被暴露通过我的用户控件的属性。

到目前为止,我喜欢最好的,直到我遇到了许多问题的干净的解决方案是使用的DataTemplates。基本上我做了以下内容:

 <的DataTemplate X:键=WorkspaceItem>
            < D​​ockPanel中WIDTH =120>
                <内容presenter
                    CONTENT ={结合标题}
                    VerticalAlignment =中心
                    />
            < / DockPanel中>
        < / DataTemplate中><数据类型的DataTemplate ={X:类型CustomerViewModel}>
   <工作区:CustomerWorkspace />
< / DataTemplate中><的TabControl的ItemsSource ={结合工作区}的ItemTemplate ={StaticResource的WorkspaceItem}/>

该TabControl.ItemsSource绑定到一个ObservableCollection(对象),它包含了所有我的工作区。

本工程除2伟大的事情:


  1. 如果我打开多个客户,那么我有多个工作区中打开。因为DataTemplate中回收的,我失去了状态,当我换从一个标签到另一个。所以,一切未绑定将失去状态。


  2. 不同的工作区之间交换(即使用不同的DataTemplates)是非常缓慢的。的性能


所以...我发现SO另一个用户的建议,给用户控件添加到的ObservableCollection和沟数据模板。现在解决了丢失状态的问题之一。但是,我现在面对的2遗留问题:


  1. 如何设置TabItem.Header属性,而无需使用一个DataTemplate

  2. 交换来回选项卡之间的速度仍然缓慢,除非它们是相同的DataTemplate的。

我然后进行实际添加一个TabItem的到的ObservableCollection在我的codebehind并设置TabItem.Content属性到用户的控制。现在被淘汰的速度问题,因为是失去状态的问题,因为我已经删除了使用的DataTemplates的。不过,我现在坚持的TabItem.header结合应显示在选项卡标题我的用户的Custome标题属性的问题。

所以,在此之后非常长的帖子,我的问题是:


  1. 有没有办法使用的DataTemplates并迫使他们创造一个新的实例的集合,prevent回收和国家的损失中的每个项目。

    1a上。难道还有比我在上面帖子中提到一个更好的选择?


  2. 有没有办法通过XAML中,而不是通过标签项目后端code建设,做到这一切的?



解决方案

WPF的默认行为是卸载这是不可见的项目,其中包括卸载的TabItems 这是不可见。这意味着当您返回到标签中, TabItem的得到重新加载,并且任何未绑定(如滚动位置,控制状态等)将被重置。

有一个很好的网站这里包含code扩展的TabControl和毁坏停止其切换标签的时候,但它似乎不再的TabItems 现在存在。

这里的code的副本,虽然我做了一些修改。它$切换标签当p $ pserves的内容presenter的的TabItems ,并用它来重绘 TabItem的当你回到页面。它占用多一点的内存,但是我发现它更好的性能,因为TabItem的不再重新创建它上面的所有控件。

//扩展的TabControl所以你没有得到的性能损失,节省显示的项目
//卸载和重装的VisualTree切换时卡//从http://eric.burke.name/dotnetmania/2009/04/26/22.09.28获得
//并提出了一些修改,因此做拖/放操作时重用TabItem的的内容presenter[TemplatePart(NAME =PART_ItemsHolder,类型= ty​​peof运算(面板))]
公共类TabControlEx:System.Windows.Controls.TabControl
{
    //保存所有项目,但只标记当前选项卡的项目为可见
    私人面板_itemsHolder = NULL;    // Temporaily持有被删除的项目,这是一个拖/放操作
    私有对象_deletedObject = NULL;    公共TabControlEx()
        :基地()
    {
        //使我们得到初步数据绑定选择的项目,这是必要的
        this.ItemContainerGenerator.StatusChanged + = ItemContainerGenerator_StatusChanged;
    }    ///<总结>
    ///如果容器完成后,生成所选择的项目
    ///< /总结>
    ///< PARAM NAME =发件人>< /参数>
    ///< PARAM NAME =E>< /参数>
    无效ItemContainerGenerator_StatusChanged(对象发件人,EventArgs的发送)
    {
        如果(this.ItemContainerGenerator.Status == GeneratorStatus.ContainersGenerated)
        {
            this.ItemContainerGenerator.StatusChanged - = ItemContainerGenerator_StatusChanged;
            UpdateSelectedItem();
        }
    }    ///<总结>
    ///得到ItemsHolder和产生任何孩子
    ///< /总结>
    公共覆盖无效OnApplyTemplate()
    {
        base.OnApplyTemplate();
        _itemsHolder = GetTemplateChild(PART_ItemsHolder)作为面板;
        UpdateSelectedItem();
    }    ///<总结>
    ///当项目改变我们删除任何生成的面板的孩子和根据需要添加任何新的
    ///< /总结>
    ///< PARAM NAME =E>< /参数>
    保护覆盖无效OnItemsChanged(NotifyCollectionChangedEventArgs E)
    {
        base.OnItemsChanged(E);        如果(_itemsHolder == NULL)
        {
            返回;
        }        开关(e.Action)
        {
            案例NotifyCollectionChangedAction.Reset:
                _itemsHolder.Children.Clear();                如果(base.Items.Count大于0)
                {
                    base.SelectedItem = base.Items [0];
                    UpdateSelectedItem();
                }                打破;            案例NotifyCollectionChangedAction.Add:
            案例NotifyCollectionChangedAction.Remove:                //搜索造成的拖放最近删除的项目/放操作
                如果(e.NewItems = NULL&放大器;!&安培;!_deletedObject = NULL)
                {
                    的foreach(在e.NewItems VAR项)
                    {
                        如果(_deletedObject ==项)
                        {
                            //如果新的项是一样的最近删除的一个(即一个拖/放事件)
                            //然后取消删除并重新使用内容presenter因此它不必须是
                            //重绘。我们确实需要在presenter(使用标签),虽然链接到新项目
                            内容presenter CP = FindChildContent presenter(_deletedObject);
                            如果(CP!= NULL)
                            {
                                INT指数= _itemsHolder.Children.IndexOf(CP);                                (_itemsHolder.Children [指数]作为内容presenter).TAG =
                                    (产品TabItem的)?项目:(this.ItemContainerGenerator.ContainerFromItem(项目));
                            }
                            _deletedObject = NULL;
                        }
                    }
                }                如果(e.OldItems!= NULL)
                {
                    的foreach(在e.OldItems VAR项)
                    {                        _deletedObject =项目;                        //我们希望在稍后的优先级在此情况下,运行此
                        //是拖/放操作,使我们可以重新使用模板
                        this.Dispatcher.BeginInvoke(DispatcherPriority.DataBind,
                            新的Action(委托()
                        {
                            如果(_deletedObject!= NULL)
                            {
                                内容presenter CP = FindChildContent presenter(_deletedObject);
                                如果(CP!= NULL)
                                {
                                    this._itemsHolder.Children.Remove(CP);
                                }
                            }
                        }
                        ));
                    }
                }                UpdateSelectedItem();
                打破;            案例NotifyCollectionChangedAction.Replace:
                抛出新NotImplementedException(替换尚未实现);
        }
    }    ///<总结>
    ///更新的ItemsHolder的可视子
    ///< /总结>
    ///< PARAM NAME =E>< /参数>
    保护覆盖无效OnSelectionChanged(SelectionChangedEventArgs E)
    {
        base.OnSelectionChanged(E);
        UpdateSelectedItem();
    }    ///<总结>
    ///生成中所选项目的内容presenter
    ///< /总结>
    无效UpdateSelectedItem()
    {
        如果(_itemsHolder == NULL)
        {
            返回;
        }        //生成内容presenter如果有必要
        TabItem的项目= GetSelectedTabItem();
        如果(项目!= NULL)
        {
            CreateChildContent presenter(项目);
        }        //显示右子
        的foreach(内容presenter孩子_itemsHolder.Children)
        {
            child.Visibility =((child.Tag作为TabItem的).IsSelected)? Visibility.Visible:Visibility.Collapsed;
        }
    }    ///<总结>
    ///创建子内容presenter对于给定的项目(可以是数据或TabItem的)
    ///< /总结>
    ///< PARAM NAME =项>< /参数>
    ///<&回报GT;< /回报>
    内容presenter CreateChildContent presenter(对象项)
    {
        如果(项目== NULL)
        {
            返回null;
        }        内容presenter CP = FindChildContent presenter(项目);        如果(CP!= NULL)
        {
            返回CP;
        }        //要添加的实际子。 cp.Tag是对TabItem的一个参考
        CP =新的内容presenter();
        cp.Content =(产品TabItem的)? (项目作为TabItem的).Content:项目;
        cp.ContentTemplate = this.SelectedContentTemplate;
        cp.ContentTemplateSelector = this.SelectedContentTemplateSelector;
        cp.ContentStringFormat = this.SelectedContentStringFormat;
        cp.Visibility = Visibility.Collapsed;
        cp.Tag =(产品TabItem的)?项目:(this.ItemContainerGenerator.ContainerFromItem(项目));
        _itemsHolder.Children.Add(CP);
        返回CP;
    }    ///<总结>
    ///查找给定对象的CP。数据可以是TabItem的或一块数据
    ///< /总结>
    ///< PARAM NAME =数据>< /参数>
    ///<&回报GT;< /回报>
    内容presenter FindChildContent presenter(对象数据)
    {
        如果(数据TabItem的)
        {
            数据=(数据截至TabItem的).Content;
        }        如果(数据== NULL)
        {
            返回null;
        }        如果(_itemsHolder == NULL)
        {
            返回null;
        }        的foreach(在_itemsHolder.Children内容presenter CP)
        {
            如果(cp.Content ==数据)
            {
                返回CP;
            }
        }        返回null;
    }    ///<总结>
    ///从TabControl的复制;希望它是在类保护私人代替
    ///< /总结>
    ///<&回报GT;< /回报>
    保护TabItem的GetSelectedTabItem()
    {
        反对将selectedItem = base.SelectedItem;
        如果(将selectedItem == NULL)
        {
            返回null;
        }        如果(_deletedObject ==将selectedItem)
        {        }        TabItem的项目= selectedItem设置为TabItem的;
        如果(项目== NULL)
        {
            项目= base.ItemContainerGenerator.ContainerFromIndex(base.SelectedIndex)作为TabItem的;
        }
        归还物品;
    }
}

我平时使用的TabControl模板看起来是这样的:

 <风格X:键=TabControlEx_NoHeadersStyle的TargetType ={X:类型本地:TabControlEx}>
    < setter属性=SnapsToDevicePixelsVALUE =真/>
    < setter属性=模板>
        < Setter.Value>
            <的ControlTemplate的TargetType ={X:类型localControls:TabControlEx}>
                <&DockPanel中GT;
                    <! - 这是需要绘制绑定物品的TabControls - >
                    < StackPanel的IsItemsHost =真HEIGHT =0WIDTH =0/>
                    <电网X:NAME =PART_ItemsHolder/>
                < / DockPanel中>
            < /控件模板>
        < /Setter.Value>
    < /二传手>
< /样式和GT;

您还可以通过使用隐式简化您的XAML 的DataTemplate 代替的ItemTemplate ,因为您的视图模型将被放置在你的 TabItem.Content 。我也不太清楚你问的是什么样的头,但如果我理解正确,你可以只设置页眉中的另一种含蓄的风格 TabItem的

 < Window.Resources>
    <数据类型的DataTemplate ={X:类型CustomerViewModel}>
       <工作区:CustomerWorkspace />
    < / DataTemplate中>
< /Window.Resources><的TabControl的ItemsSource ={结合工作区}>
    < TabControl.Resources>
        <风格的TargetType ={X:类型TabItem的}>
            < setter属性=标题VALUE ={结合HeaderProperty}/>
        < /样式和GT;
    < /TabControl.Resources>
< / TabControl的>

I am in a bind (no pun intended) when it comes to my current binding needs in WPF. I have spent the better part of the day trying to research my problem and I cannot find a solid solution to my problem. here it is:

I am trying to create a User Control that represents what I am calling a workspace (a reference from a blog by Josh Smith). The workspaces will be displayed in a tab control. I am aiming to use a tabbed interface to manage various documents that I have open much like in a browser of an excal work book.

Each time a user ooens a new workspace, that workspace should be displayed in the tab control. Each Workspace takes the form of a user control, and each workspace has its own view model. I would like for the Tab Header to display a property from my view model which I think will likely have to be exposed as a property through my user control.

So far, the cleanest solution that I liked the best until I ran into numerous issues was by using datatemplates. Basically I Did the following:

<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}"/>

The TabControl.ItemsSource is bound to an observablecollection(of Object) which contains all of my workspaces.

This works great except for 2 things:

  1. If I open multiple customers, then I have multiple workspaces open. Because of DataTemplate Recycling, I lose state when i swap from one tab to another. So everything that is not bound will lose state.

  2. The performance of swapping between Different workspaces (that use different datatemplates) is terribly slow.

So... I found a suggestion from another user on SO to add the user controls to the ObservableCOllection and ditch the data templates. that now solves one of the problems of losing state. however, now I am faced with 2 remaining problems:

  1. How do i set the TabItem.Header property without using a DataTemplate
  2. The speed of swapping back and forth between tabs is still slow unless they are of the same DataTemplate.

I then proceeded to Actually add a TabItem to the ObservableCollection in my codebehind and Set the TabItem.Content Property to that of the user control. The speed issue was now eliminated as is the losing state issue since I have removed the use of the DataTemplates. However, I am now stuck with the issue of binding a TabItem.header to the Custome "Title" Property of my usercontrol that should be displayed in the Tab Header.

So after this terribly long post, my questions are:

  1. Is there any way to use datatemplates and force them to create a new Instance for each item in the collection to prevent recycling and state loss.

    1a. Is there a better alternative than what I mentioned in the post above?

  2. is there a way to do all of this through the Xaml instead of through back end code construction of Tab Items?

解决方案

The default behavior of WPF is to unload items which are not visible, which includes unloading TabItems which are not visible. This means when you go back to the tab, the TabItem gets re-loaded, and anything not bound (such as a scroll position, control states, etc) will get reset.

There was a good site here which contains code to extend the TabControl and stop it from destroying its TabItems when switching tabs, however it no longer seems to exist now.

Here's a copy of the code, although I've made some changes to it. It preserves the ContentPresenter of TabItems when switching tabs, and uses it to redraw the TabItem when you go back to the page. It takes up a bit more memory, however I find it better on performance since the TabItem no longer has to re-create all the controls that were on it.

// 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;
    }
}

The TabControl template I usually use looks something like this:

<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>

You can also simplify your XAML by using an implicit DataTemplate instead of an ItemTemplate since your ViewModel will be placed in your TabItem.Content. I'm also not too sure what you're asking about the header, but if I understand you correctly you can just set the header in another implicit style for the 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>

这篇关于绑定到的TabControl的ItemsSource的WPF中的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆