How long does a TDataset bookmark remain valid? - delphi

I have code like below in a project I'm working.
procedure TForm.EditBtnClick(Sender:TObject);
begin
// Mark is form variable. It's private
Mark = cdsMain.GetBookmark;
// blabalbal
.
.
.
end;
procedure TForm.OkBtnClick(Sender:TObject);
var
mistakes: Integer;
begin
//Validation stuff and transaction control
//removed to not clutter the code
If cdsMain.ChangeCount <> 0 then
mistakes := cdsMain.AppyUpdates(-1);
cdsMain.Refresh;
try
cdsMain.GotoBookmark(Mark);
// Yes, I know I would have to call FreeBookmark
// but I'm just reproducing
except
cdsMain.First;
end;
end;
Personally, I do not use bookmarks much — except to reposition a dataset where I only moved the cursor position (to create a listing, fill a string list, etc). If I Refresh, update (especially when a filter can make the record invisible), refetch (Close/Open) or any operation that modifies the data in the dataset, I don't use bookmarks. I prefer to Locate on the primary key (using a TClientDataset, of course) or requery modifying the parameters.
Until when is a bookmark valid? Until a Refresh? Until a Close/Open is done to refetch data? Where does the safe zone end?
Consider in the answer I'm using TClientDataset with a TSQLQuery (DbExpress).

Like both c0rwin and skamradt already mention: the bookmark behaviour depends on the TDataSet descendant you use.
In general, bookmarks become invalid during:
close/open
refresh (on datasets that support it)
data changes (sometimes only deletions)
I know 1. and 2. can invalidate your bookmarks in TClientDataSets. I am almost sure that for TClientDataSets it does not matter which underlying provider is used (TSQLQuery, TIBQuery, etc).
The only way to make sure what works and what not is testing it.
Which means you are totally right in not using them: bookmarks have an intrinsic chance of being unreliable.
To be on the safe side, always call BookmarkValid before going to a bookmark.

Personally I rarely ever use bookmarks. I instead use the id of the record I am viewing and perform a locate on it once the refresh is complete. If I need to iterate over all of the records in the set, I do that using a clone of the tClientDataset (which gets its own cursor).
It is my understanding is that the implementation of the bookmark is up to the vendor of the tDataset descendant and can vary between implementations. In my very simple dataset (tBinData), I implemented bookmarks as the physical record number so it would persist between refreshes as long as the record was not deleted. I can not speak this true for all implementations.

TDataSet implements virtual bookmark methods. While these methods ensure that any dataset object derived from TDataSet returns a value if a bookmark method is called, the return values are merely defaults that do not keep track of the current location. Descendants of TDataSet, such as TBDEDataSet, reimplement the bookmark methods to return meaningful values as described in the following list:
BookmarkValid, for determining if a specified bookmark is in use.
CompareBookmarks, to test two bookmarks to see if they are the same.
GetBookmark, to allocate a bookmark for your current position in the dataset.
GotoBookmark, to return to a bookmark previously created by GetBookmark
FreeBookmark, to free a bookmark previously allocated by GetBookmark.
Get it from here

Related

Can VirtualStringTree avoid attaching data to a Node?

A standard VirtualStringTree in the examples I've seen has a GetText procedure to get at data which is stored in memory via InitNode. It uses a set of Nodes to manage display, ordering etc.
An application which collects and manages some data in its own class (TMyDataClass) but uses a VirtualStringTree just to display it could, however, simply access TMyDataClass's data in GetText without bothering to attach that data to a Node in InitNode (and later freeing it in FreeNode).
I'm trying to understand any downside there is to that. What functionality of VirtualStringTree will I miss if I don't use InitNode and FreeNode? I have nothing against those of course but I just want to understand better.
The TVirtualStringTree is used to handle elements related to display wihtout worrying about what the data actually is.
To allow it to do that, each of the Node records can store additional information to get to the data it needs to know. The Data property. In general you don't want to have multiple copies of your data, so in the Data property of the Node you would store a pointer (reference) to your object from which it can get the data. I understand that the Data property is defined as a record - so you need to define a record to hold your data. (Please note I may be referring to a different version than you are using - check your docs or source code to confirm what you need).
So that would mean you have, for example:
CellText := TMyNodeRecord(Node.Data).AppData.DisplayText;
Where TMyNodeRecord is defined as something like:
type TMyNodeRecord = record
AppData: TMyDataClass;
end;
Where TMyDataClass is your own data object that supplies the text through the DisplayText property (or any other property / metod you like).

How I can get the changes of a TClientDataset to another TClientDataset?

