1. 程式人生 > >大帥手把手教你做日曆控制元件——WinForm窗體控制元件庫和日曆演算法

大帥手把手教你做日曆控制元件——WinForm窗體控制元件庫和日曆演算法

WinForm窗體控制元件庫和日曆演算法

先來看看效果圖
效果圖
圖中展現的是一個日曆自定義控制元件,其顯示格里高利曆、農曆及節氣。

控制元件製作

  1. 建立一個窗體控制元件庫專案,新建兩個控制元件:PanelDay和PanelMonth;
    calendar1
  2. PanelDay的組成為三個Label控制元件,分別填寫格里曆、農曆和節氣。
    calendar2
    控制元件程式碼如下:
    public partial class PanelDay: UserControl
    {
        public PanelDay()
        {
            InitializeComponent();
            this
.labelSolar.Enabled = false; this.labelLunar.Enabled = false; this.labelTerms.Enabled = false; } private string strSolar = ""; private string strLunar = ""; private string strTerms = ""; private MyDrawingMode myDrawingMode; public
string Solar { get { return strSolar; } set { strSolar = value; labelSolar.Text = strSolar; } } public string Lunar { get { return strLunar; } set { strLunar = value
; labelLunar.Text = strLunar; } } public string Terms { get { return strTerms; } set { strTerms = value; labelTerms.Text = strTerms; workTerms(); } } public enum MyDrawingMode { Default = 0, Terms = 1 } public MyDrawingMode DrawingMode { get { return myDrawingMode; } set { myDrawingMode = value; workDM(); } } private void workDM() { switch (myDrawingMode) { case MyDrawingMode.Default: labelSolar.Location = new System.Drawing.Point(0, 0); labelSolar.Size = new System.Drawing.Size(56, 40); labelLunar.Location = new System.Drawing.Point(0, 40); labelLunar.Size = new System.Drawing.Size(56, 20); labelTerms.Location = new System.Drawing.Point(0, 45); labelTerms.Size = new System.Drawing.Size(56, 15); labelTerms.Visible = false; break; case MyDrawingMode.Terms: labelSolar.Location = new System.Drawing.Point(0, 0); labelSolar.Size = new System.Drawing.Size(56, 30); labelLunar.Location = new System.Drawing.Point(0, 30); labelLunar.Size = new System.Drawing.Size(56, 15); labelTerms.Location = new System.Drawing.Point(0, 45); labelTerms.Size = new System.Drawing.Size(56, 15); labelTerms.Visible = true; break; default: labelSolar.Location = new System.Drawing.Point(0, 0); labelSolar.Size = new System.Drawing.Size(56, 40); labelLunar.Location = new System.Drawing.Point(0, 40); labelLunar.Size = new System.Drawing.Size(56, 20); labelTerms.Location = new System.Drawing.Point(0, 45); labelTerms.Size = new System.Drawing.Size(56, 15); labelTerms.Visible = false; break; } } private void workTerms() { if (strTerms == string.Empty) myDrawingMode = MyDrawingMode.Default; else myDrawingMode = MyDrawingMode.Terms; workDM(); } }

