最近一直在做一些技術性的研究和框架改進工作,博客也落下好幾天沒有更新了,也該是時候靜下心 來,總結這段時間的一些技術改進的經驗了。和上一階段的CRM系統開發和技術研究一樣,我都喜歡在一 個項目或者模塊完成後,做一些相關的總結性工作,記錄下前一階段的技術腳印,希望給自己留下一個 腳印快照,同時給讀者了解自己的技術動向外,也有所收獲。本隨筆主要介紹在下拉列表中展示一個列 表,以便實現數據結構的良好展示,並能快速選定所需的節點,這個就是TreeListLookupEdit控件的使 用。
1、界面效果展示
首先我們來看看我的Winform開發框架之權限管理系統模塊改進完善後的主界面,然後在介紹其中用 到的功能點,以及技術實現。
系統用戶信息管理界面如下所示。

其中用戶信息編輯界面如下所示。

其中編輯用戶信息的界面包括了所屬公司、所屬部門、直屬經理三個輸入的內容,為了減少數據量的 顯示,這幾個輸入框是通過級聯的方式進行展示,也就是說,先選定所屬公司,然後在所屬公司中列出 該公司的部門列表,選定部門後,再通過獲取指定部門的人員信息,作為直屬經理的人員展示。
了解這些邏輯關系後,我們來看看所屬公司的列表展示,如下所示。

選定了所屬公司後,所屬部門就根據公司來進行過濾了,如下所示。

通過公司和部門的條件,我們就可以列出有限的人員列表,作為直屬經理的人員列表了,這個列表使 用普通的下拉列表展示即可,在此不再贅述。
以上的樹狀結構的下拉列表,在DevExpress控件中是通過TreeListLookupEdit控件進行實現的。
2、基於DevExpress控件的功能實現及代碼展示
由於這些所屬公司、所屬部門的功能模塊,一般需要不少代碼進行處理,為了更好重用這些模塊,我 們通過用戶控件的封裝方式進行,然後在數據編輯界面上,通過拖動控件方式即可實現布局,並只需要 設置或者訪問某個屬性即可,封裝的用戶界面控件如下所示。

