延迟加载WPF选项卡内容

时间:2010-07-18 06:57:39

标签: .net wpf

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

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

我该怎么做?

7 个答案:

答案 0 :(得分:16)

Tab控件有两种工作方式,

  1. 当我们显式添加Tab项时,每个标签项都会立即加载并初始化,包含所有内容。
  2. 当我们将ItemsSource绑定到项目列表,并且我们为每个数据项设置不同的数据模板时,选项卡控件将仅创建所选数据项的一个“内容”视图,并且仅当选择了选项卡项时,“已加载”将触发内容视图事件并加载内容。选择不同的标签项时,将为先前选择的内容视图触发“已卸载”事件,并为新选择的数据项触发“已加载”。
  3. 使用第二种方法并不复杂,但在运行时它肯定会减少它所使用的资源,但在切换标签时,它可能会慢一点。

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

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

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

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

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

答案 1 :(得分:12)

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

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

答案 2 :(得分:5)

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

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

然后VM只需要有一些懒惰: -

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安排,并且需要能够在DataTemplate中定义Resources。这导致问题为a DataTemplate can only be x:Typed and hence Lazy<ModelA> 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)
    {
    }
}

然后可以通过DataTemplate直接消费: -

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

答案 3 :(得分:2)

您可以查看SelectionChanged事件:

  

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

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

希望有所帮助!

答案 4 :(得分:0)

几天前我遇到了同样的问题,这是我到目前为止找到的最佳方法:

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

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

答案 5 :(得分:0)

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

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

答案 6 :(得分:0)

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

<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());