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);
Related
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.
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;
I am attempting to trigger a sort on the items within a TListBox control after adding/editing entries.
I see that there is a Sorted property which I've set to true, however, this doesn't dynamically sort the ListBox every time I make a change to the contents. There doesn't seem to be any Sort procedure or function available and calling Update or Refresh does not have the desired effect.
I've reached the stage where I'm considering pulling the contents of the ListBox into a TStringList object, sorting that and then putting everything back into the ListBox again. This seems a bit insane though, surely I am overlooking some better method.
Here's an example of changing an existing item:
myListBox.Items[myIndex] := newString; // Update Text
myListBox.Items.Objects[myIndex] := TObject(my_object); // Update associated object
I would expect the control to update to keep things sorted alphabetically but it doesn't.
The sorted property of a list box is actually backed by the Win32 list box style LBS_SORT. That will sort the list box when a new item is added. But it will not do so when an existing item is modified.
So the easy way to work around this is to set Sorted to True, then, instead of modifying existing values, remove the old value and add the new one. So your code would become:
myListBox.Items.Delete(myIndex);
myListBox.Items.AddObject(newString, TObject(my_object));
And if you think about it, your code would have been doomed to failure if the list box behaved the way you expected it to. Because after you modified the text of the item, if the list was re-sorted then myIndex would no longer refer to the same item.
I want my Form1 to have a Options button that opens up Form2. In Form2, there will be 3 Radio buttons. When a radio button is pushed, I need one of my procedures to check using:
if (RadioButton1.Pushed) then begin
for it to continue with one portion of the code, or if Radiobutton2 is pushed, a different portion, and so on. The thing is, I have no idea where to start. Any suggestions?
Might be easier to use a RadioGroup. Then, you can just set your options by adding to the Items list in the Object Inspector. You can tell which button has been set by looking at the ItemIndex like:
Case MyRadioGroup.ItemIndex of
1: DoSomething;
2: DoSomethingElse;
3: DoAnotherThing;
End;
You don't have to use a RadioGroup. All the buttons in any windowed control will have the mutual exclusion property that you expect a set of RadioButtons to have.
Jack
You can use this snippet:
if Form2.RadioButton1.Checked then
begin
// Do something
end else
if Form2.RadioButton2.Checked then
begin
// Do something else
end;
If this is going to be a bigger application, you should consider creating a global settings object, which can be changed by your options screen and is read by the procedures which need to know about certain settings.
Important: Directly accessing your forms from all over your code just increases coupling. When your application get's a little large it'll be a nightmare to maintain it.
// Form2
Config.DoSomething = RadioButton1.Checked
Config.DoSomethingElse = RadioButton2.Checked
// Form1
if Config.DoSomething then
begin
// Do something
end else
if Config.DoSomethingElse then
begin
// Do something else
end;
You could also add methods to your configuration object to save the settings to disk and reload them the next time your application starts.
Others suggested using a RadioGroup, but personally I don't like them as a long term solution, because I find them hard to adapt to my personal UI needs. (Mostly borders and distances) They may also become problematic if someday you want to reorder the items or insert a new item anywhere else than the end: Suddenly ItemIndex 2 means something completly different :) But as a quick-and-dirty solution they sure are useful.
So to re-phrase your question slightly, you are saying that
Pressing a radio button puts my application into a certain state.
Later, based on that state, I want some specific code to run.
When phrased like this it becomes very simple. In the case of Jack's answer, he suggests (quite rightly) that a simple way (to query the state) is to use a Radio Group. The ItemIndex property tells you the state of the buttons.
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.