Generating the Day Numbers

From RAD Studio
Jump to: navigation, search

Go Up to Tracking the Date


Putting numbers into the calendar involves several considerations. The number of days in the month depends on which month it is, and whether the given year is a leap year. In addition, months start on different days of the week, dependent on the month and year. Use the IsLeapYear function to determine whether the year is a leap year. Use the MonthDays array in the SysUtils unit to get the number of days in the month.

Once you have the information on leap years and days per month, you can calculate where in the grid the individual dates go. The calculation is based on the day of the week the month starts on.

Because you will need the month-offset number for each cell you fill in, the best practice is to calculate it once when you change the month or year, then refer to it each time. You can store the value in a class field, then update that field each time the date changes.

To fill in the days in the proper cells, you do the following:

  1. Add a month-offset field to the object and a method that updates the field value:
 type
  TSampleCalendar = class(TCustomGrid)
  private
    FMonthOffset: Integer;                                      { storage for the offset }
  .
  .
  .
  protected
    procedure UpdateCalendar; virtual;                      { property for offset access }
  end;
.
.
.
procedure TSampleCalendar.UpdateCalendar;
var
  AYear, AMonth, ADay: Word;
  FirstDate: TDateTime;                             { date of the first day of the month }
begin
  if FDate <> 0 then                            { only calculate offset if date is valid }
  begin
    DecodeDate(FDate, AYear, AMonth, ADay);                       { get elements of date }
    FirstDate := EncodeDate(AYear, AMonth, 1);                       { date of the first }
    FMonthOffset := 2 - DayOfWeek(FirstDate);        { generate the offset into the grid }
  end;
  Refresh;                                                  { always repaint the control }
end; 
class PACKAGE TSampleCalendar : public TCustomGrid
{
private:
    int FMonthOffset;                         // storage for the offset
    .
    .
    .
protected:
    virtual void __fastcall UpdateCalendar(void);
    .
    .
    .
};
void __fastcall TSampleCalendar::UpdateCalendar(void)
{
  unsigned short AYear, AMonth, ADay;
  TDateTime FirstDate;                          // date of first day of the month
  if ((int)FDate != 0)                          // only calculate offset if date is valid
  {
    FDate.DecodeDate(&AYear, &AMonth, &ADay);  // get elements of date
    FirstDate = TDateTime(AYear, AMonth, 1);   // date of the first
    FMonthOffset = 2 - FirstDate.DayOfWeek();  // generate the offset into the grid
  }
  Refresh();                                    // always repaint the control
}
  1. Add statements to the constructor and the SetCalendarDate and SetDateElement methods that call the new update method whenever the date changes:

    constructor TSampleCalendar.Create(AOwner: TComponent);
    begin
      inherited Create(AOwner);                                       { this is already here }
      .                                                          { other initializations here }
      .
      .
      UpdateCalendar;                                                    { set proper offset }
    end;
    procedure TSampleCalendar.SetCalendarDate(Value: TDateTime);
    begin  FDate := Value;                                                { this was already here }
      UpdateCalendar;                                       { this previously called Refresh }
    end;
    
    procedure TSampleCalendar.SetDateElement(Index: Integer; Value: Integer);
    begin
      .
      .
      .
        FDate := EncodeDate(AYear, AMonth, ADay);                 { encode the modified date }
        UpdateCalendar;                                     { this previously called Refresh }
      end;
    end; __fastcall TSampleCalendar::TSampleCalendar(TComponent *Owner)
      : TCustomGrid(Owner)
    {
      .
      .
      .
      UpdateCalendar();
    }
    void __fastcall TSampleCalendar::SetCalendarDate(TDateTime Value)
    {
      FDate = Value;                                  // this was already here
      UpdateCalendar();                               // this previously called Refresh
    }
    void __fastcall TSampleCalendar::SetDateElement(int Index, int Value)
    {
      .
      .
      .
      FDate = TDateTime(AYear, AMonth, ADay);        // this was already here
      UpdateCalendar();                              // this previously called Refresh
    }
    
  2. Add a method to the calendar that returns the day number when passed the row and column coordinates of a cell:

     function TSampleCalendar.DayNum(ACol, ARow: Integer): Integer;
    begin
      Result := FMonthOffset + ACol + (ARow - 1) * 7;          { calculate day for this cell }
      if (Result < 1) or (Result > MonthDays[IsLeapYear(Year), Month]) then
        Result := -1;                                                 { return -1 if invalid }
    end; 
    
    int __fastcall TSampleCalendar::DayNum(int ACol, int ARow)
    {
      int result = FMonthOffset + ACol + (ARow - 1) * 7;       // calculate day for this cell
      if ((result < 1)||(result > MonthDays[IsLeapYear(Year)][Month]))
        result = -1;   // return -1 if invalid
      return result;
    }
    
    Remember to add the declaration of DayNum to the component's type declaration.
  3. Now that you can calculate where the dates go, you can update DrawCell to fill in the dates:

    procedure TCalendar.DrawCell(ACol, ARow: Longint; ARect: TRect; AState: TGridDrawState);
    var
      TheText: string;
      TempDay: Integer;
    begin
      if ARow = 0 then                                        { if this is the header row ...}
        TheText := ShortDayNames[ACol + 1]                           { just use the day name }
      else begin
        TheText := ;                                           { blank cell is the default }
        TempDay := DayNum(ACol, ARow);                            { get number for this cell }
        if TempDay <> -1 then TheText := IntToStr(TempDay);        { use the number if valid }
      end;
      with ARect, Canvas do
        TextRect(ARect, Left + (Right - Left - TextWidth(TheText)) div 2,
          Top + (Bottom - Top - TextHeight(TheText)) div 2, TheText);
    end; 
    
    void __fastcall TSampleCalendar::DrawCell(int ACol, int ARow, const TRect &ARect,
      TGridDrawState AState)
    {
      String TheText;
      int TempDay;
      if (ARow == 0)                                    // this is the header row
        TheText = ShortDayNames[ACol + 1];              // just use the day name
      else
      {
        TheText = "";                                   // blank cell is the default
        TempDay = DayNum(ACol, ARow);                   // get number for this cell
        if (TempDay != -1) TheText = IntToStr(TempDay); // use the number if valid
      }
      Canvas->TextRect(ARect, ARect.Left + (ARect.Right - ARect.Left -
        Canvas->TextWidth(TheText)) / 2,
      ARect.Top + (ARect.Bottom - ARect.Top - Canvas->TextHeight(TheText)) / 2, TheText);
    }
    

Now if you reinstall the calendar component and place one on a form, you will see the proper information for the current month.

See Also