|
在默认情况下,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】源码
主要实现了按学号,姓名,年龄三列进行筛选,既可以单列筛选,又可以组合筛选。且三列的筛选实现方式一致,仅是绑定列有差异。业务逻辑【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<Student> students;
- 15
- 16 public List<Student> Students
- 17 {
- 18 get { return students; }
- 19 set { SetProperty(ref students, value); }
- 20 }
- 21
- 22 private List<FilterInfo> names;
- 23
- 24 public List<FilterInfo> Names
- 25 {
- 26 get { return names; }
- 27 set { SetProperty(ref names, value); }
- 28 }
- 29
- 30 private List<FilterInfo> nos;
- 31
- 32 public List<FilterInfo> Nos
- 33 {
- 34 get { return nos; }
- 35 set {SetProperty(ref nos , value); }
- 36 }
- 37
- 38 private List<FilterInfo> ages;
- 39
- 40 public List<FilterInfo> 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<Student>();
- 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<FilterInfo>();
- 61 this.Names= new List<FilterInfo>();
- 62 this.Ages= new List<FilterInfo>();
- 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 /// <summary>
- 19 /// Interaction logic for MainWindow.xaml
- 20 /// </summary>
- 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<object>((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<FilterInfo> 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<object>((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<string> condition = new List<string>() { "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 /// <summary>
- 293 /// 隐藏父Popup
- 294 /// </summary>
- 295 /// <param name="tag"></param>
- 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 /// <summary>
- 314 /// 隐藏菜单弹出的Popup控件
- 315 /// </summary>
- 316 /// <param name="tag"></param>
- 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 /// <summary>
- 328 /// 清除菜单中的文本过滤条件
- 329 /// </summary>
- 330 /// <param name="txt1"></param>
- 331 /// <param name="txt2"></param>
- 332 private void ClearMenuFilter(TextBox txt1, TextBox txt2)
- 333 {
- 334 txt1?.Clear();
- 335 txt2?.Clear();
- 336 }
- 337
- 338 /// <summary>
- 339 ///
- 340 /// </summary>
- 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<object>((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); }
- }
- }
- }
复制代码 不足与思考
上述筛选实现方式,并非唯一实现,也并非最优实现,同样存在许多可以优化的地方。
在本示例中,存在许多冗余代码,如视图页面,对三列的弹出窗口,内容虽然相对统一,只是列名和绑定内容不同而已,却堆积了三大段代码,是否可以从控件模块或者数据模板的角度,进行简化呢?
筛选功能实现上,同样存在许多冗余代码,是否可以进行简化呢?以上是我们需要思考的地方,希望可以集思广益,共同学习,一起进步。
来源:https://www.cnblogs.com/hsiang/archive/2023/02/28/17162319.html
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作! |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
x
|