I need help with the following problem. I have a dbgrid, the underlying query is filtered. I want to apply a new filter but stay at the same row number in the dbgrid. Here is my code:
with qrProperties do
begin
...
MyPoint:=GetBookmark;
Filter:='N<>'+IntToStr(ResultPropertyN);
Filtered:=True;
GotoBookmark(MyPoint);
end;
When it gets executed, an EDBEngineError is raised with a message "Could not find record". My explanation is that the bookmark functions do not take into account the filter and the procedure GotoBookmark searches for a record that is not present in the dbgrid (due to the filter applied). Is there any way to use bookmarks with filters?
Here are a little more details. In my application when I double click on a row in the dbgrid, it disappears (due to the filter applied) but as a result of the filtering the cursor moves to the first row (if I do not use bookmarks). I want it to stay at the same row number that is to go to the record shown immediately after the one that has been deleted.
Your assumption is correct, the record is no longer 'there'.
Wrap GotoBookmark in a try/except and decide what to do in the exception, e.g. go to the first record.
Alternatively you could go to the 'nearest' record you can find. That depends on what you consider 'nearest' and then you would not need bookmarks at all and use e.g. FindNearest.
Related
I have a DBGrid that is updated on a button click. Once updated I have it check each row vs an XML file. If the fields in that row match the data in the XML file, I want to delete that row from the dbgrid only.
The DBgrid is loaded from ADOconnection / Query / Datasource . The main connection is to a DB which is linked to an excel file. The XML file is just a report we get each month. Normally we have to check to make sure all the machines on the excel file is also on the report given to us.. With hundreds of machines I am trying to auto detect any difference.
but I cant figure out how to delete the row in the grid only when it finds a match Thus only the non-Matched items are in the grid.
You can't delete rows from a DBGrid. You delete them from the dataset to which the DBGrid is connected. A DBGrid simply displays the data from a dataset. If you want the row deleted, delete it from that dataset using it's .Delete method.
After reading the edit you made, it looks to me like you should be loading the query data into a TClientDataSet (CDS), and then attaching that CDS to the DBGrid's DataSource. You can then do whatever you want to that CDS without affecting the original data, as long as you don't call the CDS's ApplyUpdates method.
What Ken White says in his answer is 100% correct, but I think there is something else
maybe worth pointing out, especially as you mention XML.
Unlike some other TDataSet descendants, both TClientDataSet and the more modern FireDAC datasets implement the UpdateStatus
property of the dataset and this reflects what has happened to the current record
in the dataset - see the Online Help for details. It is an enumeration [1] declared as
type
TUpdateStatus = (usUnmodified, usModified, usInserted, usDeleted);
You say you want to compare the records with what you have in an XML file and delete
the records which have not been updated "from the DBGrid", which as Ken has said is
not meaningful per se.
However there is another way of going about what you seem to want. If you can load the
data displayed in the dataset feeding the grid from the XML file. then you know
that until you update the dataset, the records must match the XML. So, once the
records have been updated in your app, rather than comparing all the records in the dataset
with their XML counterparts, you could simply delete (from the dataset) the records
whose UpdateStatus is usUnmodified. This is trivial to do using a method like this:
procedure TForm1.DeleteUnmodifiedRecords;
begin
ClientDataSet1.DisableControls;
try
ClientDataSet1.First;
while not ClientDataSet1.Eof do begin
if ClientDataSet1.UpdateStatus = usUnModified then
ClientDataSet1.Delete
else
ClientDataSet1.Next;
end;
finally
ClientDataSet1.EnableControls;
end;
end;
and this will, of course, remove the records from the DBGrid.
Of course, this assumes that you can load the XML data into a TClientDataSet
or a FireDAC dataset, eithe directly or by transforming the XML into a form that
either of these can load using a TXmlTransform component but I won't go
into the details of that here.
[1]: A point to beware of is that usModified does not seem to be the exact logical negation of usUnmodified. This has the slightly surprising consequence that if you
apply a StatusFilter of [usModified] to a CDS, records that have been changed can appear twice, once in their original form and once in their changed form - see fig 6.5 of Cary Jensen's "Delphi in Depth: ClientDataSets", 1st edition.
One option to make rows/records vanish from a dbGrid and other data aware controls is to use the TDataSet's OnFilterRecord and return false for rows you no longer want seen. It does not have any effect on the underlying data - it just filters out what is visible.
procedure TDM1.FDQueryTopicListFilterRecord(DataSet: TDataSet; var Accept: Boolean);
begin
// Check if the row should be shown - set Accept to true if it should, false if not
end;
I am trying to modify a legacy Delphi 2007 application.
Under certain conditions, I want make sure no record been selected for a table by supply invalid keys before calling the TDataSet.Locate function. The general process is:
User select an item (Key No. 1 for example) in a list from Grid
User do something in other screens (X, Y, Z for example) which all using Key No. 1 to retrieve data in other tables
But on one screen (say it to be screen X), I want the grid forget the
selected item, but should still had the list and data, just nothing been selected again. and all other screen should not load any data unless the user starting from step 1 again to select an item from the list again.
The current application will always have a item selected without user specifically select anything, which will be the first record in the list, and it will almost certainly never be the right one. It can be slightly improved by make sure the most recently change record been selected by default, which will be right sometimes but not most of the times. I think a better approach would be
ask the user always to select an item in the first screen in the list, and again after they finished screen Z.
So the first thing I tried is to do a locate for invalid index. But of course it doesn't working as Locate will not change the last active record if new Locate failed (find no record). Consequently, there will always an activated record.
My question is, is there any way I can reset/clear out the activate record after there been successful Locate?
Let's say I am showing stock prices, or sports scores, or movie attendance or something.
Periodically, I will refresh the grid by Close() and then Open() of a query linked to its associated datasource.
I know how to owner draw a cell with OnDrawCell() - what I can't figure out is how to know if the new value is the same as or different from the previous value for a given cell.
I suppose there are two use cases here, one where the number of rows is fixed and they remain in the same row order and one where rows can change (insert/delete or reorder).
For the former, I can take a snapshopshot before updating and compare after the update, but that might be a lot of data. I am not sure if I want to restrict the operation to the currently visible rows. I think that a user might want to scroll down and still be notified of any which have changed during the last update.
For the latter, I am stumped, unless, of course, each row has a unique key.
How can I do this (efficiently)? A solution for TDbGrid would help everyone, a solution with TMS Software's TAdvDbGrid would be fine by me (as would a (preferably free) 3rd party component).
TDBGrid reads the data currently contained in its assigned dataset. It has no capacity to remember prior values, perform calculations, or anything else. If you want to track changes, you have to do it yourself. You can do it by multiple means (a prior value column, a history table, or whatever), but it can't be done by the grid itself. TDBGrid is for presenting data, not analyzing or storing it.
One suggestion would be to track it in the dataset using the BeforePost event, where you can store the _oldvalue of a your into a LastValue column, and then use that to see if the value has changed in your TDBGrid.OnDrawColumnCell event and alter the drawing/coloring as needed. Something like if LastValue <> CurrValue then... should work.
I'm new to delphi and have created an SQL query which displays the results of a search entered by the user through an edit box (showing data from 2 related tables) in a dbGrid.
The dbgrid displays no data until a search has been made and then the results of that search are displayed, however i was wondering whether it was possible to show all the related data in the dbgrid as if it were a table and the search then simply selects from it?
thanks
You could display all the data in the table. Then write an onExit event handler for your edit box which would look something like this (assuming that the string which you are entering in the edit box is supposed to match the value in the 'name' field of the query)
query.locate ('name', edit1.text, [loPartialValue]);
This will cause the dbgrid's cursor to jump to the first line which matches the string in the edit box.
Instead of using the OnExit event, you could also use the OnChange event of the edit box, but this would mean a search after every key stroke.
Your question was a bit vague so it's difficult to give a specific answer.
So I'm messing around with a new project in Delphi 2009 and the default components that can be dropped onto a form for accessing data consist of a SQLConnection, DataSource and SQLQuery. If I add a simple select to the query component, say:
select name from customers
and then drop a DBComboBox on the form and link it up with the DataSource I get a single record in the combo box. After using Google for half and hour to figure out what I was doing wrong it looks like you have to manually add some code to your project which loops through the dataset and adds all the records to the drop down box. Something like:
while not SQLQuery.eof do
begin
DBComboBox.items.add(SQLQuery.fieldbyname('name').asstring);
SQLQuery.next;
end;
And that actually sort of works, but then you get a list in the drop down which you can't actually select anything from. Regardless of the result though I'm wondering why would you even use a DBComboBox if you have to manually add the result of your query to it? Seems to me that if it doesn't automatically populate the db combo box with the result of the query then we might as well be using a non-data-aware component like tcombobox.
I guess what I'm asking is why does it work this way? Isn't the purpose of data aware drag-and-drop controls to minimize the amount of actual written code and speed development? Is there a method that I'm missing that is supposed to make this easier?
A TDBComboBox doesn't get its list of values from the database; it gets its current value from the database. Link it to a field in your dataset, and when you change the active record, the combo box's current value will change. Change the combo box's current value, and the corresponding field's value will change.
If you want to get the list of values from the database as well, then use a TDBLookupComboBox.
This is all covered in the help:
Using TDBListBox and TDBComboBox
Displaying and Editing Data in Lookup List and Combo Boxes
Defining a Lookup List Column
I think you want the TDBLookupComboBox because that allows you to lookup from a list of items where the list comes from a dataset.
In the TDBComboBox, the list is just a TStrings manually filled with data.
--jeroen
DbCombox is a dbaware version of the standard combobox component.