Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do you programmatically mark a date in Delphi's TDateTimePicker's calendar?

When users click a DateTimePicker, set to drop down a calendar, I would like various dates (chosen via code I create) to highlighted in some way--colored background, bold font, colored font, it doesn't matter. I just want certain dates to be marked. ... How would I do this?

like image 784
Al C Avatar asked Aug 26 '11 21:08

Al C


2 Answers

Yes, you can do this. First, you have to initialize the control:

const
  DTM_GETMCSTYLE = DTM_FIRST + 12;
  DTM_SETMCSTYLE = DTM_FIRST + 11;
...
SendMessage(DateTimePicker1.Handle,
  DTM_SETMCSTYLE,
  0,
  SendMessage(DateTimePicker1.Handle, DTM_GETMCSTYLE, 0, 0) or MCS_DAYSTATE);

(uses CommCtrl).

Then you simply have to respond to the MCN_GETDAYSTATE notification. Either you can create your own TDateTimePicker descendant, or you can use an 'interceptor class'.

type
  TDateTimePicker = class(ComCtrls.TDateTimePicker)    
  protected
    procedure WndProc(var Message: TMessage); override;
  end;

  ...

procedure TDateTimePicker.WndProc(var Message: TMessage);
var
  i: integer;
begin
  inherited;
  case Message.Msg of
    WM_NOTIFY:
      with PNMDayState(Message.LParam)^ do
        if nmhdr.code = MCN_GETDAYSTATE then
        begin
          // The first visible day is SystemTimeToDateTime(stStart);
          // cDayState is probably three, because most often three months are
          // visible at the same time. Of course, the second of these is the
          // 'currently displayed month'.
          // Each month is represented by a DWORD (32-bit unsigned integer)
          // bitfield, where 0 means not bold, and 1 means bold.
          // For instance, the following code will select all days:
          for i := 0 to cDayState - 1 do
            PMonthDayState(Cardinal(prgDayState) + i*sizeof(TMonthDayState))^ := $FFFFFFFF;
        end;
  end;
end;

Another example: Assume that the current display consists of three months, and that you only want to select days in the 'currently displayed month', that is, in the middle month. Assume that you want every third day to be selected, starting with a selected day.

Then you want to use the bitfields

Month  Bitfield
0      00000000000000000000000000000000
1      01001001001001001001001001001001
2      00000000000000000000000000000000

which are

Month  Bitfield
0      $00000000
1      $49249249
2      $00000000

in hexadecimal. So you do

for i := 0 to cDayState - 1 do
  if i = 1 then
    PMonthDayState(cardinal(prgDayState) + i*sizeof(TMonthDayState))^ := $49249249
  else
    PMonthDayState(cardinal(prgDayState) + i*sizeof(TMonthDayState))^ := $00000000;

Screenshot

like image 200
Andreas Rejbrand Avatar answered Oct 22 '22 04:10

Andreas Rejbrand


OnGetMonthInfo event looks at BoldDays array to mark days in a month.

Extracted from the help :

Use BoldDays to encode the days of the current month that should appear in bold. The value returned by BoldDays can be assigned to the MonthBoldInfo parameter of an OnGetMonthInfo event handler. Days is an array of unsigned integers corresponding to the days that should be bold.

Look it up in the help.

Edit :

DateTimePicker.BoldDays is accessible.

OnGetMonthInfo is deprecated in Delphi XE, use OnGetMonthBoldInfo instead. Still havent figured out best way to reintroduce the event yet.

Edit 2 : I did have a go with reintroducing the event, but it seems that the windows message is not tied in TDateTimePicker. Too bad. I guess Andreas solution with going straight to the windows message api is the best.

like image 34
LU RD Avatar answered Oct 22 '22 03:10

LU RD