個人理解,開發應用程序的目的,不論是B/S或是C/S結構類型,無非就是實現可供用戶進行查、增、改、刪,其中查詢用到最多,開發設計的場景也最為復雜,包括但不限於:表格記錄查詢、報表查詢、導出文件查詢等等,其次就是增加、更改、刪除,我這里統稱為編輯,而編輯在開發設計中的場景就顯得較為通用,大多都采用編輯組件(文本框、下拉框、選擇框、數字框等)來供用戶進行編輯操作,由於查詢的開發設計場景需與實際的數據及客戶要求來進行的,所以在此不作討論,本文主要講述的是如何利用FlowLayoutPanel及我(夢在旅途)自定義的編輯控件來實現快速構建C/S版的編輯表單頁面。
有經歷過開發ASP.NET MVC網站的人都知道,在構建編輯頁面時,我們可以通過HtmlHelper擴展類相關方法來快速生成相應的編輯控件,如:Html.LabelFor,Html.TextBoxFor等等,而在Winform 項目中卻並沒有類似的方法可以讓我們快速構建編輯表單頁面,所以為了減輕我們經常遇到,又比較耗時,且沒有任何技術含量的開發工作量,我開發了類似的編輯控件系列,包括:CTextBox【文本框】、CPictureBox【圖片框】、CNumberBox【數字框】、CDropDownBox【下拉框】、CDisplayBox【顯示框】、CCheckBox【勾選框】,這些控件都一些共有特性,如:Label:顯示編輯項目名稱,Value:控件的值,ValueFor<TEntity>:快速設置控件相關屬性的方法,這些共有特性是實現快速構造編輯控件的關鍵,當然每種編輯控件還有自己獨特的屬性,如:CTextBox可以設置是否顯示按鈕,是否只讀,是否多行等,CNumberBox可以設置小數位、最大值與最小值等,由於這些控件的代碼加起來可能比較多,我這里僅以CTextBox為例來進行分析與說明,CTextBox是屬性最多的控件。
首先還是看一下CTextBox控件的界面設計,如下圖:

從圖中可以看出,CTextBox控件由一個TableLayouPanel、一個Label、一個TextBox、一個Button共同組成:
TableLayouPanel作為控件的頂層容器,其DOCK屬性設置為Fill,實現占據整個控件,另外分別設有一行三列,第一列寬度設為自動調整,第二列寬度設為100%,第三列寬度設為自動調整,如下圖:

TableLayouPanel這樣設計的目的是:實現第一列的寬度依據LABEL的內容自動調整,第二列的寬度為TextBox的寬度,第三列的寬度為Button的寬度,這樣一來,我們在改變CTextBox的整個大小時,確保布局一致性(即:在CTextBox總寬度內,TextBox的寬度是CTextBox總寬度-LABEL與Button的實際寬度)
Label控件作為顯示編輯項目名稱部份,它的屬性AutoSize設為True,Anchor設為Left,設計目的:實現在第一列中向左居中,且寬度依據實際內容而改變
TextBox控件作為可供編輯內容部份,它的屬性設Anchor設為Top, Bottom, Left, Right,設計目的:實現 TextBox控件占據整個第二列,即若控件的寬度與高度改變,TextBox控件也會跟着改變,這個很重要哦!
Button控件為可選部份,若需要用到控件的值來源於其它選項(如:瀏覽文件、保存文件等),則可將其CTextBox.DispalyOpenButton設為True即可,同樣,它的屬性Anchor設為Left,並調整好固定的寬度與高度,設計目的:實現在第三列中向左居中,且寬度不變
實現的整體效果如下圖示:


我這里用紅線框出來了,大家看出效果來了嗎?也就是我直接拖放了三個CTextBox控件,然后分別將LABEL的內容設置為不相同,除非手動改變控件寬度,否則每個控件寬度均相同,這樣再結合FlowLayoutPanel控件的流式布局(即:每個控件按照順序依次排列,若寬度與高度發生變化,控件的排列就會改變),就能很好的快速構建編輯頁面,看看最終用在我的項目中的效果吧,有沒有覺得不錯呢:

這是改變寬度前的布局,有5列,我有紅框標出來了,都是對齊的

