2010-07-18 40 views
21

Ứng dụng WPF của tôi được tổ chức dưới dạng TabControl với mỗi tab chứa một màn hình khác.Tải nội dung WPF lười biếng

One TabItem bị ràng buộc vào dữ liệu mất một chút thời gian để tải. Vì TabItem này đại diện cho một màn hình mà người dùng chỉ có thể hiếm khi sử dụng, tôi không muốn tải dữ liệu cho đến khi người dùng chọn tab.

Tôi làm cách nào để thực hiện việc này?

Trả lời

15

Tab kiểm soát làm việc theo hai cách,

  1. Khi chúng ta thêm Tab mục một cách rõ ràng, từng hạng mục tab được nạp và khởi tạo ngay lập tức có chứa tất cả mọi thứ.
  2. Khi chúng tôi liên kết ItemsSource với danh sách mục và chúng tôi đặt mẫu dữ liệu khác nhau cho từng mục dữ liệu, điều khiển tab sẽ chỉ tạo một chế độ xem "Nội dung" của mục dữ liệu đã chọn và chỉ khi mục tab được chọn, "Đã tải" sự kiện xem nội dung sẽ được kích hoạt và nội dung sẽ được tải. Và khi mục tab khác nhau được chọn, sự kiện "Chưa tải" sẽ được kích hoạt cho chế độ xem nội dung đã chọn trước đó và "Đã tải" sẽ được kích hoạt cho mục dữ liệu được chọn mới.

Sử dụng phương pháp thứ 2 hơi phức tạp, nhưng khi chạy nó chắc chắn sẽ giảm tài nguyên đang sử dụng, nhưng tại thời điểm chuyển đổi tab, có thể hơi chậm hơn một chút.

Bạn phải tạo lớp dữ liệu tùy chỉnh như sau

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

Và bạn phải tạo danh sách hoặc mảng của TabItemData và bạn phải thiết lập nguồn mục TabControl để liệt kê/mảng của TabItemData.

Sau đó tạo ItemTemplate của TabControl làm mẫu dữ liệu ràng buộc thuộc tính "Tiêu đề".

Sau đó tạo tạo ContentTemplate của TabControl làm mẫu dữ liệu chứa ContentControl với ContentTemplate của khóa tài nguyên được tìm thấy trong thuộc tính ResourceKey.

+2

+1 Bạn sẽ sử dụng tùy chọn thứ hai một cách tự nhiên nếu bạn đang thực hiện MVVM. –

+0

Việc sử dụng tùy chọn thứ hai có làm cho các tab bị mất trạng thái khi chúng được tải không? – yclevine

+0

Có, nhưng bạn có thể sử dụng các thuộc tính trong MyBusinessObject để xác định trạng thái có thể được đồng bộ hóa với trạng thái trực quan và bất kỳ trạng thái điều khiển logic nào khác. –

2

Bạn có thể nhìn vào SelectionChanged sự kiện:

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

Điều đó sẽ được gọi khi tab được chọn sẽ được thay đổi; tùy thuộc vào việc các tab của bạn được tạo thông qua một ràng buộc cho một bộ sưu tập hay không (điều này hoạt động tốt nhất nếu 'không'), nó có thể đơn giản như tạo một cá thể của một UserControl chứa tất cả các điều khiển bạn muốn cho trang, sau đó thêm nó đến một số Panel (ví dụ: Grid) tồn tại dưới dạng trình giữ chỗ trên tab đó.

Hy vọng điều đó sẽ hữu ích!

12

Có thể là quá muộn :) Nhưng những ai tìm kiếm một câu trả lời có thể thử này:

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

Ngoài ra bạn có thể tạo một phong cách TabItem thể tái sử dụng với việc sử dụng của AttachedProperty sẽ chứa một "trì hoãn" nội dung. Hãy cho tôi biết nếu điều này cần thiết, tôi sẽ chỉnh sửa câu trả lời.

UPDATE:

tài sản đính kèm:

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 phong cách:

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

Ví dụ:

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

Xin chào Danil, đánh giá cao nếu bạn thêm mẫu với thuộc tính đính kèm. Cảm ơn! – Kosau

+1

Giải pháp tuyệt vời vì điều này cũng giữ nguyên hành vi tabitem bình thường khi không sử dụng nội dung bị trì hoãn. Điều này hoạt động chính xác như mong đợi. –

+1

+1 nhưng tôi buộc bản thân mình vào nút thắt với nó vì ràng buộc dữ liệu có thể bị hỏng. [Xem giải pháp thay thế của tôi] (http://stackoverflow.com/a/33827448/11635) –

0

Tôi đã được thông qua cùng một vấn đề vài ngày trở lại một d đây là cách tiếp cận tốt nhất mà tôi đã tìm thấy cho đến thời điểm này:

Trong giao diện đa khe, điều khiển người dùng nội dung bị ràng buộc vào dữ liệu trong các sự kiện được tải của họ. Điều này thêm nhiều thời gian hơn cho thời gian tải ứng dụng tổng thể. Sau đó, tôi khác với các ràng buộc của người dùng điều khiển từ các sự kiện Loaded đến một hành động ưu tiên thấp hơn qua Dispatcher:

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

Như đã nói ở in @Tomas Levesque's answer to a duplicate of this question, điều đơn giản nhất mà sẽ làm việc là để trì hoãn sự ràng buộc của các giá trị bằng cách thêm một mức độ inditection qua một ContentTemplateDataTemplate: -

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

Sau đó, VM chỉ cần có một số sự lười biếng: -

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

Và bạn đã hoàn tất.


Trong trường hợp đặc biệt của tôi, tôi có lý do để tránh điều đó sắp xếp XAML nói riêng và cần thiết để có thể xác định DataTemplate s của tôi trong Resources. Điều này gây ra sự cố là 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).

Trong trường hợp đó, con đường đơn giản nhất xung quanh đó là để xác định một loại tối thiểu có nguồn gốc cụ thể: -

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

Sử dụng một helper như vậy:

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

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

nào sau đó có thể được tiêu thụ thẳng thắn qua DataTemplate s: -

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

Người ta có thể tạo ứng dụng đó roach chung chung hơn bằng cách giới thiệu một ViewModel loại lỏng lẻo:

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

này cho phép bạn viết mã NET nhỏ gọn hơn:

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

Tại giá thêm một lớp sugaring/detyping:

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

Và cho phép Xaml là:

<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> 
Các vấn đề liên quan