在前面介紹的幾篇關於CRM系統的開發隨筆中,裡面都整合了多個頁面的功能,包括多文檔界面,以 及客戶相關信息的頁面展示,這個模塊就是利用DevExpress控件的XtraTabPage控件的動態加載實現的, 本篇文章主要介紹兩種方式的動態加載,一個是對用戶控件(UserControl)模塊的動態加載,一個是對 普通窗體(Form)的動態加載,通過這兩種方式,我們有時候可以動態實現很豐富的界面效果。
1、用戶控件(UserControl)模塊在Tab控件中的動態加載

參考了很多CRM的系統,一般都是把CRM系統中客戶相關的信息放到下邊,這也是成為了一種通用的界 面布局,由於客戶相關的信息很多,我們可以通過多文檔方式放在下面的右下角的布局面板裡面,由於 整個程序是一個MDI多文檔界面,不可以在子窗體裡面實現另外一個MDI多文檔的呈現,但是我們可以另 辟蹊徑,把它們放在一個TabPage頁面裡面,這樣我們通過動態加載,也能實現我們需要的界面布局了。
由於我們想動態加載客戶相關信息模塊,因此每個模塊可以通過用戶控件的方式獨立創建和維護,我 們在項目工程裡面創建相關的用戶控件,如下所示。

