【问题标题】:Lazy loading WPF tab content延迟加载 WPF 选项卡内容
【发布时间】:2011-03-17 12:30:16
【问题描述】:

我的 WPF 应用程序被组织为一个 TabControl,每个选项卡包含一个不同的屏幕。

一个 TabItem 绑定到需要一点时间才能加载的数据。由于此 TabItem 表示用户可能很少使用的屏幕,因此我希望在用户选择选项卡之前不加载数据。

我该怎么做?

【问题讨论】:

    标签: .net wpf


    【解决方案1】:

    可能为时已晚 :) 但是那些正在寻找答案的人可以试试这个:

    <TabItem>
        <TabItem.Style>
            <Style TargetType="TabItem">
                <Style.Triggers>
                    <Trigger Property="IsSelected" Value="True">
                        <Setter Property="Content">
                            <Setter.Value>
                                <!-- Your tab item content -->
                            </Setter.Value>
                        </Setter>
                    </Trigger>
                    <Trigger Property="IsSelected" Value="False">
                        <Setter Property="Content" Value="{Binding Content, RelativeSource={RelativeSource Self}}"/>
                    </Trigger>
                </Style.Triggers>
            </Style>
        </TabItem.Style>  
    </TabItem>
    

    您还可以使用包含“延迟”内容的 AttachedProperty 创建可重用的 TabItem 样式。如果需要,请告诉我,我将编辑答案。

    附加属性:

    public class Deferred
    {
        public static readonly DependencyProperty ContentProperty =
            DependencyProperty.RegisterAttached(
                "Content",
                typeof(object),
                typeof(Deferred),
                new PropertyMetadata());
    
        public static object GetContent(DependencyObject obj)
        {
            return obj.GetValue(ContentProperty);
        }
    
        public static void SetContent(DependencyObject obj, object value)
        {
            obj.SetValue(ContentProperty, value);
        }
    }
    

    TabItem 样式:

    <Style TargetType="TabItem">
        <Style.Triggers>
            <Trigger Property="IsSelected" Value="True">
                <Setter Property="Content" Value="{Binding Path=(namespace:Deferred.Content), RelativeSource={RelativeSource Self}}"/>
            </Trigger>
            <Trigger Property="IsSelected" Value="False">
                <Setter Property="Content" Value="{Binding Content, RelativeSource={RelativeSource Self}}"/>
            </Trigger>
        </Style.Triggers>
    </Style>
    

    例子:

    <TabControl>
        <TabItem Header="TabItem1">
            <namespace:Deferred.Content>
                <TextBlock>
                    DeferredContent1
                </TextBlock>
            </namespace:Deferred.Content>
        </TabItem>
        <TabItem Header="TabItem2">
            <namespace:Deferred.Content>
                <TextBlock>
                    DeferredContent2
                </TextBlock>
            </namespace:Deferred.Content>
        </TabItem>
    </TabControl>
    

    【讨论】:

    • 出色的解决方案,因为这也可以在不使用延迟内容时保持正常的 tabitem 行为不变。这完全符合预期。
    • +1 但我将自己与它捆绑在一起,因为数据绑定可能会中断。 See my alternate solution
    【解决方案2】:

    一个快速简单的以Data为中心的解决方案是在标签IsSelected时按样式设置DataContext

    <Style TargetType="{x:Type TabItem}">
        <Setter Property="DataContext" Value="{x:Null}"/> <!--unset previous dc-->
        <Style.Triggers>
            <Trigger Property="IsSelected" Value="True">
                <Setter Property="DataContext" Value="{Binding LazyProperty}"/>
            </Trigger>
        </Style.Triggers>
    </Style>
    

    其中LazyProperty 是使用某些延迟加载模式的属性,例如:

    private MyVM _lazyProperty;
    public MyVM LazyProperty => _lazyProperty ?? (_lazyProperty = new MyVM());
    

    【讨论】:

      【解决方案3】:

      我找到了一个更简单的方法。只需等待初始化 ViewModel,直到选项卡被激活。

      public int ActiveTab
      {
          get
          {
              return _ActiveTab;
          }
          set
          {
              _ActiveTab = value;
              if (_ActiveTab == 3 && InventoryVM == null) InventoryVM = new InventoryVM();
          }
      }
      

      【讨论】:

        【解决方案4】:

        正如in @Tomas Levesque's answer to a duplicate of this question 所提到的,最简单的方法是通过ContentTemplate DataTemplate 增加一个inditection 级别来推迟值的绑定:-

        <TabControl>
            <TabItem Header="A" Content="{Binding A}">
                <TabItem.ContentTemplate>
                    <DataTemplate>
                        <local:AView DataContext="{Binding Value}" />
                    </DataTemplate>
                </TabItem.ContentTemplate>
            </TabItem>
            <TabItem Header="B" Content="{Binding B}">
                <TabItem.ContentTemplate>
                    <DataTemplate>
                        <local:BView DataContext="{Binding Value}" />
                    </DataTemplate>
                </TabItem.ContentTemplate>
            </TabItem>
        </TabControl>
        

        那么虚拟机只需要有一些懒惰:-

        public class PageModel
        {
            public PageModel()
            {
                A = new Lazy<ModelA>(() => new ModelA());
                B = new Lazy<ModelB>(() => new ModelB());
            }
        
            public Lazy<ModelA> A { get; private set; }
            public Lazy<ModelB> B { get; private set; }
        }
        

        你已经完成了。


        在我的特殊情况下,我有理由避免这种特殊的 Xaml 安排,并且需要能够在 Resources 中定义我的 DataTemplates。这会导致a DataTemplate can only be x:Typed and hence Lazy&lt;ModelA&gt; can not be expressed via that (and custom markup annotations are explicitly forbidden in such definitions) 出现问题。

        在这种情况下,最直接的方法是定义一个最小的派生具体类型:-

        public class PageModel
        {
            public PageModel()
            {
                A = new LazyModelA(() => new ModelA());
                B = new LazyModelB(() => new ModelB());
            }
        
            public LazyModelA A { get; private set; }
            public LazyModelB B { get; private set; }
        }
        

        像这样使用帮助器:

        public class LazyModelA : Lazy<ModelA>
        {
            public LazyModelA(Func<ModelA> factory) : base(factory)
            {
            }
        }
        
        public class LazyModelB : Lazy<ModelB>
        {
            public LazyModelB(Func<ModelB> factory) : base(factory)
            {
            }
        }
        

        然后可以通过DataTemplates 直接使用:-

        <UserControl.Resources>
            <DataTemplate DataType="{x:Type local:LazyModelA}">
                <local:ViewA DataContext="{Binding Value}" />
            </DataTemplate>
            <DataTemplate DataType="{x:Type local:LazyModelB}">
                <local:ViewB DataContext="{Binding Value}" />
            </DataTemplate>
        </UserControl.Resources>
        <TabControl>
            <TabItem Header="A" Content="{Binding A}"/>
            <TabItem Header="B" Content="{Binding B}"/>
        </TabControl>
        

        可以通过引入松散类型的 ViewModel 使该方法更通用:

        public class LazyModel
        {
            public static LazyModel Create<T>(Lazy<T> inner)
            {
                return new LazyModel { _get = () => inner.Value };
            }
        
            Func<object> _get;
        
            LazyModel(Func<object> get)
            {
                _get = get;
            }
        
            public object Value { get { return _get(); } }
        }
        

        这使您可以编写更紧凑的 .NET 代码:

        public class PageModel
        {
            public PageModel()
            {
                A = new Lazy<ModelA>(() => new ModelA());
                B = new Lazy<ModelB>(() => new ModelB());
            }
        
            public Lazy<ModelA> A { get; private set; }
            public Lazy<ModelB> B { get; private set; }
        

        以添加糖化/去类型层为代价:

            // Ideal for sticking in a #region :)
            public LazyModel AXaml { get { return LazyModel.Create(A); } }
            public LazyModel BXaml { get { return LazyModel.Create(B); } }
        

        并允许 Xaml:

        <UserControl.Resources>
            <DataTemplate DataType="{x:Type local:ModelA}">
                <local:ViewA />
            </DataTemplate>
            <DataTemplate DataType="{x:Type local:ModelB}">
                <local:ViewB />
            </DataTemplate>
            <DataTemplate DataType="{x:Type local:LazyModel}">
                <ContentPresenter Content="{Binding Value}" />
            </DataTemplate>
        </UserControl.Resources>
        <TabControl>
            <TabItem Header="A" Content="{Binding AXaml}" />
            <TabItem Header="B" Content="{Binding BXaml}" />
        </TabControl>
        

        【讨论】:

          【解决方案5】:

          几天前我也遇到过同样的问题,这是迄今为止我发现的最好的方法:

          在多选项卡界面中,内容用户控件在其 Loaded 事件中绑定到数据。这增加了整个应用程序加载时间。然后我通过 Dispatcher 将用户控件从 Loaded 事件绑定到较低优先级的操作:

          Dispatcher.BeginInvoke(new Action(() => { Bind(); }), DispatcherPriority.Background, null);
          

          【讨论】:

            【解决方案6】:

            选项卡控件有两种工作方式,

            1. 当我们显式添加选项卡项时,每个选项卡项都会立即加载并初始化,其中包含所有内容。
            2. 当我们将项目源绑定到项目列表时,我们为每个数据项设置不同的数据模板时,选项卡控件将仅创建一个“内容”视图的所选数据项,且才仅在选中选项卡项时,“加载”将触发内容视图的事件并加载内容。 And when different tab item is selected, "Unloaded" event will be fired for previously selected content view and "Loaded" will be fired for new selected data item.

            使用2nd方法有点复杂,但在运行时肯定会减少它所使用的资源,但在切换标签时,它可能会慢一点。

            您必须按照以下方式创建自定义数据类

            class TabItemData{
               public string Header {get;set;}
               public string ResourceKey {get;set;}
               public object MyBusinessObject {get;set;}
            }
            

            您必须创建 TabItemData 的列表或数组,并且必须将 TabControl 的项目源设置为 TabItemData 的列表/数组。

            然后创建 TabControl 的 ItemTemplate 作为数据模板绑定“Header”属性。

            然后创建创建 TabControl 的 ContentTemplate 作为包含 ContentControl 的数据模板,其中 ContentTemplate 的资源键在 ResourceKey 属性中找到。

            【讨论】:

            • +1 如果你在做 MVVM,你自然会使用第二个选项。
            • 使用第二个选项会导致标签在卸载时丢失状态吗?
            • 是的,但是您可以使用 MyBusinessObject 中的属性来定义可以与视觉状态和任何其他逻辑控制状态同步的状态。
            • 听起来不错,但是如何将 ContentControl 绑定到视图模型中的资源键?我所有的尝试都失败了。
            • 这对于概念证明会更有用。太难读了。
            【解决方案7】:

            您可以查看SelectionChanged 事件:

            http://msdn.microsoft.com/en-us/library/system.windows.controls.primitives.selector.selectionchanged.aspx

            将在更改所选选项卡时调用;取决于您的选项卡是否是通过与集合的绑定创建的(如果“不是”,这效果最好),它可以像创建一个包含您想要的页面所有控件的 UserControl 的实例一样简单,然后将其添加到在该选项卡上作为占位符存在的一些 Panel(例如,Grid)。

            希望有帮助!

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2022-08-15
              • 1970-01-01
              • 1970-01-01
              • 2017-09-19
              • 1970-01-01
              • 2018-02-01
              • 2014-08-16
              • 1970-01-01
              相关资源
              最近更新 更多