這是我縮小寬度后的布局,有2列,我有紅框標出來了,仍然都是對齊的
以上布局效果是不需要編寫任何代碼的,代碼部份僅是設置相應的屬性與方法,如下:
CTextBox控件源碼:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Linq.Expressions;
using Zwj.TEMS.Common;
namespace TEMS.Controls
{
public partial class CTextBox : UserControl,IZwjDefControl
{
[Description("當點擊按鈕時觸發該事件")]
public event EventHandler OnOpen;
public CTextBox()
{
InitializeComponent();
this.DispalyOpenButton = false;
}
[Browsable(true)]
[Description("設置文本框的值")]
public string Value
{
get
{
return textBox1.Text;
}
set
{
textBox1.Text = value;
}
}
[Browsable(true)]
[Description("設置標簽的值")]
public string Label
{
get
{
return label1.Text;
}
set
{
label1.Text = value;
}
}
[Browsable(true)]
[Description("設置是否顯示打開按鈕")]
public bool DispalyOpenButton
{
get
{
return button1.Visible;
}
set
{
button1.Visible = value;
textBox1.ReadOnly = button1.Visible;
}
}
[Browsable(true)]
[Description("設置是否允許多行")]
public bool AllowMultiline
{
get
{
return textBox1.Multiline;
}
set
{
textBox1.Multiline = value;
if (textBox1.Multiline)
{
textBox1.ScrollBars = ScrollBars.Vertical;
}
}
}
public void ValueFor<TEntity>(Expression<Func<TEntity, dynamic>> selectField, string fieldValue, bool displayBtn = false, bool allowMultiline=false) where TEntity : class
{
var fieldInfo = General.GetPropertyInfo(selectField);
this.Label = General.GetDisplayName(fieldInfo);
this.Value = fieldValue;
this.DispalyOpenButton = displayBtn;
this.AllowMultiline = allowMultiline;
}
private void button1_Click(object sender, EventArgs e)
{
if (this.OnOpen != null)
{
this.OnOpen(this, null);
}
}
}
}
以下是系統自動生成的源碼:
namespace TEMS.Controls
{
partial class CTextBox
{
/// <summary>
/// 必需的設計器變量。
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// 清理所有正在使用的資源。
/// </summary>
/// <param name="disposing">如果應釋放托管資源,為 true;否則為 false。</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region 組件設計器生成的代碼
/// <summary>
/// 設計器支持所需的方法 - 不要
/// 使用代碼編輯器修改此方法的內容。
/// </summary>
private void InitializeComponent()
{
this.tableLayoutPanel1 = new System.Windows.Forms.TableLayoutPanel();
this.label1 = new System.Windows.Forms.Label();
this.textBox1 = new System.Windows.Forms.TextBox();
this.button1 = new System.Windows.Forms.Button();
this.tableLayoutPanel1.SuspendLayout();
this.SuspendLayout();
//
// tableLayoutPanel1
//
this.tableLayoutPanel1.ColumnCount = 3;
this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle());
this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F));
this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle());
this.tableLayoutPanel1.Controls.Add(this.label1, 0, 0);
this.tableLayoutPanel1.Controls.Add(this.textBox1, 1, 0);
this.tableLayoutPanel1.Controls.Add(this.button1, 2, 0);
this.tableLayoutPanel1.Dock = System.Windows.Forms.DockStyle.Fill;
this.tableLayoutPanel1.Location = new System.Drawing.Point(0, 0);
this.tableLayoutPanel1.Name = "tableLayoutPanel1";
this.tableLayoutPanel1.RowCount = 1;
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F));
this.tableLayoutPanel1.Size = new System.Drawing.Size(250, 40);
this.tableLayoutPanel1.TabIndex = 0;
//
// label1
//
this.label1.Anchor = System.Windows.Forms.AnchorStyles.Left;
this.label1.AutoSize = true;
this.label1.Font = new System.Drawing.Font("微軟雅黑", 11.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134)));
this.label1.Location = new System.Drawing.Point(3, 10);
this.label1.Name = "label1";
this.label1.Size = new System.Drawing.Size(44, 20);
this.label1.TabIndex = 0;
this.label1.Text = "label";
this.label1.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
//
// textBox1
//
this.textBox1.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.textBox1.Font = new System.Drawing.Font("微軟雅黑", 11.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134)));
this.textBox1.Location = new System.Drawing.Point(53, 3);
this.textBox1.Name = "textBox1";
this.textBox1.Size = new System.Drawing.Size(151, 27);
this.textBox1.TabIndex = 1;
//
// button1
//
this.button1.Anchor = System.Windows.Forms.AnchorStyles.None;
this.button1.Location = new System.Drawing.Point(210, 8);
this.button1.Name = "button1";
this.button1.Size = new System.Drawing.Size(37, 23);
this.button1.TabIndex = 2;
this.button1.Text = "...";
this.button1.UseVisualStyleBackColor = true;
this.button1.Click += new System.EventHandler(this.button1_Click);
//
// CTextBox
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.Controls.Add(this.tableLayoutPanel1);
this.Name = "CTextBox";
this.Size = new System.Drawing.Size(250, 40);
this.tableLayoutPanel1.ResumeLayout(false);
this.tableLayoutPanel1.PerformLayout();
this.ResumeLayout(false);
}
#endregion
private System.Windows.Forms.TableLayoutPanel tableLayoutPanel1;
private System.Windows.Forms.Label label1;
private System.Windows.Forms.TextBox textBox1;
private System.Windows.Forms.Button button1;
}
}
代碼就比較簡單了,在此就不再詳細說明了,只是其中用到了兩個我自定義的方法:
General.GetPropertyInfo ---根據LINQ表達式獲取屬性信息;
General.GetDisplayName ---根據屬性信息獲取顯示的名稱;
方法定義代碼如下(我以前的博文及個人網站www.zuowenjun.cn中也有說明):
/// <summary>
/// 獲取屬性需要顯示的名稱
/// </summary>
/// <param name="p"></param>
/// <returns></returns>
public static string GetDisplayName(PropertyInfo p)
{
string displayName = null;
DisplayAttribute attr = p.GetAttribute<DisplayAttribute>();
if (attr != null)
{
displayName = attr.Name;
}
else
{
displayName = p.Name;
}
return displayName;
}
/// <summary>
/// 獲取指定屬性信息(非String類型存在裝箱與拆箱)
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="select"></param>
/// <returns></returns>
public static PropertyInfo GetPropertyInfo<T>(Expression<Func<T, dynamic>> select)
{
var body = select.Body;
if (body.NodeType == ExpressionType.Convert)
{
var o = (body as UnaryExpression).Operand;
return (o as MemberExpression).Member as PropertyInfo;
}
else if (body.NodeType == ExpressionType.MemberAccess)
{
return (body as MemberExpression).Member as PropertyInfo;
}
return null;
}
如果覺得可以給個推薦吧,如果覺得有不足的地方,歡迎指出,謝謝!
另外為了方便大家學習與使用系列控件,我已將源碼打包上傳,大家可以點以下鏈接下載:
