在用戶編輯界面中,「所屬公司 → 所屬部門 → 直屬經理」三級級聯選擇是高頻需求。本文深度解析Winform開發框架如何利用DevExpress TreeListLookupEdit 控件,封裝可復用用戶控件,實現下拉面板內直接呈現樹形層級、支持圖標區分節點類型、且具備完整級聯聯動與數據綁定能力的優雅方案。


交互流程:


將「所屬公司」「所屬部門」分別封裝為繼承 XtraUserControl 的獨立控件,暴露 Text(顯示名)、Value(ID值)、EditValueChanged 事件,實現設計期拖拽、運行期屬性賦值/取值、事件聯動的全周期易用性。

public partial class CompanyControl : XtraUserControl {
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);
}
public void Init() {
DataTable dt = DataTableHelper.CreateTable("ImageIndex|int,ID,PID,Name");
List<OUInfo> list = BLLFactory<OU>.Instance.GetGroupCompany();
foreach (OUInfo info in list) {
DataRow 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 獲取顯示名
public override string Text { get { return this.txtCompany.Text; } set { this.txtCompany.Text = value; } }
// Value 獲取/設置 ID
public string Value {
get {
if (this.txtCompany.EditValue == null || this.txtCompany.EditValue.ToString() == "0") return "-1";
return this.txtCompany.EditValue.ToString();
}
set { this.txtCompany.EditValue = value; }
}
}
邏輯類似,差異在於 Init() 接收 ParentOuID(公司ID)參數,查詢該公司下的部門數據,並在數據源首行插入「無」選項(ID=0, PID=-1)避免樹形嵌套顯示異常。
public string ParentOuID { get; set; }
public void Init() {
DataTable dt = DataTableHelper.CreateTable("ImageIndex|int,ID,PID,Name,HandNo,Category,Address,Note");
if (!string.IsNullOrEmpty(ParentOuID)) {
List<OUInfo> list = BLLFactory<OU>.Instance.GetAllOUsByParent(ParentOuID.ToInt32());
foreach (OUInfo info in list) { /* 填充行 */ }
}
// 插入「無」選項
DataRow dr = dt.NewRow();
dr["ID"] = "0"; dr["PID"] = "-1"; dr["Name"] = "無";
dt.Rows.InsertAt(dr, 0);
// 配置屬性同 CompanyControl
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";
}
在 FrmEditUser 中拖入兩個控件,僅需處理值變化事件實現級聯:
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; // 傳遞選中公司ID
txtDept.Init(); // 重新加載部門樹
}
}
void txtDept_EditValueChanged(object sender, EventArgs e) {
if (!string.IsNullOrEmpty(txtDept.Value)) {
InitManagers(txtDept.Value.ToInt32()); // 加載該部門人員作為直屬經理備選
}
}
// 界面賦值
this.txtCompany.Value = info.Company_ID;
this.txtDept.Value = info.Dept_ID;
// 界面取值
info.Dept_ID = txtDept.Value;
info.DeptName = txtDept.Text;
info.Company_ID = txtCompany.Value;
info.CompanyName = txtCompany.Text;
}
若項目不使用DevExpress,推薦Brad Smith開源的 ComboTreeBox 控件(官網),效果近似,節點操作類似 TreeView,通過 ComboTreeNode 構建樹形。
private void Form1_Load(object sender, EventArgs e) {
this.comboTreeBox1.Nodes.Clear();
ComboTreeNode parentNode = new ComboTreeNode { Text = "愛奇迪集團" };
parentNode.Nodes.Add("gz", "廣州分公司");
parentNode.Nodes["gz"].ImageIndex = 1;
ComboTreeNode bjNode = new ComboTreeNode { Name = "bj", Text = "北京分公司", ImageIndex = 1, FontStyle = FontStyle.Bold };
bjNode.Nodes.Add("財務部"); bjNode.Nodes.Add("市場部"); bjNode.Nodes.Add("人力資源部");
parentNode.Nodes.Add(bjNode);
this.comboTreeBox1.Nodes.Add(parentNode);
comboTreeBox1.ExpandAll();
}

CodeProject上亦有 可定製ComboBox下拉 方案,但易用性略遜。
本文展示了基於DevExpress TreeListLookupEdit 的級聯下拉樹最佳實踐:用戶控件封裝隔離複雜度、KeyFieldName/ParentFieldName 構建樹形關系、ImageIndex 賦予視覺區分度、ValueMember/DisplayMember 分離數值與顯示、事件鏈路實現多級聯動。傳統Winform項目可引用 ComboTreeBox 實現同等體驗。此模式廣泛適用於「省市區」「分類品牌」「機構部門」等一切樹形級聯選擇場景。