1)所屬公司控件代碼
所屬公司是一個用戶控件,讓其繼承自XtraUserControl即可,為了在選擇內容後觸發值的變化事件 ,我們定義了一個事件EventHandler EditValueChanged,這樣我們在內部控件的值變化的時候,可以通 知外部的界面進行處理。
public partial class CompanyControl : XtraUserControl
{
/// <summary>
/// 選擇的值發生變化的時候
/// </summary>
public event EventHandler EditValueChanged;
public CompanyControl()
{
InitializeComponent();
this.txtCompany.EditValueChanged += new EventHandler(txtCompany_EditValueChanged);
}
void txtCompany_EditValueChanged(object sender, EventArgs e)
{
if (EditValueChanged != null)
{
EditValueChanged(sender, e);
}
}
為了實現列表數據的綁定,以及增加圖標作為區分節點,TreeListLookupEdit控件的數據綁定操作是 這個控件的核心所在。
在下面的代碼邏輯裡面,我們通過獲取公司列表,然後把它轉換為一個DataTable,並根據集團、公 司的層次給予不同的圖標,綁定數據,並設置好控件的KeyFieldName、ParentFieldName、ValueMember 、DisplayMember幾個重要屬性即可,如下所示。
/// <summary>
/// 初始化數據
/// </summary>
public void Init()
{
DataTable dt = DataTableHelper.CreateTable("ImageIndex|int,ID,PID,Name");
List<OUInfo> list = BLLFactory<OU>.Instance.GetGroupCompany();
DataRow dr = null;
foreach (OUInfo info in list)
{
dr = dt.NewRow();
dr["ImageIndex"] = Portal.gc.GetImageIndex(info.Category);
dr["ID"] = info.ID.ToString();
dr["PID"] = info.PID.ToString();
dr["Name"] = info.Name;
dt.Rows.Add(dr);
}
//設置圖形序號
this.treeListLookUpEdit1TreeList.SelectImageList = this.imageList2;
this.treeListLookUpEdit1TreeList.StateImageList = this.imageList2;
this.txtCompany.Properties.TreeList.KeyFieldName = "ID";
this.txtCompany.Properties.TreeList.ParentFieldName = "PID";
this.txtCompany.Properties.DataSource = dt;
this.txtCompany.Properties.ValueMember = "ID";
this.txtCompany.Properties.DisplayMember = "Name";
}
為了方便編輯界面中,對所屬公司的賦值與獲取操作,我們需要增加一個Text屬性和一個Value屬性 。我們需要重載Text屬性,用來獲取或設置所屬公司的名稱;添加一個Value屬性,用來獲取所屬公司的 ID,如下所示。
/// <summary>
/// 公司名稱
/// </summary>
public override string Text
{
get
{
return this.txtCompany.Text;
}
set
{
this.txtCompany.Text = value;
}
}
/// <summary>
/// 公司ID
/// </summary>
public string Value
{
get
{
string result = "-1";
if (this.txtCompany.EditValue == null || this.txtCompany.EditValue.ToString() == "0")
{
result = "-1";
}
else
{
result = this.txtCompany.EditValue.ToString();
}
return result;
}
set
{
this.txtCompany.EditValue = value;
}
}
2)所屬部門的控件代碼
所屬部門的代碼邏輯和所屬公司差不多,唯一不同的是,所屬部門需要一個上級的部門標識(公司ID )作為數據的過濾獲取,如下所示。
/// <summary>
/// 初始化部門信息
/// </summary>
public void Init()
{
//InitUpperOU
DataTable dt = DataTableHelper.CreateTable("ImageIndex|int,ID,PID,Name,HandNo,Category,Address,Note");
DataRow dr = null;
if (!string.IsNullOrEmpty(ParentOuID))
{
List<OUInfo> list = BLLFactory<OU>.Instance.GetAllOUsByParent(ParentOuID.ToInt32());
foreach (OUInfo info in list)
{
dr = dt.NewRow();
dr["ImageIndex"] = Portal.gc.GetImageIndex(info.Category);
dr["ID"] = info.ID.ToString();
dr["PID"] = info.PID.ToString();
dr["Name"] = info.Name;
dr["HandNo"] = info.HandNo;
dr["Category"] = info.Category;
dr["Address"] = info.Address;
dr["Note"] = info.Note;
dt.Rows.Add(dr);
}
}
//增加一行空的
dr = dt.NewRow();
dr["ID"] = "0"; //使用0代替-1,避免出現節點的嵌套顯示,因為-1已經作為了一般節點的頂級標識
dr["PID"] = "-1";
dr["Name"] = "無";
dt.Rows.InsertAt(dr, 0);
//設置圖形序號
this.treeListLookUpEdit1TreeList.SelectImageList = this.imageList2;
this.treeListLookUpEdit1TreeList.StateImageList = this.imageList2;
this.txtDept.Properties.TreeList.KeyFieldName = "ID";
this.txtDept.Properties.TreeList.ParentFieldName = "PID";
this.txtDept.Properties.DataSource = dt;
this.txtDept.Properties.ValueMember = "ID";
this.txtDept.Properties.DisplayMember = "Name";
}
3)主界面的控件使用
封裝好所屬公司和所屬部門的控件後,我們就可以通過在工具箱裡面拖動控件到主編輯界面裡面去了 ,這樣我們只需要簡單對控件進行設置和賦值即可實現,減少對控件邏輯過多的代碼處理。
數據編輯界面中,控件的使用代碼如下所示。
a)界面賦值操作
this.txtCompany.Value = info.Company_ID;
this.txtDept.Value = info.Dept_ID;
b)界面數據獲取操作
info.Dept_ID = txtDept.Value;
info.DeptName = txtDept.Text;
info.Company_ID = txtCompany.Value;
info.CompanyName = txtCompany.Text;
查看本欄目
c)界面事件的處理操作
由於界面上兩個控件是級聯的關系,因此需要處理他們控件的值發生變化的事件,如下所示。
public partial class FrmEditUser : BaseEditForm
{
public FrmEditUser()
{
InitializeComponent();
this.txtCompany.EditValueChanged += new EventHandler(txtCompany_EditValueChanged);
this.txtDept.EditValueChanged += new EventHandler(txtDept_EditValueChanged);
}
void txtCompany_EditValueChanged(object sender, EventArgs e)
{
if (!string.IsNullOrEmpty(this.txtCompany.Value))
{
//賦值給部門控件,並重新初始化部門列表
txtDept.ParentOuID = this.txtCompany.Value;
txtDept.Init();
}
}
void txtDept_EditValueChanged(object sender, EventArgs e)
{
if (!string.IsNullOrEmpty(txtDept.Value))
{
//獲取所屬部門後,就通過部門ID來初始化直屬經理的人員列表
InitManagers(txtDept.Value.ToInt32());
}
}
.................
3、基於傳統Winform界面的控件實現
上面的介紹內容是基於DevExpress的控件組進行功能實現的,有些人可能會問,我使用傳統樣式的界 面控件,這種效果如何實現呢,其實如果是使用內置的控件,是比較困難實現這個效果的,但是這樣的 界面控件有很多人開發好組件可以利用的,我在做這個模塊的時候,也考慮到了這一點,因此也針對這 些做了一些搜索查詢,發現國外有一個同行封裝的控件做的非常不錯,地址是http://www.brad- smith.info/blog/archives/477,上面的效果基本上沒問題的。這個人的博客和下載的例子裡面,沒有 使用Demo的代碼,我自己根據控件的屬性進行了一個Demo例子的編寫,效果和代碼如下所示。

總體來說,這個控件實現的效果還是非常不錯的,例子代碼如下所示,其節點和TreeView的操作類似 ,通過ComboTreeNode對象進行添加即可。
private void Form1_Load(object sender, EventArgs e)
{
if (!this.DesignMode)
{
this.comboTreeBox1.Nodes.Clear();
ComboTreeNode parentNode = new ComboTreeNode();
parentNode.Text = "愛奇迪集團";
parentNode.Nodes.Add("gz", "廣州分公司");
parentNode.Nodes["gz"].ImageIndex = 1;//通過名字引用
parentNode.Nodes["gz"].ExpandedImageIndex = 1;//通過名字引用
ComboTreeNode bjNode = new ComboTreeNode();
bjNode.Name = "bj";
bjNode.Text = "北京分公司";
bjNode.ImageIndex = 1;
bjNode.ExpandedImageIndex = 1;
bjNode.FontStyle = FontStyle.Bold;
bjNode.Nodes.Add("財務部");
bjNode.Nodes.Add("市場部");
bjNode.Nodes.Add("人力資源部");
parentNode.Nodes.Add(bjNode);
this.comboTreeBox1.Nodes.Add(parentNode);
comboTreeBox1.ExpandAll();
}
}
除了這個可以實現傳統界面效果的下拉樹展現外,CodeProject上的 http://www.codeproject.com/Articles/25471/Customizable-ComboBox-Drop-Down這篇文字實現的效果 也還不錯。
不過可能沒有上面的那個效果好一點。
4、總結
以上這些就是關於級聯下拉列表,並在下拉列表裡面展示層次關系的樹形結構的功能實現和核心代碼 的操作,本隨筆介紹了基於DevExpress樣式和傳統樣式兩種方案的實現過程,重點對DevExpress控件中 的TreeListLookupEdit控件的實現進行介紹,希望大家能夠在實際工作中用上。
伍華聰 http://www.iqidi.com