VB.NET 编写仿微软的日历控件

VB.NET 编写仿微软的日历控件_第1张图片

控件名称

控件类型

设置属性

DatePanl

TableLayoutPanle

 

MonthAddbtn, MonthSubbtn

YearAddBtn, YearSubBtn

Button

Flatstyle=Flat

MonthLbl, Yearlbl,label3

Label

Dock=Fill

Picture

PictureBox

 

思路是先利用用户控件,做出日历的主体,然后利用ToolStripControlHost作为容器将日历的主体包含在里面利用ToolStripDropDown控件做弹出效果,最后重写Combobox控件形成完整的日历控件。

对于日历的主体,通过将背景分成不同的小格,然后将数字绘制在背景上。在编写的时候需要注意以下几个问题。

(一)在绘制的时注意定位,我以每个月的1号做为定位点通过StartDay = CDate(m_Date.Year & "-" & m_Date.Month & "-1").DayOfWeek 1这个计算将星期和日期定位以便绘制。

(二)通过边缘检测也就是检测没个小格的坐标然后再反算为日期以产生通过点击日历产生不同日期的效果。

(三)通过申明事件Public Event DateChanged(ByVal Sender As Object, ByVal e As EventArgs)然后在不同的时候触发以传出数据。

(四)特别是Picturebox控件没有实质的用处,但是没有他的时候当整个控件包装在ToolStripDropDown控件的时候会不能完全显示,所以只有将它放在右下角。

下面是主体代码:

 
  1. Imports System.Drawing.Drawing2D
  2. Imports System.Windows.Forms.Design
  3. Imports System.Windows.Forms.ComponentModel
  4. Friend Class Calendar
  5.     Private m_Date As Date
  6.     Private m_Week() As String = New String() {"星期日""星期一""星期二""星期三""星期四""星期五""星期六"}
  7.     Private m_ClipWidth As Single     '格子的宽度
  8.     Private m_ClipTop As Integer      '绘制日历的顶点坐标
  9.     Shadows Font As New Font("宋体", 9, FontStyle.Regular, GraphicsUnit.Point)   '设置绘制时候的字体
  10.     Private Format As New StringFormat                               '绘制字体时候的对齐方式
  11.     Private StartDay As Integer                                      '绘制时候的开始点
  12.     Public Event DateChanged(ByVal Sender As ObjectByVal e As EventArgs)   '日期改变的事件
  13.     Private m_IsSelected As Boolean                                          '需要在点击日期的时候才关闭主体而点击年月增减按钮不关闭所以设置该参数
  14.     Private i, j As Integer, Rect As New RectangleF
  15.     Private MinDate As Date = CDate("1900-1-1")                              '日历可选最小日期
  16.     Private MaxDate As Date = CDate("2100-12-31")                            '日历可选最大日期
  17.     Public ReadOnly Property IsSelected() As Boolean
  18.         Get
  19.             Return m_IsSelected
  20.         End Get
  21.     End Property
  22.     Public Sub New()
  23.         InitializeComponent()
  24.         m_ClipWidth = (Me.Width - 4) / 7
  25.         m_ClipTop = 2 + DatePanl.Height
  26.         m_Date = Now.Date
  27.         Format.Alignment = StringAlignment.Center
  28.     End Sub
  29.     Public Property DateValue() As Date                           '返回日期以供其他程序使用
  30.         Get
  31.             Return m_Date
  32.         End Get
  33.         Set(ByVal value As Date)
  34.             If value >= MinDate AndAlso value <= MaxDate Then
  35.                 m_Date = value
  36.                 Me.Invalidate()
  37.             End If
  38.         End Set
  39.     End Property
  40.    
  41.     Private Sub Calendar_Load(ByVal sender As ObjectByVal e As System.EventArgs) Handles Me.Load
  42.         Label3.Text = "今天:" & Now.ToShortDateString
  43.         Label3.Left = (Me.Width - Label3.Width) / 2
  44.     End Sub
  45.     Private Sub AddBtn_Click(ByVal sender As System.ObjectByVal e As System.Windows.Forms.MouseEventArgs) Handles MonthAddbtn.MouseClick, MonthSubbtn.MouseClick, YearAddBtn.MouseClick, YearSubBtn.MouseClick             '几个按钮会产生相同的效果所以放在一个代码块
  46.         If e.Button = Windows.Forms.MouseButtons.Left Then
  47.             DatePanl.Focus()
  48.             Dim T As Button = CType(sender, Button)
  49.             Select Case T.Name
  50.                 Case "MonthAddbtn"
  51.                     m_Date = Me.DateValue.AddMonths(1)
  52.                 Case "MonthSubbtn"
  53.                     m_Date = Me.DateValue.AddMonths(-1)
  54.                 Case "YearAddBtn"
  55.                     m_Date = Me.DateValue.AddYears(1)
  56.                 Case "YearSubBtn"
  57.                     m_Date = Me.DateValue.AddYears(-1)
  58.             End Select
  59.             If m_Date >= MinDate AndAlso m_Date <= MaxDate Then              '保证设置的日期是在可选范围之内
  60.                 m_IsSelected = False
  61.                 RaiseEvent DateChanged(MeNothing)
  62.                 Me.Invalidate()
  63.             ElseIf m_Date < MinDate Then
  64.                 m_Date = CDate("1900-1-" & m_Date.Day)
  65.             Else
  66.                 m_Date = CDate("2100-12-" & m_Date.Day)
  67.             End If
  68.         End If
  69.     End Sub
  70.     Private Sub DrawWeek(ByVal Graphics As Graphics)    '绘制星期
  71.         For i = 0 To 6
  72.             Rect = New RectangleF(2 + i * m_ClipWidth, m_ClipTop + 2, m_ClipWidth, Me.Font.Height + 2)
  73.             Graphics.DrawString(m_Week(i), Font, Brushes.RoyalBlue, Rect)
  74.         Next
  75.         Graphics.DrawLine(Pens.Gray, 2, m_ClipTop + Font.Height + 2, Me.Width - 6, m_ClipTop + Font.Height + 2) '绘制星期下面的横线
  76.         Graphics.DrawRectangle(Pens.RoyalBlue, 40, Me.Height - Font.Height, m_ClipWidth - 1, Font.Height - 1)
  77.         ' Graphics.Dispose()
  78.     End Sub
  79.     Private Sub DrawDate(ByVal Graphics As Graphics)    '绘制日历
  80.         Dim MaxDays As Integer = Date.DaysInMonth(m_Date.Year, m_Date.Month)                   '由于整个主体被分成*7个格子,因此会有上个月和下个月的日期在里面,因此需要得到上个月的天数
  81.         Dim Mindays = Date.DaysInMonth(m_Date.AddMonths(-1).Year, m_Date.AddMonths(-1).Month)
  82.         StartDay = CDate(m_Date.Year & "-" & m_Date.Month & "-1").DayOfWeek - 1         '由每个月的一号定位为星期几
  83.         Dim DateString As Integer
  84.         For i = 0 To 6
  85.             For j = 0 To 5
  86.                 With Rect
  87.                     .X = 2 + i * m_ClipWidth
  88.                     .Y = m_ClipTop + j * Font.Height + Font.Height + 8
  89.                     .Width = m_ClipWidth
  90.                     .Height = Font.Height
  91.                 End With
  92.                 DateString = (i + j * 7 - StartDay)
  93.                 If DateString <= 0 Then
  94.                     Graphics.DrawString(DateString + Mindays, Font, Brushes.Gray, Rect, Format)
  95.                 ElseIf DateString > 0 AndAlso DateString <= MaxDays Then      '绘制上个月的本月的以及下个月的日期
  96.                     If DateString = m_Date.Day Then
  97.                         Graphics.FillRectangle(Brushes.Silver, Rect.X - 1, Rect.Y - 1, Rect.Width, Rect.Height)
  98.                     End If
  99.                     If DateString = Now.Day AndAlso m_Date.Month = Now.Month AndAlso m_Date.Year = Now.Year Then
  100.                         Graphics.DrawRectangle(Pens.RoyalBlue, Rect.X - 1, Rect.Y - 1, Rect.Width - 1, Rect.Height - 1)
  101.                     End If
  102.                     Graphics.DrawString(DateString, Font, Brushes.Black, Rect, Format)
  103.                 ElseIf DateString > MaxDays AndAlso DateString <= 42 Then
  104.                     Graphics.DrawString(DateString - MaxDays, Font, Brushes.Gray, Rect, Format)
  105.                 End If
  106.             Next
  107.         Next
  108.     End Sub
  109.     Private Sub Calendar_MouseClick(ByVal sender As ObjectByVal e As System.Windows.Forms.MouseEventArgs) Handles Me.MouseDown  '通过边界检查获取点击时候所处的位置以计算为日期
  110.         Dim x As Integer = e.X
  111.         Dim y As Integer = e.Y
  112.         Dim DateString As Integer
  113.         Dim Pen As New Pen(Color.Gray)
  114.         Pen.DashStyle = DashStyle.Dot
  115.         Dim MaxDays As Integer = Date.DaysInMonth(m_Date.Year, m_Date.Month)
  116.         Dim Mindays = Date.DaysInMonth(m_Date.AddMonths(-1).Year, m_Date.AddMonths(-1).Month)
  117.         m_IsSelected = False
  118.         If e.Button = Windows.Forms.MouseButtons.Left Then
  119.             For i = 0 To 6
  120.                 For j = 0 To 5
  121.                     DateString = (i + j * 7 - StartDay)
  122.                     With Rect
  123.                         .X = 2 + i * m_ClipWidth
  124.                         .Y = m_ClipTop + (j + 1) * Font.Height + 8
  125.                         .Width = m_ClipWidth
  126.                         .Height = Font.Height
  127.                     End With
  128.                     If x >= Rect.X - 1 AndAlso x <= Rect.Right AndAlso y > Rect.Y - 1 AndAlso y <= Rect.Bottom Then   '计算格子的范围使鼠标在可选范围内
  129.                         If DateString <= 0 Then        '根据选取不同的月份计算出当时点击时的正确日期,所计算的日期要在整个可选的范围内
  130.                             If CDate(m_Date.AddMonths(-1).Year & "-" & m_Date.AddMonths(-1).Month & "-" & DateString + Mindays) >= MinDate Then
  131.                                 m_Date = CDate(m_Date.AddMonths(-1).Year & "-" & m_Date.AddMonths(-1).Month & "-" & DateString + Mindays)
  132.                                 m_IsSelected = True
  133.                             End If
  134.                         ElseIf DateString > 0 AndAlso DateString <= MaxDays Then
  135.                             m_Date = CDate(m_Date.Year & "-" & m_Date.Month & "-" & DateString)
  136.                             m_IsSelected = True
  137.                         ElseIf DateString > MaxDays AndAlso DateString <= 42 Then
  138.                             If CDate(m_Date.AddMonths(1).Year & "-" & m_Date.AddMonths(1).Month & "-" & DateString - MaxDays) <= MaxDate Then
  139.                                 m_Date = CDate(m_Date.AddMonths(1).Year & "-" & m_Date.AddMonths(1).Month & "-" & DateString - MaxDays)
  140.                                 m_IsSelected = True
  141.                             End If
  142.                         End If
  143.                         Me.CreateGraphics.DrawRectangle(Pen, Rect.X - 1, Rect.Y - 1, Rect.Width - 1, Rect.Height - 1)
  144.                         RaiseEvent DateChanged(MeNothing)    '触发日期改变的事件
  145.                         Me.Invalidate()
  146.                     End If
  147.                 Next
  148.             Next
  149.             Pen.Dispose()
  150.         End If
  151.     End Sub
  152.     Private Sub Calendar_Paint(ByVal sender As ObjectByVal e As System.Windows.Forms.PaintEventArgs) Handles Me.Paint
  153.         DrawWeek(e.Graphics)
  154.         DrawDate(e.Graphics)
  155.         MonthLbl.Text = m_Date.Month & "月"
  156.         Yearlbl.Text = m_Date.Year & "年"
  157.     End Sub
  158.     Private Sub Label3_Click(ByVal sender As ObjectByVal e As System.EventArgs) Handles Label3.Click  '转到今天
  159.         m_Date = Now
  160.         Me.m_IsSelected = True
  161.         RaiseEvent DateChanged(MeNothing)
  162.         Me.Invalidate()
  163.     End Sub
  164.     Private Sub Label3_MouseEnter(ByVal sender As ObjectByVal e As System.EventArgs) Handles Label3.MouseEnter
  165.         Label3.Cursor = Cursors.Hand
  166.     End Sub
  167.     Private Sub Label3_MouseLeave(ByVal sender As ObjectByVal e As System.EventArgs) Handles Label3.MouseLeave
  168.         Label3.Cursor = Cursors.Default
  169.     End Sub
  170.     Private Sub Calendar_Resize(ByVal sender As ObjectByVal e As System.EventArgs) Handles Me.Resize '固定整个控件的大小
  171.         Me.Size = New Size(298, 150)
  172.     End Sub
  173. End Class

