續上篇,接下來我要把 XtraGrid 的排序跟過濾拿出來
關鍵技術
- 過濾事件:ColumnFilterChanged
- 取出過濾語法:CriteriaToWhereClauseHelper.GetDynamicLinqWhere(op)
- 排序事件:複寫OnColumnSortInfoCollectionChanged
- 取出排序欄位:GridView.SortInfo
開發原則:
- 採用 3-Layer 架構
- Layer 彼此之間需要的物件放在 Infrastructure,資料交換用 ViewModel
- UI 相依 BLL,BLL 相依 DAL,DAL 相依 EF,UI 不得直接相依 DAL 或 EF 資料存取
- Database 在遠端,拿資料要分段拿
開發環境:
- Windows 10 x64 eng
- VS2015 Update3
- from nuget
- EntityFramework 6.1.3 Code First | SQL Localdb v13.0
- Faker.Data 1.0.7
- System.Linq.Dynamic 1.0.7
- from nuget
- DevExpress DevExpressComponents-16.2.5
實作內容:
預設 XtraGrid 排序跟過濾是排記憶體,也就是說要把資料庫的資料都倒到記憶體,排序跟過濾的結果才會正確,由於資料庫在遠端,所以我們需要分段倒,所以需要把 XtraGrid 的排序跟過濾抓出來變成字串(這裡會需要用到 System.Linq.Dynamic ),然後餵給 EF,這功能就是動態排序跟過濾,每一個 UI 作法都不太一樣,還要搭配控制項的事件,才能正確、有效的執行。
過濾
DevExpress.Data.Filtering.CriteriaToWhereClauseHelper 可以幫我們做這件事,DevExpress 提供了相當豐富的查詢語法轉換工具,基本上大部分的控制項會提供這個功能
程式碼如下:
public static string GetFilterExpression(this GridView sourceGridView)
{
var op = sourceGridView.ActiveFilterCriteria;
var filterExpression = CriteriaToWhereClauseHelper.GetDynamicLinqWhere(op);
return filterExpression;
}
當按下 Filter Button 時,會觸發 ColumnFilterChanged 事件,我要在這裡重新拿資料,當然你也可以不要立即拿,用別的按鈕拿
private void Master_GridView_ColumnFilterChanged(object sender, EventArgs e)
{
this.BindingOnMasterView();
}
拿資料程式碼如下:
private void BindingOnMasterView()
{
this.PagingControl.Page.FilterExpression = this.Master_GridView.GetFilterExpression();
this._queryResults = new List<MemberViewModel>(this._bll.GetMasters(this.PagingControl.Page).ToList());
this._queryResultBindingSource.DataSource = this._queryResults;
this.PagingControl.UpdateControl();
}
你以為這樣就完了嗎XDDD,預設的 CustomFilter 有 EF 不支援的語法,要嘛就是改寫 IsLike 的語法,要嘛眼不見為淨
所以我上官網尋求協助,得到以下解決方案,基本上就是換個呈現方式,然後把不要的東西移掉
private void Master_GridView_CustomFilterDialog(object sender, CustomFilterDialogEventArgs e)
{
var view = (GridView) sender;
e.Handled = true;
BeginInvoke(new MethodInvoker(() => { view.ShowFilterEditor(e.Column); }));
}
private void Master_GridView_FilterEditorCreated(object sender, FilterControlEventArgs e)
{
e.FilterControl.PopupMenuShowing += this.FilterControl_PopupMenuShowing;
}
private void FilterControl_PopupMenuShowing(object sender, PopupMenuShowingEventArgs e)
{
if (e.MenuType == FilterControlMenuType.Clause)
{
for (var i = e.Menu.Items.Count - 1; i >= 0; i--)
{
if (e.Menu.Items[i].Caption == Localizer.Active.GetLocalizedString(StringId.FilterClauseLike) ||
e.Menu.Items[i].Caption == Localizer.Active.GetLocalizedString(StringId.FilterClauseNotLike))
{
e.Menu.Items.RemoveAt(i);
}
}
}
}
執行效果如下:
當然也可以使用新功能 FilterPopupMode.Excel,OptionsColumnFilter.FilterPopupMode 屬性,型別是 FilterPopupMode 列舉
protected override void OnShown(EventArgs e)
{
base.OnShown(e);
foreach (var column in this.Master_GridView.Columns.Cast<GridColumn>())
{
column.OptionsFilter.FilterPopupMode = FilterPopupMode.Excel;
}
}
最後效果長這樣
排序
排序資訊在 ColumnView.SortInfo ,型別是 GridColumnSortInfo,程式碼如下
public static string GetSortExpression(this GridView source)
{
string result = null;
var sortBuilder = new StringBuilder();
var sortSortInfos = source.SortInfo;
var index = 0;
foreach (var sortInfo in sortSortInfos.Cast<GridColumnSortInfo>())
{
var fieldName = sortInfo.Column.FieldName;
var order = sortInfo.SortOrder.ToString();
index = GetSortBuilder(index, fieldName, order, ref sortBuilder);
index++;
}
result = sortBuilder.ToString();
return result;
}
若沒有指定排序欄位 EF 是會報錯的,強制一定要 OrderBy;除了 XtraGrid 可以設定預設排序欄位,我希望 GetSortExpression 能直接塞預設值,所以變成下面這樣
public static string GetSortExpression(this GridView source,
params string[] defaultFields)
{
string result = null;
var sortExpression = GetSortExpression(source);
if (!string.IsNullOrWhiteSpace(sortExpression))
{
return sortExpression;
}
//設定預設排序欄位
var index = 0;
var sortBuilder = new StringBuilder();
foreach (var item in defaultFields)
{
ColumnSortOrder sortOrder;
string fieldName;
if (item.IndexOf(SYMBOL_SPACE) > 0)
{
GetFieldAndSortOrder(item, out fieldName, out sortOrder);
}
else
{
sortOrder = ColumnSortOrder.Descending;
fieldName = item;
}
index = GetSortBuilder(index, fieldName, sortOrder.ToString(), ref sortBuilder);
}
result = sortBuilder.ToString();
return result;
}
接下來,就是用 GridView 的事件驅動他了,每次只要實作不同的 GridView 控制項分頁,會在取 GridView 的狀態就卡不少時間。
當按下 Column Header 時會觸發 StartSorting | EndSorting 事件,不過當我們有用 ColumnFilterChanged 事件時,按下 Filter Button,會先觸發 StartSorting 事件才會觸發 ColumnFilterChanged ,這將導致 SQL 命令重複執行,為了解決這個問題向原廠請求協助,他建議我 override OnColumnSortInfoCollectionChanged ,所以我要實作 GridView 並新增一個事件,程式碼如下:
public class GridViewEx : GridView
{
public event EventHandler<CollectionChangeEventArgs> ColumnSortInfoCollectionChanged;
protected override void OnColumnSortInfoCollectionChanged(CollectionChangeEventArgs e)
{
base.OnColumnSortInfoCollectionChanged(e);
if (this.ColumnSortInfoCollectionChanged == null)
{
return;
}
this.ColumnSortInfoCollectionChanged(this, e);
}
}
接下來就要把 DevExpress.XtraGrid.Views.Grid.GridView 換成 UI.Extension.GridViewEx,然後在 ColumnSortInfoCollectionChanged 觸發的時候重新拿資料
private void Master_GridView_ColumnSortInfoCollectionChanged(object sender, CollectionChangeEventArgs e)
{
this.BindingOnMasterView();
}
為了怕事件觸發時會干擾,取資料的時候,加個開關,程式碼如下:
private void BindingOnMasterView()
{
if (this._isBinding)
{
return;
}
this._isBinding = true;
var defaultFiledAndSort = this._defaultField + " Asc";
this.PagingControl.Page.SortExpression = this.Master_GridView.GetSortExpression(defaultFiledAndSort);
this.PagingControl.Page.FilterExpression = this.Master_GridView.GetFilterExpression();
this._queryResults = new List<MemberViewModel>(this._bll.GetMasters(this.PagingControl.Page).ToList());
this._queryResultBindingSource.DataSource = this._queryResults;
this.PagingControl.UpdateControl();
this._isBinding = false;
}
專案位置:
https://dotblogsamples.codeplex.com/SourceControl/latest#XtraGrid.ExtractExpression/
若有謬誤,煩請告知,新手發帖請多包涵
Microsoft MVP Award 2010~2017 C# 第四季
Microsoft MVP Award 2018~2022 .NET