I want to use the MonthCalendar component in D4 to collect a series of
days. I want the user to pick a bunch of days (ie the 2nd, 14th
and17th) and as she picks the days, I want to trigger the BoldDays
procedure in the OnGetMonthInfo event to bold each day as it is picked.
I am keeping track of these "picked days" in an array to pass to
BoldDays. The documentation says to not call BoldDays by itself,
instead trigger OnGetMonthInfo. The problem is OnGetMonthInfo is only
triggered when the month changes. That doesn't work for me since I want
to select multiple days in the same month.
I have tried Jay McGee's SuperCalendarSet, but he uses OnGetMonthInfo
too.
Is there a way to call BoldDays as the dates are selected without
changing months?
Is there another 3rd-party component that handles this properly?
Thanks,
Craig.
Craig Symington wrote:
>
> Hi All,
>
> I want to use the MonthCalendar component in D4 to collect a series of
> days. I want the user to pick a bunch of days (ie the 2nd, 14th
> and17th) and as she picks the days, I want to trigger the BoldDays
> procedure in the OnGetMonthInfo event to bold each day as it is picked.
> I am keeping track of these "picked days" in an array to pass to
> BoldDays. The documentation says to not call BoldDays by itself,
> instead trigger OnGetMonthInfo. The problem is OnGetMonthInfo is only
> triggered when the month changes. That doesn't work for me since I want
> to select multiple days in the same month.
What I would try is to send the appropriate message to the TMonthCalendar.
Have a look at the message handler TMonthCalendar.CNNotify in the VCL unit ComCtrls.pas
and check how they do it there.
Karl
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Karl Waclawek
KD Soft Inc.
* Phone: (905) 579-3443
* E-Mail: wacl...@idirect.com
I am still confused. I'm a novice at Delphi.
I think that the FOnGetMonthInfo line of TMonthCalendar.CNNotify calls Bold Days, but I
just don't see the link. I am also a little confused as to what exactly CNNotify does and
how it's tirggered. Here's the source code:
procedure TMonthCalendar.CNNotify(var Message: TWMNotify);
var
ST: PSystemTime;
I, MonthNo: Integer;
CurState: PMonthDayState;
begin
with Message, NMHdr^ do
begin
case code of
MCN_GETDAYSTATE:
with PNmDayState(NMHdr)^ do
begin
FillChar(prgDayState^, cDayState * SizeOf(TMonthDayState), 0);
if Assigned(FOnGetMonthInfo) then
begin
CurState := prgDayState;
for I := 0 to cDayState - 1 do
begin
MonthNo := stStart.wMonth + I;
if MonthNo > 12 then MonthNo := MonthNo - 12;
FOnGetMonthInfo(Self, MonthNo, CurState^);
Inc(CurState);
end;
end;
end;
MCN_SELECT, MCN_SELCHANGE:
begin
ST := @PNMSelChange(NMHdr).stSelStart;
if not IsBlankSysTime(ST^) then
FDateTime := SystemTimeToDateTime(ST^);
if FMultiSelect then
begin
ST := @PNMSelChange(NMHdr).stSelEnd;
if not IsBlankSysTime(ST^) then
FEndDate := SystemTimeToDateTime(ST^);
end;
end;
end;
end;
inherited;
end;
Also, What is prgDayState and cDayState?
Here's the source for BoldDays:
procedure TCommonCalendar.BoldDays(Days: array of LongWord; var MonthBoldInfo: LongWord);
var
I: LongWord;
begin
MonthBoldInfo := 0;
for I := Low(Days) to High(Days) do
if (Days[I] > 0) and (Days[I] < 32) then
MonthBoldInfo := MonthBoldInfo or ($00000001 shl (Days[I] - 1));
end;
Which line does the actual bolding? How does all this work together? Please excuse my
ignorance here.
Thanks,
Craig.
Craig Symington wrote:
>
> Karl,
>
> I am still confused. I'm a novice at Delphi.
>
> I think that the FOnGetMonthInfo line of TMonthCalendar.CNNotify calls Bold Days, but I
> just don't see the link.
BoldDays is just a conversion function. You pas it an array of LongWord values
(between 1 and 31) and it will convert it to a bit array (MonthBoldInfo: LongWord) of 32 bits.
That gets passed to OnGetMonthInfo. So, your job really is to get OnGetMonthInfo called.
You should look up what message handlers are for. The message passed into CNNotify
has this format:
TWMNotify = record
Msg: Cardinal;
IDCtrl: Longint;
NMHdr: PNMHdr;
Result: Longint;
end;
and NMHdr id defined like that:
PNMHdr = ^TNMHdr;
TNMHDR = packed record
hwndFrom: HWND;
idFrom: UINT;
code: Integer; { NM_ code }
end;
But here it is actually typecast to:
TNMDAYSTATE = packed record
nmhdr: TNmHdr; // this must be first, so we don't break WM_NOTIFY
stStart: TSystemTime;
cDayState: Integer;
prgDayState: PMonthDayState; // points to cDayState TMONTHDAYSTATEs
end;
PNMDayState = ^TNMDayState;
> Which line does the actual bolding? How does all this work together? Please excuse my
> ignorance here.
Well, the message handler calls OnGetMonthInfo to get the value for
the prgDayState field in TNMDAYSTATE, which is then used by the message sender
to bold the days (since the TNMDAYSTATE record is passed by reference).
How this is done is internal to the control - it's a Windows control
and TMonthCalendar is just a wrapper for it.
Btw, OnGetMonthinfo is usually called more than once (determined by cDayState: ),
in my experience usually 3 times (once for the current, previous and next month).
How do you trigger it? That's a problem, because I can't look into the
Windows code. Just sending a CN_NOTIFY message won't do, because it has to come
from some code that knows what to do with the parameters that are filled in
by the message handler.
Maybe somebody else has a deeper knowledge and can tell both of us how to do that.
Karl Waclawek wrote:
> How do you trigger it? That's a problem, because I can't look into the
> Windows code. Just sending a CN_NOTIFY message won't do, because it has to come
> from some code that knows what to do with the parameters that are filled in
> by the message handler.
>
> Maybe somebody else has a deeper knowledge and can tell both of us how to do that.
Seems I found the solution after all.
I realized that there is a function CommCtrl.pas defined like that:
function MonthCal_SetDayState(hmc: HWND; cbds: Integer; const rgds: TNMDayState): BOOL;
begin
Result := BOOL(SendMessage(hmc, MCM_SETDAYSTATE, cbds, Longint(@rgds)));
end;
Unfortunately, it passes the wrong type of parameter through the SendMessage function.
I found though experimentation that the parameter rgds: TNMDayState should be replaced
by a simple array of type TMonthDayState.
This must be considered a VCL BUG or a bug in the docs that Borland got from MS.
I also have to mention that NONE OF THIS IS DOCUMENTED IN ANY OF THE WIN32 SDK DOCS
that come with Delphi.
Here is some sample code that works for me:
(the TMonthCalendar component is referenced by MCal)
type
TMDSArray = array[0..23767] of TMonthDayState;
PMDSArray = ^TMDSArray;
procedure TForm1.Button1Click(Sender: TObject);
var
Indx: Integer;
MonthCount: Integer;
MDSP: PMDSArray;
begin
//MonthCount usually equals 3, since you can see the days of 3 consecutive months on the display
MonthCount := MonthCal_GetMonthRange(MCal.Handle, GMR_DAYSTATE, nil);
GetMem(MDSP, MonthCount * SizeOf(TMonthDayState));
try
//put your own code to fill the array, here is just some test code
for Indx := 0 to MonthCount - 1 do
MCal.BoldDays([2, 3, 4, 11, 15, 18, 21, 28, 29, 30], MDSP^[Indx]);
Win32Check(BOOL(SendMessage(MCal.Handle, MCM_SETDAYSTATE, MonthCount, Longint(MDSP))));
finally
FreeMem(MDSP);
end;
end;
I figured out a less "elegant" solution that works. I am using Jay McGee's SuperCalendarSet and using
the IncDecMonth routine to trigger the OnGetMonthInfo event in order to refresh the bold days.
scalcntrlDates.IncDecMonth(1);
scalcntrlDates.IncDecMonth(-1);
This is not a great piece of code, but it works...
I am going to try your solution now. I would prefer to use a much more definative piece of code over
the "cheesy" solution I came up with.
Thanks again! I have learned lots from you.
Craig.
I'm sorry to bother you some more, but I am still having a bit of trouble. I set up a little test app
with your code in it, but I am having trouble getting to to compile. It tells me that TMonthDayState,
MonthCal_GetMonthRange and GMR_DAYSTATE are 'undeclared identifiers'. I must be missing something. Is
there something that I should have included? Here is the complete code:
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls, ComCtrls;
type
TMDSArray = array[0..23767] of TMonthDayState;
PMDSArray = ^TMDSArray;
TForm1 = class(TForm)
MCal: TMonthCalendar;
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.DFM}
procedure TForm1.Button1Click(Sender: TObject);
var
Indx: Integer;
MonthCount: Integer;
MDSP: PMDSArray;
begin
MonthCount := MonthCal_GetMonthRange(MCal.Handle, GMR_DAYSTATE, nil);
GetMem(MDSP,MonthCount * SizeOf(TMonthDayState);
try
for Indx := 0 to MonthCount - 1 do
MCal.BoldDays([2,3,4,11,15,18,21,28,29,30],MDSP^[Indx]);
Win32Check(BOOL(SendMessage(MCal.Handle, MCM_SETDAYSTATE,
MonthCount, Longint(MDSP))));
finally
FreeMem(MDSP);
end;
end;
Thanks in advance for your help again. Please excuse my inexperience here...
Craig
Craig Symington wrote:
>
> Hi Karl,
>
> I'm sorry to bother you some more, but I am still having a bit of trouble. I set up a little test app
> with your code in it, but I am having trouble getting to to compile. It tells me that TMonthDayState,
> MonthCal_GetMonthRange and GMR_DAYSTATE are 'undeclared identifiers'. I must be missing something. Is
> there something that I should have included? Here is the complete code:
>
> unit Unit1;
>
> interface
>
> uses
> Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
> StdCtrls, ComCtrls;
You have to add "CommCtrl" to your uses clause.
Craig Symington wrote:
>
> You're the man Karl! Thanks so much! It's working!!!!!!
>
You are welcome. :-)