|
今天學(xué)習(xí)GDI+,試著想寫一個模擬時鐘的小程序,原以為很簡單實現(xiàn);但其實還有些復(fù)雜,特別是利用三角函數(shù)的那部分,讓我四處找資料惡補了一下高中數(shù)學(xué)才算弄清楚,現(xiàn)在就回顧一下這個程序吧. 程序的目的是要模擬出時鐘的效果,那首先就是要畫出這個時鐘的樣子。不考慮美觀,一個時鐘最簡單的組成是一個圓形的表盤,三根直線代表的時針、分針和秒針。 看起來很簡單吧,但要怎么樣畫呢?讓我們一步一步來吧: 1.畫表盤 Graphics g = this.CreateGraphics(); //創(chuàng)建一個Graphics對象 Pen myPen = new Pen(Color.Blue,1); //創(chuàng)建一個自定義畫筆對象 //創(chuàng)建一個正方形rect int myRect_X = this.ClientRectangle.Right/4; int myRect_Y = this.ClientRectangle.Bottom/4; int myRect_Width; int myRect_Height; if(this.ClientRectangle.Height< this.ClientRectangle.Width) { myRect_Width = this.ClientRectangle.Height/2; myRect_Height = this.ClientRectangle.Height/2; } else { myRect_Width = this.ClientRectangle.Width/2; myRect_Height = this.ClientRectangle.Width/2; } Rectangle rect = new Rectangle(myRect_X,myRect_Y,myRect_Width,myRect_Height);
g.DrawEllipse(myPen,rect); //畫出一個內(nèi)切于矩形rect的圓 Graphics.DrawEllipse方法有幾種重載方式,其中較常用的就是Graphics.DrawEllipse(Pen,Rectangle),這個重載方法需要提供一個Rectangle參數(shù),然后根據(jù)這個矩形的形狀內(nèi)切出橢圓,若這個矩形是正方形,則切出來的是一個正圓.
這段程序最關(guān)鍵的地方就在于得到這個矩形了,因為它間接決定了表盤的位置與大小,Rectangle的構(gòu)造函數(shù)需要四個參數(shù),矩形左上角的X、Y坐標(biāo),寬度與高度. 這里的ClientRectangle所代表的是控件的工作區(qū),它也是一個Rectangle類型的對象,是指控件的邊界減去非工作區(qū)元素(如滾動條、邊框、標(biāo)題欄和菜單)。相應(yīng)的Right,Bottom,Width,Height分別代表了右邊緣的X坐標(biāo),下邊緣的Y坐標(biāo),寬度與高度. 為了使這個表盤適應(yīng)窗口的變化,這里將這些值全部設(shè)置為相對與ClientRectangle相關(guān)屬性的值,如下圖: 這樣當(dāng)窗口大小變化時,這個圓的外切矩形也隨之改變,假如這個窗口的寬度比高度大太多,或反之,則可能會出現(xiàn)表盤的部分超出工作區(qū)域無法顯示,為了防止這種情況,加入了條件判斷語句。如果寬度大于高度,則圓的直徑取高度值;反之則取寬度值。當(dāng)然高度等于寬度時,圓是在工作區(qū)域正中間。 2.畫表針 將表盤畫好后,就可以開始畫表針了,這也是最復(fù)雜的一步 //得到中心點坐標(biāo) int centerPoint_X = rect.Right - rect.Width/2; int centerPoint_Y = rect.Bottom - rect.Height/2; Point centerPoint = new Point(centerPoint_X,centerPoint_Y); int s_Len = rect.Width*0.45; //秒針長度 int m_Len = rect.Width*0.35//分針長度 int l_Len = rect.Width*0.25 //時針長度 //得到當(dāng)前時間 int h = DateTime.Now.Hour; int m = DateTime.Now.Minute; int s = DateTime.Now.Second; //得到秒針頂點坐標(biāo) int sec_X = (int)(centerPoint.X + Math.Sin(6*Math.PI/180*s)*s_Len); int sec_Y = (int)(centerPoint.Y - Math.Cos(6*Math.PI/180*s)*s_Len); Point secPoint = new Point(sec_X,sec_Y); //得到分針頂點坐標(biāo) int min_X = (int)(centerPoint.X + Math.Sin(6*Math.PI/180*m)*m_Len); int min_Y = (int)(centerPoint.Y - Math.Cos(6*Math.PI/180*m)*m_Len); Point minPoint = new Point(min_X,min_Y); //得到時針頂點坐標(biāo) int hour_X = (int)(centerPoint.X + Math.Sin(h*30+m/2)*Math.PI/180*h_Len); int hour_Y = (int)(centerPoint.Y - Math.Cos(h*30+m/2)*Math.PI/180*h_Len); Point hourPoint = new Point(hour_X,hour_Y); //以不同的顏色連接 myPen = new Pen(Color.Blue,1); g.DrawLine(myPen,centerPoint,secPoint); //連接原點和秒針頂點 myPen = new Pen(Color.Green,2); g.DrawLine(myPen,centerPoint,minPoint); //連接原點和分針頂點 myPen = new Pen(Color.Red,3); g.DrawLine(myPen,centerPoint,hourPoint); //連接原點和時針頂點 g.Dispose(); //釋放Graphics對象使用的所有資源 要畫出一條直線,必須要得到這個直線的兩個頂點坐標(biāo),在這個程序里,三根表針有一個共同的原點(表盤中心點),然后如何得到另外一個點就是我們接下來要做的工作了 我們先來回顧一下三角函數(shù)的概念,sin值是 對邊/斜邊 得到,cos值是 鄰邊/斜邊得到,要求出這個點的坐標(biāo)就是求出這個三角形的兩條邊,一條邊的長度為X,另一條的長度為Y?,F(xiàn)在我們已經(jīng)知道的是斜邊的長度,要求出這兩條邊,就要先知道弧度值 我們先來考慮最長的秒針,它每一秒都會移動一個角度,這個角度我們可以計算出來: 360/60=6 得到6度,這不難理解,因為整個圓是360度,一般的時鐘在相鄰小時之間又有5個刻度,所以一共是12*5=60個刻度,然后用360/60=6得到秒針一秒走6度,在程序里必須要將角度轉(zhuǎn)化成弧度才能進(jìn)行sin運算,所以還要乘以 PI/180(PI為圓周率) 有了這個弧度值,我們就可以得到每一秒秒針的頂點坐標(biāo)了(以在第一區(qū)間考慮): sec_X = centerPoint.X + sin(6*PI/180)*當(dāng)前秒鐘值*s_Len sce_Y = centerPoint.Y - cos(6*PI/180)*當(dāng)前秒鐘值*s_Len 接下來處理時針,因為我們這只是個很簡單的模擬,所以就不需要對細(xì)節(jié)作過多的處理,就讓它每過一分鐘就跳到下一個刻度,這樣求時針的頂點坐標(biāo)和求秒針的就是一樣的算法 min_X = centerPoint.X + sin(6*PI/180)*當(dāng)前分鐘值*m_Len min_Y = centerPoint.Y - cos(6*PI/180)*當(dāng)前分鐘值*m_Len 時針的處理方式要不同,因為時針的取值只有12個數(shù)字,而分鐘和秒針都有60個數(shù)字,如果以之前的方式處理時針,效果就是時針到點的那一瞬間它一下子轉(zhuǎn)30度,當(dāng)然不會有這樣效果的時鐘。那么我們該如何處理這個棘手的時針呢? 假如現(xiàn)在是06:45:30,在整6點時它的度數(shù)是30*6,整7點是30*7,為了不至于將改變?nèi)谧詈罂贪l(fā)生,我們加入時針值來參與計算,這樣當(dāng)前時間就是h*30+m/2,m為60時 m/2=30 ,也就是 m/2這個值在0--30之間,當(dāng)角度變化為30度時,就指到了下一個整點 這樣我們得到了時針的算法 hour_X = centerPoint.X + sin((h*30+m/2)*PI/180)*h_Len hour_Y = centerPoint.Y - cos((h*30+m/2)*PI/180)*h_Len 最關(guān)鍵的代碼處理了,剩下的工作就簡單了,我們在程序里加入一個標(biāo)簽lblTime,來顯示當(dāng)前時間 string TimeInString = ""; int hour = DateTime.Now.Hour; int min = DateTime.Now.Minute; int sec = DateTime.Now.Second; TimeInString = (hour<10)? "0"+hour.ToString():hour.ToString(); TimeInString += (min<10)? "0"+min.ToString():min.ToString(); TimeInString == (sec<10)? "0"+sec.ToString():sec.ToString(); lblTime.Text = TimeInString;
|