開發使用者控制項 - 下拉式日期選取器

  • 4685
  • 0
  • C#
  • 2014-08-22

開發使用者控制項 - 下拉式日期選取器

前言

在系統中就有一個 DateTimePicker 可以很方便的使用,為什麼還要開發這樣的控制項呢?其實就只是為了練習,拿來當範例而以。

 

控制項的畫面預想為 XXXX 年 YY 月 ZZ 日,XXXX、YY、ZZ 使用 ComboBox 來實現,年月日則使用 Label 控制項。但是既然是叫做開發,那麼只使用 ComboBox 的連動似乎沒有什麼技巧可言。所以全部都只有使用 Label 控制項,XXXX、YY、ZZ 為什麼不使用 TextBox 呢?當然也是可以,只不過使用 TextBox 後必需要在使用者輸入完成後多加年月日的資料判斷,來確定輸入的年月日資料是允許的字元,以及是一個合法的日期,總不能出現一個 20 月 99 日這樣的日期吧。因此最後的考慮就是年月日全部都使用選取的,不提供手動輸入方式。

 

使用 Label 控制項要怎麼實現下拉式清單來選取資料呢?答案就是搭配 ContextMenuStrip (註:上一篇文章中已介紹)。也就是說透過滑鼠雙擊後,PopUp 一個下拉式選單來選取資料。(請不要問我為什麼不用滑鼠單擊,你總不希望滑鼠一不小心點到之後,就彈出選單吧!) 綜合以上所說的,全部就只有 7 個控制項 ( Label * 6,ContextMenuStrip * 1)。

 

專案製作

執行 VisualStudio 2010/2012 (Whatever 依你目前所使用的版本),新增一個專案(MyControl),選取 Windows 下的 Windows Form 控制項程式庫。

專案建立完成後,新增一個使用者控制項(DatePicker)

為了要達到可以在自由改變大小後,依照比例來產生版面,所以多加了一個控制項 TableLayoutPanel(__MainTable)。在 __MainTable 中產生 1 個 Row 高度為 100%,6 個 Column 寬度各為 26.5%、11.5%、19.5%、11.5%、19.5%、11.5%,然後在所有的欄位中各加入一個 Label 控制項,最後再加入 ContextMenuStrip(__PopMenu) 控制項。

_PopMenu 中新增 1 個 ToolStripComboBox(__PopMenuItem) 來提供彈現視窗中的下拉選單。

 

接下來設定各個控制項的屬性。

首先是顯示年(__YearLabel)、月(__MonthLabel)、日(__DayLabel)字樣的三個 Label:

  • Font-Size:9pt
  • TextAlign:MiddleCenter
  • UseMnemonic:False
  • Dock:Fill
  • Margin:2, 0, 2, 0

再來就是顯示年月日資料的三個 Label (__SelectYearValue__SelectMonthValue__SelectDayValue):

  • BackColor:Window
  • BorderStyle:FixedSingle
  • Font-Size:9pt
  • TextAlign:MiddleCenter
  • UseMnemonic:False
  • Dock:Fill

另外 __PopMenu 控制項只設定 ShowImageMargin 為 False。

__PopMenuItem 控制項則設定:

  • DropDownStyle:DropDownList
  • InteralHeight:False
  • MaxDropDownItems:10
  • Size:120, 20

 

完成各控制項的屬性設定之後,接下來就是要來設定各控制項所使用的事件。

__SelectYearValue、__SelectMonthValue、__SelectDayValue 均使用兩個事件 DoubleClick 與 MouseDoubleClick,然後各自再去呼叫 CreateYearList、CreateMonthList、CreateDayList 來產生下拉選單中的選項資料,並開啟顯示 __PopMenu 視窗。

__PopMenu 使用一個事件 Closed,處理將選取的資料填入到相關的位置。__PopMenuItem 使用一個事件 SelectedIndexChanged,來將已開啟的 __PopMenu 關閉。

簡單的來說滑鼠雙擊點選年月日資料→建立下拉式選單→顯示彈現視窗→選取資料→關閉彈現式窗→將選取的資料填入。

最後就是為了達到控制項縮放之後,畫面上的文字可以同時間依比例來縮放,所以使用了 DatePicker 的 Resize 事件。

 

公用屬性、事件

當然只有這樣的功能還不夠,使用者要怎麼知道選取後的值,還有當資料變更的時後是否可以有事件回應。因此大略分析了一下,提供了 12 個屬性值和 1 個事件。

屬性名稱

資料格式

說明

DefaultDateValue

DateTime

設定控制項的預設日期。

DateValue

DateTime

設定/讀取 控制項目前所顯示的日期。

Year

int

讀取目前控制項目所顯示日期的年份。

Month

int

讀取目前控制項目所顯示日期的月份。

Day

int

讀取目前控制項目所顯示日期的。

MinYear

int

定義顯示年份清單時,最小的年份,預設為 2010,最低不得低於 2000。

MaxYear

int

定義顯示年份清單時,最大的年份,預設為 2100,最高不得超過 2500。

TextFont

FontFamily

設定/讀取 文字顯示時的字型。

TextColor

Color

設定/讀取 文字欄位所使用的文字顏色。

TextBackGroundColor

Color

設定/讀取 文字欄位的背景顏色

TextBorderStyle

BorderStyle

設定/讀取 文字欄位的框線樣式

MenuTextColor

Color

設定/讀取 選單文字欄位的文字顏色

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

事件名稱 說明
DateValueChanged 當控制項中的日期變更之後,則會觸發本事件。

 

注意

