Make an options form in Delphi - delphi

I want my Form1 to have a Options button that opens up Form2. In Form2, there will be 3 Radio buttons. When a radio button is pushed, I need one of my procedures to check using:
if (RadioButton1.Pushed) then begin
for it to continue with one portion of the code, or if Radiobutton2 is pushed, a different portion, and so on. The thing is, I have no idea where to start. Any suggestions?

Might be easier to use a RadioGroup. Then, you can just set your options by adding to the Items list in the Object Inspector. You can tell which button has been set by looking at the ItemIndex like:
Case MyRadioGroup.ItemIndex of
1: DoSomething;
2: DoSomethingElse;
3: DoAnotherThing;
End;
You don't have to use a RadioGroup. All the buttons in any windowed control will have the mutual exclusion property that you expect a set of RadioButtons to have.
Jack

You can use this snippet:
if Form2.RadioButton1.Checked then
begin
// Do something
end else
if Form2.RadioButton2.Checked then
begin
// Do something else
end;
If this is going to be a bigger application, you should consider creating a global settings object, which can be changed by your options screen and is read by the procedures which need to know about certain settings.
Important: Directly accessing your forms from all over your code just increases coupling. When your application get's a little large it'll be a nightmare to maintain it.
// Form2
Config.DoSomething = RadioButton1.Checked
Config.DoSomethingElse = RadioButton2.Checked
// Form1
if Config.DoSomething then
begin
// Do something
end else
if Config.DoSomethingElse then
begin
// Do something else
end;
You could also add methods to your configuration object to save the settings to disk and reload them the next time your application starts.
Others suggested using a RadioGroup, but personally I don't like them as a long term solution, because I find them hard to adapt to my personal UI needs. (Mostly borders and distances) They may also become problematic if someday you want to reorder the items or insert a new item anywhere else than the end: Suddenly ItemIndex 2 means something completly different :) But as a quick-and-dirty solution they sure are useful.

So to re-phrase your question slightly, you are saying that
Pressing a radio button puts my application into a certain state.
Later, based on that state, I want some specific code to run.
When phrased like this it becomes very simple. In the case of Jack's answer, he suggests (quite rightly) that a simple way (to query the state) is to use a Radio Group. The ItemIndex property tells you the state of the buttons.

Related

Delphi and Firedac : query Active vs query Open

Most of my application's forms use 2 or more db grids : basically, the user clicks a record in main grid and get child results in a secondary grid.
All my primary DBGrids FDQueries (that is the one with SELECT) have been set on the form but none are "active", they fire on FormShow.
I noticed that, wether I write :
FDQuery1.Active := True;
or
FDQuery1.Open;
the result is the same : my rows are displayed in the main DBGrid.
Accordingly, I call Close or Active := False on FormClose.
But there must be a difference between those approaches and this is the subject of my question : what is the difference between Query.Open and Query.Active := True; ?
What should I use if there is any significant difference ?
Thanks in advance
Math, getting less and less noob as you take the time to answer my questions :)
SIDE NOTE : my INSERT and UPDATE queries are set up with a CLEAR then, SQL.ADD, then Parameters declaration and finally an ExecSQL
Active is a property, Open is a method. When Active is set to true, it calls the Open method, when it is set to false, it calls Close. Active is useful as it can be used to check whether the dataset is actually open.
if Qry.Active then DoSomething;
SIDE NOTE : my INSERT and UPDATE queries are set up with a CLEAR then,
SQL.ADD, then Parameters declaration and finally an ExecSQL
Between Active and Open is no difference.(see whosrdaddy comment) They do the same thing - The dataset becomes active and returns the result from SELECT statement.
You can also use property Active to check if the dataset is active for example:
if not MyQuery.Active then
MyQuery.Open; // or MyQuery.Active := true;
ExecSQL execute queries that do not return a cursor to data (such as INSERT, UPDATE, DELETE, and CREATE TABLE).

Key value for this row was changed or deleted at the data store. The local row is now deleted

