C#繪制時鐘的方法

本文實例為大傢分享瞭使用C#寫一個時鐘,供大傢參考,具體內容如下

時鐘是這樣的

一共使用四個控件即可:

WinFrom窗體應用程序代碼:

using SpeechLib;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace MyClockTest
{
    public partial class Form1 : Form
    {
        //創建對象myclock
        MyClock myclock = new MyClock();

        //定義date1,共電子表使用 -- 獲取到當下時間點
        DateTime date1 = DateTime.Now; //Now 為靜態隻讀屬性(屬性可計算)

        //定義中心點center和半徑radius -- 時鐘的圓心和半徑大小
        Point center = new Point(160, 200); // 圓心點的坐標
        int radius = 150; // 半徑 150
        //定義判定是否擊中表盤的bool變量 -- 鼠標點擊拖拽功能
        bool down = false;
        //記錄鼠標點擊的點
        Point pre;

        //程序入口
        public Form1()
        {
            InitializeComponent();
        }

        //開始畫時鐘
        private void OnPaint(object sender, PaintEventArgs e)
        {
            //這裡不使用Graphics,而選擇使用BufferedGraphics:
            //Graphics和BufferedGraphics對比:
            //5000次下時Graphics效率高一些;5000次以後BufferedGraphics效率高一些
            //Bitmap bmp = new Bitmap(width ,height );
            //Graphics g2 = Graphics.FromImage(bmp );
            //myclock.Draw(g2);
            //g2.CreateGraphics();
            //g2.DrawImage(bmp,0,0);
            //bmt.dispose();

            //Image iamge = new Bitmap(this.Width ,this .Height );
            //Graphics g1 = Graphics.FromImage(iamge);
            //g1.Clear(Form .DefaultBackColor );

            //C#雙緩沖解釋:
            //簡單說就是當我們在進行畫圖操作時,系統並不是直接把內容呈現到屏幕上,而是先在內存中保存,然後一次性把結果輸出來。
            //如果沒用雙緩沖的話,你會發現在畫圖過程中屏幕會閃的很厲害,因為後臺一直在刷新,
            //而如果等用戶畫完之後再輸出就不會出現這種情況,具體的做法,其實也就是先創建一個位圖對象,然後把內容保存在裡面,最後把圖呈現出來。

            //解決空間重繪時閃爍的問題,並在改變大小時重繪圖形
            this.SetStyle(ControlStyles.OptimizedDoubleBuffer | ControlStyles.ResizeRedraw | ControlStyles.AllPaintingInWmPaint, true);
            //矩形繪圖區域
            Rectangle rect = e.ClipRectangle;

            //關於BufferedGraphicsContext類:
            //    手工設置雙緩沖.netframework提供瞭一個類BufferedGraphicsContext負責單獨分配和管理圖形緩沖區。
            //    每個應用程序域都有自己的默認 BufferedGraphicsContext 實例來管理此應用程序的所有默認雙緩沖。
            //    大多數情況下,每個應用程序隻有一個應用程序域,所以每個應用程序通常隻有一個默認 BufferedGraphicsContext。

            //關於此處的繪圖:
            //通過管理BufferedGraphicsContext實現雙緩沖的步驟如下:

            //(1)獲得對 BufferedGraphicsContext 類的實例的引用。

            //(2)通過調用 BufferedGraphicsContext.Allocate 方法創建 BufferedGraphics 類的實例。

            //(3)通過設置 BufferedGraphics.Graphics 屬性將圖形繪制到圖形緩沖區。

            //(4)當完成所有圖形緩沖區中的繪制操作時,可調用 BufferedGraphics.Render 方法將緩沖區的內容呈現到與該緩沖區關聯的繪圖圖面或者指定的繪圖圖面。

            //(5)完成呈現圖形之後,對 BufferedGraphics 實例調用釋放系統資源的 Dispose 方法。

            BufferedGraphicsContext current = BufferedGraphicsManager.Current; //(1)
            BufferedGraphics bg;
            bg = current.Allocate(e.Graphics, e.ClipRectangle); //(2)
            Graphics g = bg.Graphics; //(3)
            //定義畫佈 
            //Graphics g = CreateGraphics();
            //消除鋸齒
            //g.SmoothingMode = SmoothingMode.AntiAlias;
            g.SmoothingMode = SmoothingMode.HighQuality; //高質量
            g.PixelOffsetMode = PixelOffsetMode.HighQuality; //高像素偏移質量
            //每次繪畫後的背景色填充 && 定義畫電子鐘的字體
            g.Clear(this.BackColor);
            Font font = new Font("Times New Roman", 20);
            //在每次繪畫時,更新時間,半徑及中心點
            myclock.Date = DateTime.Now;
            myclock.Week = DateTime.Now;
            myclock.P = center;
            myclock.R = radius;

            //調用myclock中的Draw函數畫出鐘
            myclock.Draw(g);
            //直接調用DrawString函數,顯示電子表,並且設置顯示格式
            g.DrawString(date1.ToString(), font, Brushes.Black, 10, 10);//10,10表示字的左上角在哪
            bg.Render(e.Graphics); //(4)---> 開啟繪制
            bg.Dispose(); //(5)---> 繪制完成,釋放資源

            //如果為整點,則報時
            if (Convert.ToString(DateTime.Now.Minute) == "0" && Convert.ToString(DateTime.Now.Second ) == "0")
            {
                SpVoice voice = new SpVoice();
                voice.Speak("現在時間為" + Convert.ToString(DateTime.Now.Month) + "月" + Convert.ToString(DateTime.Now.Day) + "日" + Convert.ToString(DateTime.Now.Hour) + "點" + Convert.ToString(DateTime.Now.Minute) + "分", SpeechVoiceSpeakFlags.SVSFDefault);
            }
        }

        //更新時鐘的時間
        private void OnTime(object sender, EventArgs e)
        {
            //使用timer控件來更新時間 ---> 1s/次

            //更新鐘的時間
            myclock.Date = DateTime.Now;
            myclock.Week = DateTime.Now;
            //更新電子表的時間
            date1 = DateTime.Now;
            //刷新 --- 每走動一秒都更新畫佈(重繪)
            Invalidate();
            //Refresh();
        }

        //鼠標單擊到時鐘上的坐標
        private void Form1_MouseDown(object sender, MouseEventArgs e)
        {
            //記錄點擊的點,並且計算是否擊中時鐘 -- 記錄點擊時的坐標
            pre = new Point(e.X, e.Y);
            down = myclock.HitTest(pre, center, radius);
        }

        //在點擊後,鼠標拖動時
        private void Form1_MouseMove(object sender, MouseEventArgs e)
        {
            //使用if來判斷是否擊中,若擊中,更新中心點
            Point p = new Point(e.X, e.Y);
            //如果點擊持續
            if (down)
            {
                //更新圓心坐標點
                center = new Point(center.X + p.X - pre.X, center.Y + p.Y - pre.Y);
                //pre = 當前移動的點的坐標
                pre = p;
                //重繪
                Invalidate();
                //Refresh();
            }
        }

        private void Form1_MouseUp(object sender, MouseEventArgs e)
        {
            //取消擊中 -- 拖拽結束
            down = false;
        }

        private void trackBar1_Scroll(object sender, EventArgs e)
        {
            //使用 trackBar1來控制半徑大小,並且更新 ---> trackBar控件改變時鐘的大小
            radius = trackBar1.Value;
            Invalidate();
            //refresh
        }

        //單擊:報時功能
        private void button1_Click(object sender, EventArgs e)
        {
            DateTime da = new DateTime();
            da = DateTime.Now;
            //SpVoice報時
            SpVoice voice = new SpVoice();
            voice.Speak("現在時間為" + Convert.ToString(da.Month) + "月" + Convert.ToString(da.Day) + "日" + Convert.ToString(da.Hour) + "點" + Convert.ToString(da.Minute) + "分", SpeechVoiceSpeakFlags.SVSFDefault);
        }
    }

    //初始化時鐘:類應該寫在外面(此處不規范)
    public class MyClock
    {
        enum time { XII, III, VI, IX } //定義結構體 -- 時鐘的4頂點
        public DateTime date; //Now 為靜態隻讀{get}屬性(屬性可計算),智能作為右值 -- 電子表時間
        public DateTime week; // 周
        public Point p; // 圓心點的坐標
        public int r; // 半徑

        public MyClock()
        {
            //定義一個構造函數,賦予date當前時間
            date = DateTime.Now;
            week = DateTime.Now;
            p = new Point(160, 200); // 圓心點的坐標
            r = 150; // 半徑
        }

        //畫表盤
        public void DrawDial(Graphics g)
        {
            //定義兩個pen,p1用來畫小刻度及表盤,p2用來畫大刻度
            Pen p1 = new Pen(Color.Black, 2);
            Pen p2 = new Pen(Color.Green, 2);
            //時鐘頂點時間的字體大小
            float f = r / 10;
            //字體樣式 && 字體大小
            Font font = new Font("Times New Roman", f);

            //定義兩個點數組來存儲刻度線的兩點 --- [60] 數組中共存放瞭60個點的坐標
            PointF[] pointf1 = new PointF[60];
            PointF[] pointf2 = new PointF[60];
            // 
            for(int i = 0; i < 59; i++)
            {
                //通過for循環來給pointf1賦值 ---> f1點就在圓形的邊上,r
                float x1 = (float)(p.X + r * Math.Sin((i + 1) * Math.PI * 2 / 60));
                float y1 = (float)(p.Y + r * Math.Cos((i + 1) * Math.PI * 2 / 60));
                pointf1[i] = new PointF(x1, y1);

                //使用if來判斷,能整除5就畫大刻度,不能就畫小刻度
                if ((i + 1) % 5 == 0)
                {
                    float x2 = (float)(p.X + (r - r / 20) * Math.Sin((i + 1) * 2 * Math.PI / 60));
                    float y2 = (float)(p.Y + (r - r / 20) * Math.Cos((i + 1) * 2 * Math.PI / 60));
                    pointf2[i] = new PointF(x2, y2);
                    //畫大刻度
                    g.DrawLine(p2, pointf1[i], pointf2[i]);
                }
                else
                {
                    float x2 = (float)(p.X + (r - r / 40) * Math.Sin((i + 1) * 2 * Math.PI / 60));
                    float y2 = (float)(p.Y + (r - r / 40) * Math.Cos((i + 1) * 2 * Math.PI / 60));
                    pointf2[i] = new PointF(x2, y2);
                    //畫小刻度
                    g.DrawLine(p2, pointf1[i], pointf2[i]);
                }
            }
            //用pointf3來儲存四個點的位置 表盤的4個頂點位置 ---> 橫坐標 && 縱坐標調整距離到合適的位置上
            PointF[] pointf3 =
            {
            new PointF (pointf1 [30].X-f/4,pointf1 [30].Y +r/20),
            new PointF (pointf1 [15].X-r/20-2*f ,pointf1 [15].Y+f/3 ),
            new PointF (pointf1 [0].X-2*f ,pointf1 [0].Y -r/20-4*f/3),
            new PointF (pointf1 [45].X+r/20 ,pointf1 [45].Y -r/20-4*f/3),
            };

            //畫出四個點相應的時間值
            for (int m = 0; m < 4; m++)
            {
                g.DrawString(((time)m).ToString(), font, Brushes.Black, pointf3[m]);
            }

            //代碼重復 ---> 代碼保持高內聚低耦合 ---> (time)將阿拉伯數字改成時間類型
            //g.DrawString(((time)0).ToString(), font, Brushes.Black, pointf3[0]);
            //g.DrawString(time.III.ToString(), font, Brushes.Black, pointf3[1]);
            //g.DrawString(time.VI.ToString(), font, Brushes.Black, pointf3[2]);
            //g.DrawString(time.IX.ToString(), font, Brushes.Black, pointf3[3]);

            //使用DrawEllipse畫表盤 --- pen 左上角的x坐標 左上角的y坐標 寬度 高度 ---> 畫圓
            g.DrawEllipse(p1, p.X - r, p.Y - r, 2 * r, 2 * r);
        }

        //畫時針
        public void DrawHourPointer(Graphics g)
        {
            //定義畫筆p1,設置其屬性,使其帶有箭頭,圓點尾部
            Pen p1 = new Pen(Color.Green, 4);
            p1.EndCap = LineCap.ArrowAnchor; // 箭頭
            p1.StartCap = LineCap.Round; // 尾部

            //獲取分鐘,轉化為int型
            int i = Convert.ToInt32(date.Minute);
            //獲取小時,並轉換為int型,同時對12取餘,獲得十二小時制的小時數
            int j = (Convert.ToInt32(date.Hour)) % 12;

            //計算點值 --- (2 * Math.PI) * j / 720) 根據分鐘表示時針的位置 ---> 1分鐘再小時裡占比1/720 ---> 60分鐘*12個小時數 = 720
            //點1的坐標
            float x1 = (float)(p.X + (r - r / 3) * Math.Sin((j * 2 * Math.PI / 12) + (2 * Math.PI) * i / 720));
            float y1 = (float)(p.Y - (r - r / 3) * Math.Cos((j * 2 * Math.PI / 12) + (2 * Math.PI) * i / 720));
            //點2的坐標
            float x2 = (float)(p.X - r / 30 * Math.Sin((j * 2 * Math.PI / 12) + (2 * Math.PI) * i / 720));
            float y2 = (float)(p.Y + r / 30 * Math.Cos((j * 2 * Math.PI / 12) + (2 * Math.PI) * i / 720));
            PointF pointf1 = new PointF(x1, y1);
            PointF pointf2 = new PointF(x2, y2);

            //用DrawLine畫時針
            g.DrawLine(p1, pointf2, pointf1);
        }

        //畫分針
        public void DrawMinutePointer(Graphics g)
        {
            //定義畫筆p1,設置其屬性,使其帶有箭頭,圓點尾部
            //畫筆寬度為3 ---> 分針比時針細一點
            Pen p1 = new Pen(Color.Blue, 3);
            p1.EndCap = LineCap.ArrowAnchor;
            p1.StartCap = LineCap.Round;

            //獲取當前分鐘,並轉換為int型
            int i = Convert.ToInt32(date.Minute);

            //計算點值
            //點1
            float x1 = (float)(p.X + (r - r / 5) * Math.Sin(i * 2 * Math.PI / 60));
            float y1 = (float)(p.Y - (r - r / 5) * Math.Cos(i * 2 * Math.PI / 60));
            //點2
            float x2 = (float)(p.X - r / 20 * Math.Sin(i * 2 * Math.PI / 60));
            float y2 = (float)(p.Y + r / 20 * Math.Cos(i * 2 * Math.PI / 60));
            PointF pointf1 = new PointF(x1, y1);
            PointF pointf2 = new PointF(x2, y2);

            //用DrawLine畫分針
            g.DrawLine(p1, pointf2, pointf1);
        }

        //畫秒針
        public void DrawSecondPointer(Graphics g)
        {
            Pen p1 = new Pen(Color.Red, 2);
            p1.EndCap = LineCap.ArrowAnchor;
            p1.StartCap = LineCap.Round;

            //獲取當前秒,並轉換為int型
            int i = Convert.ToInt32(date.Second);

            //計算點值
            float x1 = (float)(p.X + (r - r / 20) * Math.Sin(i * 2 * Math.PI / 60));
            float y1 = (float)(p.Y - (r - r / 20) * Math.Cos(i * 2 * Math.PI / 60));
            float x2 = (float)(p.X - r / 15 * Math.Sin(i * 2 * Math.PI / 60));
            float y2 = (float)(p.Y + r / 15 * Math.Cos(i * 2 * Math.PI / 60));
            PointF pointf1 = new PointF(x1, y1);
            PointF pointf2 = new PointF(x2, y2);
            //用DrawLine畫秒針 ---> 根據兩點之間畫線
            g.DrawLine(p1, pointf2, pointf1);
        }

        //周和日期顯示 --->  表盤上
        public void DrawWeeks(Graphics g)
        {
            //字體大小
            float f = r / 15;
            Font font = new Font("Times New Roman", f);
            //放置位置
            float f1 = (float)(p.X + r * Math.Sin(Math.PI) / 2);
            float f2 = (float)(p.Y - r * Math.Cos(Math.PI) / 2);
            //DrawString ---> 畫字符串
            g.DrawString(week.DayOfWeek.ToString(), font, Brushes.Black, f1, f2);
            g.DrawString(week.Day.ToString(), font, Brushes.Black, f1 + 80, f2);
        }

        public void Draw(Graphics g)
        {
            //定義函數Draw,畫出時鐘

            //畫表盤
            DrawDial(g);
            //畫時針
            DrawHourPointer(g);
            //畫分針
            DrawMinutePointer(g);
            //畫秒針
            DrawSecondPointer(g);
            //畫周和日期
            DrawWeeks(g);
        }

        public bool HitTest(Point p, Point center, int r)
        {
            //HitTest函數表示鼠標是否點在時鐘裡面

            //通過計算鼠標點擊的點到中心點的距離和半徑的比較來判定是否擊中時鐘
            //圓心到半徑的距離畫圓,便是圓的全部面積
            double f1 = (double)(p.X - center.X);
            double f2 = (double)(p.Y - center.Y);

            //test即為鼠標點擊的點到中心點的距離
            //根據勾股定理,求距離,直角三角形 a² + b² = c²
            double test = Math.Sqrt(f1 * f1 + f2 * f2);

            //斜邊長於圓的半徑,則在圓的范圍之外;斜邊小於圓的半徑,則在圓的范圍之內
            if (test <= r)
            {
                return true;
            }
            else
            {
                return false;
            }
        }

        #region 屬性
        //此處設置成瞭隻讀 ---> 不可寫 ---> 導致時鐘的DateTime時間無法寫入進來
        //public DateTime Date { get; internal set; }
        //public DateTime Week { get; internal set; }
        //public Point P { get; internal set; }
        //public int R { get; internal set; }

        public DateTime Date
        {
            set { date = value; }
        }
        public Point P
        {
            set { p = value; }
        }
        public int R
        {
            set { r = value; }
        }
        public DateTime Week
        {
            set { week = value; }
        }
        #endregion
    }
}

以上就是本文的全部內容,希望對大傢的學習有所幫助,也希望大傢多多支持WalkonNet。

推薦閱讀: