Delphi TClientDataSet sorting (insert) issue - delphi

as far is I know sorting in a ClientDataSet works over the indexDefs.
I can add an indexDef, set the field(s) I want to sort, and over the ixDescending property I can define the direction to sort.
I have a ClientDataSet connected to a dataSource which is the source for a DBGrid.
When I now insert a new record in the ClientDataSet, it's either inserted at the top of the table (ixDescending = false) or at the bottom of the table (ixDescending = true).
But I want a descending order AND new records should be at the top at the table - not at the bottom.
I tried to change the the indexDefs at runtime to achieve this - but without success.
Somebody has an idea how to do this?

When you have inserted a record unless you set values for the indexed fields they are NULL, and sorted accordingly.

I don't know of an elegant solution (although I'm a novice in this area.)
Perhaps there's some way for you to create a temporary boolean field in your ClientDataSet... (Maybe a calculated field?) Suppose you name the new field "NewRecord" and include it in your IndexDef so that it is the most significant sort criteria.
In an .OnCreate event, you set it true (which would be 1 internally?). In an .OnPost event you set it false.
If you don't want to depend on the internal representation of booleans, then you could create a string field and put "ZZZZZZZ" in it. Or an integer field and put MaxInt in it.

Related

Inserting after the last record (bottom) shown in DBGrid

