Sorry, background's a little convoluted on this one... I am in the process of converting a D5 project to DXE... It has a listbox with several thousand items. A full progressive text search is done on these items with each keystroke in the searchbox. In D5 (pre-virtual lists), I had to make my own virtual listbox using the LMD listbox (as there were several columns with headers in the listbox), a separate scrollbar and an Array of records. The Listbox would then be populated as the user navigated through the search results or by modifying the search. This performed very well but since now virtual listboxes are native to Delphi I was going to convert my custom listbox to the native one but I cannot find a listbox component with headers that is virtual-capable. Help?
Is there a component available that has virtual lists and headers/columns?
I forgot to mention: I am aware of Soft Gems VirtualTreeView components - these are excellent and is probably what I'll be using but... Is there a way in DXE to accomplish this without 3rd party utilities? I'm concerned that I'm missing something obvious in DXE as I've only been using it for about a month.
TListView is a thin wrapper around the Windows list view common control. Run it in virtual mode with report view style to achieve what I believe you are asking for.
In order to set up a virtual list view you need to set OwnerData to True and supply an OnData event handler.
procedure TVirtualListViewForm.FormCreate(Sender: TObject);
begin
ListView1.ViewStyle := vsReport;
ListView1.Columns.Add.Caption := 'Column1';
ListView1.Columns.Add.Caption := 'Column2';
ListView1.OwnerData := True;
ListView1.OnData := ListViewData;
ListView1.Items.Count := 42;
end;
procedure TVirtualListViewForm.ListViewData(Sender: TObject; Item: TListItem);
begin
Item.Caption := Format('Column 0, index %d', [Item.Index]);
Item.SubItems.Add(Format('Column 1, index %d', [Item.Index]));
end;
For your needs an OnDataFind may be needed to implement the progressive text search.
You can use my component TDzListHeader, available at GitHub: https://github.com/digao-dalpiaz/DzListHeader
This component allows you to create columns in a TCollection and you should drop a TListBox inside the TListHeader, and link them.
DzListHeader example
All usage and detailed documentation are available at github project.
Related
Having recently received a 'Tumbleweed' badge for my last question, I am not sure whether I should be asking any more questions, but here goes.
I am populating a TComboBox with items from a sqlite table and this works fine. In my previous version of Delphi I was able to use ComboBox1.Sorted := True; to sort the items, but this seems to have disappeared in Delphi 10.2. I can sort the items in the table by applying a query and then populate the TComboBox from the sorted table. However, for curiosities sake I would like to find out how one now sorts items in a TComboBox. I have found some references to TComboBox(Sort:Compare) but have not succeeded in getting this to work to as of yet.
Can somebody please shed some light on this - many thanks
In Firemonkey you can populate a TComboBox instance either simply with the Items property of type TStrings or you add TListBoxItem instances with the form designer. But internally always TListBoxItem for the elements is used.
To use the TComboBox.Sort you need to provide an anonymous compare-function.
This is a simple example usage of TComboBox.Sort
cbxItems.Sort(
function (pLeft, pRight: TFMXObject): Integer
var
lLeft, lRight: TListBoxItem;
begin
lLeft := TListBoxItem(pLeft);
lRight := TListBoxItem(pRight);
Result := String.Compare(lLeft.Text, lRight.Text);
end
);
I am developing a Delphi 10.1 VCL application for Windows.
For integer or float input I need a number input field which is connected with a slider. When the user changes the number in the input field the slider position changes accordingly. When the user changes the slider position the number in the number field is updated.
I can solve this by using a TEdit and a TTrackBar and add the necessary update functionality in their OnChange event handlers.
The problem is that I need many of such inputs on different forms. Therefore I would like to create a new component which combines the two controls TEdit and TTrackBar in one component.
Is the creation of a new component the best strategy for the multiple use of such a slider input?
What is the best way to create such a new component?
Is the creation of a new component the best strategy for the multiple
use of such a slider input?
Not necessarily true all the time. (by my standards at least).
What is the best way to create such a new component?
I know three ways to solve your problem.
Way number 1:
create the component using the new component wizard where you create dynamically the TEdit and the TTrackBar sub components in a TGroupBox descendant.
the following is how I would do that.
unit Combindedittrack;
interface
uses
System.SysUtils,
System.Classes,
Vcl.Controls,
Vcl.comctrls,
Vcl.StdCtrls;
type
TCombindEditTrack = class(TGroupBox)
private
{ Private declarations }
FEdit: TEdit;
FTrackBar: TTrackBar;
procedure EditOnChangeProc(Sender: TObject);
procedure TrackBaroOnChangeProc(Sender: TObject);
protected
{ Protected declarations }
public
{ Public declarations }
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
published
{ Published declarations }
end;
procedure Register;
implementation
procedure Register;
begin
RegisterComponents('Samples', [TCombindEditTrack]);
end;
constructor TCombindEditTrack.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
SetBounds(0, 0, 250, 50);
FEdit := TEdit.Create(Self);
with FEdit do
begin
Text := ''; //<-- you control the appearence here
Top := 10;
Left := 10;
Height := 27;
Width := 50;
Parent := Self;
OnChange := EditOnChangeProc; // your Onchange event handler for the Tedit
end;
FTrackBar := TTrackBar.Create(self);
with FTrackBar do
begin
Top := 10; //<-- you control the appearence here
Left := 60;
Height := 30;
Width := 50;
Parent := self;
Onchange := TrackBaroOnChangeProc; // your Onchange event handler for the Ttrackbar
end;
end;
destructor TCombindEditTrack.Destroy;
begin
FTrackBar.Free;
FEdit.Free;
inherited;
end;
procedure TCombindEditTrack.TrackBaroOnChangeProc(Sender: TObject);
begin
// <-- track bar onchange handling here.
end;
procedure TCombindEditTrack.EditOnChangeProc(Sender: TObject);
begin
// <-- edit onchange handling here.
end;
end.
Way number 2:
Use frames like this (I'm on delphi 10 seattle).
File-->New-->Other-->(search for frames on delphi files).
Now add the edit and the track bar and set their Onchange events.
Save the unit.
on the tool palette (the standard component section) click on the frame component.
choose the frame you just created.
You will have a replica of the frame each time you use it.
Way number 3:
Use component template like this (again I'm on delphi 10 seattle)
Select your already created and modified tedit and ttrackbar.
On the toolbar's "component" click "create component template".
Name your template and press OK.
Select the Template palette and then your template.
Now notice that even your code (events) are added as well to your project.
Finally
With the level I have on delphi and the IDE I'm really unable to give you a clear answer to which is the best way but nevertheless I have shared all what I know that could help you.
edit: Since a lot of comments are insisting that the answer should state which is the best way to do this. this is the best way based on the following.
let's put some of the key point that should be accounted for when choosing
1. Ease of modifying the combined control(s) if you wish so (by my experience you will).
2. time needed to complete this task (it means the time it will take
you to fully complete the task with minimum debugging and coding).
3. general source code readability.
4. usefulness for the future of your projects.
Now lets start criticizing the three methods based on the those criteria.
Way number 1:
C1(criteria number 1): Just modify the the source implementation of the component and each replica/use will have the same effects and properties. However this is not the case for way number 3.
C2: It depends on your knowledge of component writing but for this component it took me 5 min to create it and I'm only a beginner in delphi. For the debugging if something went wrong and the problem is in the component implementation than you just need to fix once (see C1)
C3: their is no implementation in your form(s) source code for your component just add it to your form and every thing is hidden (for example add a tedit and go to see the implementation in your forms source).
C4: You are creating a component after all this will open the door for you to create your own set of components like the Flatstyle or Indy open source components. so next time you need some thing like this you just drop it in your form designer and you are done.
Way number 2: frames
C1: It is like way number 1 because you are creating a component but it is visually this time. modifying the source frame will change the effects and properties of the replicas, also you can add extra handling to your replicas.
the event handler of the replica's Onchange event is like this
procedure TForm1.Frame2Edit1Change(Sender: TObject);
begin
Frame2.Edit1Change(Sender); //<-- this is the original onchange event you setup at the beginning
//<-- you can extra handling here if you want one of the replicas to behave differently than the original
end;
C2: same time and maybe faster than way number 1.
C3: over all, it has the same out come as way number 1.
C4: unlike way number 1 you can not use frames created in project A in project B. So your coding and debugging will stay in project A.
Way number 3: component template.
C1: you are not creating a component you are creating a repleca/macro of the exact steps you did in your last project. changing one will not change the others they are separated.
C2: same time and maybe faster than way number 1.
C3: each time you add a template to your form the events code will be added (not a good view if it is a long Onchange code).
C4: You can use templates created in project A in project B. However what you wrote in project A will be in project B (see c1) even the references of variables that don't exist in project B (this can be hard to debug and misleading, considering the period of time between each use of the template).
Conclusion: each of the ways presented will consume time to code and debug and all of them will do the task, How ever for the sake of simplicity and the reuse with minimum risks Way number 1 is the safe choice here because it will give you the chance to update and upgrade safely. also debug faster.
the good thing also about way number 1 is that after a while when you will forget the implementation and how things are working internally. The only thing that should stay in mind is the purpose of the component because it will become one of the various component you use (you don't know how Tedit is implemented and you don't need to but yet you use it in every single project you create).
based on the criteria given Way number 1 is the best.
Maybe using a container control that contains both controls is a simpler alternative. I am using ccpack for this.
https://sourceforge.net/projects/ccpack/
Custom Containers Pack (CCPack) is an integrated tool and component mini-
library to produce and maintain composite controls (or simply “composites”)
and other containers (forms, data modules and frames). The process of
building composite components looks like ActiveForm and Frame creating, but
the result is the native VCL component. You can create new composites just
as usual forms.
You can create a Frame and then register that Frame as a component. The end result is very similar to creating a code only component where the sub components are created in the constructor (Nasreddine's number 1 option). However this method allows you to visually design the component and use the object inspector to create your event handlers.
Here is a Stack Overflow question that shows how to register the frame:
How to Improve the Use of Delphi Frames
Short Version
I am trying to implement my first ever Component Editor for a custom button I have made. With the help of some online articles I have successfully installed the editor and can see the menu item when I right click on my button in the Form Designer.
But this component editor menu is not showing when selecting more than one of my button controls.
Do Component Editors only work with single selected controls by default, or can they work with multiple selected controls and if so how?
Long Version
I was in the process of implementing a TPropertyEditor for one of my own components but have now decided that a TComponentEditor would be better served, or so I thought.
Basically I have a TCustomButton which I have ownerdrawn, this button component has several published properties for changing the appearance such as the border and fill color etc.
The Component Editor I am implementing displays in the context menu a new menu item to "Load settings from a File". When executed a simple TOpenDialog is shown to which you can select the appropriate file, for example an Ini File which I then read and set the values from the File accordingly.
Everything is working good from what I can see, but as I am still sort of new and getting to grips with the whole custom controls side of Delphi I noticed something that does not happen - I am not sure if this is the actual intended behavior or whether I can change it.
The problem is using the Component Editor menu on multiple selected instances of my button control. If just one button is selected and I right click in the Designer, my menu is shown at the top of the context menu, however multiple selected controls do not display the Component Editor menu.
Code Sample
type
TMyButtonEditor = class(TComponentEditor)
public
procedure ExecuteVerb(Index: Integer); override;
function GetVerb(Index: Integer): string; override;
function GetVerbCount: Integer; override;
end;
implementation
{ TMyButtonEditor }
procedure TMyButtonEditor.ExecuteVerb(Index: Integer);
var
OpenDialog: TOpenDialog;
begin
case Index of
0:
begin
OpenDialog := TOpenDialog.Create(nil);
try
OpenDialog.Filter := 'All Files (*.*)|*.*';
if OpenDialog.Execute then
begin
// handle opened file..
end;
finally
OpenDialog.Free;
end;
end;
end;
end;
function TMyButtonEditor.GetVerb(Index: Integer): string;
begin
case Index of
0:
begin
Result := 'Load settings from File...';
end;
end;
end;
function TMyButtonEditor.GetVerbCount: Integer;
begin
Result := 1;
end;
In register procedure unit:
RegisterComponentEditor(TMyButton, TMyButtonEditor);
From what I can see only single components can use a Component Editor at any given time, or am I wrong and they can be used on multiple controls?
I was hoping to select say maybe 3 or 4 of my buttons on the Form Designer and use the Component Editor to apply imported settings on those buttons all at once.
Component editors can only operate on a single component.
This is one very good reason to prefer making properties available through the Object Inspector rather than component editors, wherever possible. Because the Object Inspector can operate on multiple components at once.
When the number of actions in DXE IDE grows, in a single actionlist, it quickly becomes hard to locate the action you want to inspect or change.
There is no search / filter box like there is for components, and actions are not automatically sorted. Sorting action declarations in code does not alter the order in which they are displayed in the actionlist editor.
Not even incremental search works: if you focus the actionlist pane and start typing, the keypresses go to the object inspector (and you inadvertently change some property or other). Major annoyance!
Is there perhaps a hidden setting, a registry hack (there are quite a few for Delphi), or maybe a third-party extension that would keep actions sorted?
Write a small IDE plugin that extends the context menu of a TActionList with a Sort option. For sorting the actionlist you can use this code:
procedure SortActions(ActionList: TActionList);
var
act: TContainedAction;
arr: TArray<TContainedAction>;
I: Integer;
begin
SetLength(arr, ActionList.ActionCount);
for I := 0 to ActionList.ActionCount - 1 do begin
arr[I] := ActionList[I];
end;
TArray.Sort<TContainedAction>(arr,
TDelegatedComparer<TContainedAction>.Create(
function(const Left, Right: TContainedAction): Integer
begin
result := CompareText(Left.Name, Right.Name);
end));
for I := 0 to High(arr) do
arr[I].Index := I;
end;
You could sort them in the dfm file. You would want to write a little utility script to do it.
Or a workaround would be to use categories to make the list of actions more manageable.
I have a TPanel pnlMain, where several dynamic TPanels are created (and pnlMain is their Parent) according to user actions, data validations, etc. Every panel contains one colored grid full of strings. Apart from panels, there are some open source arrows components and a picture. Whole bunch of stuff.
Now I want user to be able to print this panel (I asked how to do it on this question), but before printing, user must be presented with a new form, containing copy of pnlMain. On this form user has to do some changes, add few components and then print his customized copy of pnlMain. After printing user will close this form and return to original form with original pnlMain. And – as you can guess – original pnlMain must remain intact.
So is there any clever way to copy whole TPanel and it’s contents? I know I can make it manually iterating through pnlMain.Controls list.
Code based as iterating on child controls, but not bad in anyway ;-)
procedure TForm1.btn1Click(Sender: TObject);
function CloneComponent(AAncestor: TComponent): TComponent;
var
XMemoryStream: TMemoryStream;
XTempName: string;
begin
Result:=nil;
if not Assigned(AAncestor) then
exit;
XMemoryStream:=TMemoryStream.Create;
try
XTempName:=AAncestor.Name;
AAncestor.Name:='clone_' + XTempName;
XMemoryStream.WriteComponent(AAncestor);
AAncestor.Name:=XTempName;
XMemoryStream.Position:=0;
Result:=TComponentClass(AAncestor.ClassType).Create(AAncestor.Owner);
if AAncestor is TControl then TControl(Result).Parent:=TControl(AAncestor).Parent;
XMemoryStream.ReadComponent(Result);
finally
XMemoryStream.Free;
end;
end;
var
aPanel: TPanel;
Ctrl, Ctrl_: TComponent;
i: integer;
begin
//handle the Control (here Panel1) itself first
TComponent(aPanel) := CloneComponent(pnl1);
with aPanel do
begin
Left := 400;
Top := 80;
end;
//now handle the childcontrols
for i:= 0 to pnl1.ControlCount-1 do begin
Ctrl := TComponent(pnl1.Controls[i]);
Ctrl_ := CloneComponent(Ctrl);
TControl(Ctrl_).Parent := aPanel;
TControl(Ctrl_).Left := TControl(Ctrl).Left;
TControl(Ctrl_).top := TControl(Ctrl).top;
end;
end;
code from Delphi3000 article
Too much code... ObjectBinaryToText and ObjectTextToBinary do the job nicely using streaming.
Delphi 7 have a code example, don't know 2009 (or 2006, never bothered to look) still have it.
See D5 help file for those functions (don't have d5 available here).
I'd do it by using RTTI to copy all the properties. You'd still have to iterate over all the controls, but when you need to set up the property values, RTTI can help automate the process. You can get an example towards the bottom of this article, where you'll find a link to some helper code, including a CopyObject routine.