How to change behaviour of TDBNavigator component? - delphi

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.

Related

How to get range of OnData indexes from Virtual Listview

I'm dealing with large text files (bigger than 100MB). I'm using a TListView (OwnerData=True). ListView's OnData event gives me required items one by one.
procedure TForm1.ListView1Data(Sender: TObject; Item: TListItem);
begin
Item.Caption:=MyStringList.strings[Item.Index];
end;
But I need the full range of required items at once (after scrolling). I need this conceptual event:
procedure TForm1.ListView1DataRange(Sender: TObject; ItemIndexStarts: int64; ItemIndexEnds: int64);
begin
LoadMyItemsByRange(ItemIndexStarts,ItemIndexEnds);
end;
LoadMyItemsByRange is easy to implement. How can I get ItemIndexStarts and ItemIndexEnds values? Any idea is appreciated.
The OnData event is for returning data for a specific item only, there is no range provided. You are correct to use the Item.Index property to know which item is being requested.
If you want to pre-load your data ahead of time, you need to use the OnDataHint event, in addition to the OnData event. The OnDataHint event gives you a range of indexes that are the ListView's best guess to which item(s) are likely to be needed soon, due to scrolling, drawing, etc.
However, that being said, there is no guarantee that the OnData event will ask only for items that were previously requested by the OnDataHint event. That is why it is called a hint. Your OnData handler needs to be prepared to load items that have not been loaded yet. And, for good measure, it is a good idea to have the first few items and the last few items always loaded at all times if possible, as the ListView refers to those items fairly often.

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.

current focussed field of a TClientDataSet

I have a situation where I have to allow the user to update either of 2 fields of a dbgrid(connected to a CDS) persisting the last entered one. So (after user enters data) I need to get the current focused control, so that i will remove the previous field data if it has any. I also have other fields apart from these two.
It was insisted not to use any grid related events, i have to use only CDS event to achieve this.
Thanks in advance,
Vijay.
Use the field's OnChange event, and just clear the other field value if any:
Something like this:
TForm1.cdsField1Change(Sender: TField);
begin
if not Sender.IsNull then
cdsField2.Clear;
end;
TForm1.cdsField2Change(Sender: TField);
begin
if not Sender.IsNull then
cdsField1.Clear;
end;

Make an options form in 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.

Resources