大帅手把手教你做日历控件——WinForm窗体控件库和日历算法

WinForm窗体控件库和日历算法

  • WinForm窗体控件库和日历算法
    • 控件制作
    • 算法

先来看看效果图
大帅手把手教你做日历控件——WinForm窗体控件库和日历算法_第1张图片
图中展现的是一个日历自定义控件,其显示格里高利历、农历及节气。

控件制作

  1. 创建一个窗体控件库项目,新建两个控件:PanelDay和PanelMonth;
    大帅手把手教你做日历控件——WinForm窗体控件库和日历算法_第2张图片
  2. PanelDay的组成为三个Label控件,分别填写格里历、农历和节气。
    大帅手把手教你做日历控件——WinForm窗体控件库和日历算法_第3张图片
    控件代码如下:
    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;
大帅手把手教你做日历控件——WinForm窗体控件库和日历算法_第4张图片
代码如下:

    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

节气本来可以分为二十四等份,如果地球对着太阳作匀速圆周运动的话。但是太阳只是在一个地球椭圆轨道的焦点上,这就造成了节气时长不等。如果只是这样的话还可以时长固定,但是地球的自转轴和地球椭圆轨道所在面并不垂直,地球每年西退,这种现象有专业名词表示,岁差。此外由于地球并不是非常接近于球形的,在自转的过程中会收到太阳和月亮的拉拉扯扯,这种现象也有专业名词表示,章动。所幸人们只可以活百岁,生活中不需要了解大周期中的小周期。某种情形下,暂时的可以为永久的,可以直接当做一个表来读取数据。

你可能感兴趣的:(Programmer)