WinUI 3學習筆記(2)—— 用ListView來展示集合


在WPF的時代,我們多是使用ListBox和ListView來展示,縱向滾動條顯示的集合數據。這兩個控件的默認樣式,以及對觸控的支持,已完全落后於時代。他們兩個分別長這樣,和Win10及Win11的風格完全不沾邊。

今天我來看下WinUI 3中適用於Desktop App的ListView,我們略過ListBox是因為ListBox使用較為簡單,通常用於將少量數據同時顯示在頁面上。
而顯示需要滾動條的大量數據,會推薦使用ListView,同時ListView的表現形式也較為豐富。例如分組,拖拽,縮放,以及頁頭頁腳的自定義。
本篇我們將試着應用以上這些特性到Desktop App中,通過WinUI 3庫,我們不再需要Xaml Islands這樣曲線救國的做法。而是真正在Desktop App中用上原生的新ListView。
首先我們來看分組,WinUI 3中的ListView通過CollectionViewSource這個組件來實現分組功能。當然我們也可以通過嵌套集合的方式來實現,例如將ListView的ListViewItem同樣也包含一個ListView/ItemsControl。但是這樣做有一些缺陷,第一是會破壞UI虛擬化(UI virtualization),因為用於虛擬化的容器(Item Container)在面對重復的Item才會起到效果,用ListView/ItemsControl這樣的可變集合作為Item,在數量增多后會有明顯的性能問題。第二是嵌套集合的話,SeletedItem和ItemClick處理起來會比較困難。所以我更推薦使用CollectionViewSource。

ListView的分組並不復雜,在XAML中我們需要於Resources節點中放置CollectionViewSource對象,該對象是真正數據源的一個視圖。我們可以對這個視圖做分組和排序,但不會自動影響到真正的數據源。
假設我們有一個Person類:

    public class Person
    {
        public string Name { get; set; }
    }

並構建了PersionList作為數據源,同時創建了分組視圖PersonGroup。

            this.PersonList = new List<Person>
            {
                new Person{ Name = "Abe"},
                new Person{ Name = "Alice"},
                new Person{ Name = "Bell"},
                new Person{ Name = "Ben"},
                new Person{ Name = "Bob"},
                new Person{ Name = "Fox"},
                new Person{ Name = "Gray"},
                new Person{ Name = "James"},
                new Person{ Name = "Jane"},
                new Person{ Name = "Roy"},
                new Person{ Name = "Vincent"}
            };
            PersonGroup = PersonList.GroupBy(p => p.Name.First().ToString());

那么我們在XAML中,將通過如下形式的binding來使用該分組視圖:

    <Grid>
        <Grid.Resources>
            <CollectionViewSource x:Name="personListCVS" IsSourceGrouped="True" Source="{x:Bind PersonGroup}"/>
        </Grid.Resources>
        <ListView ItemsSource="{x:Bind personListCVS.View}">
            <ListView.GroupStyle>
                <GroupStyle>
                <GroupStyle.HeaderTemplate>
                    <DataTemplate>
                        <TextBlock Text="{Binding Key}"/>
                    </DataTemplate>
                </GroupStyle.HeaderTemplate>
                </GroupStyle>
            </ListView.GroupStyle>
            <ListView.ItemTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding Name}"></TextBlock>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>
    </Grid>

上面的XAML中定義簡單的僅TextBlock的HeaderTemplate和ItemTemplate,實際生產中可按需編寫更復雜的模板。
接下來我們看拖拽的實現,這里我們創建一個DragDropPage.xaml,假設ListView作為被拖走數據的一方,在DataTemplate的子節點上,將CanDrag設置為True,因為接受數據的需要,我們要明確被拖拽走的是個什么東西,通過DragStarting事件可以獲得被操作的UIElement。

        <ListView Grid.Column="0" ItemsSource="{x:Bind PersonList}">
            <ListView.ItemTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding Name}" CanDrag="True" DragStarting="TextBlock_DragStarting"></TextBlock>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>

作為接受拖拽對象的ComboxBox,除了設置AllowDrop=True以外,還需要通過DragOver來響應圖標的變化(假設在鼠標移過來后,圖標從“無效”的樣式轉變成了“復制”,表示可以接受該數據),以及通過Drop來處理接受數據。

        <ComboBox Grid.Column="1" ItemsSource="{x:Bind PersonList}" SelectedItem="{x:Bind SelectedPerson,Mode=TwoWay}"
                  AllowDrop="True" DragOver="ComboBox_DragOver" Drop="ComboBox_Drop">
            <ComboBox.ItemTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding Name}"></TextBlock>
                </DataTemplate>
            </ComboBox.ItemTemplate>
        </ComboBox>

在創建本篇的Sample Code時,WinUI 3 的SDK版本已經從0.5更新到0.8了(穩定版)。但是在Drop事件里依然遇到了bug,無法訪問DragEventArgs中的DataView來獲取DragStarting時存放的數據。這個問題已經有大量的issue開給了某軟,並不是WinUI 3庫本身的bug,鍋已丟給CsWinRT項目,但什么時候修好就不知道了。
Unable to drop files onto Grid in WinUI3 Desktop · Issue #2715 · microsoft/microsoft-ui-xaml (github.com)
DataView無法使用,直接影響了應用間的數據傳遞。對於本篇的APP內部的拖拽,倒是可以寫個Property繞過去。拖拽操作的流程先是通過DragStarting來獲取和存放要傳遞的數據。DragOver負責更改圖標,反饋給用戶某個控件可以接受Drop操作。最后由Drop來接受數據和更新UI。

        private void ComboBox_Drop(object sender, DragEventArgs e)
        {
            //if (e.DataView.Contains(StandardDataFormats.Text))
            //{
            //    var name = await e.DataView.GetTextAsync();
            //    this.SelectedPerson = this.PersonList.FirstOrDefault(p => p.Name == name);
            //}

            this.SelectedPerson = DragPerson;
        }

        private void ComboBox_DragOver(object sender, DragEventArgs e)
        {
            e.AcceptedOperation = DataPackageOperation.Copy;
        }

        private void TextBlock_DragStarting(UIElement sender, DragStartingEventArgs args)
        {
            DragPerson = (sender as TextBlock).DataContext as Person;
            //args.Data.SetData(StandardDataFormats.Text, DragPerson.Name);
        }

本篇最后我想討論下<ListView.Header>和<ListView.Footer>,通常認為額外做一個<StackPanel>或<Grid>放置在ListView的上下方即可。不過我想說的是,如果遇到Layout變化,通過VisaulState等方式來位移控件的情況,一個整體的ListView會比較方便。在ListViewHeaderFooterPage中,假設ListView上方存在MenuBar,下部需要CommandBar。如果這兩個控件一直和ListView保持緊密的聯系,那就可以放到Header和Footer中作為一個整體。

 以上就是本篇對WinUI 3中ListView的一些討論。縮放視圖打算后面結合SemanticZoom 再來演示。感謝閱讀到這里的同學們!

Sample Code:
https://github.com/manupstairs/WinUI3Samples/tree/main/WinUI3Samples/ListViewSample

以下鏈接,是MS Learn上Windows開發的入門課程,單個課程三十分鍾到60分鍾不等,如需補充基礎知識的同學點這里:

開始使用 Visual Studio 開發 Windows 10 應用

開發 Windows 10 應用程序

編寫首個 Windows 10 應用

創建 Windows 10 應用的用戶界面 (UI)

增強 Windows 10 應用的用戶界面

在 Windows 10 應用中實現數據綁定

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM