RAD Studio |  |

Generating the Day Numbers

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:

Copy Code

    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;

Copy Code

    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:

Copy Code

    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;

Copy Code

    __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
    }

1. Add a method to the calendar that returns the day number when passed the row and column coordinates of a cell:

Copy Code

    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;

Copy Code

    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.

1. Now that you can calculate where the dates go, you can update DrawCell to fill in the dates:

Copy Code

    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;

Copy Code

    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.

Copyright(C) 2009 Embarcadero Technologies, Inc. All Rights Reserved.
What do you think about this topic? Send feedback!