I am using delphi 2005 and converting a program i wrote in 1997.with turbo pascal 6 ,that controls the temperature of wine tanks .
I have attached some of my code to do with writing and reading edit fields and a screen shot of the running form . as you can see when i try to enter -2 for a setpoint the message pops up . but if i enter 2 and then it will take an inserted - in front of the 2.
The other issue i have is entering a decimal number . if i type 25.5 for a setpoint i end up with 525 but if i enter 25 and wait a bit and the enter .5 i get 25.5 in the field .
I have looked at a few forums , but not much luck . then i found this group and hope someone can help.
OnChange fires as you type so maybe 25. is not a valid floating point number.
You could use OnExit I think or clean up the text before calling strtofloat
To look at the issue try
Try
sp[1]:=strtofloat(edit46.text);
Except
sp[1]:=1.0;
End;
This will let you set a break in the exception and look at the value of edit46.text causing the exception
Thanks Roger , I think i found the problem with the decimal point issue . I was cycling the main routine of the program with the TTimer at 2 sec , i have taken it out to 4 sec and problem went away. Did you have any thought on the negative number issue ?
TMaskEdit has been in Delphi since at least Delphi 7 afaik …
But otherwise … You can make ‘-’ impossible to enter
[I misunderstood the problem
]
Hi Ted,
Sounds like two issues:
- Decimal point. I assume when you mention the timer that you update all of the edit box values periodically. That would mess up any input the user is typing. If the edit box is input only then only display it at the start and don’t update it each timer tick. If it is input and output then you need to avoid updating while the user is editing. A few ways to do that - let us know.
- Minus: Don’t assume that the number is valid in OnChange (in any case). Instead of StrToFloat use TryStrToFloat() and only WriteSetpoints if it is valid. If not valid then I suggest highlighting the edit box or displaying an error message (e.g. status bar)
Cheers,
Jarrod
And as an annoying aside…
If your app must function in the wider world, don’t assume that every one uses a decimal point like Aussies do.
I’m talking about you, my German friends…
Hi Jarrod , i have been doing some tinkering and have nearly got there . i changed all the edit boxes to onexit and made the screen update a separate procedure which is only run after the local variables have been written to file. that solved the minus numbers , but the issue then was how to exit the edit box , which i found that using the tab key worked and just moved to the next editbox . i can’t seem to be able to just use the enter key to exit ?
another problem i would like to solve is how to use for/loops to process array variables
for example -something like this ----
for a = 1 to 90 , get temp[a] (local variable ) and put it in edit[a].text (form variable)
does delpihi have indirect addressing ? where you can set up an array of the form editbox names and then use that to point to the required edit box in the for/loop ??
instead of having to do the stores individually
eg.
frmBestsTemp.Edit1.text := floattostr(sp[1])
frmBestsTemp.Edit2.text := floattostr(sp[2]) etc…
the program controls the temperature of 90 wine tanks at a local winery
each tank has several variables on/off , name , setpoint , actual temp , differential , alarm , calibration , span , heat .
so the reason i ask is there is a lot of coding if i have to do it individually.
my original program is written in turbo pascal 6 where i wite direct to the x,y position on the display . heavy use of for/loops .
thanks
Ted
Kind of..
If the controls have a consistent name you can do something like this:
procedure UpdateControlNumber(const LArrayNumber: integer);
var
CMP: TComponent;
begin
CMP := FindComponent('Editbox' + LArrayNumber.ToString);
if (CMP <> Nil) then
if (CMP is TEdit) then
// do something with the control such as
TEdit(CMP).Text := 'something';
end;
So if you have edits called Editbox1, Editbox2 etc you could change Editbox2 like this:
UpdateControlNumber(2);
But you can also have arrays of controls too and address them that way.
Your form is very regular in the layout, so I was going to suggest you get AI help to change your form to generation in code, at least the EditBoxes. But Claude returned a verbose solution (200 lines) for the small form below.
It was trying to be helpful, providing procedures to iterate All, ByRow, ByCol … but I don’t think it was great.
So I asked it to take the DFM and just create a reference array (could by 1-dim, 2-dim, … whatever you want).
It returned this setup :
EditGrid: array[0..5, 0..2] of TEdit;
procedure InitializeEditGrid;
Hi Ted,
I had another look at your original post. Ideally you would build a more flexible system that centralises the data and logic. If it were me and I was starting fresh I would define a data type for a tank to store its associated data (and perhaps some validation and related code) and create the UI programmatically. That makes it easier to change the behaviour, reduces the duplicated code, and makes it easier when anything changes - e.g. adding a wine tank.
I don’t know if that’s too much of a change to introduce into your app but it would look something like the following (untested but shows the concepts - I tried to keep it simple but there is room for improvement):
TWineTank = class
public
Number: Integer;
Name: string;
Temp: Double;
SetPoint: Double;
...
end;
TWineTankList = class(TObjectList)
// Add/SetItem/GetItem/Items[]/... using TWineTank instead of TObject
end;
TTankDisplay = class
private
FTank: TWineTank;
FNameEdit: TEdit;
FSetPointEdit: TEdit;
FActEdit: TEdit;
FDifEdit: TEdit;
FNameValid: Boolean;
FSetPointValid: Boolean;
FActEditValid: Boolean;
FDifEditValid: Boolean;
procedure NameChange(Sender: TObject);
procedure SetPointChange(Sender: TObject);
procedure ActChange(Sender: TObject);
procedure DifChange(Sender: TObject);
const
LEFT_MARGIN = 8;
TOP_MARGIN = 60;
TANK_NUMBER_WIDTH = 20;
TANK_EDIT_WIDTH = 30;
EDIT_SPACING = 10;
COLUMN_WIDTH = 200;
ROW_HEIGHT = 14;
COLUMN_COUNT = 3;
ROW_COUNT = 15;
public
constructor Create(const Tank: TWineTank; const Parent: TControl; const Column: Integer; const Row: Integer);
destructor Destroy; override;
procedure UpdateDisplay;
function IsValid: Boolean;
end;
TTankDisplayList = class(TObjectList)
// Add/SetItem/GetItem/Items[]/... using TTankDisplay instead of TObject
end;
constructor TTankDisplay.Create(const Tank: TWineTank; const Parent: TControl;
const Column: Integer; const Row: Integer);
var
controlLeft: Integer;
controlTop: Integer;
begin
inherited Create;
FTank := Tank;
controlLeft := LEFT_MARGIN + COLUMN_WIDTH * Column;
controlTop := TOP_MARGIN + ROW_HEIGHT * Row;
// Create & draw tank number control like the edits below but using tpanel/etc
...
Inc(controlLeft, TANK_NUMBER_WIDTH + EDIT_SPACING);
// Create edit boxes for this tank
FNameEdit := TEdit.Create(nil);
FNameEdit.Parent := ParentControl;
FNameEdit.Left := controlLeft;
FNameEdit.Top := controlTop;
FNameEdit.Width := TANK_EDIT_WIDTH;
Inc(controlLeft, TANK_EDIT_WIDTH + EDIT_SPACING);
FSetPointEdit := TEdit.Create(nil);
FSetPointEdit.Parent := ParentControl;
FSetPointEdit.Left := controlLeft;
FSetPointEdit.Top := controlTop;
FSetPointEdit.Width := TANK_EDIT_WIDTH;
Inc(controlLeft, TANK_EDIT_WIDTH + EDIT_SPACING);
// etc for FActEdit/FDifEdit
FNameValid := True;
FSetPointValid := True;
FActEditValid := True;
FDifEditValid := True;
end;
destructor TTankDisplay.Destroy;
begin
FNameEdit.Free;
FSetPointEdit.Free;
FActEdit.Free;
FDifEdit.Free;
inherited;
end;
procedure TTankDisplay.NameChange(Sender: TObject);
begin
FNameValid := NameEdit.Text <> '';
if FNameValid then
begin
FTank.Name := NameEdit.Text;
NameEdit.Color := clWindow;
else
NameEdit.Color := clRed;
end;
procedure TTankDisplay.SetPointChange(Sender: TObject);
var
value: TDouble;
begin
FSetPointValid := TryStrToFloat(SetPointEdit.Text, value);
if FSetPointValid then
begin
FTank.SetPoint := value;
NameEdit.Color := clWindow;
else
NameEdit.Color := clRed;
end;
// etc. for ActChange/DifChange
procedure TTankDisplay.UpdateDisplay;
begin
FNameEdit.Text := FTank.Name;
FSetPointEdit.Text := FloatToStr(FTank.SetPoint);
...
end;
function TTankDisplay.IsValid: Boolean;
begin
Result := FNameValid and FSetPointValid and ...
end;
Then you just need to create as many TWineTank objects as needed (e.g. hard coded TANK_COUNT = 90, or create as many as defined in the file). When displaying a page 1-45/46-90 create TTankDisplay objects like this:
TFormTanks
private
FTanks: TWineTankList;
FTankDisplays: TTankDisplayList;
...
end;
procedure TFormTanks.DisplayPage(cont PageNumber: Integer {1/2});
var
tankIndex: Integer;
col, row: Integer;
tankDisplay: TTankDisplay;
begin
FTankDisplays.Clear;
tankIndex := (PageNumber - 1) * COLUMN_COUNT * ROW_COUNT;
for col := 0 to COLUMN_COUNT - 1 do
for row := 0 to ROW_COUNT - 1 do
begin
tankDisplay := TTankDisplay.Create(FTanks.Items[tankIndex], Self {form}, col);
FTankDisplays.Add(tankDisplay);
tankDisplay.UpdateDisplay;
Inc(tankIndex);
end;
end;
If your form is resizeable then you would need to calculate the widths instead of using hard-coded constants and you could adjust the row and col count based on the form size.
I suggest that you don’t save on change but rather add an explicit save button. You could add an IsValid method to TTankDisplayList that checks IsValid of all items in the list. Only enable save if everything is valid. If you want auto save then you could add an OnChange event to TTankDisplay (and possibly TTankDisplayList) that you can assign an event handler on the form to perform the save.
Cheers,
Jarrod


