[XtraGrid] 實作分頁並整合 GridView 的排序、過濾

上篇,接下來我要把 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
  • DevExpress DevExpressComponents-16.2.5

實作內容:

預設 XtraGrid 排序跟過濾是排記憶體,也就是說要把資料庫的資料都倒到記憶體,排序跟過濾的結果才會正確,由於資料庫在遠端,所以我們需要分段倒,所以需要把 XtraGrid 的排序跟過濾抓出來變成字串(這裡會需要用到 System.Linq.Dynamic ),然後餵給 EF,這功能就是動態排序跟過濾,每一個 UI 作法都不太一樣,還要搭配控制項的事件,才能正確、有效的執行。

 
過濾

@XtraGridExtenstion.cs

我希望取出來的資料要符合 System.Linq.Dynamic 的 Where

DevExpress.Data.Filtering.CriteriaToWhereClauseHelper 可以幫我們做這件事,DevExpress 提供了相當豐富的查詢語法轉換工具,基本上大部分的控制項會提供這個功能

程式碼如下:

public static string GetFilterExpression(this GridView sourceGridView)
{
	var op = sourceGridView.ActiveFilterCriteria;
	var filterExpression = CriteriaToWhereClauseHelper.GetDynamicLinqWhere(op);

	return filterExpression;
}

 

@Form1.cs

當按下 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;
	}
}

最後效果長這樣

 
排序

@XtraGridExtenstion.cs

我希望取出來的資料最後要長這樣 SequentialId Ascending, Name Ascending

 

排序資訊在 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;
}

 

@GridViewEx.cs

接下來,就是用 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);
	}
}

 

@Form1.cs

接下來就要把 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

Image result for microsoft+mvp+logo