敬业的IT人 >> 编程开发 >> Delphi >> Kylix预览

Kylix预览

敬业的IT人 互联网 佚名 2008-1-4 16:41:24

Cross-platform Controls

From Windows to Linux, and Back

These are exciting times for Borland. Not since the first whisper of Delphi has there been this much excitement about a Borland product. I'm talking, of course, about Kylix, the project to bring C Builder and Delphi to the Linux operating system. The Delphi version will be available first, so for the rest of this article, Kylix refers to Delphi for Linux.

We're developing a new VCL that will work with the Windows and Linux versions of Delphi. This means you can write an application in Windows, then move the source to a Linux box and recompile it - or vice versa. This new VCL is named CLX, for Component Library Cross-Platform. CLX encompasses the entire cross-platform library distributed with Kylix. There are a few sub-categories, which, as of this writing, break down as follows:

  • BaseCLX is the RTL, up to, and including, Classes.pas.
  • VisualCLX includes the user interface classes, i.e. the usual controls.
  • DataCLX comprises the cross-platform database components.
  • NetCLX includes the Internet stuff, e.g. Apache, etc.

At the time of this writing [early May 2000], the first Field Test for Kylix is just beginning. By the time you read this, there will be a big difference between the Kylix I'm using and working width=330>
Figure 2: The differences in the class hierarchy are subtle.

The CLX prefix of "Q" may or may not end up as the permanent prefix in the final release. TWinControl is now a TWidgetControl, but to ease the pain we added a TWinControl alias to TWidgetControl. TWidgetControl and descendants all have a Handle property that is an opaque reference to the Qt object; and a Hooks property, which is a reference to the hook objects that handle the event mechanism. (Hooks are part of a complex topic that is outside the scope of this article.)

OwnerDraw will be replaced with a new idea called Styles. Styles are basically a mechanism whereby a widget or application can take>OnMouseDown, OnMouseMove, OnClick, etc., are still there.

Show Me the Meaning ...

Let's move>TWidgetControl has CreateWidget, InitWidget, and HookEvents virtual methods that almost always have to be overridden. CreateWidget creates the Qt widget, and assigns the Handle to the FHandle private field variable. InitWidget gets called after the widget is constructed, and the Handle is valid. Some of your property assignments will move from the Create constructor to InitWidget. This will allow delayed construction of the Qt object until it's really needed. For example, say you have a property named Color. In SetColor, you can check with HandleAllocated to see if you have a Qt handle. If the Handle is allocated, you can make the proper call to Qt to set the color. If not, you can store the value in a private field variable, and, in InitWidget, you set the property.

There are two types of events: Widget and System. HookEvents is a virtual method that hooks the CLX controls event methods to a special hook object that communicates with the Qt object. (At least that's how I like to look at it.) The hook object is really just a set of method pointers. System events now go through EventHandler, which is basically a replacement for WndProc.

Larger Than Life

All of this is just background information, because you really don't need to know it in order to write cross-platform custom controls. It helps, but with CLX, writing cross-platform controls is a snap. Just as you didn't have to understand the complexities of the Windows API to write a VCL control, the same goes for CLX and Qt. Listing>class(TForm)

Calc: TCalculator;

public

constructor Create(AOwner: TComponent); override;

end;

var

TestForm: TTestForm;

{ TTestForm }

constructor TTestForm.Create(AOwner: TComponent);

begin

inherited CreateNew(AOwner);

SetBounds(10,100,640,480);

Calc := TCalculator.Create(Self);

// Don't forget: we have to set the parent.

Calc.Parent := Self;

Calc.Top := 100;

Calc.Left := 200;

// Uncomment these to try other Border effects:

// Calc.BorderStyle := bsEtched;

end;

begin

Application := TApplication.Create(nil);

Application.CreateForm(TTestForm, TestForm);

TestForm.Show;

Application.Run;

end.

Figure 3: The project file for the CLX calculator control.


Figure 4: The calculator in Windows at run time.


Figure 5: The control at run time, as it appears>TFrameControl is a new control introduced to the hierarchy under TWidgetControl that provides a frame for your controls. The property we're most interested in is BorderStyle:

TBorderStyle = (bsNone, bsSingle, bsDouble, bsRaisedPanel,bsSunkenPanel,

bsRaised3d, bsSUnken3d, bs Etched, bsEmbossed);

There are two important methods in this control. BuildCalc creates all of the buttons, and places them in their proper locations. As you can see, I used an enumerator named TButtonType to hold the "function" of the button, and this tidbit of information is stored as an integer in the Tag property. I refer to this later in the Calc method. All of the calculator buttons are stored in a protected array of TButtonRecord records named Btns:

TButtonRecord = record

Top: Integer;

Left: Integer;

Width: Integer;

Height: Integer;

Caption: string;

Color: TColor;

end;

This makes it easy to set up all of the buttons in a loop, rather than using an ugly bunch of TButton.Create calls. Notice that the buttons' OnClick handlers get assigned to the TCalculator's Calc method. It's alright to do a direct assignment to what is typically a user event, because all of these buttons are internal to the calculator, and these events won't be published (see Figure 6).

for i := Low(TButtonType) to High(TButtonType) do

with TButton.Create(Self) do begin

Parent := Self;

SetBounds(Btns[i].Left, Btns[i].Top, Btns[i].Width,

Btns[i].Height);

Caption := Btns[i].Caption;

Color := Btns[i].Color;

>Edit control.

The other main method is Calc, which is essentially a huge case statement that evaluates which button was pushed, and decides what to do about it. I use the private field variables FCurrentValue, FLastValue, and FRepeatValue to handle the value of the calculations, so I don't have to implement a stack. The idea was to show how to create a cross-platform control, not how to write a calculator.

Oh yeah! Remember, that I used the Tag property in BuildCalc to hold its function? That's retrieved in this method by casting the Sender to a TButton, and casting the Tag back to a TButtonType. ButtonType is the selector expression of the case statement:

ButtonType := TButtonType(TButton(Sender).Tag);

Are you wondering how we convert this to a cross-platform control? No? Good! That means you've been paying attention. This code will compile in Windows and Linux with absolutely no changes. There are no extra steps involved. Just by the virtue of using CLX, this control is ready to go.

All I Have to Give

As you can see, writing a cross-platform control isn't all that different from writing a VCL component. If you're a new component developer, it won't be difficult to learn. If you're an experienced VCL component builder, most of your knowledge will transfer to Kylix nicely.

As I said earlier, there are a lot of differences, but that should>TGraphic control, then you shouldn't have much trouble porting it to Linux.

This article describes features of software products that are in development and subject to change without notice. Description of such features here is speculative and does not constitute a binding contract or commitment of service.

The files referenced in this article are available for download.

Involved as a user of Delphi since the initial beta, Robert Kozak is a member of the Kylix R&D team and has been with Borland since the later half of 1999. Since he joined Borland, he has been involved in the development of C Builder 5 and Kylix. Robert was involved with the start of TaDDA! (Toronto Area Delphi Developers Association), which later merged with TDUG (Toronto Delphi Users Group). Robert continues to stay active in the user community, and is active>override;

procedure SetText(const Value : string); override;

public

constructor Create(AOwner: TComponent); override;

property Value : Single read FCurrentValue;

published

property Text : string read GetText write SetText;

property BorderStyle;

property LineWidth;

property Margin;

property MidLineWidth;

property FrameRect;

end;

implementation

function ButtonRecord(aTop, aLeft, aWidth,

aHeight: Integer; aCaption: string;

aColor: TColor = clBtnFace): TButtonRecord;

begin

Result.Top := aTop;

Result.Left := aLeft;

Result.Width := aWidth;

Result.Height := aHeight;

Result.Caption := aCaption;

Result.Color := aColor;

end;

{ TCalculator }

constructor TCalculator.Create(AOwner: TComponent);

begin

inherited Create(AOwner);

SetBounds(0,0,250,200);

FMemoryValue := 0;

FCurrentValue := 0;

FLastValue := 0;

FRepeatValue := 0;

BorderStyle := bsRaisedPanel;

BuildCalc;

end;

procedure TCalculator.BuildCalc;

var

i: TButtonType;

begin

Btns[bt7] := ButtonRecord(70, 48, 36, 29, '7');

Btns[bt4] := ButtonRecord(102, 48, 36, 29, '4');

Btns[bt1] := ButtonRecord(134, 48, 36, 29, '1');

Btns[bt0] := ButtonRecord(166, 48, 36, 29, '0');

Btns[bt8] := ButtonRecord(70, 88, 36, 29, '8');

Btns[bt5] := ButtonRecord(102, 88, 36, 29, '5');

Btns[bt2] := ButtonRecord(134, 88, 36, 29, '2');

Btns[btPlusMinus] :=

ButtonRecord(166, 88, 36, 29, ' /-');

Btns[bt9] := ButtonRecord(70, 128, 36, 29, '9');

Btns[bt6] := ButtonRecord(102, 128, 36, 29, '6');

Btns[bt3] := ButtonRecord(134, 128, 36, 29, '3');

Btns[btDecimal] := ButtonRecord(166, 128, 36, 29, '.');

Btns[btDivide] := ButtonRecord(70, 168, 36, 29, '/');

Btns[btMultiply] := ButtonRecord(102, 168, 36, 29, '*');

Btns[btSubtract] := ButtonRecord(134, 168, 36, 29, '-');

Btns[btAdd] := ButtonRecord(166, 168, 36, 29, ' ');

Btns[btBackspace] :=

ButtonRecord(37, 49, 63, 25, 'Backspace');

Btns[btClear] := ButtonRecord(37, 115, 63, 25, 'CE');

Btns[btClearAll] := ButtonRecord(37, 181, 63, 25, 'C');

Btns[btsqrt] := ButtonRecord(70, 208, 36, 29, 'sqrt');

Btns[btPercent] := ButtonRecord(102, 208, 36, 29, '%');

Btns[btInverse] := ButtonRecord(134, 208, 36, 29, '1/x');

Btns[btEquals] := ButtonRecord(166, 208, 36, 29, '=');

Btns[btMemoryAdd] := ButtonRecord(166, 5, 36, 29, 'M ');

Btns[btMemoryStore] :=

ButtonRecord(134, 5, 36, 29, 'MS');

Btns[btMemoryRecall] :=

ButtonRecord(102, 5, 36, 29, 'MR');

Btns[btMemoryClear] := ButtonRecord(70, 5, 36, 29, 'MC');

for i := Low(TButtonType) to High(TButtonType) do

with TButton.Create(Self) do begin

Parent := Self;

SetBounds(Btns[i].Left, Btns[i].Top, Btns[i].Width,

Btns[i].Height);

Caption := Btns[i].Caption;

Color := Btns[i].Color;

>end;

FResultEdit := TEdit.Create(Self);

FResultEdit.Parent := Self;

FResultEdit.SetBounds(5, 5, 240, 25);

FResultEdit.Alignment := taRightJustify;

FResultEdit.Font.Height := -13;

FResultEdit.Font.Name := 'Arial';

FResultEdit.Text := '0';

end;

procedure TCalculator.Calc(Sender: TObject);

const

MemoryStoreMap: array [Boolean] of string = (' M',');

var

ButtonType: TButtonType;

Temp: string;

TempValue: Single;

begin

ButtonType := TButtonType(TButton(Sender).Tag);

try

case ButtonType of

bt0..bt9:

begin

FBackSpaceValid := True;

if (FResultEdit.Text = '0') or

(FCurrentValue = 0) then

FResultEdit.Text := ';

FResultEdit.Text :=

FResultEdit.Text Btns[ButtonType].Caption;

FCurrentValue := StrToFloat(FResultEdit.Text);

FRepeatValue := 0;

end;

btDecimal:

if Pos('.', FResultEdit.Text) < 1 then begin

FCurrentValue := StrToFloat(FResultEdit.Text);

FLastValue := 0;

FResultEdit.Text :=

FResultEdit.Text Btns[ButtonType].Caption;

end;

btPlusMinus:

begin

FCurrentValue := StrToFloat(FResultEdit.Text);

FCurrentValue := FCurrentValue * -1;

FResultEdit.Text := FloatToStr(FCurrentValue);

end;

btClearAll:

begin

FCurrentValue := 0;

FLastValue := 0;

FResultEdit.Text := '0';

FState := csNone;

end;

btClear:

begin

FCurrentValue := 0;

FResultEdit.Text := '0';

end;

btAdd:

begin

FCurrentValue := StrToFloat(FResultEdit.Text);

FState := csAdd;

FLastValue := FCurrentValue;

FCurrentValue := 0;

end;

btSubtract:

begin

FCurrentValue := StrToFloat(FResultEdit.Text);

FState := csSubtract;

FLastValue := FCurrentValue;

FCurrentValue := 0;

end;

btDivide:

begin

FCurrentValue := StrToFloat(FResultEdit.Text);

FState := csDivide;

FLastValue := FCurrentValue;

FCurrentValue := 0;

end;

btMultiply:

begin

FCurrentValue := StrToFloat(FResultEdit.Text);

FState := csMultiply;

FLastValue := FCurrentValue;

FCurrentValue := 0;

end;

btBackSpace:

if FBackSpaceValid then begin

Temp := FResultEdit.Text;

Delete(Temp, Length(Temp),1);

if Temp = ' then

Temp := '0';

FCurrentValue := StrToFloat(Temp);

FResultEdit.Text := FloatToStr(FCurrentValue);

end;

btInverse:

begin

FCurrentValue := StrToFloat(FResultEdit.Text);

FCurrentValue := 1 / FCurrentValue;

FResultEdit.Text := FloatToStr(FCurrentValue);

end;

btPercent:

begin

FCurrentValue := StrToFloat(FResultEdit.Text);

FCurrentValue := FCurrentValue / 100;

FResultEdit.Text := FloatToStr(FCurrentValue);

end;

btSqrt:

begin

FCurrentValue := StrToFloat(FResultEdit.Text);

FCurrentValue := Sqrt(FCurrentValue);

FResultEdit.Text := FloatToStr(FCurrentValue);

end;

btMemoryStore:

begin

FMemoryValue := StrToFloat(FResultEdit.Text);

FMemoryValue := FMemoryValue * 1;

FCurrentValue := 0;

end;

btMemoryAdd:

begin

TempValue := FMemoryValue;

FMemoryValue := StrToFloat(FResultEdit.Text);

FMemoryValue := (FMemoryValue * 1) TempValue;

end;

btMemoryRecall:

begin

FResultEdit.Text := FloatToStr(FMemoryValue);

FCurrentValue := 0;

end;

btMemoryClear:

begin

FMemoryValue := 0;

end;

btEquals:

if FState <> csNone then begin

FBackSpaceValid := False;

FCurrentValue := StrToFloat(FResultEdit.Text);

if FRepeatValue = 0 then begin

FRepeatValue := FCurrentValue;

FCurrentValue := FLastValue;

end;

FLastValue := FRepeatValue;

case FState of

csAdd:

FCurrentValue := FCurrentValue FLastValue;

csMultiply:

FCurrentValue := FCurrentValue * FLastValue;

csSubtract:

FCurrentValue := FCurrentValue - FLastValue;

csDivide:

FCurrentValue := FCurrentValue / FLastValue;

end;

FLastValue := FCurrentValue;

FResultEdit.Text := FloatToStr(FCurrentValue);

FCurrentValue := 0;

end;

end; // case ButtonType of...

except

>string);

begin

FResultEdit.Text := Value;

end;

end.

粤ICP备06119539号
Copyright CiscoSky.Org,Some Rights Reserved.
Email:me1228#tom.com