WPF WrapPanel 实现虚拟化

 WrapPanel 实现虚拟化

控件名:VirtualizingWrapPanel

作者:WPFDevelopersOrg

原文链接:    https://github.com/WPFDevelopersOrg/WPFDevelopers

  • 框架使用大于等于.NET40

  • Visual Studio 2022;

  • 项目使用 MIT 开源许可协议;.

  • 众所周知 WPF 的 StackPanel 在加载大量数据时性能会特别差,但是官方提供了一个虚拟化容器VirtualizingStackPanel[1] ;

    • VirtualizingStackPanel.IsVirtualizing 附加属性设置为 true时就开启虚拟化。
    • VirtualizingStackPanel.IsVirtualizing 附加属性设置为 falseVirtualizingStackPanel行为与普通StackPanel属性的行为相同。
  • WrapPanel 默认是不支持虚拟化的,所以需要自行实现。

1) VirtualizingWrapPanel 查看源码1[2]  |   VirtualizingWrapPanel 查看源码2[3]

2) 准备数据HospitalList.cs如下:

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Windows.Media;

namespace WPFDevelopers.Minimal.Sample.Models
{
    public class HospitalList : ObservableCollection<Hospital>
    {
        public HospitalList()
        {
            var hospitals = new string[] { "No. 189, Grove St, Los Angeles", "No. 3669, Grove St, Los Angeles" };
            var names = new string[] { "Doctor Fang", "Judge Qu" };
            var images = new string[] 
                { "https://pic2.zhimg.com/80/v2-0711e97955adc9be9fbcff67e1007535_720w.jpg",
                  //"https://pic2.zhimg.com/80/v2-5b7f84c63075ba9771f6e6dc29a54615_720w.jpg",
                  "https://pic3.zhimg.com/80/v2-a3d6d8832090520e7ed6c748a8698e4e_720w.jpg",
                  "https://pic3.zhimg.com/80/v2-de7554ac9667a59255fe002bb8753ab6_720w.jpg"
                };
            var state = 0;
            for (var i = 1; i < 10000; i++)
            {
                Add(new Hospital { Id = $"9999{i}", DoctorName = i % 2 == 0 ? names[0]:names[1], HospitalName = i % 2 == 0 ? hospitals[0] : hospitals[1] ,State = state ,UserImage = images[state] });
                state++;
                if (state > 2)
                    state = 0;
            }
        }
    }

    public class Hospital
    {
        public string Id { get; set; }
        public string DoctorName { get; set; }
        public string HospitalName { get; set; }
        public string UserImage { get; set; }
        public int State { get; set; }
    }
}

3) 新建展示VirtualizingWrapPanelExample.xaml如下:

 <ws:Window x:Class="WPFDevelopers.Minimal.Sample.ExampleViews.VirtualizingWrapPanelExample"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:ws="https://github.com/WPFDevelopersOrg/WPFDevelopers.Minimal"
        xmlns:local="clr-namespace:WPFDevelopers.Minimal.Sample.ExampleViews"
        xmlns:model="clr-namespace:WPFDevelopers.Minimal.Sample.Models"
        xmlns:converts="clr-namespace:WPFDevelopers.Minimal.Sample.Converts"
        mc:Ignorable="d" WindowStartupLocation="CenterScreen"
        Title="System V1.0" Height="450" Width="900">
    <Window.Resources>
        <model:HospitalList x:Key="myHospitalList"/>
        <converts:StateConvert  x:Key="stateConvert"></converts:StateConvert>
    </Window.Resources>
    <Grid Margin="4">
        <WrapPanel HorizontalAlignment="Left">
            <WrapPanel.Resources>
                <Style TargetType="Border">
                    <Setter Property="Padding" Value="2"></Setter>
                    <Setter Property="BorderThickness" Value="1"></Setter>
                </Style>
                <Style TargetType="Rectangle">
                    <Setter Property="Width" Value="15"></Setter>
                    <Setter Property="Height" Value="15"></Setter>
                    <Setter Property="Opacity" Value=".2"></Setter>
                </Style>
            </WrapPanel.Resources>
            <WrapPanel>
                <Border BorderBrush="Green">
                    <Rectangle Fill="Green"/>
                </Border>
                <TextBlock Text="Idle" Foreground="Black" Margin="4,0"/>
            </WrapPanel>
            <WrapPanel>
                <Border BorderBrush="Orange">
                    <Rectangle Fill="Orange"/>
                </Border>
                <TextBlock Text="Slightly Idle" Foreground="Black" Margin="4,0"/>
            </WrapPanel>
            <WrapPanel>
                <Border BorderBrush="Red">
                    <Rectangle Fill="Red"/>
                </Border>
                <TextBlock Text="Busy" Foreground="Black" Margin="4,0"/>
            </WrapPanel>
        </WrapPanel>
        <TextBlock HorizontalAlignment="Right" Foreground="Black"
                   Margin="4,2" FontSize="16">
            <Run Text="Count:"></Run>
            <Run Text="{Binding ElementName=DocumentsList,Path=.Items.Count,Mode=OneTime}"></Run>
        </TextBlock>

        <ListBox x:Name="DocumentsList"
      ItemsSource="{Binding Source={StaticResource myHospitalList}}"
                     Margin="0,24,0,0">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <Border BorderBrush="{Binding State,Converter={StaticResource stateConvert}}" 
                            BorderThickness="1"
                            Width="196"
                            Height="94">
                        <Grid>
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition/>
                                <ColumnDefinition/>
                            </Grid.ColumnDefinitions>
                            <Grid.RowDefinitions>
                                <RowDefinition/>
                                <RowDefinition/>
                                <RowDefinition/>
                            </Grid.RowDefinitions>
                            <Rectangle 
                                Fill="{Binding State,Converter={StaticResource stateConvert}}" 
                                Opacity=".2" Grid.ColumnSpan="2" 
                                Grid.RowSpan="3"/>

                            <Border Grid.RowSpan="2" Grid.Column="0" Width="60" Height="60"
                                       Margin="0,4,0,0" CornerRadius="10">
                                <Border.Background>
                                    <ImageBrush ImageSource="{Binding UserImage}" Stretch="Uniform"/>
                                </Border.Background>
                            </Border>
                            <TextBlock Grid.Column="1" Grid.Row="0"
                     Text="{Binding Path=Id}" Margin="0,4,0,0"/>
                            <TextBlock Grid.Column="1" Grid.Row="1"
                     Text="{Binding Path=DoctorName}"/>
                            <TextBlock Grid.ColumnSpan="2" Grid.Row="2"
                                       Padding="10,0"
                     Text="{Binding Path=HospitalName}" TextTrimming="CharacterEllipsis"/>
                        </Grid>
                    </Border>
                </DataTemplate>
            </ListBox.ItemTemplate>
            <ListBox.Template>
                <ControlTemplate>
                    <Border CornerRadius="2" 
                   BorderBrush="{TemplateBinding BorderBrush}"
                   BorderThickness="{TemplateBinding BorderThickness}">
                        <ScrollViewer x:Name="ScrollViewer"
                                      Padding="{TemplateBinding Padding}" 
                                      Background="{TemplateBinding Background}" 
                                      BorderBrush="Transparent" BorderThickness="0"  IsTabStop="False">
                            <ItemsPresenter />
                        </ScrollViewer>
                    </Border>
                </ControlTemplate>
            </ListBox.Template>

            <ListBox.ItemsPanel>
                <ItemsPanelTemplate>
                    <ws:VirtualizingWrapPanel ItemWidth="200"
                                                 ItemHeight="100"/>
                </ItemsPanelTemplate>
            </ListBox.ItemsPanel>
        </ListBox>
    </Grid>
</ws:Window>

4) 状态StateConvert.cs如下:

 using System;
using System.Windows.Data;
using System.Windows.Media;

namespace WPFDevelopers.Minimal.Sample.Converts
{
    public class StateConvert : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo cultureInfo)
        {
            var color = Brushes.Green;
            if (value != null)
            {
                var state = int.Parse(value.ToString());
                switch (state)
                {
                    case 0:
                        color = Brushes.Green;
                        break;
                    case 1:
                        color = Brushes.Orange;
                        break;
                    case 2:
                        color = Brushes.Red;
                        break;
                }
            }

            return color;
        }
        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo cultureInfo)
        {
            throw new NotImplementedException();
        }
    }
}
WPF WrapPanel 实现虚拟化