PanelDay為自定義控制元件,三個屬性為格里曆、農曆和節氣,都為字串。兩個繪圖模板用列舉來表示。
PanelMonth由三個主要面板組成,即Search、Week、Wall;
calendar3
程式碼如下:

    public partial class PanelMonth : UserControl
    {
        public PanelMonth()
        {
            InitializeComponent();
            DisplayPD(datetime);
        }

        private DateTime datetime=System.DateTime.Now;

        public DateTime Datetime
        {
            get { return datetime; }
            set
            {
                datetime = value;
                dateTimePicker.Value = datetime;
            }
        }

        public void Add(PanelDay pd,int x,int y)
        {
            pd.Location = new System.Drawing.Point(x * 56, y * 62);
            panelWall.Controls.Add(pd);
        }

        public void Add(PanelDay pd)
        {
            panelWall.Controls.Add(pd);
        }

        public void DisplayPD(DateTime datetime)
        {
            panelWall.Controls.Clear();
            DateTimeDS dt = new DateTimeDS();
            int dim = dt.daysInMonth(dateTimePicker.Value.Year, dateTimePicker.Value.Month);
            PanelDay[] panelday = new PanelDay[dim];
            for (int d = 0; d < dim; d++)
            {
                panelday[d] = new PanelDay();
                panelday[d].Name = "pd" + (d + 1).ToString();
                panelday[d].Solar = (d + 1).ToString();
                panelday[d].Lunar = dt.getLunarDay(dateTimePicker.Value.Year, dateTimePicker.Value.Month, d + 1);
                panelday[d].MouseEnter += new EventHandler(PanelMonth_MouseEnter);
                panelday[d].MouseLeave += new EventHandler(PanelMonth_MouseLeave);
                panelday[d].MouseClick += PanelMonth_MouseClick;
                panelday[d].Terms = dt.terms(new DateTime(dateTimePicker.Value.Year, dateTimePicker.Value.Month, d + 1));
                if (datetime.Day == d + 1)
                {
                    panelday[d].BackColor = Color.Green;
                }
            }
            int index = 0;
            DateTime newtime = dateTimePicker.Value;
            DateTime firstdaytime = new DateTime(newtime.Year, newtime.Month, 1);
            int firstday = (int)firstdaytime.DayOfWeek;
            for (int i = 0; i < 6; i++)
            {
                for (int j = 0; j < 7; j++)
                {

                    if (i == 0 && j < firstday) { }
                    else if (index < dim)
                    {
                        Add(panelday[index], j, i);
                        index++;
                    }

                }
            }
        }

        private void PanelMonth_MouseClick(object sender, EventArgs e)
        {
            PanelDay pd = (PanelDay)sender;
            pd.BackColor = System.Drawing.Color.Green;
            datetime = new DateTime(dateTimePicker.Value.Year, dateTimePicker.Value.Month, Convert.ToInt32(pd.Solar));
            dateTimePicker.Value = datetime;
            DisplayPD(datetime);
        }

        private void PanelMonth_MouseEnter(object sender, EventArgs e)
        {
            PanelDay pd = (PanelDay)sender;
            if (dateTimePicker.Value.Year != datetime.Year || dateTimePicker.Value.Month != datetime.Month || dateTimePicker.Value.Day != Convert.ToInt32(pd.Solar))
            {
                pd.BackColor = System.Drawing.Color.DarkSeaGreen;
            }
        }

        private void PanelMonth_MouseLeave(object sender, EventArgs e)
        {
            PanelDay pd = (PanelDay)sender;
            if (dateTimePicker.Value.Year != datetime.Year || dateTimePicker.Value.Month != datetime.Month || dateTimePicker.Value.Day != Convert.ToInt32(pd.Solar))
            {
                pd.BackColor = System.Drawing.Color.Azure;
            }
        }

        private void dateTimePicker_ValueChanged(object sender, EventArgs e)
        {
            datetime = dateTimePicker.Value;
            DisplayPD(datetime);
        }
    }

PanelMonth主要區域有三個:
一為時間定義,輸入日期,顯示日曆;
二為星期,只有顯示的作用;
三為填充面板,顯示動態建立的PanelDay。
PanelMonth自定義了一個屬性就是Datetime,預設為當前時間,起選擇的作用。

演算法

  1. 格里曆演算法
        #region 根據年月獲得當月天數
        public int daysInMonth(int year, int month)
        {
            int days = 0;
            switch (month)
            {
                case 1: case 3: case 5: case 7: case 8: case 10: case 12:days = 31;break;
                case 4: case 6: case 9: case 11:days = 30;break;
                case 2:
                    if ((year % 100 != 0 && year % 4 == 0) || (year % 400 == 0)) days = 29;
                    else days = 28;
                    break;
                default:days = 0;break;
            }
            return days;
        }
        #endregion

這只是人為地創造出更符合紀日的方法,跟地理人文沒啥關係。1582年還人為修改過,呵呵。
2. 農曆新增

