Mastering Delphi 7 [Electronic resources] نسخه متنی

اینجــــا یک کتابخانه دیجیتالی است

با بیش از 100000 منبع الکترونیکی رایگان به زبان فارسی ، عربی و انگلیسی

Mastering Delphi 7 [Electronic resources] - نسخه متنی

Marco Cantu

| نمايش فراداده ، افزودن یک نقد و بررسی
افزودن به کتابخانه شخصی
ارسال به دوستان
جستجو در متن کتاب
بیشتر
تنظیمات قلم

فونت

اندازه قلم

+ - پیش فرض

حالت نمایش

روز نیمروز شب
جستجو در لغت نامه
بیشتر
توضیحات
افزودن یادداشت جدید






Writing Field-Oriented Data-Aware Controls

Now that you understand the theory of how the data link classes work, let's begin building some data-aware controls. The first two examples are data-aware versions of the ProgressBar and TrackBar common controls. You can use the first to display a numeric value, such as a percentage, in a visual way. You can use the second to allow a user to change the numeric value.





Note

The code for all of the components built in this chapter is in the MdDataPack folder, which also includes a similarly named package for installing them all. Other folders include sample programs that use these components.



A Read-Only ProgressBar


A data-aware version of the ProgressBar control is a relatively simple case of a data-aware control, because it is read-only. This component is derived from the version that's not data-aware and adds a few properties of the data link object it encapsulates:


type
TMdDbProgress = class(TProgressBar)
private
FDataLink: TFieldDataLink;
function GetDataField: string;
procedure SetDataField (Value: string);
function GetDataSource: TDataSource;
procedure SetDataSource (Value: TDataSource);
function GetField: TField;
protected
// data link event handler
procedure DataChange (Sender: TObject);
public
constructor Create (AOwner: TComponent); override;
destructor Destroy; override;
property Field: TField read GetField;
published
property DataField: string read GetDataField write SetDataField;
property DataSource: TDataSource read GetDataSource write SetDataSource;
end;

As with every data-aware component that connects to a single field, this control makes available the DataSource and DataField properties. There is little code to write; simply export the properties from the internal data link object, as follows:


function TMdDbProgress.GetDataField: string;
begin
Result := FDataLink.FieldName;
end;
procedure TMdDbProgress.SetDataField (Value: string);
begin
FDataLink.FieldName := Value;
end;
function TMdDbProgress.GetDataSource: TDataSource;
begin
Result := FDataLink.DataSource;
end;
procedure TMdDbProgress.SetDataSource (Value: TDataSource);
begin
FDataLink.DataSource := Value;
end;
function TMdDbProgress.GetField: TField;
begin
Result := FDataLink.Field;
end;

Of course, to make this component work, you must create and destroy the data link when the component itself is created or destroyed:


constructor TMdDbProgress.Create (AOwner: TComponent);
begin
inherited Create (AOwner);
FDataLink := TFieldDataLink.Create;
FDataLink.Control := Self;
FDataLink.OnDataChange := DataChange;
end;
destructor TMdDbProgress.Destroy;
begin
FDataLink.Free;
FDataLink := nil;
inherited Destroy;
end;

In the preceding constructor, notice that the component installs one of its own methods as an event handler for the data link. This is where the component's most important code resides. Every time the data changes, you modify the output of the progress bar to reflect the value of the current field:


procedure TMdDbProgress.DataChange (Sender: TObject);
begin
if FDataLink.Field is TNumericField then
Position := FDataLink.Field.AsInteger
else
Position := Min;
end;

Following the convention of the VCL data-aware controls, if the field type is invalid, the component doesn't display an error message—it disables the output. Alternatively, you might want to check the field type when the SetDataField method assigns it to the control.

In Figure 17.1 you can see an example of the DbProgr application's output, which uses both a label and a progress bar to display an order's quantity information. Thanks to this visual clue, you can step through the records and easily spot orders for many items. One obvious benefit to this component is that the application contains almost no code, because all the important code is in the MdProgr unit that defines the component.


Figure 17.1: The data-aware ProgressBar in action in the DbProgr example

As you've seen, a read-only data-aware component is not difficult to write. However, it becomes extremely complex to use such a component inside a DBCtrlGrid container.





Note

If you remember the discussion of the Notification method in Chapter 9, you might wonder what happens if the data source referenced by the data-aware control is destroyed. The good news is that the data source has a destructor that removes itself from its own data links. So, there is no need for a Notification method for data-aware controls, although you'll see books and articles suggesting it, and VCL includes plenty of this useless code.









Replicable Data-Aware Controls


Extending a data-aware control to support its use inside a DBCtrlGrid component is complex and not well documented. You can find a complete replicable version of the progress bar in the MdRepPr unit of the MdDataPack package and an example of its use in the RepProgr folder, along with an HTML file describing its development. The DBCtrlGrid component has a peculiar behavior: It displays on screen multiple versions of the same physical control, using smoke and mirrors. The grid can attach the control to a data buffer other than the current record and redirects the control paint operations to another portion of the monitor.

In short, to appear in the DBCtrlGrid, a component's csReplicatable control style must be set; this flag indicates that your component supports being hosted by a control grid. First, the component must respond to the cm_GetDataLink Delphi message and return a pointer to the data link, so that the control grid can use and change it. Second, it needs a custom Paint method to draw the output in the appropriate canvas object, which is provided in a parameter of the wm_Paint message if the ControlState property's csPaintCopy flag is set.

The example code is involved, and the DBCtrlGrid component is not heavily used, so I decided not to give you full details here; you can find the full code and more information in the source code. Here's the output of a test program that uses this component:













A Read-Write TrackBar


The next step is to write a component that allows a user to modify the data in a database, not just browse it. The overall structure of this type of component isn't very different from the previous version, but there are a few extra elements. In particular, when the user begins interacting with the component, the code should put the dataset into edit mode and then notify the dataset that the data has changed. The dataset will then use a FieldDataLink event handler to ask for the updated value.

To demonstrate how you can create a data-aware component that modifies the data, I extended the TrackBar control. This isn't the simplest example, but it demonstrates several important techniques.

Here's the definition of the component's class (from the MdTrack unit of the MdDataPack package):


type
TMdDbTrack = class(TTrackBar)
private
FDataLink: TFieldDataLink;
function GetDataField: string;
procedure SetDataField (Value: string);
function GetDataSource: TDataSource;
procedure SetDataSource (Value: TDataSource);
function GetField: TField;
procedure CNHScroll(var Message: TWMHScroll); message CN_HSCROLL;
procedure CNVScroll(var Message: TWMVScroll); message CN_VSCROLL;
procedure CMExit(var Message: TCMExit); message CM_EXIT;
protected
// data link event handlers
procedure DataChange (Sender: TObject);
procedure UpdateData (Sender: TObject);
procedure ActiveChange (Sender: TObject);
public
constructor Create (AOwner: TComponent); override;
destructor Destroy; override;
property Field: TField read GetField;
published
property DataField: string read GetDataField write SetDataField;
property DataSource: TDataSource read GetDataSource write SetDataSource;
end;

Compared to the read-only data-aware control you built earlier, this class is more complex, because it has three message handlers, including component notification handlers, and two new event handlers for the data link. The component installs these event handlers in the constructor, which also disables the component:


constructor TMdDbTrack.Create (AOwner: TComponent);
begin
inherited Create (AOwner);
FDataLink := TFieldDataLink.Create;
FDataLink.Control := Self;
FDataLink.OnDataChange := DataChange;
FDataLink.OnUpdateData:= UpdateData;
FDataLink.OnActiveChange := ActiveChange;
Enabled := False;
end;

The get and set methods and the DataChange event handler are similar to those in the TMdDbProgress component. The only difference is that whenever the data source or data field changes, the component checks the current status to see whether it should enable itself:


procedure TMdDbTrack.SetDataSource (Value: TDataSource);
begin
FDataLink.DataSource := Value;
Enabled := FDataLink.Active and (FDataLink.Field <> nil) and
not FDataLink.Field.ReadOnly;
end;

This code tests three conditions: the data link should be active, the link should refer to an actual field, and the field shouldn't be read-only.

When the user changes the field, the component should consider that the field name might be invalid; to test for this condition, the component uses a try/finally block:


procedure TMdDbTrack.SetDataField (Value: string);
begin
try
FDataLink.FieldName := Value;
finally
Enabled := FDataLink.Active and (FDataLink.Field <> nil) and
not FDataLink.Field.ReadOnly;
end;
end;

The control executes the same test when the dataset is enabled or disabled:


procedure TMdDbTrack.ActiveChange (Sender: TObject);
begin
Enabled := FDataLink.Active and (FDataLink.Field <> nil) and
not FDataLink.Field.ReadOnly;
end;

The most interesting portion of this component's code is related to its user interface. When a user begins moving the scroll thumb, the component puts the dataset into edit mode, lets the base class update the thumb position, and alerts the data link (and therefore the data source) that the data has changed. Here's the code:


procedure TMdDbTrack.CNHScroll(var Message: TWMHScroll);
begin
// enter edit mode
FDataLink.Edit;
// update data
inherited;
// let the system know
FDataLink.Modified;
end;
procedure TMdDbTrack.CNVScroll(var Message: TWMVScroll);
begin
// enter edit mode
FDataLink.Edit;
// update data
inherited;
// let the system know
FDataLink.Modified;
end;

When the dataset needs new data—for example, to perform a Post operation—it requests it from the component via the TFieldDataLink class's OnUpdateData event:


procedure TMdDbTrack.UpdateData (Sender: TObject);
begin
if FDataLink.Field is TNumericField then
FDataLink.Field.AsInteger := Position;
end;

If the proper conditions are met, the component updates the data in the proper table field. Finally, if the component loses the input focus, it should force a data update (if the data has changed) so that any other data-aware components showing the value of that field will display the correct value as soon as the user moves to a different field. If the data hasn't changed, the component doesn't bother updating the data in the table. This is the standard CMExit code for components used by VCL and borrowed for this component:


procedure TMdDbTrack.CMExit(var Message: TCMExit);
begin
try
FDataLink.UpdateRecord;
except
SetFocus;
raise;
end;
inherited;
end;

A demo program is available for testing this component; you can see its output in Figure 17.2. The DbTrack program contains a check box to enable and disable the table, the visual components, and a couple of buttons you can use to detach the vertical TrackBar component from the field it relates to. I placed these on the form to test enabling and disabling the track bar.


Figure 17.2: The DbTrack example's track bars let you enter data in a database table. The check box and buttons test the enabled status of the components.

/ 279