最新要闻

广告

手机

iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?

iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?

警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案

警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案

家电

世界百事通!让WPF中的DataGrid像Excel一样可以筛选

来源:博客园

在默认情况下,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              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                          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                         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                         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:ObservableObject11     {12         #region 属性及构造函数13 14         private List students;15 16         public List Students17         {18             get { return students; }19             set { SetProperty(ref students, value); }20         }21 22         private List names;23 24         public List Names25         {26             get { return names; }27             set { SetProperty(ref names, value); }28         }29 30         private List nos;31 32         public List Nos33         {34             get { return nos; }35             set {SetProperty(ref nos , value); }36         }37 38         private List ages;39 40         public List Ages41         {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         #endregion72 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         #endregion179 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                 else198                 {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);//隐藏父Popup235             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         /// 隐藏父Popup294         /// 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); }        }    }}

不足与思考

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

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

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

关键词: 弹出窗口 进行筛选 右键菜单