我們自己編寫程序的界面,會遇到各種屏幕分辨率,只有自適應才能顯的美觀。實際上,做到這點也很簡單,就是首先記錄窗體和它上面控件的初始位置和大小,當窗體改變比例時,其控件的位置和大小也按此比例變化即可。因為窗體上控件的位置和大小是相對於自己所在的窗體的,也就是所謂的窗口坐標。
在這里我們只考慮相對於自己窗體的窗口坐標更簡單,也就是成比例變化。為了多個窗體共用,我在這里創建一個類AutoSizeFormClass ,1.使用它去記錄窗體和其控件的初始位置和大小,2.判斷窗體中的控件是否為容器控件,如果是記錄容器控件中的控件的初始位置和大小3.根據窗體變化了的大小,成比例地實現其控件的水平和垂直方向的變化,也就是自適應。
二。使用方法
使用方法很簡單,
1.把自適應的類整體復制到你的工程命名空間里,
然后在需要自適應的窗體中做2步即可:
2.聲明自適應類實例。
3.為窗體添加大小改變事件,並在其方法中,調用類的自適應方法,完成自適應
二。使用方法
使用方法很簡單,
1.把自適應的類整體復制到你的工程命名空間里,
然后在需要自適應的窗體中做2步即可:
2.聲明自適應類實例。
3.為窗體添加大小改變事件,並在其方法中,調用類的自適應方法,完成自適應
通過上面的介紹我們會發現其中的幾個實現難點:
1.如何保存窗體的以及其中控件的位置以及大小等屬性,當然我們最常用的方法就是自己定義一個實體,實體中包含我們需要保持的屬性(主要包括left,top,width,height,以及fontsize屬性)這個我提供的解決方案是定義一個數據結構。
普及一下結構體的知識:結構體中包含其中要存儲的數據,使用結構體的好處在於可以將不同類型的數據有序的組合在一起,結構造出一個新的數據類型,不占內存空間,只用定義結構體的變量時才開辟內存空間,結構體類型的變量在內存依照其成員順序順序排列,所占內存空間的大小是其全體成員所占空間的總和,結構體可以作為函數的參數,函數也可以返回結構體。
看我們定義的結構體:
/// <summary> /// 聲明一個結構,用於保存控件位置的基本屬性。 /// </summary> public struct controlRect { /// <summary> /// 控件的left屬性 /// </summary> public int Left; /// <summary> /// 控件的Right屬性 /// </summary> public int Top; /// <summary> /// 控件的Weight屬性 /// </summary> public int Width; /// <summary> /// 控件的High屬性 /// </summary> public int Height; /// <summary> /// 控件的Fontsize屬性 /// </summary> public float FontSize; }
//這里只是列出兩個容器控件分別為panel控件和groupbox控件 if (c.GetType().ToString() == "System.Windows.Forms.Panel") { //要執行的代碼 } //如果是GroupBox控件 if (c.GetType().ToString() == "System.Windows.Forms.GroupBox") { //要執行的代碼 }
但我發現自己很難把所有的控件都想全了,即使是想全了也會重復太多的代碼,最后采取了一個很有效的方法就是加上這個判斷:
if (c.Controls.Count > 0)
一旦這個判斷成立就說明這個控件就是一個容器控件了。
問題3.遞歸調用保存控件信息類,實現所有控件(包括容器控件)的位置和大小信息的保存。
問題4.如何保存畫窗體時窗體的大小,聽起來有些別嘴,其實也可以說是你想要窗體呈現的大小,這個大大家好像會有疑問但雖然說窗體是控件的一種,但是進過我的實現,當我們改變分辨率的同時,比如我們原來設置窗體每次打開時最大化顯示,但是無論我們編寫程序時設置的窗體的大小多大,我們運行起來時窗體都會占滿整個屏幕,這也是出現由於分辨率改變,大窗體中部分控件無法顯示完全的原因。比如我們的做的這個很大的登陸界面,看看效果:
小分辨率下的情況:
大分辨率下應該是這樣:
我們會發現窗體最大化了。但是窗體中的控件沒有跟上。
說了這么多我想大家都迫不及待的想看代碼了:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Windows.Forms; using System.Drawing; namespace AutoSizeFormClass { public class AutoSizeFormClass { /// <summary> /// 聲明一個結構,用於保存控件位置的基本屬性。 /// </summary> public struct controlRect { /// <summary> /// 控件的left屬性 /// </summary> public int Left; /// <summary> /// 控件的Right屬性 /// </summary> public int Top; /// <summary> /// 控件的Weight屬性 /// </summary> public int Width; /// <summary> /// 控件的High屬性 /// </summary> public int Height; /// <summary> /// 控件的Fontsize屬性 /// </summary> public float FontSize; } /// <summary> /// 聲明一個泛型,類型為什么的保存控件屬性的結構類, /// </summary> public List<controlRect> oldCtrl = new List<controlRect>(); int ctrlNo = 0;//初始化標識控件的變量為0,表示窗體本身。 /// <summary> /// 保存控件的位置和大小信息 /// </summary> /// <param name="ctl">需要被保存的控件</param> private void AddControl(Control ctl) { foreach (Control c in ctl.Controls) { controlRect objCtrl; objCtrl.Left = c.Left; objCtrl.Top = c.Top; objCtrl.Width = c.Width; objCtrl.Height = c.Height; objCtrl.FontSize = c.Font.Size; oldCtrl.Add(objCtrl); //**放在這里,是先記錄控件本身,后記錄控件的子控件,重點是前后要一致 if (c.Controls.Count > 0) AddControl(c);//窗體內其余控件還可能嵌套控件(比如panel),要單獨抽出,因為要遞歸調用 } } /// <summary> /// 窗體自適應分辨率類 /// </summary> /// <param name="mForm">需要進行設置的窗體</param> public void controlAutoSize(Control mForm) { if (ctrlNo == 0) { //*如果在窗體的Form1_Load中,記錄控件原始的大小和位置,正常沒有問題,但要加入皮膚就會出現問題,因為有些控件如dataGridView的的子控件還沒有完成,個數少 //*要在窗體的Form1_SizeChanged中,第一次改變大小時,記錄控件原始的大小和位置,這里所有控件的子控件都已經形成 controlRect cR; cR.Left = mForm.Left; cR.Top = mForm.Top; cR.Width = mForm.Width; cR.Height = mForm.Height; cR.Width = int.Parse(mForm.Tag.ToString().Split(',')[0]); cR.Height = int.Parse(mForm.Tag.ToString().Split(',')[1]); cR.FontSize = mForm.Font.Size; oldCtrl.Add(cR);//第一個為"窗體本身",只加入一次即可 AddControl(mForm);//窗體內其余控件可能嵌套其它控件(比如panel),故單獨抽出以便遞歸調用 } float wScale = (float)mForm.Width / (float)oldCtrl[0].Width;//新舊窗體之間的比例,與最早的舊窗體比較 float hScale = (float)mForm.Height / (float)oldCtrl[0].Height;//.Height; ctrlNo = 1;//進入=1,第0個為窗體本身,窗體內的控件,從序號1開始 AutoScaleControl(mForm, wScale, hScale);//窗體內其余控件還可能嵌套控件(比如panel),要單獨抽出,因為要遞歸調用 } /// 設置控件的屬性 /// </summary> /// <param name="ctl">需要設置的控件</param> /// <param name="wScale">調整的高度比例</param> /// <param name="hScale">調整的寬度比例</param> private void AutoScaleControl(Control ctl, float wScale, float hScale) { int ctrLeft0, ctrTop0, ctrWidth0, ctrHeight0; float ctrFontSize0; //第1個是窗體自身的 Left,Top,Width,Height,所以窗體控件從ctrlNo=1開始 foreach (Control c in ctl.Controls) { //獲得控件原有的位置和大小信息 ctrLeft0 = oldCtrl[ctrlNo].Left; ctrTop0 = oldCtrl[ctrlNo].Top; ctrWidth0 = oldCtrl[ctrlNo].Width; ctrHeight0 = oldCtrl[ctrlNo].Height; ctrFontSize0 = oldCtrl[ctrlNo].FontSize; //設置控件新的位置和大小信息。 c.Left = (int)((ctrLeft0) * wScale);//新舊控件之間的線性比例。控件位置只相對於窗體 c.Top = (int)((ctrTop0) * hScale);// c.Width = (int)(ctrWidth0 * wScale);//只與最初的大小相關,所以不能與現在的寬度相乘 c.Height = (int)(ctrHeight0 * hScale);// c.Font = new Font(c.Font.Name, (float)(ctrFontSize0 * wScale));//設置控件中字體的大小以適應控件的大小 ctrlNo++;//累加序號 //**放在這里,是先縮放控件本身,后縮放控件的子控件,重點是前后要一致(與保存時) if (c.Controls.Count > 0) AutoScaleControl(c, wScale, hScale);//窗體內其余控件還可能嵌套控件(比如panel),要單獨抽出,因為要遞歸調用 } } } } 代碼中的注釋比較詳細了,如果你想實現窗體的自適應分辨率,你只需要在窗體的Layout事件中添加如下代碼: /// <summary> /// 聲明一個窗體自適應分辨率類 /// </summary> public AutoSizeFormClass As = new AutoSizeFormClass(); /// <summary> /// 在窗體的layout事件中調用 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void Form1_Layout(object sender, LayoutEventArgs e) { As.controlAutoSize(this); }
最后想說的是為什么用layout事件,不用Resize或者是SizeChanged事件,這個我查了好長時間,也自己試了所有的方法,但還是沒能明白只是發現這幾個事件的觸發順序是不同的首先觸發的是Resize→然后是SizeChanged→然后是layout→最后是Load事件,是不是把適應分辨率的代碼寫在那個事件下都可以呢,這個我也嘗試了,當窗體中含有tabcontrol控件時只有layout事件觸發時才能檢測出窗體中包含控件,這幾個事件的區別我實在不知道有聲明區別。希望讀者給出幫助。