How to get range of OnData indexes from Virtual Listview - delphi

I'm dealing with large text files (bigger than 100MB). I'm using a TListView (OwnerData=True). ListView's OnData event gives me required items one by one.
procedure TForm1.ListView1Data(Sender: TObject; Item: TListItem);
begin
Item.Caption:=MyStringList.strings[Item.Index];
end;
But I need the full range of required items at once (after scrolling). I need this conceptual event:
procedure TForm1.ListView1DataRange(Sender: TObject; ItemIndexStarts: int64; ItemIndexEnds: int64);
begin
LoadMyItemsByRange(ItemIndexStarts,ItemIndexEnds);
end;
LoadMyItemsByRange is easy to implement. How can I get ItemIndexStarts and ItemIndexEnds values? Any idea is appreciated.

The OnData event is for returning data for a specific item only, there is no range provided. You are correct to use the Item.Index property to know which item is being requested.
If you want to pre-load your data ahead of time, you need to use the OnDataHint event, in addition to the OnData event. The OnDataHint event gives you a range of indexes that are the ListView's best guess to which item(s) are likely to be needed soon, due to scrolling, drawing, etc.
However, that being said, there is no guarantee that the OnData event will ask only for items that were previously requested by the OnDataHint event. That is why it is called a hint. Your OnData handler needs to be prepared to load items that have not been loaded yet. And, for good measure, it is a good idea to have the first few items and the last few items always loaded at all times if possible, as the ListView refers to those items fairly often.

Related

How to extract Object of a BindSource in Delphis LiveBindings?

In Delphi 10.1 I have an ObjectList named DogCollection
and each entry is of the type TDog, a custom class.
thanks to tutorials from malcolm groves I was able to populate a Stringgrid
with my DogCollection.
http://www.malcolmgroves.com/blog/?p=1084
Now I'd like to be able to scroll through the stringgrid and everytime I scroll I want to update the variable "CurrentDog" from the type TDog, with whatever Object is highlighted in the stringgrid.
So I have an Overview about my DogObjects and also a single Object of my Dog
which I can independently view/manipulate.
I am out of ideas at this point.
If it is of any help to you, I can also not get the AfterScroll events of the Adapter to trigger, not even when I add a Navigator with RightClick->Add Navigator.
I thank you for your help and time.
Not sure to understand the question but I think you don't need to have a variable "CurrentDog" to work on the selected object of your list.
You can create all the components (TEdit) you need for your dog (Name, Age...) and bind these components to the same fields (Name, Age...) in your TDataGeneratorAdapter (which is linked to the "Adapter" property of your TAdapterBindSource).
Then, when you select a row in your grid, the corresponding object appears in your edit components. When you modify the "Text" properties, the grid is updated.
EDIT : InternalAdapter
After few searches, you can get your object with the InternalAdapter of your TAdapterBindSource
On the OnClick event :
procedure TForm1.Button1Click(Sender: TObject);
var
Adapter: TBindSourceAdapter;
begin
Adapter:= AdapterBindSource1.InternalAdapter;
CurrentDog:= TDog(Adapter.Current);
end;

Rearranging TreeViewItems in a FMX TreeView

After having tried and thought for several days I still haven't come close to an answer.
I am looking for a way to rearrange TreeViewItems in a Firemonkey TreeView, preferably by drap-and-drop. What worked in VCL doesn't in FMX. I hope there is someone who can help me get on track.
-OK apparently someone was a bit annoyed - let me rephrase: I cannot find a way te reorder Items at the same Level, so the TreeViewItems belonging to one TreeViewItem-Parent.
Can that be done?
For items of same level. Do something like that:
var
Src, Dst: TTreeViewItem;
tmpIndex: Integer;
begin
tmpIndex:= Src.Index;
Src.Index:= Dst.Index;
Dst.Index:= tmpIndex;
end;
The order of the items in a TTreeView depends on the order of the items in the Parents Children list.
You can modify Children list with the methods AddObject, RemoveObject, InsertObject and Sort http://docwiki.embarcadero.com/Libraries/XE7/en/FMX.Types.TFmxObject_Methods
So, to move your item to be the first use:
Item.Parent.InsertObject(0, Item);

Ensure changes in a data aware control get captured on form close

I've noticed that if a form with data aware controls is closed from the title bar and the active control has had its data changed that change never makes it into the underlying data source. I've traced this to the CM_EXIT message never getting fired for the control.
How can I ensure that no matter which control last had focus these changes get pushed to the data source?
You could send the needed CM_Exit in the OnCloseQuery event of your form.
procedure TMyForm.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
begin
if Assigned(ActiveControl) then
SendMessage(ActiveControl.Handle, CM_EXIT,0,0);
end;
I usually handle this by testing all data set components. If they're in the Modified state, then I simply call Post on them.
You can do this in the OnCloseQuery for the form.
On your OnClose event handler just add the line below:
Perform(WM_NEXTDLGCTL, 0, 0);
It will make the focus go to the next component in the TabOrder and, by removing the focus from the current component, will send the value to the field.
Just FYI. A cross platform way of forcing the control to exit is:
SelectNext(ActiveControl, True, True);
This will work for any control that has at lease one other sibling in the same parent container.
I think the right way is to call UpdateRecord on the underlying dataset components (e.g. in OnCloseQuery). This will update the data from the controls.
If you call Post instead, this will internally call UpdateRecord as well. This is why Marcus' answer will work too.

current focussed field of a TClientDataSet

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;

How to change behaviour of TDBNavigator component?

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.

Resources