What should be the code for inserting after the last record using the ClientDataSet?
I tried the following:
cdsSomething.Last;
cdsSomething.Insert:
But it appears it replaces the last record instead. I am sure there must be a quick code for this.
The method to append a record to the end of the Dataset (let alone any index) is Append. You don't even need to call Last before.
cdsSomething.Append;
Insert inserts a row before the selected record, so with your code, the new record should become the second to last record.
In general, where an added record (or, in fact, any record) appears in the DBGrid does not depend on the dataset operations which were used to insert it.
In fact, the DBGrid is irrelevant to this question, because it simply displays the added row in the ClientDataSet in the position the added row occurs in the CDS according to its current index order.
So, for example, if the CDS contains an integer ID field, and its current index is this ID field (e.g. because the CDS's IndexFieldNames property is set to 'ID'), to make the added row appear at the end, all you need to do is to set its ID value to something higher than any existing record in the CDS. If the field is of type ftAutoInc, this will, of course, happen automatically.
Uwe Raabe has answered this q a bit differently. What he says is correct if the CDS is not using any index, so the records are displayed in the physical order they appear in the CDS's datafile. However, relying on the physical order to determine the display order is not necessarily a good idea if the display order is important. If it is, then use an indexed field (or fields) to determine the order.

How to implement a TDataSet-bound combobox with not-in-list values

I need to implement a combobox, which is bound to a TpFIBDataSet (descendant of TDataSet). I've done this several times before. It's not a big thing if it contains only predefined values.
This time, I'd like to have a combobox that accepts custom values entered by the user, also giving the ability to the user to select some predefined value. Newly entered values shall be inserted into some table of the database just before the record the combobox's field belongs to is posted.
The main problem seems to me, that predefined values are internally represented as integer IDs (the combobox I use is TwwDBComboBox from Roy Woll's InfoPower package, as it implements maplist functionality) because the field is a foreign key, while custom values may be nearly everything (only restricted by a mask).
How can I distinguish between an integer ID and integer user-input, for example?
See the set properties of the combobox:
AComboBox.Style := csDropDown;
AComboBox.MapList := True;
I don't request a solution as take this piece of code and be happy. I'm rather looking for some advice by others who might have or had a similar problem.
How can I distinguish between an integer ID and integer user-input, for example?
You go back to the database. Either query directly select count(*) from table where id = ComboBoxId.
Or use the Locate method of the dataset.
Or keep a cache handy in a MyList: TList<Integer> and do a MyList.BinarySearch to see if the item is already in the DB.
Obviously the cache will only work if the DB is single-user, because otherwise you will not be able to keep it up-to-date.
If it is not in the DB, you run the insert query.
After it's inserted you run the default combobox behavior, because now the values is sure to be in the DB.

cxDBnavigator applies sorting independent of the grid's settings

In the grid's properties I have selected one column to sort records (sort order) ASC.Works OK.However, when I use the cxDBNavigator and try and go to the last record, it does not go to the last record in the grid but the last record entered in the database. What is the use of using the sorting properties of the grid if the dbnavigator does not follow? Is there a way to correct this (apart from sorting records in code) ?
CxDBNavigator is navigating in the dataset, not in the grid.
Since the data in the cxGridDBTableView or cxGridDBBandedTableView is sorted in the grid you will have to take cxNavigator instead of cxDBNavigator and bind either the view or the grid to the property Control of the cxNavigator.

Return no records if FIndKey results in False?

Using TDataSet.FindKey you can locate records. When it results in True the datasets cursor will be positioned on the found record.
I use this when I select items in a list on the left, corresponding data should appear on the right.
When it results in False the cursor is not moved. This results in the record data prior to FindKey being displayed in data aware components on the right.
How can I code the result of FindKey to return an empty record?
if Not tblSomeTable.FindKey([SomeSearchData]) then
begin
< code to return empty or move data cursor to neutral position >
end;
Update: (Waited a few days before selecting right answer as I believe that is the custom and didn't wan to discourage further feedback.) There were several suggestions on tackling this situation although I believe the correct answer was from Marcelo in that it is not possible to have a cursor not be on a record. Several workarounds were suggested. I chose one of my own. It went something like:
If Not tblSomeTable.FindKey([SomeSearchData]) then
begin
tblSomeTable.FindKey([-1,2010]);
end
What I did is create a dummy, blank record with an index that the actual data can never be, ie: The first index value will never be -1. If the initial search comes up empty then the FindKey will position the cursor on this empty record. This will provide the visual effect I was after.
TDataSet does not have a "neutral position". But as always you have few options:
Set dataset into insert / append record mode. So, the controls and code will see empty record. Be careful, as something may incidentally assign data to a field and then the new record may be posted to DB.
Depending on the data access components, you are using, you can set dataset to Cached Updates mode, insert and post new empty record into dataset, and mark all changes as applied. Then assign a filter, normally rejecting this empty record. Then in your code you have to switch the filter over, so it will reject all records, excluding this empty one.
Consider to disconnect the TDataSource from the dataset and later connect it again.
Note sure, but probably there may be invented some other approaches :)
Hallo,
use SetRange instead of FindKey.
tblSomeTable.SetRange([SomeSearchData],[SomeSearchData]);
try
while not tblSomeTable.Eof do begin
<do something with the Record>
tblSomeTable.Next;
end;
finally
tblSomeTable.CanelRange;
end;
when you criteria ensures that the maximum of matching records is one you retrieve with the statement above zero or one record.
This is not possible as far as I know. The cursor must always be on a record unless Bof and Eof are both true (empty data set).

Multiselect listbox bound to database in Delphi 6

I'm using Delphi 6, and I want a database bound list box with multiselect. I found three types of List boxes: TListBox, TDBListBox and TDBLookupListBox.
As far as I can understand, TListbox is not bound to database. TDBListBox and TDBLookupListBox can't be multiselected.
Is there a way to get a multiselect listbox binded to database?
The problem with databinding components is that they rely on a datasource and a datasource has only a single cursor. That is probably the reason why.
By the way, do you need to change the data? Else you could fill a normal listbox from a dataset. Or even use an invisible data listbox and copy the contents to a normal listbox.
Not as far as I know.
The standard is that you offer with the list a bunch of values in which 1 represents the current record.
Unless you have a multivalued field (against best practices) I can't see how you could multiselect...
Or what you might want is actually a sub-table?
DevExpress TcxDBListBox supports multiselect. I use their multiselect drop down check box bound to a database, it's sweet.
The components have methods you can implement to convert to and from your list; EditValueToStates and StatesToEditValue. While the data I store is not normalized (I store a semi-colon delimited list of version numbers), I created a full text search index on the field, with a semi-colon as a delimiter, and now I can still perform optimized searches on that field.
You could create your own custom listbox component that descends from TCustomListBox and add a Datasource property for your list, and another property such as TStrings to be used as a container to hold selected values. You could then post changes to your database using a button click.
If you fiddle with some of the options of a TDBGrid and restrict the columns it displays, you can make something that looks a whole lot like a listbox. Try setting the Options property to [dgTitles,dgTabs,dgRowSelect,dgAlwaysShowSelection,dgCancelOnExit,dgMultiSelect] and work from there.
In a TDbLookupListBox you have the option to bind two different things to data; first you can bind the list to a dataset (ListSource/ListField/KeyField), second you can bind the selected item to a field in another dataset (DataSource, DataField). There is nothing conceptually wrong with wanting to bind the list of items to a dataset, and then manually manage multiple selections, however I don't think it is possible with the current implementation without subclassing and enabling the required control styles.
Based on your comment to François, I would use a normal TListbox and write code to insert all distinct values into the list, and then handle the multi-select values yourself. Jeremy's solution also works, and the DevExpress Express Quantum Grid has a nice filter system which might even save you some other programming.

Resources