【发布时间】:2011-03-17 12:30:16
【问题描述】:
我的 WPF 应用程序被组织为一个 TabControl,每个选项卡包含一个不同的屏幕。
一个 TabItem 绑定到需要一点时间才能加载的数据。由于此 TabItem 表示用户可能很少使用的屏幕,因此我希望在用户选择选项卡之前不加载数据。
我该怎么做?
【问题讨论】:
我的 WPF 应用程序被组织为一个 TabControl,每个选项卡包含一个不同的屏幕。
一个 TabItem 绑定到需要一点时间才能加载的数据。由于此 TabItem 表示用户可能很少使用的屏幕,因此我希望在用户选择选项卡之前不加载数据。
我该怎么做?
【问题讨论】:
可能为时已晚 :) 但是那些正在寻找答案的人可以试试这个:
<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>
【讨论】:
一个快速简单的以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());
【讨论】:
我找到了一个更简单的方法。只需等待初始化 ViewModel,直到选项卡被激活。
public int ActiveTab
{
get
{
return _ActiveTab;
}
set
{
_ActiveTab = value;
if (_ActiveTab == 3 && InventoryVM == null) InventoryVM = new InventoryVM();
}
}
【讨论】:
正如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<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)
{
}
}
然后可以通过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>
【讨论】:
几天前我也遇到过同样的问题,这是迄今为止我发现的最好的方法:
在多选项卡界面中,内容用户控件在其 Loaded 事件中绑定到数据。这增加了整个应用程序加载时间。然后我通过 Dispatcher 将用户控件从 Loaded 事件绑定到较低优先级的操作:
Dispatcher.BeginInvoke(new Action(() => { Bind(); }), DispatcherPriority.Background, null);
【讨论】:
选项卡控件有两种工作方式,
使用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 属性中找到。
【讨论】:
您可以查看SelectionChanged 事件:
将在更改所选选项卡时调用;取决于您的选项卡是否是通过与集合的绑定创建的(如果“不是”,这效果最好),它可以像创建一个包含您想要的页面所有控件的 UserControl 的实例一样简单,然后将其添加到在该选项卡上作为占位符存在的一些 Panel(例如,Grid)。
希望有帮助!
【讨论】: