[Windows App 開發] 動態載入 ListView/GridView item 內容,ContainerContentChanging

摘要:[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: 目前處於第幾個階段。

流程說明:

  1. GirdView 於初始化過程中拋出 ContainerContentChanging 事件
  2. 我們於 Event handler 中得知目前為 Phase = 0,執行指定動作 (LoadItem、ShowTitle)
  3. 若我們預期會有下一個 Phase,則需註冊 args.RegisterUpdateCallback(OnContainerContentChanging);
  4. args.Handled = true; 結束此次事件
  5. GridView 發現我們註冊了一次  args.RegisterUpdateCallback(OnContainerContentChanging);,則會再次拋出 ContainerContentChanging 事件
  6. 我們於 Event handler 中得知目前為 Phase = 1,並執行指定動作 (ShowContent)
  7. 此時我們已經將此 Item 顯示完畢,預期沒有下一個 Phase,故不再註冊 args.RegisterUpdateCallback(OnContainerContentChanging);
  8. args.Handled = true; 結束此次事件
  9. 由於我們於 7. 時並無註冊事件,故 GridView 不會再次拋出 ContainerContentChanging 事件,結束動態載入流程

 

後記