How can I get from a TClientDataset the changes?
I have a TClientDataset named GetDataset and I have a grid. I want the changes in a new TClientDataset named ChangeDataset.
How can I do this?
If you have a source ClientDataSet CDS1, you should be able to copy the changed records to a second ClientDataSet CDS2 by doing
if CDS1.ChangeCount > 0 then
CDS2.Data := CDS1.Delta;
As you'll see if you try that, it gives you a "before" record and one with the change(s). That may not necessarily be what you want - frankly you'd do best to read the Whipple article posted in a comment and in the OLH to get the exact result you might be wanting to achieve. The point is, all the information you need is in the source CDS until you flush it away (by calling ApplyUpdates() - after that, if it succeeds, the change log is empty).
If you look at the rows in CDS2, it's not immediately obvious how you tell whether a particular field contains a changed value and how you distinguish one that does from one which is just empty. Istr there was a very good post quite a long time ago in one of the Borland NTTP newsgroups by their Mark Edington, I think, explaining how to do this. Basically, it's a question of evaluating VarIsClear on the field's NewValue property:
if VarIsClear(CDS2.Fields[i].NewValue) then
// means Fields[i] does not have a changed value
Incidentally, since you can save the state of a CDS to XML, you can use XML manipulations, e.g. with a DOM parser such is the Windows built-in one (see MSXML.Pas) to do easily many things which are troublesome to do using the TDataSet paradigm.

Is it possible to modify data in a client dataset without changing current record?

I have a TClientDataSet which stores data coming from a medical instrument. This client dataset is linked to a grid to display data in real time. My problem is, when the user is editing the data, and the instrument sends a new packet, the data which the user has modified but not yet posted is lost because I only can get a TBookmark on current record, append the new record, and then goto the saved bookmark (which is sometimes not the correct record, apparently due to the new record). I can check dataset's State, Post if necessary, and then set the State afterwards, I'm looking for a way to update data in client dataset without affecting its State. Is this even possible?
Clone the dataset and modify the data on the clone.
A document on it by Cary Jensen is here: http://edn.embarcadero.com/article/29416
Basically you need something like
var
lEdDataset: TClientdataset;
begin
lEdDataset := TClientDataSet.create(nil);
try
lEdDataset.CloneCursor(SourceDataSet, True**);
StoreMedDeviceRecord(lEdDataset);
finally
lEdDataset.free;
end;
** You'll need to read the documentation on the True/False settings and decide what you actually need (I can't remember off-hand)

Using a ClientDataSet in Delphi, are you able to display both Data & Delta records in a DBGrid?

I am making a app in Delphi 6 + MySQL database using standard data-aware components and dbExpress. The app allows a users to view records in a grid and edit data (insert and/or delete records) client side. These data edits are then only written to database on clicking the submit button. All of this works fine and has the following setup:
Controls:
1. DBGrid1 linked to a DataSource1 to display data visually.
2. DataSource1 is linked to ClientDataSet1 to offer data for DBGrid to display.
3. ClientDataSet1 is linked to DataSetProvider1 to provide client-side data for editing.
4. DataSetProvider1 is linked SQLDataSet1 which selects records from a single DB table.
5. SQLDataSet1 is linked to SQLConnection to provide connection to MySQL database.
Actions:
1. User inserts a record: I use ClientDataSet1.InsertRecord;
2. User deletes a record: I use ClientDataSet.Delete;
3. User submits data: I use ClientDataSet1.ApplyUpdates(-1);
This all works great in terms of handling data & posting data (with the inclusions of a small hack on DataSetProvider1BeforeUpdateRecord to delete records).
NOW FOR MY PROBLEM:
When the user first loads the form, the DBGrid1 displays all original records, removes all deleted records. But when the user inserts a new record in ClientDataSet1, a blank record is displayed in DBGrid1. The actual data is not lost or set as NULLS as when you ClientDataSet1.ApplyUpdates, this record is correctly written to the DB.
I know TClientDataSet has a data property for original data and a Delta property for edited data. Can these two properties with data by displayed in a single DBGrid at one time & still allowing the user to edit the data?
I have looked at 30+ resources and demo apps & all avoid this issue. Can this be done?
Ok...this question has been viewed a fair amount without much feedback. I did some research by downloading many tutorials, demo apps & read multiple articles/help info discussing the use of these controls.
The net result:
These controls are a bit buggy in general.
In specific reference to my question, it is safe to say the controls cannot do what I describe as a standard.
This is based on the 30+ demo apps, articles and tutorials I reviewed that either don't describe displaying both the original data with Delta data in a single data grid. Sure you could hack this outcome (which is what I did) using a Listbox or StringGrid, but that also indicates the control cannot or not usable at providing this functionality (on the slim chance it might).
This functionality is an obvious oversight in my opinion as a user wants to see the potential outcome of their actions, conveniently in a single data grid AND a developer wants to provide that in a simple & pain free way i.e. using a datagrid to display data!
Question Answered [if you can prove differently with a demo app that I want to review, I will remove this].
If you created a demo app and couldn't get it to work, up vote my answer. Thanks
The TDBGrid control provided with Delphi does not have the built-in functionality to display both the old and new values for fields. You are welcome, of course, to inherit from the Grid or create your own and add the functionality, or buy a third party component that accomplishes what you want. You are not limited to the standard controls, though they do provide the most commonly required functions.
You can also accomplish what you want by using calculated fields. For example, if you have a String field Name, add a new calculated String field to the dataset called OldName, with the same length as Name.
Then in your OnCalcFields event for the dataset, simply enter code like the following:
if DataSet.State = dsEdit then
begin
DataSet.FieldByName('OldName').Value := DataSet.FieldByName('Name').OldValue;
end
else
begin
DataSet.FieldByName('OldName').Value := Null;
end;
TClientDataset will handle itself the correct record status. Changes are logged, but unless you ask it explicity to show some other status (see StatusFilter property), it will show the actual state of a record.
It is possible InsertRecord bypasses some notification mechanism, so the field display is not updated. What if you perform simpy an Insert and the set field values?

