大帥手把手教你做日曆控制元件——WinForm窗體控制元件庫和日曆演算法
阿新 • • 發佈:2018-12-27
WinForm窗體控制元件庫和日曆演算法
先來看看效果圖
圖中展現的是一個日曆自定義控制元件,其顯示格里高利曆、農曆及節氣。
控制元件製作
- 建立一個窗體控制元件庫專案,新建兩個控制元件:PanelDay和PanelMonth;
- PanelDay的組成為三個Label控制元件,分別填寫格里曆、農曆和節氣。
控制元件程式碼如下:
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;
程式碼如下:
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,預設為當前時間,起選擇的作用。
演算法
- 格里曆演算法
#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
節氣本來可以分為二十四等份,如果地球對著太陽作勻速圓周運動的話。但是太陽只是在一個地球橢圓軌道的焦點上,這就造成了節氣時長不等。如果只是這樣的話還可以時長固定,但是地球的自轉軸和地球橢圓軌道所在面並不垂直,地球每年西退,這種現象有專業名詞表示,歲差。此外由於地球並不是非常接近於球形的,在自轉的過程中會收到太陽和月亮的拉拉扯扯,這種現象也有專業名詞表示,章動。所幸人們只可以活百歲,生活中不需要了解大週期中的小週期。某種情形下,暫時的可以為永久的,可以直接當做一個表來讀取資料。