摘要:[Windows App 開發] 動態載入 ListView/GridView item 內容的新寫法,ContainerContentChanging
在進行 Xaml-related 應用程式開發時,經常會碰到某個 ListView/GridView 內容過多、讀取過慢的問題。在 Windows App 8.1 中,可以透過 ListView/GridView 的 ContainerContentChanging 事件來指定我們想要載入的 Item 內容優先順序 (例如標題較重要、則優先顯示)。
首先,我們必須預先刻出一個 Control,未來做為 ListView/GridView 的 ItemTemplate 顯示用。(我在刻自製元件時,比較喜歡使用自由度高的 Custom Control 而非 User Control,以下即以 Custom Control 的刻法做為範例)
新增一個名為 ContainerContentChangingSampleItem 的類別 (Class),並使其繼承於 Control,並加入名為 Title 與 Content 的 DependencyProperty。
public class ContainerContentChangingSampleItem : Control
{
public String Title
{
get { return (String)GetValue(TitleProperty); }
set { SetValue(TitleProperty, value); }
}
public static readonly DependencyProperty TitleProperty =
DependencyProperty.Register("Title", typeof(String), typeof(ContainerContentChangingSampleItem), new PropertyMetadata(""));
public String Content
{
get { return (String)GetValue(ContentProperty); }
set { SetValue(ContentProperty, value); }
}
public static readonly DependencyProperty ContentProperty =
DependencyProperty.Register("Content", typeof(String), typeof(ContainerContentChangingSampleItem), new PropertyMetadata(""));
public ContainerContentChangingSampleItem()
{
DefaultStyleKey = typeof(ContainerContentChangingSampleItem);
}
}
接著制定此 Control 的 Template 內應有名為 TitleTextBlock 與 ContentTextBlock 的 TextBlock。
[TemplatePartAttribute(Name = "TitleTextBlock", Type = typeof(TextBlock))]
[TemplatePartAttribute(Name = "ContentTextBlock", Type = typeof(TextBlock))]
在此 Control 中加入兩個名為 titleTextBlock 與 contentTextBlock 的 TextBlock,並於 OnApplyTemplate 時從 Template 中找到 TitleTextBlock 與 ContentTextBlock 以儲存於 titleTextBlock 與 contentTextBlock。
private TextBlock titleTextBlock { get; set; }
private TextBlock contentTextBlock { get; set; }
protected override void OnApplyTemplate()
{
base.OnApplyTemplate();
titleTextBlock = base.GetTemplateChild("TitleTextBlock") as TextBlock;
contentTextBlock = base.GetTemplateChild("ContentTextBlock") as TextBlock;
}
到此階段為止,我們完成了 ContainerContentChangingSampleItem 這個 Control 的後置程式碼 (code-behind),下一個階段將要處理這個 Control 的 Template。
於 App.xaml 中加入這個 Control 的預設 Template 。
請注意,TitleTextBlock 與 ContentTextBlock 的 Opacity 設為 0,代表二者目前是透明的,而且我沒有為二者的 Text 屬性設定 Binding。
<Style TargetType="customControl:ContainerContentChangingSampleItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="customControl:ContainerContentChangingSampleItem">
<StackPanel Width="300">
<TextBlock x:Name="TitleTextBlock" FontSize="18" Foreground="White" TextTrimming="WordEllipsis" VerticalAlignment="Center" Opacity="0"></TextBlock>
<TextBlock x:Name="ContentTextBlock" FontSize="14" Foreground="#FF00AED8" TextTrimming="WordEllipsis" VerticalAlignment="Center" Opacity="0"></TextBlock>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
接著回到 ContainerContentChangingSampleItem.cs 裡,為這個 Control 制定一些 method,做為 ContainerContentChanging 使用。
public void LoadItem(SampleDataItem sampleItem)
{
if (sampleItem != null)
{
this.Title = sampleItem.Title;
this.Content = sampleItem.Content;
}
}
public void ShowTitle()
{
if (titleTextBlock != null && !String.IsNullOrEmpty(this.Title))
{
titleTextBlock.Text = this.Title;
titleTextBlock.Opacity = 1.0;
}
}
public void ShowContent()
{
if (contentTextBlock != null && !String.IsNullOrEmpty(this.Content))
{
contentTextBlock.Text = this.Content;
contentTextBlock.Opacity = 1.0;
}
}
此三 method 中寫道,我將會讀取 SampleDataItem 的值,指定 Text 的值,並將 Opacity 設為 1,以顯示出此字
重頭戲來了,刻出一個 GridView,指定 ContainerContentChanging 事件的 Handler
<GridView ContainerContentChanging="OnContainerContentChanging" ItemsSource="{Binding SampleData}">
<GridView.ItemTemplate>
<DataTemplate>
<customControl:ContainerContentChangingSampleItem/>
</DataTemplate>
</GridView.ItemTemplate>
</GridView>
在 OnContainerContentChanging 這個 Event handler 中寫入我們想要實現動態載入的過程。
private void OnContainerContentChanging(ListViewBase sender, ContainerContentChangingEventArgs args)
{
ContainerContentChangingSampleItem sourceContainer = args.ItemContainer.ContentTemplateRoot as ContainerContentChangingSampleItem;
if (sourceContainer == null)
{
throw new InvalidOperationException("動態載入 GridViewItem,ItemTemplate 必須為 ContainerContentChangingSampleItem 物件。");
}
if (args.InRecycleQueue == true)
{
sourceContainer.ClearData();
}
else if (args.Phase == 0)
{
sourceContainer.LoadItem(args.Item as SampleDataItem);
sourceContainer.ShowTitle();
args.RegisterUpdateCallback(OnContainerContentChanging);
}
else if (args.Phase == 1)
{
sourceContainer.ShowContent();
}
args.Handled = true;
}
在此 Event handler 中,必須先判斷傳入的來源是否為 ContainerContentChangingSampleItem。
接著我們可以從 args 中得知目前的狀態,InRecycleQueue: 此 Item 是否將被回收;Phase: 目前處於第幾個階段。
流程說明:
- GirdView 於初始化過程中拋出 ContainerContentChanging 事件
- 我們於 Event handler 中得知目前為 Phase = 0,執行指定動作 (LoadItem、ShowTitle)
- 若我們預期會有下一個 Phase,則需註冊 args.RegisterUpdateCallback(OnContainerContentChanging);
- args.Handled = true; 結束此次事件
- GridView 發現我們註冊了一次 args.RegisterUpdateCallback(OnContainerContentChanging);,則會再次拋出 ContainerContentChanging 事件
- 我們於 Event handler 中得知目前為 Phase = 1,並執行指定動作 (ShowContent)
- 此時我們已經將此 Item 顯示完畢,預期沒有下一個 Phase,故不再註冊 args.RegisterUpdateCallback(OnContainerContentChanging);
- args.Handled = true; 結束此次事件
- 由於我們於 7. 時並無註冊事件,故 GridView 不會再次拋出 ContainerContentChanging 事件,結束動態載入流程
後記
- 哪個文字/圖片/內容優先載入,可以自己決定
- 該如何載入,可以自己決定
- ListView 的做法與 GridView 一模一樣
- 範例原始碼: https://github.com/renewal-wu/ContainerContentChangingSample