What is the purpose of the 'Tag' property of Delphi VCL components?

Is there any specific purpose for the 'Tag' property of Delphi VCL components?
I have Googled a few examples using it as, for example, a 'color' property or using the value as a pointer address, but is it 'good practice' to use it, or is it considered 'bad practice' as it ties the program logic to the UI?
The "tag" property is there as a "cargo container" for whatever you might want to do with it.
Something it's often used for is in event handlers when you have a lot of similar components sharing one event handler. The event handler can find its caller and then query its tag value to get some more information about what it's supposed to be acting on.
EDIT:
Example: A calculator app might tag the number buttons with their respective numbers... silly and incomplete example, but you get the idea. The event handler could then pull the number to add into the display and accumulator right out of the tag instead of having to go figure out which button is meant to do what.
It is a place to add a piece of information to any component, even if you don't have the source for that component. It should be used carefully, because you can use it for only one purpose per component. For that reason Libraries should never use it.
I have some fundamental problems with the Tag property.
Well not exactly this property itself because it works as intended.
In general I consider using any universal/general/multi-purpose variables as a 'bad practice'.
They can be useful during debugging but are very harmful in production/mission critical environment.
They reduce code readability and understandability because nobody knows what a 'Tag' named attribute or property does. Of course you know why you are using this variable. But sooner or later you will forget (I know you will) and relying on this value makes anything more complicated.
This is why we should properly name every variable and property to help us understand what the code does.
Using Tag property is just a workaround/shortcut to avoid implementing understandable and well written code.
This is the PRACTICE and it is addictive.
Next time you need to store a new integer value bound to a component you will use the Tag property without considering any other way to store the desired values.
And storing a pointer in Tag property is a horrible idea: you have to cast this value every time you debug pointers.
Tell me: how many times did you find yourself in a situation where you wanted to store a new value in the Tag property but you realized this property is already used for a different purpose (if only there would be a 'Tag2' property in every component...).
As others have said, it's a place to put anything. Typically this comes in handy when associating two objects via an object reference or pointer. The tag happens to be perfectly sized to hold a pointer, so if you need to, say, keep an object tied to an item in a listbox, it becomes pretty straightforward.
Also it can be used for grouping purposes, say you'd want to access all components with a specific tag value, regardless of the component's type.
It's great! A freebie. I use it all the time to store one additional piece of information associated with the object.
Often I store a pointer to an associated data structure, or sometimes an integer which may be an index into some other array.
You can use it as a counter for times the object is accessed, or whatever.
The only downside is if your program uses lots of memory and you have millions of objects, those 4 bytes for each tag add up, especially if you're not using it. In that case, for your most prolific object type, you may want to create your own version without the tag.
You have 2 buttons on your form, on one you set the Tag = 1, and the other one Tag = 2. Now you assign the same OnClick event to both buttons and writhe the code like this:
procedure TForm28.Button1Click(Sender: TObject);
begin
case (Sender as TButton).Tag of
1: Caption := 'you pressed button 1';
2: Caption := 'you pressed button 2';
end;
end;
or more compact:
procedure TForm28.Button1Click(Sender: TObject);
begin
Caption := 'you pressed button ' + IntToStr((Sender as TButton).Tag);
end;
Basically,Tag will let you identify what control fired the event. Think if you have a form with dynamically created buttons... a list with users from the database, and on each record you put a button "Delete User". In this situation you can't create an event for each button, you will create one event that will assigned to all the buttons... and you can put in the Tag the userid for example. That way when you implement the event to handle all the buttons, you'll know what user to delete.
I Use tags all the time.
here are some examples;
a simple sample: you have a notebook (Like a pagecontroll without tabs)
so you can define buttons as tabs and write
NoteBook.ActivePage := TButton(Sender).Tag;
A more complicated sample;
an integer can hold 16 bitwise bolleans;
I can then check the senders up to 16 conditions to decide how to continue the pricedure
If (BitCheck (Bit2,TButton(sender).tag=True) And BitCheck(bit12,TButton(Sender).Tag=False) Then
Begin
end;
If (BitCheck (Bit9,TButton(sender).tag=True) Or BitCheck(bit14,TButton(Sender).Tag=True) Then
Begin
end;
You Get the idea

Resources