In this part, we develop a data-aware calendar that requires a genuine datalink.
This data-aware visual control was required to highlight special days in a database-driven personnal information manager then under development. The component TGtroDBPushButtonCalendar is a class, derived from TCustomGrid, that can be used to implement a special purpose calendar whose main action is to associate or disassociate a record in a dataset with the date selected in the calendar.
The code that generates the internal mechanics of the calendar is essentially that of the TCalendar component found on the Sample pane of the component palette. Clicking on a date in the calendar automatically selects that date whereas changes in the calendar trigger the OnChange event. Public methods NextMonth, NextYear, PrevMonth and PrevYear are available to move the calendar forward and backward by one month or by one year. CalendarDate, a TDateTime type property has the value of the date selected by the user in the calendar. As shown in Figure 1, our new component looks exactly like the TCalendar component.
We derived the new component from the TCustomGrid class and copied most of the code from the Calendar.pas unit so that we could publicize only those properties that we wanted to be public or published.
We wanted to provide storage for the state of the calendar and, in the context of a database-driven personnal information manager, such storage had to be a data set. To make the component data-aware, we only needed to link it to a data set. This is what we will do in the next section.
Generally, database programs connect to some data-aware control through a DataSource component, and then connect the DataSource component to a data set, usually a TTable or a TQuery. The connection between a data-aware control and the TDataSource is called a data link, and is represented programmatically by an object of class TDataLink declared and implemented in DB.pas. Though TDataLink is not technically an abstract class, it is seldom used directly. Either you use one of the Delphi-provided data link classes derived from it or you derive a new one yourself.
The following declaration of the TDataLink class is extracted from the db.pas unit of Delphi 6. I have added comments explaining the role of most of the methods and properties. Expand it below.
// returns true when the data link is connected to an active datasource.
// sets or returns the current record within the TDatalink's buffer window.
// the dataset the TDataLink is attached to. This is a shortcut to DataSource.DataSet.
// sets or returns data source control the TDataLink is attached to.
All the virtual methods are called by the DataEvent protected method, which is a sort of window procedure for a data source, triggered by several data events. These events originate in the dataset, fields, or data source, and are generally applied to a dataset. The DataEvent() method of the dataset component dispatches the events to the connected data sources. Each data source calls the NotifyDataLinks() method to forward the event to each connected data link, and then the data source triggers its own OnDataChange or OnUpdateData event.
The mechanism for having the TDataLink object communicate with a component is to override its virtual procedures. This is done in its descendants and in particular in its most important sub-class, the TFieldDataLink class which is used by data-aware controls that relate to single fields of a data set. This is the class that we need here as we will link only the CalendarDate property of the calendar component to one field of a data set.
These properties appear in the Object Inspector where they can be modified at design time. They can also be changed programmatically during execution.
There was no need to elaborate much about the datalink as we could use a Delphi-provided datalink but the field of the dataset that we must link to contains a date. A custom property editor was therefore required for the DataField property in order to avoid selecting fields that cannot contain date formats. We derived the TGtroDBPushButtonCalendarDataFieldEditor field editor from the TStringProperty class: this process is detailed in a separate article entitled "A data-aware Treeview front-end component".
The main action of the component is to create or delete a record of the dataset corresponding to the date that is clicked or double-clicked on the calendar. Once a record is created, its other fields can be used for whatever you want.
This main action is executed either with a click or a double-click depending on the setting of the MainActions property (maClick or maDblClick). If an associate record does not exists when the calendar cell is activated, one is created. If one exists already, it is deleted from the dataset.
In the Click method, CalendarDate is set to its value by the RowColToDate method that translate the position of the click on the calendar into a date. (the Day, Month and Year public properties are set at the same time). It searches the record of the data set associated with the date of the click and activates it if it exists. It then triggers the OnCalendarDataChange event before executing the ActionOnRecord method if the MainActions property is set to maClick. If it is set to maDblClick, this action is performed by the DblClick method.
In this declaration, for the sake of brievety, I have removed the publication of the TCustomGrid properties as well as the read and write methods of the new properties.
that prepares to draw the calendar. In this method, the rows and columns of the calendar are initialized: the first row is filles with the names of the days (from Sunday to Saturday) wheras the other rows are filles fith the number of the day. The dataset is then examined and the days that have corresponding records are highlighted. The calendar is displayed when the Invalidate statement of the constructor is executed.
but they must be called by the host application. They change the public properties Month and Year by one and, through their write methods, call the UpdateCalendarData() method, the code of which appears above in order to display a new month. In Figure 2, the component is shown on a panel and the external buttons that effect the changes of month are displayed just above the component. Here, days 1, 17 and 19 of May 2005 are highlighted meaning that the event associated with the component has occurred on these dates.
FHasRecord is an array that contains, for each cell of the calendar, a boolean value which is true when the cell has to be highlighted (there is a record corresponding to it in the data set). If it is true, clicking or double-clicking on it causes the record to be deleted. Otherwise, one is created, its date field is set to the data of the calendar, the internal data of the calendar is set and the calendar is redrawn.
for each cell of the calendar, writes the day of the month, determine if there is a record of the data set corresponding to the date of the cell and sets FHasRecord accordingly (true if one exists) .
Finally the calendar is redrawn on the screen.
This component was inspired by the calendar-like display provided by a piece of software that I used some years ago under Windows 3.1 and that was called "Calendar Creator Plus". Using this display, the user could select dates by pushing button corresponding to each date on this display and the calendar would display features related to the selected dates.
This component has been designed to highlight those dates when a given event has occurred. In order to link the calendar to a dataset, we had to develop a datalink and publish the DataSource and the FieldName properties. In order to restrict the selection of the FieldName property to a date field, we haave develop a field editor. The rest of the article has described how the component can be used and how it works.