C# WinForm版万年历 GDI+绘制
2012-08-06 16:35 Eric.Hu 阅读(7258) 评论(16) 编辑 收藏 举报最近做一个项目需要用到一个万年历,其中对阳历可做事件,生日提醒,生日提醒能支持农历日期.可C#自带的控件实现起来非常之痛苦,有太多局限性,因此便百度google看能否找到现成的控件,结果很失望.于是只能自己想办法了,这里我用了一个最笨的办法直接绘制一个万年历,并实现相关事件.这里主要介绍使用GDI+实现万年历的过程,个人拙见还望高人指点.
万年历截图:
图中除菜单栏和主panel外,全部是代码生成的.
首先往panelMonthInfo控件上添加控件,如年份,月份 及 跳转到今天等,代码如下:
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控件,一个是绘制月历,一个是显示选中或者当前日期的详细信息,如图右侧.
下一步绘制月历方格,代码如下:
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,现在 我把源码发出来,有兴趣的朋友可以看看,不足之处还请多多指教. 非常感谢 @项工 的建议.