I have a rather strange issue about which I am having trouble finding documentation. I have a Delphi program that uses Delphi's built-in TDBMemo component, as the component needs to pull data from a field in a database. The user must also be able to edit this information, namely add information to the component's field. Text typed to the field is visible and does not disappear; however, that is not my problem.
The form gives the user the option to post pre-defined comments from a list, accessed through the equivalent of a pop-up triggered by a TBitButton; however, once the selected text is added to the TDBMemo and the user clicks anywhere, the added values disappear - again, not the typed text.
Here is the code for the assignment:
var NoteString: String;
if DBMemo1.Text <> '' then
begin
NoteString := frmSelectNoteCodeView.GetTextfromField + ' - ' + User.ID
+ ' on ' + FormatDateTime('mm/dd/yyyy', Now);
DBMemo1.Text := dbedComments.Text + #13#10 + NoteString;
end;
This is purposefully a code fragment (if the field is blank, the value is just assigned). I am posting this code fragment as I believe this is where the issue is, i.e. that a regular assignment cannot be used with TDBMemo?
Here's the thing: there are no events dealing with user clicks. Any ideas as to why the posted text disappears?
As I said in my comment, the TDBMemo is a data-aware control, meaning that it's set up to display the text of the associated field in the dataset; you can't just stuff a value into its text property, because any time the TDBMemo is told to refresh itself (via its TDatalink), it retrieves the text from the field, overwriting whatever you thought you'd assigned to it.
I'd do something like this
var
ADataSet : TDataSet;
begin
ADataSet := DBMemo1.DataSource.DataSet; //just to reduce typing
if not (ADataSet.State in [dsInsert, dsEdit) then
ADataSet.Edit;
ADataSet.FieldByName(DBMemo1.FieldName).AsString := 'your text goes here';
ADataSet.Post;
end;
Or you could leave it to the user to call Post.
Might be better if the .Post were in a finally block.
Btw, your problem arises from the tight control that Delphi's db-aware framework exerts over the on-screen display of db-aware controls. Generally, this will fight you trying to directly alter what these controls display; if you want to change dataset data, then change it, not what an associated control displays.
Related
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.
In my cxGrid I have a Yes/No field which is by default 'NO' . Next to that field, I have another field,a LookupComboBox field that
gets its values from another table. It is empty by default however I would like that, when the value gets changed in this
particular field, my Yes/No field should change to 'YES' (Only in the row that I am currently editing) How am I to do this ? Also Not sure where to implement the code ....OnChange,Oneditvaluechanged,Onvalidate ???
Since your grid semms to be bound on datasets one easy way would be using the fieldchange event of your selection field.
For immediate behavior you should use a TcxEditRepositoryLookupComboBoxItem with ImmediatePost instead a of a Lookupfield in your dataset (which would anyway the worse approach with at least ADO)
procedure TForm4.MainSelectionChange(Sender: TField);
begin
if Main.State in [dsEdit,dsInsert] then
if not Sender.IsNull then
MainYesNo.Value := true;
{ maybe you are looking for that instead the code above
if Main.State in [dsEdit,dsInsert] then
MainYesNo.Value := not Sender.IsNull
}
end;
I have to convert many Excell files (converted to CSV) to the database layout of my application.
In my table there are some fields that uses foreign keys for other tables lists and when my convert code finds a field like that, it does not have the foreign id, but the simple text typed on the Excell.
I find that at the moment, the best approach is to insert a new row, focus that TDBLookupComboBox field and then send messages through SO to simulate that I am typing the text. The ComboBox autocomplete feature will then select the right item on the foreign list.
If this is the best way, how do I do that? To send these keyboard messages using Delphi 2006 and Firebird?
Instead of "emulating" a user, you should do what the component does by code. It uses the TDataSet.Locate method to find the partial string and sets the DataSet cursor to the found record. Then it uses the lookup source to get the value.
var
SearchString: string;
begin
SearchString := 'Dat';
if LookupDataSet.Locate('MySearchField', SearchString, [loCaseInsensitive, loPartialKey]) then
begin
DestDataSet.Edit;
DestDataSet.FieldByName('FLD_ID').AsInteger := LookupDataSet.FieldByName('FLD_ID').AsInteger;
// DestDataSet.Post;
end
else
raise Exception.CreateFmt('Lookup value not found for "%s"', [SearchString]);
end;
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;
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.