我們為了方便更好的控制,我們需要定義一個所有用戶控件的基類(BasePageControl)方便處理,該 類定義了一些常規的數據和接口,代碼如下所示。
public partial class BasePageControl : XtraUserControl, IPageApply
{
/// <summary>
/// 控件的客戶ID
/// </summary>
public string CustomerID { get; set; }
/// <summary>
/// 登陸用戶基礎信息
/// </summary>
public LoginUserInfo LoginUserInfo { get; set; }
...............
/// <summary>
/// 用作頁面的應用接口
/// </summary>
public interface IPageApply
{
/// <summary>
/// 初始化頁面的相關用戶權限信息
/// </summary>
/// <param name="userInfo">用戶信息</param>
/// <param name="functionDict">權限信息</param>
void InitFunction(LoginUserInfo userInfo, Dictionary<string, string> functionDict);
/// <summary>
/// 根據客戶ID屬性綁定數據
/// </summary>
/// <param name="customerID">客戶ID</param>
void BindData(string customerID);
}
設計好基類,然後設計相關模塊的界面,繼承自BasePageControl即可,該模塊利用了我的 Winform分頁控件實現數據的展示,並在頂端設計了一個工具欄Bar。

以上的界面和普通的列表界面差不多,只是沒有查詢條件而已,所以可以利用代碼生成工具 Database2Sharp生成一個標准的列表頁面,然後把大部分的代碼復制過來進行適當的調整即可。
public partial class ActivityControl : BasePageControl
{
public ActivityControl()
{
InitializeComponent();
InitDictItem();
this.winGridViewPager1.OnPageChanged += new EventHandler(winGridViewPager1_OnPageChanged);
this.winGridViewPager1.OnStartExport += new EventHandler(winGridViewPager1_OnStartExport);
this.winGridViewPager1.OnEditSelected += new EventHandler(winGridViewPager1_OnEditSelected);
this.winGridViewPager1.OnAddNew += new EventHandler(winGridViewPager1_OnAddNew);
this.winGridViewPager1.OnDeleteSelected += new EventHandler(winGridViewPager1_OnDeleteSelected);
this.winGridViewPager1.OnRefresh += new EventHandler(winGridViewPager1_OnRefresh);
this.winGridViewPager1.AppendedMenu = this.contextMenuStrip1;
this.winGridViewPager1.ShowLineNumber = true;
this.winGridViewPager1.BestFitColumnWith = false;//是否設置為自動調整寬度,false為不設置
this.winGridViewPager1.gridView1.DataSourceChanged += new EventHandler(gridView1_DataSourceChanged);
this.winGridViewPager1.gridView1.CustomColumnDisplayText += new DevExpress.XtraGrid.Views.Base.CustomColumnDisplayTextEventHandler(gridView1_CustomColumnDisplayText);
}
.............................
設計好相關的客戶相關資料模塊後,我們就需要在主界面(上圖)中加載相關的內容了,由於前面博 客隨筆說到,需要根據用戶的配置選擇進行動態加載,因此,我們需要先獲取客戶的選擇列表,然後在 根據列表進行判斷是否加載顯示。
userPageList = BLLFactory<UserTreeSetting>.Instance.GetTreeSetting(pageCategory, LoginUserInfo.ID.ToString());
然後在獲取標准的Tab選項卡配置列表,代碼如下所示。
List<SystemTreeNodeInfo> propList = BLLFactory<SystemTree>.Instance.GetTree(pageCategory);
接著定義一個函數,看標准的頁面列表是否包含在用戶配置列表裡面。
/// <summary>
/// 如果列表為空或包含指定ID,則認為包含
/// </summary>
/// <param name="id">頁面ID節點</param>
/// <returns></returns>
private bool ContainPage(string id)
{
bool result = false;
if (userPageList == null || userPageList.Count == 0 || userPageList.Contains(id))
{
result = true;
}
return result;
}
然後就是,根據相關信息,動態創建相關的Page頁面,然後添加到Tab選項卡控件裡面去即可,期間 為了方便對界面的控制顯示,我們需要傳入主窗體的用戶身份信息。
XtraTabPage page = new XtraTabPage();
page.Name = nodeInfo.ID;
page.Text = nodeInfo.TreeName;
page.ImageIndex = i % 20;//設置圖標,總圖標只有21個
BasePageControl control = CreateCustomerControl(nodeInfo.SpecialTag);
if (control != null)
{
control.InitFunction(LoginUserInfo, FunctionDict);//給子窗體賦值用戶權限信息
control.Dock = DockStyle.Fill;
page.Controls.Add(control);
}
this.tabCustomerRelated.TabPages.Add(page);
創建對象的函數CreateCustomerControl函數實現如下所示,通過上面的代碼,我們就能動態創建相 關的頁面並顯示出來了。
/// <summary>
/// 通過數據庫配置的控件名稱,反射創建對象
/// </summary>
/// <param name="controlName">控件名稱(簡稱)</param>
/// <returns></returns>
private BasePageControl CreateCustomerControl(string controlName)
{
string namePrefix = "WHC.CRM.UI.CustomerPage.";
controlName = namePrefix + controlName;//控件名稱全稱
BasePageControl userControl = null;
try
{
userControl = CreateInstance(controlName, System.Reflection.Assembly.GetExecutingAssembly().GetName().Name);
}
catch (Exception ex)
{
LogTextHelper.Error(ex);
}
return userControl;
}
/// <summary>
/// 根據全名和路徑構造對象
/// </summary>
/// <param name="sName">對象全名</param>
/// <param name="sFilePath">程序集路徑</param>
/// <returns></returns>
private static BasePageControl CreateInstance(string sName, string sFilePath)
{
Assembly assemblyObj = Assembly.Load(sFilePath);
if (assemblyObj == null)
{
throw new ArgumentNullException("sFilePath", string.Format("無法加載sFilePath={0} 的程序集", sFilePath));
}
BasePageControl obj = (BasePageControl)assemblyObj.CreateInstance(sName); //反射創建
return obj;
}
最後提一下,我們創建的TabPage頁面,雖然可以加上關閉圖標,但是默認是關閉不了頁面的,需要 我們特殊處理,也就是需要在後台代碼移除選定的選項卡頁面,另外,為了考慮如果客戶關閉了,我們 可以保存它的設置,認為它在配置中隱藏顯示這個頁面。具體的代碼邏輯如下所示。
private void tabCustomerRelated_CloseButtonClick(object sender, EventArgs e)
{
//關閉移除
this.tabCustomerRelated.TabPages.RemoveAt(this.tabCustomerRelated.SelectedTabPageIndex);
List<string> nodeIdList = new List<string>();
foreach (XtraTabPage page in this.tabCustomerRelated.TabPages)
{
//Tree ID= Page.Name
if(!string.IsNullOrEmpty(page.Name));
{
nodeIdList.Add(page.Name);
}
}
bool result = BLLFactory<UserTreeSetting>.Instance.SaveTreeSetting(pageCategory, LoginUserInfo.ID.ToString(), nodeIdList);
}
2、普通窗體(Form)在Tab控件中的動態加載

查看本欄目
第一小節我們介紹了在TabControl控件裡面動態加載多個用戶控件(UserControl)頁面,但是,由 於我的CRM系統模塊會涉及很多相關數據處理的界面的,如果把界面全部封裝成用戶控件,使用起來可能 會麻煩一些。
如有時候在啟動界面(Starter)模塊裡面,我們可能在菜單配置一個獨立的模塊放到一級菜單裡面 ,如果是普通的列表窗口界面,那麼創建多文檔的子窗口就非常方便,如果是用戶控件,就調用不了; 因此我希望能夠盡可能保留普通窗口的類型,而不是把它改為用戶控件類型。
在上圖中,我們看到,每個管理模塊裡面,還需要展示多個子模塊頁面,如果利用前面小節說到的用 戶控件方式,在TabControl控件加載肯定完全沒有問題,本小節繼續探尋是否可以不改變窗口類型的情 況下,動態加載普通窗口界面內容到TabControl控件裡面。
首先我們設計一個模塊管理的主界面,包含了列表和TabControl的控件樣式。

然後再找後台代碼實現TabControl控件動態加載普通窗口對象,創建窗口對象前,我們需要判斷是否 該類型已經在TabControl裡面存在了,如果存在者前置選項卡頁面即可,主要的加載代碼如下所示。
/// <summary>
/// 加載或者激活指定類型的對話框
/// </summary>
/// <param name="tabcontrol">XtraTabControl控件</param>
/// <param name="formType">窗體類型,必須繼承自BaseForm類型
</param>
private void LoadTabPageForm(XtraTabControl tabcontrol, Type formType, int imageIndex)
{
bool found = false;
XtraTabPage selectedPage = null;
foreach (XtraTabPage page in tabcontrol.TabPages)
{
if (page.Tag != null && page.Tag.ToString() == formType.Name)
{
found = true;
selectedPage = page;
break;
}
}
if (!found)
{
selectedPage = new XtraTabPage();
BaseDock dlg = (BaseDock)Activator.CreateInstance(formType);
dlg.Visible = true;
dlg.Dock = DockStyle.Fill;
dlg.FormBorderStyle = FormBorderStyle.None;
dlg.TopLevel = false;//在這裡一定要注意
dlg.InitFunction(LoginUserInfo, FunctionDict);//給子窗體賦值用戶權限信息
selectedPage.Text = dlg.Text;
selectedPage.ImageIndex = imageIndex;
selectedPage.Tag = dlg.GetType().Name;
selectedPage.Controls.Add(dlg);
tabcontrol.TabPages.Add(selectedPage);
}
selectedPage.BringToFront();
tabcontrol.SelectedTabPage = selectedPage;
}
實現上面的函數後,我們只需要在按鈕事件裡面,調用上面的函數,並傳入相關的參數即可。
private void itemProduct_LinkClicked(object sender, DevExpress.XtraNavBar.NavBarLinkEventArgs e)
{
LoadTabPageForm(this.xtraTabMain, typeof(FrmProduct), 0);
}
private void itemManufacturer_LinkClicked(object sender, DevExpress.XtraNavBar.NavBarLinkEventArgs e)
{
LoadTabPageForm(this.xtraTabMain, typeof(FrmManufacturer), 1);
}
private void itemCompetitor_LinkClicked(object sender, DevExpress.XtraNavBar.NavBarLinkEventArgs e)
{
LoadTabPageForm(this.xtraTabMain, typeof(FrmCompetitor), 2);
}
同時關閉事件我們需要增加代碼進行處理,關閉事件的代碼如下所示。
private void xtraTabMain_CloseButtonClick(object sender, EventArgs e)
{
XtraTabPage currentPage = this.xtraTabMain.SelectedTabPage;
BaseForm form = currentPage.Controls[0] as BaseForm;
if (form != null)
{
form.Close();
form.Dispose();
}
this.xtraTabMain.TabPages.RemoveAt(this.xtraTabMain.SelectedTabPageIndex);
}
以上就是在TabControl控件裡面,動態加載用戶控件、普通窗口的兩種不同的方式,都能為我們實現 豐富的界面布局展現,希望對大家在開發Winform界面效果上有所參考。
伍華聰 http://www.iqidi.com