让WPF中的DataGrid像Excel一样可以筛选 您所在的位置:网站首页 动态数据表格如何实现筛选功能 让WPF中的DataGrid像Excel一样可以筛选

让WPF中的DataGrid像Excel一样可以筛选

2024-07-05 06:18| 来源: 网络整理| 查看: 265

在默认情况下,WPF提供的DataGrid仅拥有数据展示等简单功能,如果要实现像Excel一样复杂的筛选过滤功能,则相对比较麻烦。本文以一个简单的小例子,简述如何通过WPF实话DataGrid的筛选功能,仅供学习分享使用,如有不足之处,还请指正。

涉及知识点

在本示例中,从数据绑定,到数据展示,涉及知识点如下所示:

DataGrid,要WPF提供的进行二维数据展示在列表控件,默认功能非常简单,但是可以通过数据模板或者控件模板进行扩展和美化,可伸缩性很强。 MVVM,是Model-View-ViewModel的简写,主要进行数据和UI进行前后端分离,在本示例中,主要用到的MVVM第三方库为CommunityToolkit.Mvvm,大大简化原生MVVM的实现方式。 集合视图, 要对 DataGrid 中的数据进行分组、排序和筛选,可以将其绑定到支持这些函数的 CollectionView。 然后,可以在不影响基础源数据的情况下处理 CollectionView 中的数据。 集合视图中的更改反映在 DataGrid 用户界面 (UI) 中。 Popup控件,直接继承FrameworkElement,提供了一种在单独的窗口中显示内容的方法,该窗口相对于指定的元素或屏幕坐标,浮动在当前Popup应用程序窗口上,可用于悬浮窗口。 示例截图

本示例主要模仿Excel的筛选功能进行实现,右键标题栏打开浮动窗口,悬浮于标题栏下方,既可以通过文本框进行筛选,也可以通过筛选按钮弹出右键菜单,选择具体筛选方式,截图如下所示:

 

选择筛选方式,弹出窗口,如下所示:

 

 输入筛选条件,点击确定,或者取消筛选。如筛选学号里面包含2的,效果如下所示:

 

 注意:以上筛选都是客户端筛选,不会修改数据源,也不会重连数据库。

核心源码

在本示例中,核心源码主要包含以下几个部分:

前端视图【MainWindow.xaml】源码

主要实现了按学号,姓名,年龄三列进行筛选,既可以单列筛选,又可以组合筛选。且三列的筛选实现方式一致,仅是绑定列有差异。

1 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 M608 864C588.8 864 576 851.2 576 832L576 448c0-6.4 6.4-19.2 12.8-25.6L787.2 256c6.4-6.4 6.4-19.2 0-19.2 0-6.4-6.4-12.8-19.2-12.8L256 224c-12.8 0-19.2 6.4-19.2 12.8 0 6.4-6.4 12.8 6.4 19.2l198.4 166.4C441.6 428.8 448 441.6 448 448l0 256c0 19.2-12.8 32-32 32S384 723.2 384 704L384 460.8 198.4 307.2c-25.6-25.6-32-64-19.2-96C185.6 179.2 217.6 160 256 160L768 160c32 0 64 19.2 76.8 51.2 12.8 32 6.4 70.4-19.2 89.6l-192 160L633.6 832C640 851.2 627.2 864 608 864z 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312

业务逻辑【MainWindowViewModel】

业务逻辑处理主要复责数据初始化等业务相关内容,和UI无关,如下所示:

1 using CommunityToolkit.Mvvm.ComponentModel; 2 using System; 3 using System.Collections.Generic; 4 using System.Linq; 5 using System.Text; 6 using System.Threading.Tasks; 7 8 namespace DemoDataGrid 9 { 10 public class MainWindowViewModel:ObservableObject 11 { 12 #region 属性及构造函数 13 14 private List students; 15 16 public List Students 17 { 18 get { return students; } 19 set { SetProperty(ref students, value); } 20 } 21 22 private List names; 23 24 public List Names 25 { 26 get { return names; } 27 set { SetProperty(ref names, value); } 28 } 29 30 private List nos; 31 32 public List Nos 33 { 34 get { return nos; } 35 set {SetProperty(ref nos , value); } 36 } 37 38 private List ages; 39 40 public List Ages 41 { 42 get { return ages; } 43 set {SetProperty(ref ages , value); } 44 } 45 46 47 48 public MainWindowViewModel() 49 { 50 this.Students= new List(); 51 for (int i = 0; i < 20; i++) { 52 this.Students.Add(new Student() 53 { 54 Id = i, 55 Name = $"张{i}牛", 56 Age = (i % 10) + 10, 57 No = i.ToString().PadLeft(4, '0'), 58 }); 59 } 60 this.Nos= new List(); 61 this.Names= new List(); 62 this.Ages= new List(); 63 this.Students.ForEach(s => { 64 this.Nos.Add(new FilterInfo() { FilterText=s.No,IsChecked=false }); 65 this.Names.Add(new FilterInfo() { FilterText = s.Name, IsChecked = false }); 66 this.Ages.Add(new FilterInfo() { FilterText = s.Age.ToString(), IsChecked = false }); 67 }); 68 this.Ages=this.Ages.Distinct().ToList();//去重 69 } 70 71 #endregion 72 73 74 } 75 }

筛选功能实现【MainWindow.xaml.cs】

本示例为了简化实现,筛选功能处理主要在cs后端实现,如下所示:

1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 using System.Windows; 7 using System.Windows.Controls; 8 using System.Windows.Data; 9 using System.Windows.Documents; 10 using System.Windows.Input; 11 using System.Windows.Media; 12 using System.Windows.Media.Imaging; 13 using System.Windows.Navigation; 14 using System.Windows.Shapes; 15 16 namespace DemoDataGrid 17 { 18 /// 19 /// Interaction logic for MainWindow.xaml 20 /// 21 public partial class MainWindow : Window 22 { 23 private MainWindowViewModel viewModel; 24 25 public MainWindow() 26 { 27 InitializeComponent(); 28 viewModel = new MainWindowViewModel(); 29 this.DataContext = viewModel; 30 } 31 32 33 #region 筛选 34 35 private void TextBlock_MouseRightButtonDown(object sender, MouseButtonEventArgs e) 36 { 37 if (sender != null && sender is TextBlock) 38 { 39 var textBlock = sender as TextBlock; 40 var tag = textBlock.Tag.ToString(); 41 var pop = this.FindName($"popup{tag}"); 42 if (pop != null) 43 { 44 var popup = pop as System.Windows.Controls.Primitives.Popup; 45 if (popup != null) 46 { 47 popup.IsOpen = true; 48 popup.PlacementTarget = textBlock; 49 popup.Placement = System.Windows.Controls.Primitives.PlacementMode.RelativePoint; 50 popup.VerticalOffset = 10; 51 popup.HorizontalOffset = 10; 52 } 53 } 54 } 55 } 56 57 private void TextBox_TextChanged(object sender, TextChangedEventArgs e) 58 { 59 TextBox textBox = e.OriginalSource as TextBox; 60 var tag = textBox.Tag;//条件 61 var text = textBox.Text; 62 if (tag != null) 63 { 64 if (tag.ToString() == "No") 65 { 66 Filter(this.lbNos.ItemsSource, this.txtNo.Text); 67 } 68 if (tag.ToString() == "Name") 69 { 70 Filter(this.lbNames.ItemsSource, this.txtName.Text); 71 } 72 if (tag.ToString() == "Age") 73 { 74 Filter(this.lbAges.ItemsSource, this.txtAge.Text); 75 } 76 } 77 78 } 79 80 private void Filter(object source, string filter) 81 { 82 var cv = CollectionViewSource.GetDefaultView(source); 83 if (cv != null && cv.CanFilter) 84 { 85 cv.Filter = new Predicate((obj) => { 86 bool flag = true; 87 var t = obj as FilterInfo; 88 if (t != null) 89 { 90 flag = t.FilterText.Contains(filter); 91 } 92 return flag; 93 }); 94 } 95 } 96 97 private void popup_MouseLeave(object sender, MouseEventArgs e) 98 { 99 var popup = e.OriginalSource as System.Windows.Controls.Primitives.Popup; 100 var showContext = (this.FindResource("queryConditionMenu") as ContextMenu)?.IsOpen; 101 if (popup != null && showContext==false) 102 { 103 popup.IsOpen = false; 104 } 105 } 106 107 private void btnCancel_Click(object sender, RoutedEventArgs e) 108 { 109 var btn = e.OriginalSource as Button; 110 if (btn != null) 111 { 112 var tag = btn.Tag; 113 if (tag.ToString() == "No") 114 { 115 ClearFilter(this.txtNo, this.viewModel.Nos); 116 } 117 if (tag.ToString() == "Name") 118 { 119 ClearFilter(this.txtName, this.viewModel.Names); 120 121 } 122 if (tag.ToString() == "Age") 123 { 124 ClearFilter(this.txtAge, this.viewModel.Ages); 125 } 126 FilterTask();//清除以后,重新刷新 127 } 128 } 129 130 private void ClearFilter(TextBox textBox, List collection) 131 { 132 textBox.Clear(); 133 foreach (var f in collection) 134 { 135 f.IsChecked = false; 136 } 137 } 138 139 private void btnOk_Click(object sender, RoutedEventArgs e) 140 { 141 // 142 FilterTask(); 143 } 144 145 146 private void FilterTask() 147 { 148 var cv = CollectionViewSource.GetDefaultView(this.dgStudents.ItemsSource); 149 if (cv != null && cv.CanFilter) 150 { 151 cv.Filter = new Predicate((obj) => 152 { 153 bool flag = true; 154 var t = obj as Student; 155 if (t != null) 156 { 157 var nos = this.viewModel.Nos.Where(r => r.IsChecked == true).ToList(); 158 var names = this.viewModel.Names.Where(r => r.IsChecked == true).ToList(); 159 var ages = this.viewModel.Ages.Where(r => r.IsChecked == true).ToList(); 160 if (nos.Count() > 0) 161 { 162 flag = flag && nos.Select(r => r.FilterText).Contains(t.No); 163 } 164 if (names.Count() > 0) 165 { 166 flag = flag && names.Select(r => r.FilterText).Contains(t.Name); 167 } 168 if (ages.Count() > 0) 169 { 170 flag = flag && ages.Select(r => r.FilterText).Contains(t.Age.ToString()); 171 } 172 } 173 return flag; 174 }); 175 } 176 } 177 178 #endregion 179 180 private List condition = new List() { "Equal", "NotEqual", "Begin", "End", "In", "NotIn" }; 181 182 private void ButtonFilter_Click(object sender, RoutedEventArgs e) 183 { 184 var btn = e.OriginalSource as Button; 185 if (btn != null) 186 { 187 var tag = btn.Tag; 188 var popup = this.FindName($"popup{tag}") as System.Windows.Controls.Primitives.Popup; 189 if (popup != null) 190 { 191 popup.IsOpen = true; 192 } 193 if (btn.ContextMenu.IsOpen) 194 { 195 btn.ContextMenu.IsOpen = false; 196 } 197 else 198 { 199 btn.ContextMenu.Tag = tag; 200 btn.ContextMenu.Width = 100; 201 btn.ContextMenu.Height = 150; 202 btn.ContextMenu.IsOpen = true; 203 btn.ContextMenu.PlacementTarget = btn; 204 btn.ContextMenu.Placement = System.Windows.Controls.Primitives.PlacementMode.Bottom; 205 } 206 } 207 } 208 209 private void ContextMenu_MouseLeave(object sender, MouseEventArgs e) 210 { 211 var menu = e.OriginalSource as ContextMenu; 212 if (menu != null) 213 { 214 menu.IsOpen = false; 215 } 216 } 217 218 private void ContextMenu_Click(object sender, RoutedEventArgs e) 219 { 220 var contextMenu = sender as ContextMenu; 221 if (contextMenu == null) 222 { 223 return; 224 } 225 var menuItem = e.OriginalSource as MenuItem; 226 if (menuItem == null) 227 { 228 return; 229 } 230 var tag1 = contextMenu.Tag.ToString();//点击的哪一个按钮 231 var tag2 = menuItem.Tag.ToString();//点击的是哪一个菜单 232 var pop = this.FindName($"popup{tag1}Menu"); 233 var comb = this.FindName($"comb{tag1}Menu1"); 234 HideParentPopup(tag1);//隐藏父Popup 235 if (comb != null) 236 { 237 var combMenu = comb as ComboBox; 238 combMenu.SelectedIndex = condition.IndexOf(tag2); 239 } 240 if (pop != null) 241 { 242 var popup = pop as System.Windows.Controls.Primitives.Popup; 243 popup.IsOpen = true; 244 popup.PlacementTarget = dgStudents; 245 popup.Placement = System.Windows.Controls.Primitives.PlacementMode.Center; 246 } 247 } 248 249 private void btnCancelFilter_Click(object sender, RoutedEventArgs e) 250 { 251 if (sender == null) 252 { 253 return; 254 } 255 var btn = sender as System.Windows.Controls.Button; 256 if (btn != null) 257 { 258 var tag = btn.Tag.ToString(); 259 HidePopupMenu(tag);//隐藏Popup控件 260 if (tag == "No") 261 { 262 ClearMenuFilter(this.txtNoMenu1, this.txtNoMenu2); 263 } 264 if (tag == "Name") 265 { 266 ClearMenuFilter(this.txtNameMenu1, this.txtNameMenu2); 267 } 268 if (tag == "Age") 269 { 270 ClearMenuFilter(this.txtAgeMenu1, this.txtAgeMenu2); 271 } 272 FilterMenuTask(); 273 } 274 } 275 276 277 private void btnOkFilter_Click(object sender, RoutedEventArgs e) 278 { 279 if (sender == null) 280 { 281 return; 282 } 283 var btn = sender as System.Windows.Controls.Button; 284 if (btn != null) 285 { 286 var tag = btn.Tag.ToString(); 287 HidePopupMenu(tag); 288 FilterMenuTask(); 289 } 290 } 291 292 /// 293 /// 隐藏父Popup 294 /// 295 /// 296 private void HideParentPopup(string tag) 297 { 298 //点击右键菜单时,隐藏父Popup控件 299 if (tag == "No") 300 { 301 this.popupNo.IsOpen = false; 302 } 303 if (tag == "Name") 304 { 305 this.popupName.IsOpen = false; 306 } 307 if (tag == "Age") 308 { 309 this.popupAge.IsOpen = false; 310 } 311 } 312 313 /// 314 /// 隐藏菜单弹出的Popup控件 315 /// 316 /// 317 private void HidePopupMenu(string tag) 318 { 319 var pop = this.FindName($"popup{tag}Menu"); 320 if (pop != null) 321 { 322 var popup = pop as System.Windows.Controls.Primitives.Popup; 323 popup.IsOpen = false; 324 } 325 } 326 327 /// 328 /// 清除菜单中的文本过滤条件 329 /// 330 /// 331 /// 332 private void ClearMenuFilter(TextBox txt1, TextBox txt2) 333 { 334 txt1?.Clear(); 335 txt2?.Clear(); 336 } 337 338 /// 339 /// 340 /// 341 private void FilterMenuTask() 342 { 343 var cv = CollectionViewSource.GetDefaultView(this.dgStudents.ItemsSource); 344 if (cv != null && cv.CanFilter) 345 { 346 cv.Filter = new Predicate((obj) => 347 { 348 bool flag = true; 349 var t = obj as Student; 350 if (t != null) 351 { 352 string noText1 = this.txtNoMenu1.Text.Trim(); 353 string noText2 = this.txtNoMenu2.Text.Trim(); 354 int noConditionType1 = this.combNoMenu1.SelectedIndex; 355 int noConditionType2 = this.combNoMenu2.SelectedIndex; 356 string nameText1 = this.txtNameMenu1.Text.Trim(); 357 string nameText2 = this.txtNameMenu2.Text.Trim(); 358 int nameConditionType1 = this.combNameMenu1.SelectedIndex; 359 int nameConditionType2 = this.combNameMenu2.SelectedIndex; 360 string ageText1 = this.txtAgeMenu1.Text.Trim(); 361 string ageText2 = this.txtAgeMenu2.Text.Trim(); 362 int ageConditionType1 = this.combAgeMenu1.SelectedIndex; 363 int ageConditionType2 = this.combAgeMenu2.SelectedIndex; 364 bool? isNoAnd = this.rbNoAnd.IsChecked; 365 bool? isNoOr = this.rbNoOr.IsChecked; 366 bool? isNameAnd = this.rbNameAnd.IsChecked; 367 bool? isNameOr = this.rbNameOr.IsChecked; 368 bool? isAgeAnd = this.rbAgeAnd.IsChecked; 369 bool? isAgeOr = this.rbAgeOr.IsChecked; 370 bool flagNo = true; 371 bool flagName = true; 372 bool flagAge = true; 373 flagNo = CheckConditions(noConditionType1, noConditionType2, t.No, noText1, noText2, isNoAnd, isNoOr); 374 flagName = CheckConditions(nameConditionType1, nameConditionType2, t.Name, nameText1, nameText2, isNameAnd, isNameOr); 375 flagAge = CheckConditions(ageConditionType1, ageConditionType2, t.Age.ToString(), ageText1, ageText2, isAgeAnd, isAgeOr); 376 flag = flag && flagNo && flagName && flagAge; 377 } 378 return flag; 379 }); 380 } 381 } 382 383 private bool CheckConditions(int conditionIndex1, int conditionIndex2, string source, string condition1, string condition2, bool? isAnd, bool? isOr) 384 { 385 bool flag = true; 386 bool flag1 = true; 387 bool flag2 = true; 388 if (!string.IsNullOrEmpty(condition1) && !string.IsNullOrWhiteSpace(condition1) && conditionIndex1 != -1) 389 { 390 flag1 = CheckCondition(conditionIndex1, source, condition1); 391 } 392 if (!string.IsNullOrEmpty(condition2) && !string.IsNullOrWhiteSpace(condition2) && conditionIndex2 != -1) 393 { 394 flag2 = CheckCondition(conditionIndex2, source, condition2); 395 } 396 if (isAnd == true) 397 { 398 flag = flag1 && flag2; 399 } 400 if (isOr == true) 401 { 402 flag = flag1 || flag2; 403 } 404 return flag; 405 } 406 407 private bool CheckCondition(int condtionIndex, string source, string condition) 408 { 409 bool flag = true; 410 if (condtionIndex == 0) 411 { 412 flag = flag && source == condition; 413 } 414 if (condtionIndex == 1) 415 { 416 flag = flag && source != condition; 417 } 418 if (condtionIndex == 2) 419 { 420 flag = flag && source.StartsWith(condition); 421 } 422 if (condtionIndex == 3) 423 { 424 flag = flag && source.EndsWith(condition); 425 } 426 if (condtionIndex == 4) 427 { 428 flag = flag && source.Contains(condition); 429 } 430 if (condtionIndex == 5) 431 { 432 flag = flag && !source.Contains(condition); 433 } 434 return flag; 435 } 436 } 437 }

学号,姓名,年龄三列过滤列表绑定内容模型一致,为FilterInfo,如下所示:

using CommunityToolkit.Mvvm.ComponentModel; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace DemoDataGrid { public class FilterInfo : ObservableObject { private string filterText; public string FilterText { get { return filterText; } set { SetProperty(ref filterText, value); } } private bool isChecked; public bool IsChecked { get { return isChecked; } set { SetProperty(ref isChecked, value); } } } } 不足与思考

上述筛选实现方式,并非唯一实现,也并非最优实现,同样存在许多可以优化的地方。

在本示例中,存在许多冗余代码,如视图页面,对三列的弹出窗口,内容虽然相对统一,只是列名和绑定内容不同而已,却堆积了三大段代码,是否可以从控件模块或者数据模板的角度,进行简化呢?

筛选功能实现上,同样存在许多冗余代码,是否可以进行简化呢?以上是我们需要思考的地方,希望可以集思广益,共同学习,一起进步。



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有