qidao123.com技术社区-IT企服评测·应用市场

标题: WPF封装一个懒加载下拉列表控件(支持搜索) [打印本页]

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


 
    
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。




欢迎光临 qidao123.com技术社区-IT企服评测·应用市场 (https://dis.qidao123.com/) Powered by Discuz! X3.4