以下是主体在运行时的效果

VB.NET 编写仿微软的日历控件_第2张图片

 

接下来是将Combobox控件重写然后将主体包装的代码

  1. Imports Calendar
  2. Public Class DatePicker
  3.     Inherits ComboBox
  4.     Private WithEvents Calendar As Calendar
  5.     Private DateTool As ToolStripDropDown
  6.     Private Const WM_LBUTTONDOWN = 
  7.     Private Const WM_LBUTTONDBLCLK = 
  8.     Public Sub New()
  9.         InitTool()
  10.     End Sub
  11.     Public Property Value() As Date
  12.         Get
  13.             Return Calendar.DateValue
  14.         End Get
  15.         Set(ByVal value As Date)
  16.             If Date.TryParse(value, Calendar.DateValue) = True Then
  17.                 Me.Text = value.ToLongDateString
  18.             End If
  19.         End Set
  20.     End Property
  21.     Protected Overrides Sub WndProc(ByRef m As System.Windows.Forms.Message)   '截获鼠标左键点击的消息以显示包装的日历主体
  22.         If m.Msg = WM_LBUTTONDOWN OrElse m.Msg = WM_LBUTTONDBLCLK Then
  23.             ShowDrop()
  24.             Me.Focus()
  25.             Return
  26.         End If
  27.         MyBase.WndProc(m)
  28.     End Sub
  29.     Private Sub InitTool()
  30.         Calendar = New Calendar
  31.         Dim ToolHost As New ToolStripControlHost(Calendar)
  32.         DateTool = New ToolStripDropDown
  33.         DateTool.Items.Add(ToolHost)
  34.     End Sub
  35.     Private Sub ShowDrop()
  36.         If Date.TryParse(Me.Text, Calendar.DateValue) = True Then
  37.             DateTool.Show(Me, 0, Me.Height)
  38.         End If
  39.     End Sub
  40.     Private Sub Calendar_DateChanged(ByVal Sender As ObjectByVal e As System.EventArgs) Handles Calendar.DateChanged
  41.         If Calendar.IsSelected = True Then
  42.             Threading.Thread.Sleep(100)
  43.             DateTool.Hide()
  44.         End If
  45.         Me.Text = Calendar.DateValue.ToLongDateString
  46.     End Sub
  47. End Class

最后进行测试

测试代码如下:

  1. Private Sub Form1_Load(ByVal sender As ObjectByVal e As System.EventArgs) Handles Me.Load
  2.         DatePicker1.Text = Now.ToLongDateString
  3. End Sub

运行效果如下图

VB.NET 编写仿微软的日历控件_第3张图片

VB.NET 编写仿微软的日历控件_第4张图片

 

你可能感兴趣的:(VB.NET 编写仿微软的日历控件)