WPF 保留 Tab 控件状态

发布于 2024-12-10 17:59:39 字数 2609 浏览 0 评论 0原文

我已阅读帖子@ 如何停止 Wpf Tabcontrol 以在选项卡更改时卸载可视化树,但我无法让它工作,我一定是错过了一些东西。请帮忙。谢谢,

我正在使用示例项目中的 TabControlEx 类 @ http://www.pluralsight-training.net/community/blogs/eburke/archive/2009/04/30/keeping-the-wpf-tab-control-from-destroying-its-children.aspx

<Window x:Class="MyMainwindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns:sharedC="clr-namespace:myShared.Converter;assembly=myShared"
    xmlns:mainTab="clr-namespace:MyWPFLib.View" Title="{Binding Title}"        
    Height="600" Width="850" Top="223" Left="164" ResizeMode="CanResize" Closing="WindowClosing"
    WindowStyle="ToolWindow">
<Grid>
 <mainTab:TabControlEx IsSynchronizedWithCurrentItem="True"
                        Grid.Row="0"  
                        Margin="10 0 5 5"
                        x:Name="MainTabRegion"  
                        TabStripPlacement="Top" 
                        SelectedIndex="{Binding Tabs.SelectedIndex}" 
                        ItemsSource="{Binding Tabs.TabItems}"                         
                        >
                        <TabControl.ItemContainerStyle>
                            <Style TargetType="{x:Type TabItem}">
                                <Setter Property="Header">
                                    <Setter.Value>
                                        <Binding Path="Header"/>
                                    </Setter.Value>
                                </Setter>
                                <Setter Property="Visibility" 
                                Value="{Binding IsVisible, Mode=OneWay, Converter={StaticResource boolToVis}}"/>
                            </Style>
                        </TabControl.ItemContainerStyle>
                    </mainTab:TabControlEx>
</Grid>
</Window>

Datasource for the maintab is Tabs.TabItems

    public ObservableCollection<ITabControl> TabItems
    {
        get
        {
            return _items;
        }
    }

每个 TabItem 都是用

<DataTemplate DataType="{x:Type vm:Item1ViewModel}">
    <vw:View1 />
</DataTemplate>

I have read the posts @ How to stop Wpf Tabcontrol to unload Visual tree on Tab change, but I could not get it work, I must have missed something. Pls help. thanks

I am using class TabControlEx from sample project @ http://www.pluralsight-training.net/community/blogs/eburke/archive/2009/04/30/keeping-the-wpf-tab-control-from-destroying-its-children.aspx

<Window x:Class="MyMainwindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns:sharedC="clr-namespace:myShared.Converter;assembly=myShared"
    xmlns:mainTab="clr-namespace:MyWPFLib.View" Title="{Binding Title}"        
    Height="600" Width="850" Top="223" Left="164" ResizeMode="CanResize" Closing="WindowClosing"
    WindowStyle="ToolWindow">
<Grid>
 <mainTab:TabControlEx IsSynchronizedWithCurrentItem="True"
                        Grid.Row="0"  
                        Margin="10 0 5 5"
                        x:Name="MainTabRegion"  
                        TabStripPlacement="Top" 
                        SelectedIndex="{Binding Tabs.SelectedIndex}" 
                        ItemsSource="{Binding Tabs.TabItems}"                         
                        >
                        <TabControl.ItemContainerStyle>
                            <Style TargetType="{x:Type TabItem}">
                                <Setter Property="Header">
                                    <Setter.Value>
                                        <Binding Path="Header"/>
                                    </Setter.Value>
                                </Setter>
                                <Setter Property="Visibility" 
                                Value="{Binding IsVisible, Mode=OneWay, Converter={StaticResource boolToVis}}"/>
                            </Style>
                        </TabControl.ItemContainerStyle>
                    </mainTab:TabControlEx>
</Grid>
</Window>

Datasource for the maintab is Tabs.TabItems

    public ObservableCollection<ITabControl> TabItems
    {
        get
        {
            return _items;
        }
    }

each TabItem is built with

<DataTemplate DataType="{x:Type vm:Item1ViewModel}">
    <vw:View1 />
</DataTemplate>

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

生来就爱笑 2024-12-17 17:59:39

我一直使用该链接中的代码,没有任何问题,尽管我注意到自从我第一次获得代码以来该网站已经发生了变化。您可能想检查我所拥有的代码与网站所拥有的代码之间是否存在任何差异。

这是我在他们更改站点之前获得的代码:

// 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://www.pluralsight-training.net/community/blogs/eburke/archive/2009/04/30/keeping-the-wpf-tab-control-from-destroying-its-children.aspx
// 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;
    }
}