這裡面有一個需要注意的地方,也就是當年份與月份變更之後,要確認目前日數的設定是否在合理的範圍中。比如4月只有30天不可以出現31的資料。所以當關閉彈現視窗時,將目前年月日的資料組合並轉為 DateTime 格式存入私有變數,然後再來比對目前的日數是否有超過當月的日數,有的話直接將日數變更為當月的日數,也就是說原本設定 2014/03/31 現在變更月份為 4 月,經過判斷目前日數為 31 超過當月日數 30,所以直接調整日數為 30,所以最後顯示出來的日期為 2014/04/30。

然後再來進行判斷新日期與舊日期是否相同,在不相同的情況下,就觸發 DateValueChanged 事件。

 

相關程式碼

CreateYearList 方法


private void CreateYearList()
{
    __PopMenuItem.Items.Clear();
    __PopMenuItem.Items.Add("");
    for (int i = __MinYear; i <= __MaxYear; i++)
    {
        __PopMenuItem.Items.Add(i);
    }
    __PopMenuItem.SelectedIndex = 0;
    __PopMenuItem.Text = __SelectYearValue.Text;
    __PopMenu.Tag = "Year";
    __PopMenu.Show(__SelectYearValue, 0, __SelectYearValue.Height);
}

先將選項值全數清空,先塞入一個空值,好讓第一個選項內容是空白的。最後將目前的選取值設定為選項值,這可以讓選單打開之後,目前的資料就會在選項中被選取。最後將 PopMenu 彈現出來讓使用者應用。同理在處理月份的時後也是相同的邏輯,所以在此不多做贅述,只有在日數的建立會有點不同。

 

CreateDayList 方法


private void CreateDayList()
{
    __PopMenuItem.Items.Clear();
    __PopMenuItem.Items.Add("");

    int LastDay = GetLastDay(Convert.ToInt32(__SelectYearValue.Text), Convert.ToInt32(__SelectMonthValue.Text));

    for (int i = 1; i <= LastDay; i++)
    {
        __PopMenuItem.Items.Add(i);
    }
    __PopMenuItem.SelectedIndex = 0;
    __PopMenuItem.Text = __SelectDayValue.Text;
    __PopMenu.Tag = "Day";
    __PopMenu.Show(__SelectDayValue, 0, __SelectDayValue.Height);
}

首先透過目前的年份與月份,取回該月份的總天數,之後就如同年份與月份的做法一樣。至於要怎麼取回該月份的總天數,其實只是透過日期的加減就可以做到了。先設定一個 DateTime 變數為當年當月的1日,然後再上 1 個月,再減去 1 天。最後將這個 DateTime 變數的日數傳回就是指定年份與月份的天數了。

 

DatePicker_Resize 方法


private void DatePicker_Resize(object sender, EventArgs e)
{
    float NewFontSize;
    if (((float)__YearLabel.Height / (float)__YearLabel.Width) > 1.15f)
    {
        NewFontSize = (float)__YearLabel.Width * 0.69f;
    }
    else
    {
        NewFontSize = (float)__YearLabel.Height * 0.69f;
    }

    if (NewFontSize < 6.0f) NewFontSize = 6.0f;
    __TextFont = new Font(__TextFont.FontFamily, NewFontSize, GraphicsUnit.Point);
    UpdateTextFont();
}

當高度與寬度的比例大於 1.15 時,使用寬度值否則使用高度值。再乘上 0.69 換算為 Point (pt)。最後利用原字型與新的字體大小產生字型變數,再使用 UpdateTextFont 方法變更為新字型。

 

UpdateTextFont 方法


private void UpdateTextFont()
{
    __YearLabel.Font = new Font(__TextFont.FontFamily, __TextFont.Size, GraphicsUnit.Point);
    __MonthLabel.Font = new Font(__TextFont.FontFamily, __TextFont.Size, GraphicsUnit.Point);
    __DayLabel.Font = new Font(__TextFont.FontFamily, __TextFont.Size, GraphicsUnit.Point);

    __SelectYearValue.Font = new Font(__TextFont.FontFamily, __TextFont.Size, GraphicsUnit.Point);
    __SelectMonthValue.Font = new Font(__TextFont.FontFamily, __TextFont.Size, GraphicsUnit.Point);
    __SelectDayValue.Font = new Font(__TextFont.FontFamily, __TextFont.Size, GraphicsUnit.Point);

    float tmpItemSize = __TextFont.Size * 0.75f;
    if (tmpItemSize < 8.0f) tmpItemSize = 8.0f;
    __PopMenu.Font = new Font(__TextFont.FontFamily, tmpItemSize, GraphicsUnit.Point);
    __PopMenuItem.Font = new Font(__TextFont.FontFamily, tmpItemSize, GraphicsUnit.Point);
}

其中會依照新字型的大小,自動縮小 75% 做為下拉選單字型的大小。

 

原始程式碼下載

 

應用範例畫面

 

004

第一個為原始設定大小。

第二個變更控制項大小,並且變更為標楷體。

第三個變更控制項大小,並且變更為華康中圓體,去除文字欄位的框線,並且變更文字的背景顏色與文字顏色。

第四個變更控制項大小,去除文字欄位的框線,變更文字的背景顏色與文字顏色,加上控制項的框線,變更下拉選單中選項文字的顏色。


程式是運氣與直覺堆砌而成的奇蹟。
若不具備這兩者,不可能以這樣的工時實現這樣的規格。
修改規格是對奇蹟吐槽的褻瀆行為。
而追加修改則是相信奇蹟還會重現的魯莽行動。