How can I copy a Delphi TTable including its calculated fields? - delphi

I have defined a Delphi TTable object with calculated fields, and it is used in a grid on a form. I would like to make a copy of the TTable object, including the calculated fields, open that copy, do some changes to the data with the copy, close the copy, and then refresh the original copy and thusly the grid view. Is there an easy way to get a copy of a TTable object to be used in such a way?
The ideal answer would be one that solves the problem as generically as possible, i.e., a way of getting something like this:
newTable:=getACopyOf(existingTable);

You can use the TBatchMove component to copy a table and its structure.
Set the Mode property to specify the desired operation. The Source and Destination properties indicate the datasets whose records are added, deleted, or copied. The online help has additional details.
(Although I reckon you should investigate a TClientDataSet approach - it's certainly more scalable and faster).

Let me propose several things:
Let us suppose that you want to make changes programmatically. You could then use DisableControls and EnableControls methods of the TTable to disallow screen updates during that time.
If you want to have two screens with the same data (f.e. to compare data during online changes), you could actually create the same screen twice, with the TTable object being on the screen itself. It will have the exact same configuration (but not carry over previously made changes on the first screen but read the data from the database). Changes made on one screen will not be automatically refreshed on the other.
Another way: Try using TDataSetProvider with TTable as Dataset (source) feeding a TClientDataSet. ApplyUpdates would feed back the changes to the TTable. Since the calculated fields are read only, they are not affected. (untested, but should work)

I believe that the second approach (TClientDataset) is probably the best method to use in this scenario. An alternative would be to use a memory table (kbmMemTable for instance). Either way, you would clone your original table and then after making your changes loop thru the memory version of your dataset and update your original table.

You should be able to select the table on the form, copy it using Ctrl-C, then paste it into any text editor. You will get the text version of the object's properties which you can then edit as needed. When you are done, select all the text again and you can copy it to the clipboard and paste it back onto a form.

Related

Assign, memcpy or other method required for TClientDataset or TDataSetProvider (dbexpress)

I'm usign the dbExpress components within the Embarcadero C++Builder XE environment.
I have a relatively large table with something between 20k and 100k records, which I display in a DBGrid.
I am using a DataSetProvider, which is connected to a SQLQuery and a ClientDataSet, which is connected to the DataSetProvider.
I also need to analyze the data and therefore I need to run through the whole table. For smaller tables I always used code, which is basically something like this:
Form1->ClientDataSet1->First();
while(!Form1->ClientDataSet1->Eof){
temp=Form1->ClientDataSet1->FieldByName("FailReason")->AsLargeInt;
//do something with temp
Form1->ClientDataSet1->Next();
}
Of course this works out, but it is very slow, when I need to run through the whole DBGrid. For some 50000 records in can take up to some minutes. My suspicion is that the most perform is lost since the DBGrid needs to be repainted as the actual Dataset increments its address.
Therefore I am looking for a method which allows me to either read the data without manipulating the actual ClientDataSet. Maybe a method which copies the data of a column into a variable, or another way to run through the datasets, which is more efficient. I am sure if I would have a copy in a variable the operation would take less than a few seconds...
I googled now for hours, but didn't find anything useful so far.
Best regards,
Bodo
if your cds is connected to some db-aware control(-s) (via TDataSource) then first of all consider using DisableControls()
another option would be to avoid utilizing FieldByName within the loop

Stop Auto Edit working in TDBGrid

D5, ZEOS 6.6, SQLite.
I have srcAccount.AutoEdit = False;
I have All Edit functions set to False in the TDBGrid Options. Only options are set to true are Indicator, grind lines and Titles.
I have a form with a few TDBEdits and a TDBGrid on it showing all the current accounts.
When the user clicks the "New" button for a new account I have
dbedAcct.SetFocus;
tblAccounts.Insert;
If, after clicking the New button, the user wants to scroll to check Account names OR happens to click in the grid, it saves the new data and drops out of Insert mode.
How can I stop this happening? I need for them to be able to check account names.
Or, is this a bug with D5? If so, how do I work around it?
I also tried using SMDBGrid and it does exactly the same thing.
http://www.scalabium.com/smdbgrid.htm
I need for them to be able to check account names.
You can't do that using the same grid + dataset if you're allowing the user to do data entry to the grid. You are creating this problem for yourself by trying to use the grid for data entry and look-up at the same time. A simple solution is to use the grid for look-up and have a separate form (or panel on the same form as the grid) for doing the inserts, and these need to be connected to different dataset instances.
The thing is, you can't scroll around a dataset (as you need to do you look up other records) while it's in the middle of inserting a record. The dsBrowse state needed for a dataset to allow scrolling around and the dsInsert state needed to insert are mutually exclusive. Attempting to scroll the dataset will automatically post the pending insert, as you've found.
So, you actually need two dataset instances, one for lookup and one for inserts. If you use two client-side ClientDataSet instances, it can be quite straightforward because of the ease with which you can copy the data from one to the other using the CDS's Data property (cdsLookup.Data := cdsLive.Data), so making a local copy for look-up is trivial. Or, if you prefer you can use a cloned cursor - see http://edn.embarcadero.com/article/29416

How do I add row programatically in TDBGrid in Delphi

I want to add some data in grid to show user.
I want to use TDBGrid
How do I add any row to grid without a database?
Thank you
A TDBGrid reflects the data in an underlying dataset (query, clientdataset etc). To have new or changed data appear, update the data in the dataset (and/or maybe refresh it).
If you do not use an external database, your are still able to use e.g. a TClientDataSet and store its data to file (proprietary format or XML, depending on your Delphi version - see its documentation). Given the flexibility of using datasets (e.g. editing the data), I recommend this.
Alternatively, use a TstringGrid and store your data in any (other) way you want.
[It also depends on what else you want to do with the data once it's presented in the grid. If you want the user to be able to edit it, TClientDataSet is the way to go.]

How do I determine when a record is inserted in a TDataSet?

I am writing a grid control that will display the contents of either a TDataSet or a TObjectList. When you only need to support TDataSet, things are quite simple:
Link to the dataset via a TDataLink descendant.
When painting the contents of the grid, you can use the records buffered in that TDataLink to paint what you need to.
There is no need to have individual objects somewhere to represent rows in the TDataSet, because you always just paint the rows in the buffer.
In my case, I need to accept data from a few other sources as well, which meant that I needed to have an object representing each row (also because the control required quite a bit of row state).
But this causes problems with the model described above. Because I have an object representing each row, I need to be informed when records are added or deleted from the TDataSet. And I just cannot see how to do that.
Clearly, I don't want to be hooking to the dataset events; they may already be in use and the TDataLink is meant to be the mediator between my control and the dataset. And my attempts at using the DataEvent virtual method failed, because it simply doesn't tell you if a record is being added/deleted.
Any ideas?
If you hook your TDataLink descendant to a TDataSource that is connected to the TDataSet you get a call in the RecordChanged procedure when data changes.
You can use the events OnDataChange and OnUpdateData of a TDataSource connected to the TDataSet.
It seems, you have to derive your own class from the base dataset class you are going to use. There you will need override InternalAddRecord, InternalPost, InternalDelete methods and handle records addition / deletion.

Custom Listbox: Limit Maximum Item Count

I have silverlight 3.0 project that has a listbox that is databound to a list of items. What I want to do is limit the number of items displayed in the listbox to be <= 10. I originally accomplished this by limiting the data bound to the list to 10 items by doing a .Take(10) on my orignal data and databinding the result.
The problem w/ the .Take(10) approach is that the original datasource may change and since .Take() returns a reference (or copy not sure) of the original data I sometimes do not see changes in the data reflected in my UI.
I'm trying to figure out a better way of handling this rather than the .Take() approach. It seems you shouldn't 'filter' your data using LINQ functions if you have more than one UI element bound to the same data. My only thought on how to do this better is to make a custom container that will limit the count, but that seems like it might be a mountain of work to make a custom stackpanel or equivalent.
Take(10) does not make a copy, it just appends another step to the LINQ query. But all execution is still deferred till someone pulls the items of the query.
If you were setting the items statically, a copy would indeed be created, by running the query once. But since you set the constructed query as the ItemsSource property of the list box, it can run and update it any time, so it is the right approach.
The real reason why you sometimes do not see changes in the data reflected in the UI is that the list box has no way to determine why the data returned by the query have changed and it surely doesn't want to keep constantly trying to refetch the data and maybe update itself. You need to let it know.
How can you let it know? The documentation for ItemsSource says that "you should set the ItemsSource to an object that implements the INotifyCollectionChanged interface so that changes in the collection will be reflected (...).". Apparently the default way of doing things by .Net itself does not work in your case.
So there are some examples how to implement that yourself e.g. in this SO answer. If even the top-level source data collection (over which you are doing the LINQ query) does not support these notifications (which you would just forward), you might need to update the list box manually from your other code which changes the underlying data.

Resources