我确实对原始代码进行了一些更改,因为我拖放选项卡并且不想在删除项目并重新添加它时重新创建选项卡,但老实说时间太久了,我都忘了哪部分是我的,哪部分不是。

我做了一个快速测试来仔细检查,是的,它确实保留了您的非绑定值,例如选定的或扩展的值。

编辑

为了回应您下面的评论,请将 DataTemplates 放入您的资源中定义每个选项卡使用哪个 UserControl 的位置。当 TabControl 尝试绘制每个选项卡的内容时,它将使用您为该数据类型

<Window.Resources>
    <DataTemplate TargetType="{x:Type local:TabAViewModel}">
        <local:TabAView />
    </DataTemplate>
    <DataTemplate TargetType="{x:Type local:TabBViewModel}">
        <local:TabBView />
    </DataTemplate>
</Window.Resources>

<TabControl ItemsSource="{Binding TabItems}">
    <TabControl.ItemContainerStyle> 
        <Style TargetType="{x:Type TabItem}"> 
            <Setter Property="Header" Value="{Binding Header}" />
        </Style> 
    </TabControl.ItemContainerStyle>
</TabControl>

ViewModel 定义的任何 DataTemplate...

public MyViewModelConstructor()
{
    TabItems.Add(New TabAViewModel { Header = "Tab A" });
    TabItems.Add(New TabBViewModel { Header = "Tab B" });
}

EDIT #2

Text XAML,根据您的要求

<local:TabControlEx>
    <TabItem Header="Test1">
        <DataGrid ItemsSource="{Binding Test}">
            <DataGrid.Columns>
                <DataGridTextColumn Binding="{Binding TestValue}" Header="Test" />
            </DataGrid.Columns>
        </DataGrid>
    </TabItem>
    <TabItem Header="Test2">
        <DataGrid ItemsSource="{Binding Test}">
            <DataGrid.Columns>
                <DataGridTextColumn Binding="{Binding TestValue}" Header="Test" />
            </DataGrid.Columns>
        </DataGrid>
    </TabItem>
</local:TabControlEx>

I use the code from that link all the time without a problem, although I notice that the website has changed since when I first got the code. You may want to check for any code differences between what I have and what the website has.

Here's the code I got before they changed the site:

// 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://www.pluralsight-training.net/community/blogs/eburke/archive/2009/04/30/keeping-the-wpf-tab-control-from-destroying-its-children.aspx
// 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;
    }
}

I did make some changes to the original code because I was dragging/dropping tabs and didn't want to re-create the tabs when removing an item and re-adding it, but honestly it's been so long that I forget what part is mine and what isn't.

I did a quick test to double-check, and yes it does keep your non-bound values such as selected or expanded values.

EDIT

In response to your comments below, put DataTemplates in your Resources somewhere that define which UserControl to use for each Tab. When the TabControl tries to draw each Tab's Content, it will use whatever DataTemplate you defined for that data type

<Window.Resources>
    <DataTemplate TargetType="{x:Type local:TabAViewModel}">
        <local:TabAView />
    </DataTemplate>
    <DataTemplate TargetType="{x:Type local:TabBViewModel}">
        <local:TabBView />
    </DataTemplate>
</Window.Resources>

<TabControl ItemsSource="{Binding TabItems}">
    <TabControl.ItemContainerStyle> 
        <Style TargetType="{x:Type TabItem}"> 
            <Setter Property="Header" Value="{Binding Header}" />
        </Style> 
    </TabControl.ItemContainerStyle>
</TabControl>

ViewModel...

public MyViewModelConstructor()
{
    TabItems.Add(New TabAViewModel { Header = "Tab A" });
    TabItems.Add(New TabBViewModel { Header = "Tab B" });
}

EDIT #2

Text XAML, as you requested

<local:TabControlEx>
    <TabItem Header="Test1">
        <DataGrid ItemsSource="{Binding Test}">
            <DataGrid.Columns>
                <DataGridTextColumn Binding="{Binding TestValue}" Header="Test" />
            </DataGrid.Columns>
        </DataGrid>
    </TabItem>
    <TabItem Header="Test2">
        <DataGrid ItemsSource="{Binding Test}">
            <DataGrid.Columns>
                <DataGridTextColumn Binding="{Binding TestValue}" Header="Test" />
            </DataGrid.Columns>
        </DataGrid>
    </TabItem>
</local:TabControlEx>
浅笑轻吟梦一曲 2024-12-17 17:59:39

我放弃了解决方法,它对我来说不起作用。我购买了一个第三方 WPF 库 (DevExpress WPF) 来解决这个问题。谢谢

I gave up with the workaround, it just did not work in my case. I purchase a third party WPF library (DevExpress WPF) which solves the issue. thanks

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文