#region 根據日期獲得節氣
#region
        public string getLunarDay(int year, int month, int day)
        {
            string[] lunarstr1 = { "一", "二", "三", "四", "五", "六", "七", "八", "九", "十" };
            string[] lunarstr2 = { "初", "十", "廿", "卅" };
            string[] lunarmonthstr = { "正月", "二月", "三月", "四月", "五月", "六月", "七月", "八月", "九月", "十月", "冬月", "臘月" };
            ChineseLunisolarCalendar calendar = new ChineseLunisolarCalendar();
            DateTime datetime = new DateTime(year, month, day);
            int lunaryear = calendar.GetYear(datetime);
            int lunarmonth = calendar.GetMonth(datetime);
            int lunarday = calendar.GetDayOfMonth(datetime);
            int leapmonth = calendar.GetLeapMonth(lunaryear);
            if (leapmonth > 0)
            {
                if (leapmonth <= lunarmonth)
                {
                    lunarmonth--;
                }
            }
            if (lunarday == 1)
            {
                return lunarmonthstr[lunarmonth - 1].ToString();
            }
            else
            {
                if (lunarday > 1 && lunarday <= 10)
                {
                    return lunarstr2[0].ToString() + lunarstr1[lunarday - 1].ToString();
                }
                else if (lunarday > 10 && lunarday < 20)
                {
                    return lunarstr2[1].ToString() + lunarstr1[lunarday % 10 - 1].ToString();
                }
                else if (lunarday == 20)
                {
                    return "二十";
                }
                else if (lunarday > 20 && lunarday < 30)
                {
                    return lunarstr2[2].ToString() + lunarstr1[lunarday % 10 - 1].ToString();
                }
                else if (lunarday == 30)
                {
                    return "三十";
                }
                else
                {
                    return "卅一";
                }
            }
        }
        #endregion

.NET中有個ChineseLunisolarCalendar類,我們一定要尊重前人的勞動成果,我們現在所做的一切都是以踩在巨人的肩膀上為代價的。從中我們很輕易獲取農曆日期,我們只需改為漢字形式就行了。
3. 節氣

        #region 根據日期獲得節氣
        public string terms(DateTime date)
        {
            string[] SolarTerm = new string[] { "小寒", "大寒", "立春", "雨水", "驚蟄", "春分", "清明", "穀雨", "立夏", "小滿", "芒種", "夏至", "小暑", "大暑", "立秋", "處暑", "白露", "秋分", "寒露", "霜降", "立冬", "小雪", "大雪", "冬至" };
            int[] sTermInfo = new int[] { 0, 21208, 42467, 63836, 85337, 107014, 128867, 150921, 173149, 195551, 218072, 240693, 263343, 285989, 308563, 331033, 353350, 375494, 397447, 419210, 440795, 462224, 483532, 504758 };
            DateTime baseDateAndTime = new DateTime(1900, 1, 6, 2, 5, 0); //#1/6/1900 2:05:00 AM#
            DateTime newDate;
            double num;
            int y;
            string tempStr = "";

            y = date.Year;

            for (int i = 1; i <= 24; i++)
            {
                num = 525948.76 * (y - 1900) + sTermInfo[i - 1];
                newDate = baseDateAndTime.AddMinutes(num);
                if (newDate.DayOfYear == date.DayOfYear)
                {
                    tempStr = SolarTerm[i - 1];
                    break;
                }
            }
            return tempStr;
        }
        #endregion

節氣本來可以分為二十四等份,如果地球對著太陽作勻速圓周運動的話。但是太陽只是在一個地球橢圓軌道的焦點上,這就造成了節氣時長不等。如果只是這樣的話還可以時長固定,但是地球的自轉軸和地球橢圓軌道所在面並不垂直,地球每年西退,這種現象有專業名詞表示,歲差。此外由於地球並不是非常接近於球形的,在自轉的過程中會收到太陽和月亮的拉拉扯扯,這種現象也有專業名詞表示,章動。所幸人們只可以活百歲,生活中不需要了解大週期中的小週期。某種情形下,暫時的可以為永久的,可以直接當做一個表來讀取資料。