最近做一個項目需要用到一個萬年歷,其中對陽歷可做事件,生日提醒,生日提醒能支持農歷日期.可C#自帶的控件實現起來非常之痛苦,有太多局限性,因此便百度google看能否找到現成的控件,結果很失望.於是只能自己想辦法了,這里我用了一個最笨的辦法直接繪制一個萬年歷,並實現相關事件.這里主要介紹使用GDI+實現萬年歷的過程,個人拙見還望高人指點.
萬年歷截圖:

圖中除菜單欄和主panel外,全部是代碼生成的.
首先往panelMonthInfo控件上添加控件,如年份,月份 及 跳轉到今天等,代碼如下:
View Code
2 // 繪制控件
3 private void DrawControls()
4 {
5 var btnToday = new Button();
6 btnToday.Location = new System.Drawing.Point( 300, 15);
7 btnToday.Name = " btnToday ";
8 btnToday.Size = new System.Drawing.Size( 80, 21);
9 btnToday.TabIndex = 0;
10 btnToday.Text = " 跳轉到今天 ";
11 btnToday.UseVisualStyleBackColor = true;
12 btnToday.Click += new System.EventHandler( this.btnToday_Click);
13
14 var lblYear = new Label();
15 lblYear.Name = " lblYear ";
16 lblYear.Text = " 年份 ";
17 lblYear.Location = new Point( 91, 19);
18 lblYear.Size = new Size( 29, 20);
19 lblYear.BackColor = Color.Transparent;
20
21 var lblMonth = new Label();
22 lblMonth.Name = " lblMonth ";
23 lblMonth.Text = " 月份 ";
24 lblMonth.Location = new Point( 190, 19);
25 lblMonth.Size = new Size( 29, 20);
26 lblMonth.BackColor = Color.Transparent;
27
28 var cmbSelectYear = new ComboBox();
29 cmbSelectYear.DropDownStyle = ComboBoxStyle.DropDownList;
30 cmbSelectYear.FormattingEnabled = true;
31 cmbSelectYear.Location = new Point( 120, 15);
32 cmbSelectYear.Name = " cmbSelectYear ";
33 cmbSelectYear.AutoSize = false;
34 cmbSelectYear.Size = new Size( 50, 20);
35 cmbSelectYear.TabIndex = 0;
36 cmbSelectYear.SelectionChangeCommitted += new EventHandler(cmbSelectYear_SelectionChangeCommitted);
37
38 var cmbSelectMonth = new ComboBox();
39 cmbSelectMonth.DropDownStyle = ComboBoxStyle.DropDownList;
40 cmbSelectMonth.FormattingEnabled = true;
41 cmbSelectMonth.Location = new Point( 220, 15);
42 cmbSelectMonth.Name = " cmbSelectYear ";
43 cmbSelectMonth.AutoSize = false;
44 cmbSelectMonth.Size = new Size( 50, 20);
45 cmbSelectMonth.TabIndex = 0;
46 cmbSelectMonth.SelectionChangeCommitted += new EventHandler(cmbSelectMonth_SelectionChangeCommitted);
47
48 var panelDateInfo = new Panel();
49 panelDateInfo.BackColor = Color.White;
50 panelDateInfo.Location = new Point( 575, 45);
51 panelDateInfo.Size = new Size( 165, 390);
52 panelDateInfo.Paint += new PaintEventHandler(panelDateInfo_Paint);
53
54 var lblShowTime = new Label();
55 lblShowTime.Location = new Point( 600, 470);
56 lblShowTime.BackColor = Color.Transparent;
57 lblShowTime.AutoSize = true;
58 lblShowTime.Name = " lblShowTime ";
59
60 var label1 = new Label();
61 label1.AutoSize = false;
62 label1.Font = new System.Drawing.Font( " Microsoft Sans Serif ", 8.25F, System.Drawing.FontStyle.Italic, System.Drawing.GraphicsUnit.Point, (( byte)( 134)));
63 label1.Location = new System.Drawing.Point( 252, 449);
64 label1.Name = " label1 ";
65 label1.Size = new System.Drawing.Size( 176, 13);
66 label1.TabIndex = 0;
67 label1.Text = " 飛鴻踏雪泥 www.zhuoyuegzs.com ";
68 label1.BackColor = Color.Transparent;
69 label1.TextAlign = ContentAlignment.MiddleCenter;
70 label1.Width = 400;
71 label1.Click += new EventHandler(label1_Click);
72
73 for ( int i = 1949; i <= 2049; i++)
74 {
75 cmbSelectYear.Items.Add(i);
76 if (i == dtNow.Year)
77 {
78 cmbSelectYear.SelectedItem = i;
79 selectYear = i;
80 }
81 }
82 for ( int i = 1; i <= 12; i++)
83 {
84 cmbSelectMonth.Items.Add(i);
85 if (i == dtNow.Month)
86 {
87 cmbSelectMonth.SelectedItem = i;
88 selectMonth = i;
89 }
90 }
91 panelMonthInfo.Controls.Add(btnToday);
92 panelMonthInfo.Controls.Add(lblMonth);
93 panelMonthInfo.Controls.Add(lblYear);
94 panelMonthInfo.Controls.Add(cmbSelectYear);
95 panelMonthInfo.Controls.Add(cmbSelectMonth);
96 panelMonthInfo.Controls.Add(panelDateInfo);
97 panelMonthInfo.Controls.Add(lblShowTime);
98 panelMonthInfo.Controls.Add(label1);
99 }
100
101
102 #endregion
這里共有兩個panel控件,一個是繪制月歷,一個是顯示選中或者當前日期的詳細信息,如圖右側.
下一步繪制月歷方格,代碼如下:
View Code
2 private void panelMonthInfo_Paint( object sender, PaintEventArgs e)
3 {
4 Graphics g = e.Graphics;
5 var pen = new Pen(Color.FromArgb( 255, 235, 211), 1);
6 var tb = new TextureBrush( global::ChineseCalender.Properties.Resources.wnlbg, WrapMode.TileFlipXY);
7 g.FillRectangle(tb, 0, 0, 750, 475); // 繪制黃色背景,此處使用了圖片作為背景
8 g.FillRectangle( new SolidBrush(Color.White), 5, 40, 740, 400); // 繪制月歷顯示區域,背景為白色
9
10 SolidBrush sb = new SolidBrush(Color.FromArgb( 50, 255, 247, 241));
11 g.FillRectangle(sb, 10, 45, 560, 30); // 此處繪制月歷表頭的背景填充
12
13 // 畫橫線
14 g.DrawLine(pen, 10, 45, 570, 45);
15 g.DrawLine(pen, 10, 75, 570, 75);
16 for ( int i = 1; i < 7; i++)
17 {
18 g.DrawLine(pen, 10, 75+ 60*i, 570, 75+ 60*i);
19 }
20
21
22 // 畫豎線
23 for ( int i = 0; i < 8; i++)
24 {
25 g.DrawLine(pen, 10+ 80*i, 45, 10+ 80*i, 435);
26 }
27
28
29 var solidBrushWeekday = new SolidBrush(Color.Gray);
30 var solidBrushWeekend = new SolidBrush(Color.Chocolate);
31 g.DrawString( " 日 ", new Font( " 微軟雅黑 ", 12), solidBrushWeekend, 35, 50);
32 g.DrawString( " 一 ", new Font( " 微軟雅黑 ", 12), solidBrushWeekday, 115, 50);
33 g.DrawString( " 二 ", new Font( " 微軟雅黑 ", 12), solidBrushWeekday, 195, 50);
34 g.DrawString( " 三 ", new Font( " 微軟雅黑 ", 12), solidBrushWeekday, 275, 50);
35 g.DrawString( " 四 ", new Font( " 微軟雅黑 ", 12), solidBrushWeekday, 355, 50);
36 g.DrawString( " 五 ", new Font( " 微軟雅黑 ", 12), solidBrushWeekday, 435, 50);
37 g.DrawString( " 六 ", new Font( " 微軟雅黑 ", 12), solidBrushWeekend, 515, 50);
38
39 if (flag) // 判斷是否需要全部刷新
40 {
41 GetWeekInfo( ref weekOfFirstDay, ref daysOfMonth, dtNow.Year, dtNow.Month); // 此方法是根據給出的年份和月份計算當月的第一天的星期,和當月的總天數
42 DrawDateNum(weekOfFirstDay, daysOfMonth, dtNow.Year, dtNow.Month);
43 // DrawDateInfo(dtNow);
44 }
45 }
下面介紹一下在方格內繪制日期信息,首先我們要確定當月一號的位置,這里是通過星期來控制的,然后通過兩個for循環進行日期繪制,看代碼:
2 private void DrawDateNum( int firstDayofWeek, int endMonthDay, int year, int month)
3 {
4 DateTime dtNow = DateTime.Parse(DateTime.Now.ToShortDateString());
5
6 var font = new Font( "", 14);
7 var solidBrushWeekdays = new SolidBrush(Color.Gray);
8 var solidBrushWeekend = new SolidBrush(Color.Chocolate);
9 var solidBrushHoliday = new SolidBrush(Color.BurlyWood);
10 Graphics g = panelMonthInfo.CreateGraphics();
11 int num = 1;
12
13 for ( int j = 0; j < 6; j++)
14 {
15 for ( int i = 0; i < 7; i++)
16 {
17 if (j == 0 && i < firstDayofWeek) // 定義當月第一天的星期的位置
18 {
19 continue;
20 }
21 else
22 {
23 if (num > endMonthDay) // 定義當月最后一天的位置
24 {
25 break;
26 }
27 string cMonth = null;
28 string cDay = null;
29 string cHoliday = null;
30 string ccHoliday = null;
31
32 if (i > 0 && i < 6)
33 {
34 DateTime dt = DateTime.Parse(year + " - " + month + " - " + num);
35 TimeSpan ts = dt - dtNow;
36 dateArray[i, j] = dt.ToShortDateString();
37
38 if (ts.Days == 0)
39 {
40 g.DrawEllipse( new Pen(Color.Chocolate, 3), ( 15 + 80 * i), ( 85 + 60 * j), 30, 15);
41 }
42
43 cMonth = ChineseDate.GetMonth(dt);
44 cDay = ChineseDate.GetDay(dt);
45 cHoliday = ChineseDate.GetHoliday(dt);
46 ccHoliday = ChineseDate.GetChinaHoliday(dt);
47
48 if (cHoliday != null)
49 {
50 // 繪陽歷節日
51 g.DrawString(cHoliday.Length > 3 ? cHoliday.Substring( 0, 3) : cHoliday, new Font( "", 9),
52 solidBrushHoliday, ( 40 + 80 * i), ( 90 + 60 * j));
53 }
54 // 繪農歷
55 if (ccHoliday != "")
56 {
57 g.DrawString(ccHoliday, new Font( "", 10), solidBrushWeekdays, ( 25 + 80 * i),
58 ( 115 + 60 * j));
59 }
60 else
61 {
62 g.DrawString(cDay == " 初一 " ? cMonth : cDay, new Font( "", 10), solidBrushWeekdays, ( 25 + 80 * i),
63 ( 115 + 60 * j));
64 }
65
66
67 // 繪日期
68 g.DrawString(num.ToString(CultureInfo.InvariantCulture), font, solidBrushWeekdays,
69 ( 15 + 80 * i), ( 80 + 60 * j));
70
71 }
72 else
73 {
74 var dt = DateTime.Parse(year + " - " + month + " - " + num);
75 var ts = dt - dtNow;
76 dateArray[i, j] = dt.ToShortDateString();
77 if (ts.Days == 0)
78 {
79 g.DrawEllipse( new Pen(Color.Chocolate, 3), ( 15 + 80 * i), ( 85 + 60 * j), 30, 15);
80 }
81
82 cMonth = ChineseDate.GetMonth(dt);
83 cDay = ChineseDate.GetDay(dt);
84 cHoliday = ChineseDate.GetHoliday(dt);
85 ccHoliday = ChineseDate.GetChinaHoliday(dt);
86
87 if (cHoliday != null)
88 {
89 // 繪陽歷節日
90 g.DrawString(cHoliday.Length > 3 ? cHoliday.Substring( 0, 3) : cHoliday, new Font( "", 9),
91 solidBrushHoliday, ( 40 + 80 * i), ( 90 + 60 * j));
92 }
93 // 繪農歷
94 if (ccHoliday!= "")
95 {
96 g.DrawString(ccHoliday, new Font( "", 10), solidBrushWeekend, ( 25 + 80 * i),
97 ( 115 + 60 * j));
98 }
99 else
100 {
101 g.DrawString(cDay == " 初一 " ? cMonth : cDay, new Font( "", 10), solidBrushWeekend, ( 25 + 80 * i),
102 ( 115 + 60 * j));
103 }
104
105 // 繪日期
106 g.DrawString(num.ToString(CultureInfo.InvariantCulture), font, solidBrushWeekend,
107 ( 15 + 80 * i), ( 80 + 60 * j));
108 }
109
110 num++;
111
112 }
113
114 }
115 }
116 }
117
118 // 獲取某月首日星期及某月天數
119 private void GetWeekInfo( ref int weekOfFirstDay, ref int daysOfMonth, int year = 1900, int month = 2, int day = 1)
120 {
121 DateTime dt =
122 DateTime.Parse(year.ToString(CultureInfo.InvariantCulture) + " - " +
123 month.ToString(CultureInfo.InvariantCulture) + " - " +
124 day.ToString(CultureInfo.InvariantCulture));
125 weekOfFirstDay = ( int)dt.DayOfWeek;
126 daysOfMonth = ( int)DateTime.DaysInMonth(year, month);
127 }
這里調用了兩個陽歷轉農歷的方法,這個不是我寫的,網上有現成的代碼,后面我會附在附件里
到這里,整個月歷的樣子就已經顯示出來了,然后我想要選擇年份和日期來控制月歷的顯示,比如在下拉列表中選擇2013,01,然后顯示當月的月歷信息,如是,
這里使用了Combobox控件的SelectionChangeCommitted事件,因為我在初始化控件的時候對其賦值了,如果使用了 selectedchange事件將會在窗體初始化是重復繪制.
2 {
3 flag = false;
4 var cmbSelectMonth = sender as ComboBox;
5 selectMonth = ( int)cmbSelectMonth.SelectedItem;
6 panelMonthInfo.Refresh();
7 GetWeekInfo( ref weekOfFirstDay, ref daysOfMonth, selectYear, selectMonth);
8 DrawDateNum(weekOfFirstDay, daysOfMonth, selectYear, selectMonth);
9 }
10
11 private void cmbSelectYear_SelectionChangeCommitted( object sender, EventArgs e)
12 {
13 flag = false;
14 var cmbSelectYear = sender as ComboBox;
15 selectYear = ( int)cmbSelectYear.SelectedItem;
16 panelMonthInfo.Refresh();
17 GetWeekInfo( ref weekOfFirstDay, ref daysOfMonth, selectYear, selectMonth);
18 DrawDateNum(weekOfFirstDay, daysOfMonth, selectYear, selectMonth);
19 }
Ok,到這里基本上已經可以實現了萬年歷的顯示效果了,但還有一個地方未實現,我希望在單擊日期的時候能顯示當天的詳細農歷情況及當日的提醒等等,這里就需要實現日期選擇的單擊效果,我這里用panel的mouseClick事件+鼠標坐標來實現單擊效果,
2 private void panelMonthInfo_MouseClick( object sender, MouseEventArgs e)
3 {
4
5 // MessageBox.Show(e.X + "\n" + e.Y);
6 if (e.Button == MouseButtons.Left)
7 {
8
9
10 if (e.X < 10 || e.X > 575)
11 {
12 return;
13 }
14 if (e.Y < 75 || e.Y > 435)
15 {
16 return;
17 }
18 int x = (e.X - 10) / 80;
19 int y = (e.Y - 75) / 60;
20 if (dateArray[x, y] == null)
21 {
22 return;
23 }
24 DateTime dtSelect = DateTime.Parse(dateArray[x, y]);
25 dtInfo = dtSelect;
26 // DrawDateInfo(dtSelect);
27 }
28 panelDateInfo.Refresh();
29 }
下面是繪制選中日期的詳細信息,如下:
{
ChineseCalendar cc = new ChineseCalendar(dtInfo);
string dateString = cc.DateString; // 陽歷
string chineseDateString = cc.ChineseDateString; // 農歷
string dateHoliday = cc.DateHoliday; // 陽歷節日
string chineseTwentyFourDay = cc.ChineseTwentyFourDay; // 農歷節日
string constellation = cc.Constellation; // 星座
string weekDayString = cc.WeekDayStr; // 星期
string ganZhiDateString = cc.GanZhiDateString;
string animalString = cc.AnimalString;
string chineseConstellation = cc.ChineseConstellation;
if (panelDateInfo != null)
{
Graphics g = panelDateInfo.CreateGraphics();
if (dateString != null)
g.DrawString(dateString + " " + weekDayString, new Font( "", 9), new SolidBrush(Color.Gray), 7, 10);
g.DrawString(dtInfo.Day.ToString(CultureInfo.InvariantCulture), new Font( "", 45, FontStyle.Bold),
new SolidBrush(Color.Gainsboro), 50, 30);
var family = new FontFamily( " 宋體 ");
g.DrawString(chineseDateString.Substring( 7, chineseDateString.Length - 7), new Font(family, 10),
new SolidBrush(Color.Goldenrod), 50, 100);
// g.DrawString(constellation, new Font(family, 9), new SolidBrush(Color.Goldenrod), 60, 120);
g.DrawString(ganZhiDateString.Substring( 0, 3) + " 【 " + animalString + " 年】 ", new Font(family, 10),
new SolidBrush(Color.Goldenrod), 30, 120);
g.DrawString(ganZhiDateString.Substring( 3, ganZhiDateString.Length - 3), new Font(family, 10),
new SolidBrush(Color.Goldenrod), 40, 140);
g.DrawString(constellation + " " + chineseConstellation, new Font(family, 10),
new SolidBrush(Color.Goldenrod), 30, 160);
// g.DrawString(chineseConstellation, new Font(family, 10), new SolidBrush(Color.Goldenrod), 50, 180);
g.DrawString(chineseTwentyFourDay, new Font(family, 10), new SolidBrush(Color.Goldenrod), 40, 200);
}
}
這里有兩個地方需要注意,一是獲取選中區域的日期信息,我是用一個二維數組在繪制月歷的時候記錄區域的日期信息,
private string[,] dateArray = new string[7, 6]; //記錄日期信息
dateArray[i, j] = dt.ToShortDateString();
二是界面重繪問題,之前是使用初始化,但效果一直不佳,后使用更改參數,然后刷新重繪整個panel控件,
panelDateInfo.Refresh();
到這里這個萬年歷的繪制過程已完成,由於時間比較短,這里描述比較粗糙,見諒.
其中使用到的兩個陽歷轉農歷的類:/Files/long-gengyun/ChineseCalender方法.rar
萬年歷效果可運行程序(需.net 2.0):/Files/long-gengyun/ChineseCalender.rar
不好意思,一直沒有上傳源碼,因為這個程序是用在其他項目上的,遷移后就沒有再去修改它了,目前還有很多bug,現在 我把源碼發出來,有興趣的朋友可以看看,不足之處還請多多指教. 非常感謝 @項工 的建議.
