首頁 > 軟體

C#繪製時鐘的方法

2022-06-13 18:03:40

本文範例為大家分享了使用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
    }
}

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支援it145.com。


IT145.com E-mail:sddin#qq.com