因为项目中PC端前端针对基础数据选择时的下拉列表做了懒加载控件,PC端使用现成的组件,为保持两端的选择方式同一,WPF客户端上也需要使用懒加载的下拉选择。WPF这种懒加载的控件未找到现成可用的组件,于是自己封装了一个懒加载和支持模糊过滤的下拉列表控件,控件使用了虚拟化加载,解决了大数据量时的渲染数据卡顿题目,下面是完整的代码和示例: 一、控件所需的关键实体类- 1 /// <summary>
- 2 /// 下拉项
- 3 /// </summary>
- 4 public class ComboItem
- 5 {
- 6 /// <summary>
- 7 /// 实际存储值
- 8 /// </summary>
- 9 public string? ItemValue { get; set; }
- 10 /// <summary>
- 11 /// 显示文本
- 12 /// </summary>
- 13 public string? ItemText { get; set; }
- 14 }
- 15
- 16 /// <summary>
- 17 /// 懒加载下拉数据源提供器
- 18 /// </summary>
- 19 public class ComboItemProvider : ILazyDataProvider<ComboItem>
- 20 {
- 21 private readonly List<ComboItem> _all;
- 22 public ComboItemProvider()
- 23 {
- 24 _all = Enumerable.Range(1, 1000000)
- 25 .Select(i => new ComboItem { ItemValue = i.ToString(), ItemText = $"Item {i}" })
- 26 .ToList();
- 27 }
- 28 public async Task<PageResult<ComboItem>> FetchAsync(string filter, int pageIndex, int pageSize)
- 29 {
- 30 await Task.Delay(100);
- 31 var q = _all.AsQueryable();
- 32 if (!string.IsNullOrEmpty(filter))
- 33 q = q.Where(x => x.ItemText.Contains(filter, StringComparison.OrdinalIgnoreCase));
- 34 var page = q.Skip(pageIndex * pageSize).Take(pageSize).ToList();
- 35 bool has = q.Count() > (pageIndex + 1) * pageSize;
- 36 return new PageResult<ComboItem> { Items = page, HasMore = has };
- 37 }
- 38 }
- 39
- 40 /// <summary>
- 41 /// 封装获取数据的接口
- 42 /// </summary>
- 43 /// <typeparam name="T"></typeparam>
- 44 public interface ILazyDataProvider<T>
- 45 {
- 46 Task<PageResult<T>> FetchAsync(string filter, int pageIndex, int pageSize);
- 47 }
- 48
- 49 /// <summary>
- 50 /// 懒加载下拉分页对象
- 51 /// </summary>
- 52 /// <typeparam name="T"></typeparam>
- 53 public class PageResult<T>
- 54 {
- 55 public IReadOnlyList<T> Items { get; set; }
- 56 public bool HasMore { get; set; }
- 57 }
复制代码 二、懒加载控件视图和数据逻辑- 1 <UserControl
- 2 x:Class="LazyComboBoxFinalDemo.Controls.LazyComboBox"
- 3 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
- 4 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
- 5 xmlns:local="clr-namespace:LazyComboBoxFinalDemo.Controls">
- 6 <UserControl.Resources>
- 7 <local:ZeroToVisibleConverter x:Key="ZeroToVisibleConverter" />
- 8
- 9
- 22
- 23
- 95
- 96
- 119
- 120
- 127
- 128
- 145 </UserControl.Resources>
- 146 <Grid>
- 147 <ToggleButton
- 148 x:Name="PART_Toggle"
- 149 Click="OnToggleClick"
- 150 Style="{StaticResource ComboToggleButtonStyle}">
- 151 <Grid>
- 152
- 153 <TextBlock
- 154 Margin="4,0,24,0"
- 155 VerticalAlignment="Center"
- 156 Text="{Binding DisplayText, RelativeSource={RelativeSource AncestorType=UserControl}}" />
- 157
- 158 </Grid>
- 159 </ToggleButton>
- 160 <Popup
- 161 x:Name="PART_Popup"
- 162 AllowsTransparency="True"
- 163 PlacementTarget="{Binding ElementName=PART_Toggle}"
- 164 PopupAnimation="Fade"
- 165 StaysOpen="False">
- 166
- 167 <Border Width="{Binding ActualWidth, ElementName=PART_Toggle}" Style="{StaticResource PopupBorder}">
- 168 <Border.Effect>
- 169 <DropShadowEffect
- 170 BlurRadius="15"
- 171 Opacity="0.7"
- 172 ShadowDepth="0"
- 173 Color="#e6e6e6" />
- 174 </Border.Effect>
- 175 <Grid Height="300">
- 176 <Grid.RowDefinitions>
- 177 <RowDefinition Height="Auto" />
- 178 <RowDefinition Height="*" />
- 179 </Grid.RowDefinitions>
- 180
- 181 <TextBox
- 182 x:Name="PART_SearchBox"
- 183 Margin="0,0,0,8"
- 184 VerticalAlignment="Center"
- 185 Style="{StaticResource WatermarkTextBox}"
- 186 TextChanged="OnSearchChanged" />
- 187
- 188 <ListBox
- 189 x:Name="PART_List"
- 190 Grid.Row="1"
- 191 DisplayMemberPath="ItemText"
- 192 ItemsSource="{Binding Items, RelativeSource={RelativeSource AncestorType=UserControl}}"
- 193 ScrollViewer.CanContentScroll="True"
- 194 ScrollViewer.ScrollChanged="OnScroll"
- 195 SelectionChanged="OnSelectionChanged"
- 196 VirtualizingStackPanel.IsVirtualizing="True"
- 197 VirtualizingStackPanel.VirtualizationMode="Recycling" />
- 198 </Grid>
- 199 </Border>
- 200 </Popup>
- 201 </Grid>
- 202 </UserControl>
复制代码  - 1 public partial class LazyComboBox : UserControl, INotifyPropertyChanged
- 2 {
- 3 public static readonly DependencyProperty ItemsProviderProperty =
- 4 DependencyProperty.Register(nameof(ItemsProvider), typeof(ILazyDataProvider<ComboItem>),
- 5 typeof(LazyComboBox), new PropertyMetadata(null));
- 6
- 7 public ILazyDataProvider<ComboItem> ItemsProvider
- 8 {
- 9 get => (ILazyDataProvider<ComboItem>)GetValue(ItemsProviderProperty);
- 10 set => SetValue(ItemsProviderProperty, value);
- 11 }
- 12
- 13 public static readonly DependencyProperty SelectedItemProperty =
- 14 DependencyProperty.Register(nameof(SelectedItem), typeof(ComboItem),
- 15 typeof(LazyComboBox),
- 16 new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnSelectedItemChanged));
- 17
- 18 public ComboItem SelectedItem
- 19 {
- 20 get => (ComboItem)GetValue(SelectedItemProperty);
- 21 set => SetValue(SelectedItemProperty, value);
- 22 }
- 23
- 24 private static void OnSelectedItemChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
- 25 {
- 26 if (d is LazyComboBox ctrl)
- 27 {
- 28 ctrl.Notify(nameof(DisplayText));
- 29 }
- 30 }
- 31
- 32 public ObservableCollection<ComboItem> Items { get; } = new ObservableCollection<ComboItem>();
- 33 private string _currentFilter = "";
- 34 private int _currentPage = 0;
- 35 private const int PageSize = 30;
- 36 public bool HasMore { get; private set; }
- 37 public string DisplayText => SelectedItem?.ItemText ?? "请选择...";
- 38
- 39 public LazyComboBox()
- 40 {
- 41 InitializeComponent();
- 42 }
- 43
- 44 public event PropertyChangedEventHandler PropertyChanged;
- 45 private void Notify(string prop) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(prop));
- 46
- 47 private async void LoadPage(int pageIndex)
- 48 {
- 49 if (ItemsProvider == null) return;
- 50 var result = await ItemsProvider.FetchAsync(_currentFilter, pageIndex, PageSize);
- 51 if (pageIndex == 0) Items.Clear();
- 52 foreach (var it in result.Items) Items.Add(it);
- 53 HasMore = result.HasMore;
- 54 PART_Popup.IsOpen = true;
- 55 }
- 56
- 57 private void OnClearClick(object sender, RoutedEventArgs e)
- 58 {
- 59 e.Handled = true; // 阻止事件冒泡,不触发 Toggle 打开
- 60 SelectedItem = null; // 清空选中
- 61 Notify(nameof(DisplayText)); // 刷新按钮文本
- 62 PART_Popup.IsOpen = false; // 确保关掉弹窗
- 63 }
- 64
- 65 private void OnToggleClick(object sender, RoutedEventArgs e)
- 66 {
- 67 _currentPage = 0;
- 68 LoadPage(0);
- 69 PART_Popup.IsOpen = true;
- 70 }
- 71
- 72 private void OnSearchChanged(object sender, TextChangedEventArgs e)
- 73 {
- 74 _currentFilter = PART_SearchBox.Text;
- 75 _currentPage = 0;
- 76 LoadPage(0);
- 77 }
- 78
- 79 private void OnScroll(object sender, ScrollChangedEventArgs e)
- 80 {
- 81 if (!HasMore) return;
- 82 if (e.VerticalOffset >= e.ExtentHeight - e.ViewportHeight - 2)
- 83 LoadPage(++_currentPage);
- 84 }
- 85
- 86 private void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
- 87 {
- 88 if (PART_List.SelectedItem is ComboItem item)
- 89 {
- 90 SelectedItem = item;
- 91 Notify(nameof(DisplayText));
- 92 PART_Popup.IsOpen = false;
- 93 }
- 94 }
- 95 }
复制代码 LazyComboBox.cs  - 1 /// <summary>
- 2 /// 下拉弹窗搜索框根据数据显示专用转换器
- 3 /// 用于将0转换为可见
- 4 /// </summary>
- 5 public class ZeroToVisibleConverter : IValueConverter
- 6 {
- 7 public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
- 8 {
- 9 if (value is int i && i == 0)
- 10 return Visibility.Visible;
- 11 return Visibility.Collapsed;
- 12 }
- 13
- 14 public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
- 15 => throw new NotImplementedException();
- 16 }
复制代码 转换器 三、视图页面使用示例- xmlns:ctrl="clr-namespace:LazyComboBoxFinalDemo.Controls"
- <Grid Margin="10">
- <ctrl:LazyComboBox
- Width="200"
- Height="40"
- ItemsProvider="{Binding MyDataProvider}"
- SelectedItem="{Binding PartSelectedItem, Mode=TwoWay}" />
- </Grid>
复制代码- [/code]//对应视图的VM中绑定命据:[code]public ILazyDataProvider<ComboItem> MyDataProvider { get; }
- = new ComboItemProvider();
- /// <summary>
- /// 当前选择值
- /// </summary>
- [ObservableProperty]
- private ComboItem partSelectedItem;
复制代码
四、结果图
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
|