Migrating 1.5 million lines of D7,BDE,Paradox to XE2,ADO,MS-SQL.
We have a TDBLookupComboBox that works fine. We provide the user with an ellipsis button so they can add or delete records from the combo box's ListSource table while the combo box is visible.
If the user clicks on the ellipsis, we let them edit the table and then we Refresh the comboboxes datasource, like this:
EditTable.ShowModal; // user edits ListSource.Dataset table
Form1.DBComboBox1.ListSource.DataSet.Refresh
This worked fine in the Paradox world.
In the SQL/ADO world, if the user deletes a record from the ListSource, we get the message on the Refresh statement above:
Key value for this row was changed or deleted at the data store.
The local row is now deleted.
This occurs even if the record the user deleted was not the currently selected item in the combo box.
We don't understand why this is happening now but not in the Paradox version.
Our solution has been (after the user edits) to close and open the ListSource dataset as shown below, but this is clumsy (and we'll have to replicate in almost 100 places we do this kind of thing.)
Here's our current fix:
var
KeyBeforeUserEdit: Integer;
KeyBeforeUserEdit:= Form1.DBComboBox.KeyValue;
EditTable.ShowModal; // user edits ListSource.Dataset table
Form1.DBComboBox1.ListSource.DataSet.Close;
Form1.DBComboBox1.ListSource.DataSet.Open;
if Form1.DBComboBox1.ListSource.DataSet.Locate('UniqueKey', KeyBeforeUserEdit, []) then
From1.DBComboBox1.KeyValue := KeyBeforeUserEdit;
Any alternate suggestions or explanations why this is necessary?
I can't know for sure what is going on but you may be able to simplify your migration (albeit not good practice) in the following way.
ShowModal is a virtual function so you can override it in the class EditTable belongs to (TEditTable?) providing that you have that source. Within the unit add the Form1 unit to the uses clause in the implementation section (if it is not already there) and add your override as follows
function TEditTable.ShowModal : integer;
var
KeyBeforeUserEdit: Integer;
begin
KeyBeforeUserEdit:= Form1.DBComboBox.KeyValue;
Result := inherited ShowModal; // user edits ListSource.Dataset table
Form1.DBComboBox1.ListSource.DataSet.Close;
Form1.DBComboBox1.ListSource.DataSet.Open;
if Form1.DBComboBox1.ListSource.DataSet.Locate('UniqueKey', KeyBeforeUserEdit, []) then
From1.DBComboBox1.KeyValue := KeyBeforeUserEdit;
end;
It is a bit of a kludge but may be pragmatic and save a lot of work.

Ensure changes in a data aware control get captured on form close

I've noticed that if a form with data aware controls is closed from the title bar and the active control has had its data changed that change never makes it into the underlying data source. I've traced this to the CM_EXIT message never getting fired for the control.
How can I ensure that no matter which control last had focus these changes get pushed to the data source?
You could send the needed CM_Exit in the OnCloseQuery event of your form.
procedure TMyForm.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
begin
if Assigned(ActiveControl) then
SendMessage(ActiveControl.Handle, CM_EXIT,0,0);
end;
I usually handle this by testing all data set components. If they're in the Modified state, then I simply call Post on them.
You can do this in the OnCloseQuery for the form.
On your OnClose event handler just add the line below:
Perform(WM_NEXTDLGCTL, 0, 0);
It will make the focus go to the next component in the TabOrder and, by removing the focus from the current component, will send the value to the field.
Just FYI. A cross platform way of forcing the control to exit is:
SelectNext(ActiveControl, True, True);
This will work for any control that has at lease one other sibling in the same parent container.
I think the right way is to call UpdateRecord on the underlying dataset components (e.g. in OnCloseQuery). This will update the data from the controls.
If you call Post instead, this will internally call UpdateRecord as well. This is why Marcus' answer will work too.

Which event is fired before current record changes in ADOQuery and how NOT to change the current record?

In my application, there are these data components linked like so:
DBGrid > (DataSource > ADOQuery > ADOConnection)
DBNavigator > (DataSource > ADOQuery > ADOConnection)
Whenever the user selects a different row from the DBGrid, or uses the DBNavigator, the ADOQuery's current record changes. Fine, but when the user makes some changes to the current record, then navigating away from it, the changes made are lost.
I would like to display a confirmation dialogue where the user would need to confirm navigating away from the current record in case there were any changes made. And, when the user clicks 'No' then I would like the application NOT to change the current record.
Where should I insert the code? Which event is it that is fired before user navigates away from the current record and how do I stop the action from continuing?
if anythingChanged then
if messageDlg(...)=mrNo then
ADOQuery.dontChangeCurrentRecord;
Put a conditional Abort on 'BeforeScroll';
procedure TForm1.ADOQuery1BeforeScroll(DataSet: TDataSet);
begin
if TAdoQuery(DataSet).Modified then //if anythingChanged then
Abort;
end;
Strange. What sort of grid are you using that doesn't automatically call Post in this situation?
If you want to do something like this, probably the best place to put the event handler, if the grid doesn't provide a convenient event for it, is on the dataset's BeforeScroll event. To keep the changes from being applied, you can call Abort.

How to change behaviour of TDBNavigator component?

I would like to change the behaviour of the insert button on the standard DBNavigator bar, from a dataset insert to append.
I could trap the button click in the BeforeAction event, do the append, etc; and then in the OnClick event abort the original insert, but this seems a bit of a hack. Any better ideas? I'm using D6 (500,000 kms on the clock, and still going strong...).
Thanks for any advice
Regards,
PhilW.
You could derive your own class from TDBNavigator and override BtnClick method.
Or, for a quick and dirty fix, you could change the insert button's click handler at runtime, e.g.:
type
THackDBNavigator = class(TDBNavigator);
procedure TForm1.DBNavigatorInsertClick(Sender: TObject);
var
DBNavigator: TDBNavigator;
begin
DBNavigator := ((Sender as TControl).Parent as TDBNavigator);
if Assigned(DBNavigator.DataSource) and (DBNavigator.DataSource.State <> dsInactive) then
begin
if Assigned(DBNavigator.BeforeAction) then
DBNavigator.BeforeAction(DBNavigator, nbInsert);
DBNavigator.DataSource.DataSet.Append;
if Assigned(DBNavigator.OnClick) then
DBNavigator.OnClick(DBNavigator, nbInsert);
end;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
THackDBNavigator(DBNavigator1).Buttons[nbInsert].OnClick := DBNavigatorInsertClick;
end;
There is no difference in most databases between insert and append. Doing an actual physical insert would mean actually moving all data, starting with the place the new row would be inserted, down the size of one row, and then writing that new row in the newly open spot. This would be very slow because of all of the disk activity.
Databases instead do an append, which writes the data to the end of the physical file, and the index order controls the way the row appears to be positioned in the correct place in the file.
So for most intents and purposes, you're probably already getting an append instead of an insert, regardless of which method you use or what the button on the DBNavigator says. It's the index that makes it appear otherwise.
You can check that for validity by creating a database without an index, and try doing both an insert and an append a few times, examining the data carefully after every operation.
#TOndrej: Great! I hadn't appreciated this technique. Thanks!
#Ken White: I understand your point, but visually to my users it makes a difference - the DBNavigator controls a DBGrid where, in the majority of cases, there is plenty of unused rows in the grid. It appears to be more consistent to have new records appear at the bottom of the grid rather then just above where ever the current record is at that moment. But thanks for your answer.
Regards,
PhilW.

Resources