在单个ContentControl中管理多个视图/视图模型 [英] Managing Multiple Views/ViewModels In A Single ContentControl

查看:62
本文介绍了在单个ContentControl中管理多个视图/视图模型的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个应用程序,它在ContentControl中一次显示一个视图.我有一个当前的解决方案,但很好奇是否有更好的内存管理解决方案.

I have an application which shows a single View at a time in a ContentControl. I have a current solution, but was curious if there is a better one for memory management.

我当前的设计在需要显示新对象时创建它们,并在不再可见时销毁它们.我很好奇这是否是更好的方法,还是维护对每个视图的引用并在这些引用之间进行交换会更好?

My current design creates new objects when they need to be displayed, and destroys them when they are no longer visible. I'm curious if this is the better approach, or maintaining references to each view and swapping between those references is better?

以下是我的应用程序布局的更多解释:

Here is a little more explanation of my application layout:

MainWindow.xaml的非常简化的版本如下所示:

A very simplified version of my MainWindow.xaml looks like this:

<Window ... >
  <Window.Resources>
    <DataTemplate DataType="{x:Type vm:SplashViewModel}">
        <view:SplashView />
    </DataTemplate>
    <DataTemplate DataType="{x:Type vm:MediaPlayerViewModel}">
        <view:MediaPlayerView />
    </DataTemplate>
  </Window.Resources>
  <Grid>
    <ContentControl Content="{Binding ActiveModule}" />
  </Grid>
</Window>

在MainViewModel.cs中,我将ActiveModule参数与新初始化的ViewModels交换.例如,我用于交换内容的伪代码逻辑检查将类似于:

In my MainViewModel.cs I swap the ActiveModule parameter with a newly initialized ViewModels. For example, my pseudo-code logic check for swapping content would be something like:

if (logicCheck == "SlideShow")
  ActiveModule = new SlideShowViewModel();
else if (logicCheck == "MediaPlayer")
  ActiveModule = new MediaPlayerViewModel();
else
  ActiveModule = new SplashScreenViewModel();

但是,仅保持引用在速度和内存使用方面更合适吗?

But, would just maintaining a reference be more appropriate in speed and memory usage?

Alt选项1:创建对每个ViewModel的静态引用并在它们之间交换...

Alt Option 1: Create static references to each ViewModel and swap between them...

private static ViewModelBase _slideShow = new SlideShowViewModel();
private static ViewModelBase _mediaPlayer = new MediaPlayerViewModel();
private static ViewModelBase _splashView = new SplashScreenViewModel();

private void SwitchModule(string logicCheck) {
  if (logicCheck == "SlideShow")
    ActiveModule = _slideShow;
  else if (logicCheck == "MediaPlayer")
    ActiveModule = _mediaPlayer;
  else
    ActiveModule = _splashView;
}

我并不是在这里不断创建/销毁程序,但是在我看来,这种方法浪费了内存,因为闲置的模块一直闲逛着.还是...在幕后有什么特别的WPF可以避免这种情况?

I'm not constantly creating/destroying here, but this approach appears to me to be wasteful of memory with unused modules just hanging out. Or... is there something special WPF is doing behind the scenes that avoids this?

备用选项2:将每个可用模块放在XAML中,并在其中显示/隐藏它们:

Alt Option 2: Place each available module in the XAML and show/hide them there:

<Window ... >
  <Grid>
    <view:SplashScreenView Visibility="Visible" />
    <view:MediaPlayerView Visibility="Collapsed" />
    <view:SlideShowView Visibility="Collapsed" />
  </Grid>
</Window>

再次,我对我不熟悉的后台可能发生的内存管理感到好奇.当我折叠东西时,它会完全进入休眠状态吗?我读过一些东西(没有命中测试,事件,键输入,焦点等),但是动画和其他东西呢?

Again, I'm curious about what memory management might be happening in the background that I'm not familiar with. When I collapse something, does it go fully into a sort of hibernation? I've read that some stuff does (no hittesting, events, key inputs, focus, ...) but what about animations and other stuff?

感谢您的任何输入!

推荐答案

我曾经遇到过这样的情况,即我的视图创建起来非常昂贵,所以我想将它们存储在内存中以避免重新创建它们用户随时来回切换.

I ran into that kind of situation once where my Views were pretty expensive to create, so I wanted to store them in memory to avoid having to re-create them anytime the user switched back and forth.

我的最终解决方案是重用扩展的TabControl,我用它来完成相同的行为(在切换选项卡时停止WPF销毁TabItems),该功能在您切换选项卡时存储ContentPresenter,并在可能的情况下重新加载你转回去.

My end solution was to reuse an extended TabControl that I use to accomplish the same behavior (stop WPF from destroying TabItems when switching tabs), which stores the ContentPresenter when you switch tabs, and reloads it if possible when you switch back.

我唯一需要更改的是我必须覆盖TabControl.Template,所以唯一要显示的是TabControl的实际SelectedItem部分

The only thing I needed to change was I had to overwrite the TabControl.Template so the only thing displaying was the actual SelectedItem part of the TabControl

我的XAML最终看起来像这样:

My XAML ends up looking something like this:

<local:TabControlEx ItemsSource="{Binding AvailableModules}"
                    SelectedItem="{Binding ActiveModule}"
                    Template="{StaticResource BlankTabControlTemplate}" />

和扩展TabControl的实际代码如下:

and the actual code for the extended TabControl looks like this:

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

我也不是很肯定,但是我认为我空白的TabControl模板看起来像这样:

Also I'm not positive, but I think my blank TabControl template looked something like this:

<Style x:Key="BlankTabControlTemplate" TargetType="{x:Type local:TabControlEx}">
    <Setter Property="SnapsToDevicePixels" Value="true"/>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type local: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>

这篇关于在单个ContentControl中管理多个视图/视图模型的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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