I have a TTreeView on my form which gets populated from a DB table. The list currently has 22 items and all of them have checkboxes that can be checked.
The TTreeView is on a TForm that has a TPageControl with a pre-made TTabSheet and all other TTabSheets are created dynamically and assigned TFrames to them.
My current code to create a new TTabSheet at runtime looks like this:
procedure TForm1.Button2Click(Sender: TObject);
var
aTab: TTabSheet;
begin
aTab := TTabSheet.create(self);
aTab.Name := 'tabProduct_' + IntToStr(PageControl1.PageCount+1);
aTab.PageControl := PageControl1;
aTab.Caption := 'Product ' + IntToStr(aTab.PageIndex);
LoadFrame(aTab.PageIndex);
end;
The code for the LoadFrame() procedure is:
procedure TForm1.LoadFrame(const index: integer);
var
aClassName: string;
aFrameClass : TFrameClass;
I: Integer;
begin
if index >= 50 then
raise Exception.create('Max product count reached');
if index >= Length(frames) then
SetLength(frames, index+1);
if assigned(frames[curIndex]) then frames[curIndex].hide;
if not assigned(frames[index]) then
begin
if index = 0 then
aClassname := 'TframeClient' // client
else
aClassname := 'TframeProdus'; // anything over pageindex 0 is a product
aFrameClass := TFrameClass(GetClass(aClassname));
if not assigned(aFrameClass) then
raise exception.createfmt('Could not find class %s', [aClassname]);
frames[index] := aFrameClass.Create(self);
frames[index].name := 'myframe' + IntToStr(index); // unique name
frames[index].parent := PageControl1.pages[index];
frames[index].align := alClient;
end;
frames[index].show;
curIndex := Index;
end;
Other relevant code:
type
TFrameArray = array of TFrame;
<...>
private
{ Private declarations }
curIndex: integer;
frames: TFrameArray;
procedure LoadFrame(const index: integer);
public
{ Public declarations }
end;
TFrameClass = class of TFrame;
<...>
Let's say I check the box next to items 1, 5 and 13 in the TTreeView.
How can I determine that and modify the Button2 code to create TTabSheets and their TFrames only for the items I have checked in the TTreeView?
Meanwhile, managed to figure out this approach and it seems to work well
procedure TForm1.Button2Click(Sender: TObject);
var
aTab: TTabSheet;
i: Integer;
begin
for i:=1 to TreeView1.Items.Count do
begin
if TreeView1.Items[i-1].Checked then
begin
aTab := TTabSheet.create(self);
aTab.Name := 'tabProduct_' + IntToStr(PageControl1.PageCount+1);
aTab.PageControl := PageControl1;
aTab.Caption := TreeView1.Items.Item[i-1].Text;
LoadFrame(aTab.PageIndex);
end;
end;
end;
Related
I have a problem with Text inside of a found TEdit.
This is my code:
function TfrmGenerateExam.zlicz_liczby(Component: TControl): integer;
var
i, j: integer;
begin
Result := 0;
for i := 0 to Component.ComponentCount - 1 do
begin
for j := 0 to Panel.ComponentCount - 1 do
begin
if Components[j] is TEdit then
begin
Result := Result + ???;
end;
end;
end;
end;
In a nutshell:
I create dynamic panels with ComboBoxes, Edits, Buttons etc.
When I have some panels, I want to count the edits which are in panels, which are in ScrollBox:
What do I need to put here?
if Components[j] is TEdit then
begin
Result := Result + ???;
end;
The code provided does not match the screenshot shown. What is being passed as the Component parameter to zlicz_liczby()? Is it the Form itself? The ScrollBox? A specified Panel?
Let's just iterate the Panels in the ScrollBox directly. Try something more like this:
function TfrmGenerateExam.zlicz_liczby: Integer;
var
i, j: integer;
Panel: TPanel;
begin
Result := 0;
for i := 0 to ScrollBox1.ControlCount - 1 do
begin
Panel := ScrollBox1.ControlCount[i] as TPanel;
for j := 0 to Panel.ControlCount - 1 do
begin
if Panel.Controls[j] is TEdit then
Result := Result + StrToIntDef(TEdit(Panel.Controls[j]).Text, 0);
end;
end;
end;
That being said, as #AndreasRejbrand stated in comments, you should use an array instead. When you create a new TPanel with a TEdit on it, put its TEdit into a TList<TEdit>, for instance. If you destroy the TPanel, remove its TEdit from the list. And then you can simply loop through that list whenever needed, without having to hunt for the TEdit controls at all. For example:
private
Edits: TList<TEdit>;
procedure TfrmGenerateExam.FormCreate(Sender: TObject);
begin
Edits := TList<TEdit>.Create;
end;
procedure TfrmGenerateExam.FormDestroy(Sender: TObject);
begin
Edits.Free;
end;
function TfrmGenerateExam.FillScrollBox;
var
Panel: TPanel;
Edit: TEdit;
begin
...
Panel := TPanel.Create(Self);
Panel.Parent := ScrollBox1;
...
Edit := TEdit.Create(Panel);
Edit.Parent := Panel;
...
Edits.Add(Edit);
...
end;
function TfrmGenerateExam.zlicz_liczby: Integer;
var
i: integer;
begin
Result := 0;
for i := 0 to Edits.Count - 1 do
Result := Result + StrToInt(Edits[i].Text);
end;
I want to add scroll bars (and/or scroll wheel support) to my existing Delphi application's popup menus, because they are often higher than the screen, and the built in scrolling is not good enough. How to make a popup menu with scrollbar? would be a great solution for me, except that it doesn't support sub-menus, which I absolutely require. The author of that solution hasn't been on StackOverflow since last July, so I don't think he'll reply to my comment. Can anyone see how to modify that code to add support for sub-menus? In case it matters, I need it to work with Delphi 2007.
I share #KenWhite's reservations about how users might receive a huge menu. So apologies to him and readers whose sensibilities the following might offend ;=)
Anyway, I hope the code below shows that in principle, it is straightforward
to create a TreeView based on a TPopUpMenu (see the routine PopUpMenuToTree) which reflects the structure of the PopUpMenu, including sub-items,
and make use of the TreeView's automatic vertical scroll bar. In the code, the
PopUpMenu happens to be on the same form as the TreeView, but that's only for
compactness, of course - the PopUpMenu could be on anothe form entirely.
As mentioned in a comment, personally I would base something like this on a
TVirtualTreeView (http://www.soft-gems.net/index.php/controls/virtual-treeview)
because it is far more customisable than a standard TTreeView.
type
TForm1 = class(TForm)
PopupMenu1: TPopupMenu;
TreeView1: TTreeView; // alClient-aligned
Start1: TMenuItem;
procedure FormCreate(Sender: TObject);
procedure TreeView1Click(Sender: TObject);
private
protected
procedure MenuItemClick(Sender : TObject);
procedure PopUpMenuToTree(PopUpMenu : TPopUpMenu; TreeView : TTreeView);
public
end;
var
Form1: TForm1;
[...]
procedure TForm1.FormCreate(Sender: TObject);
var
Item,
SubItem : TMenuItem;
i,
j : Integer;
begin
// (Over)populate a PopUpMenu
for i := 1 to 50 do begin
Item := TMenuItem.Create(PopUpMenu1);
Item.Caption := 'Item ' + IntToStr(i);
Item.OnClick := MenuItemClick;
PopUpMenu1.Items.Add(Item);
for j := 1 to 5 do begin
SubItem := TMenuItem.Create(PopUpMenu1);
SubItem.Caption := Format('Item %d Subitem %d ', [i, j]);
SubItem.OnClick := MenuItemClick;
Item.Add(SubItem);
end;
end;
// Populate a TreeView from the PopUpMenu
PopUpMenuToTree(PopUpMenu1, TreeView1);
end;
procedure TForm1.MenuItemClick(Sender: TObject);
var
Item : TMenuItem;
begin
if Sender is TMenuItem then
Caption := TMenuItem(Sender).Caption + ' clicked';
end;
procedure TForm1.PopUpMenuToTree(PopUpMenu: TPopUpMenu;
TreeView: TTreeView);
// Populates the TreeView with the Items in the PopUpMenu
var
i : Integer;
Item : TMenuItem;
RootNode : TTreeNode;
procedure AddItem(Item : TMenuItem; ParentNode : TTreeNode);
var
Node : TTreeNode;
j : Integer;
begin
Node := TreeView.Items.AddChildObject(ParentNode, Item.Caption, Item);
for j := 0 to Item.Count - 1 do begin
AddItem(Item.Items[j], Node);
end;
end;
begin
TreeView.Items.BeginUpdate;
TreeView.Items.Clear;
try
for i := 0 to PopUpMenu.Items.Count - 1 do begin
AddItem(PopUpMenu.Items[i], Nil);
end;
finally
TreeView.Items.EndUpdate;
end;
end;
procedure TForm1.TreeView1Click(Sender: TObject);
var
Node : TTreeNode;
Item : TMenuItem;
begin
if Sender is TTreeView then begin
Node := TTreeView(Sender).Selected;
Item := TMenuItem(Node.Data);
Item.Click;
end;
end;
Here's what I have done, by merging How to make a popup menu with scrollbar?, MartynA's code, and some of my own:
unit PopupUnit;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
StdCtrls, Menus, ComCtrls;
type
TPopupMode = (pmStandard, pmCustom);
TPopupMenu = class(Menus.TPopupMenu)
private
FPopupForm: TForm;
FPopupMode: TPopupMode;
public
constructor Create(AOwner: TComponent); override;
procedure Popup(X, Y: Integer); override;
property PopupForm: TForm read FPopupForm write FPopupForm;
property PopupMode: TPopupMode read FPopupMode write FPopupMode;
end;
type
TPopupForm = class(TForm)
private
FPopupForm: TForm;
FPopupMenu: TPopupMenu;
FTreeView: TTreeView;
procedure DoResize;
procedure TreeViewClick(Sender: TObject);
procedure TreeViewCollapsedOrExpanded(Sender: TObject; Node: TTreeNode);
procedure TreeViewKeyPress(Sender: TObject; var Key: Char);
procedure WMActivate(var AMessage: TWMActivate); message WM_ACTIVATE;
protected
procedure CreateParams(var Params: TCreateParams); override;
public
constructor Create(AOwner: TComponent; APopupForm: TForm;
APopupMenu: TPopupMenu); reintroduce;
end;
var
PopupForm: TPopupForm;
implementation
{$R *.dfm}
{ TPopupForm }
constructor TPopupForm.Create(AOwner: TComponent; APopupForm: TForm;
APopupMenu: TPopupMenu);
procedure AddItem(Item : TMenuItem; ParentNode : TTreeNode);
var
I : Integer;
Node : TTreeNode;
begin
if Item.Caption <> '-' then begin
Node := FTreeView.Items.AddChildObject(ParentNode, Item.Caption, Item);
Node.ImageIndex := Item.ImageIndex;
for I := 0 to Item.Count - 1 do begin
AddItem(Item.Items[I], Node);
end;
end;
end;
var
I: Integer;
begin
inherited Create(AOwner);
BorderStyle := bsNone;
FPopupForm := APopupForm;
FPopupMenu := APopupMenu;
FTreeView := TTreeView.Create(Self);
FTreeView.Parent := Self;
FTreeView.Align := alClient;
FTreeView.BorderStyle := bsSingle;
FTreeView.Color := clMenu;
FTreeView.Images := FPopupMenu.Images;
FTreeView.ReadOnly := TRUE;
FTreeView.ShowHint := FALSE;
FTreeView.ToolTips := FALSE;
FTreeView.OnClick := TreeViewClick;
FTreeView.OnCollapsed := TreeViewCollapsedOrExpanded;
FTreeView.OnExpanded := TreeViewCollapsedOrExpanded;
FTreeView.OnKeyPress := TreeViewKeyPress;
FTreeView.Items.BeginUpdate;
try
FTreeView.Items.Clear;
for I := 0 to FPopupMenu.Items.Count - 1 do
begin
AddItem(FPopupMenu.Items[I], NIL);
end;
finally
FTreeView.Items.EndUpdate;
end;
DoResize;
end;
procedure TPopupForm.CreateParams(var Params: TCreateParams);
begin
inherited;
Params.WindowClass.Style := Params.WindowClass.Style or CS_DROPSHADOW;
end;
procedure TPopupForm.DoResize;
const
BORDER = 2;
var
ItemRect, TVRect : TRect;
MF : TForm;
Node : TTreeNode;
begin
TVRect := Rect(0, 0, 0, 0);
Node := FTreeView.Items[0];
while Node <> NIL do begin
ItemRect := Node.DisplayRect(TRUE);
ItemRect.Right := ItemRect.Right + FTreeView.Images.Width + 1;
if ItemRect.Left < TVRect.Left then
TVRect.Left := ItemRect.Left;
if ItemRect.Right > TVRect.Right then
TVRect.Right := ItemRect.Right;
if ItemRect.Top < TVRect.Top then
TVRect.Top := ItemRect.Top;
if ItemRect.Bottom > TVRect.Bottom then
TVRect.Bottom := ItemRect.Bottom;
Node := Node.GetNextVisible;
end;
MF := Application.MainForm;
if Top + TVRect.Bottom - TVRect.Top > MF.Top + MF.ClientHeight then begin
TVRect.Bottom := TVRect.Bottom -
(Top + TVRect.Bottom - TVRect.Top - (MF.Top + MF.ClientHeight));
end;
if Left + TVRect.Right - TVRect.Left > MF.Left + MF.ClientWidth then begin
TVRect.Right := TVRect.Right -
(Left + TVRect.Right - TVRect.Left - (MF.Left + MF.ClientWidth));
end;
ClientHeight := TVRect.Bottom - TVRect.Top + BORDER * 2;
ClientWidth := TVRect.Right - TVRect.Left + BORDER * 2;
end;
procedure TPopupForm.TreeViewClick(Sender: TObject);
var
Node : TTreeNode;
Item : TMenuItem;
begin
if Sender is TTreeView then begin
Node := TTreeView(Sender).Selected;
if assigned(Node) then begin
Item := TMenuItem(Node.Data);
if assigned(Item.OnClick) then begin
Item.Click;
Close;
end;
end;
end;
end;
procedure TPopupForm.TreeViewCollapsedOrExpanded(Sender: TObject;
Node: TTreeNode);
begin
DoResize;
end;
procedure TPopupForm.TreeViewKeyPress(Sender: TObject; var Key: Char);
begin
if Ord(Key) = VK_RETURN then begin
TreeViewClick(Sender);
end
else if Ord(Key) = VK_ESCAPE then begin
Close;
end;
end;
procedure TPopupForm.WMActivate(var AMessage: TWMActivate);
begin
SendMessage(FPopupForm.Handle, WM_NCACTIVATE, 1, 0);
inherited;
if AMessage.Active = WA_INACTIVE then
Release;
FTreeView.Select(NIL, []);
end;
{ TPopupMenu }
constructor TPopupMenu.Create(AOwner: TComponent);
begin
inherited;
FPopupMode := pmStandard;
end;
procedure TPopupMenu.Popup(X, Y: Integer);
begin
case FPopupMode of
pmCustom:
with TPopupForm.Create(nil, FPopupForm, Self) do
begin
Top := Y;
Left := X;
Show;
end;
pmStandard: inherited;
end;
end;
end.
I am writing a class which will map a large legacy application's TMainMenu hierarchy to TActionMainMenuBar items.
The most important method, which borrows heavily from a EDN CodeCentralC article by Steve Trevethen, looks like this. I apologize for the length, but this really is probably the shortest length of code I could meaningfully show in this case:
procedure TMainMenuSkin.DoLoadMenu(
ActionList: TCustomActionList;
Clients: TActionClients;
AMenu: TMenuItem;
SetActionList: Boolean = True;
bRecurseFlag:Boolean = False);
var
I: Integer;
J: Integer;
AC: TActionClientItem;
ca : TCustomAction;
newAction : TSkinnedMenuAction;
aci:TActionClientItem;
submenuitem : TMEnuItem;
procedure PopulateNodeFromMenuItem(menuitem:TMenuItem);
begin
newAction := TSkinnedMenuAction.Create(Application.MainForm);
menuitem.FreeNotification(newAction);
newAction.FMenuItem := menuitem;
newAction.Name := 'action_'+newAction.FMenuItem.Name;
FNewMenuActions.Add(newAction);
newAction.ActionList := ActionManager;
newAction.Tag := menuitem.Tag;
ca := newAction;
ca.ImageIndex := menuitem.ImageIndex;
ca.HelpContext := menuitem.HelpContext;
ca.Visible := menuitem.Visible;
ca.Checked := menuitem.Checked;
ca.Caption := menuitem.Caption;
ca.ShortCut := menuitem.ShortCut;
ca.Enabled := menuitem.Enabled;
ca.AutoCheck := menuitem.AutoCheck;
ca.Checked := menuitem.Checked;
ca.GroupIndex := menuitem.GroupIndex;
AC.ImageIndex := menuitem.ImageIndex;
AC.ShortCut := menuitem.ShortCut;
AC.Action := newAction;
end;
begin
if not Assigned(AMenu) then
exit;
AMenu.RethinkHotkeys;
AMenu.RethinkLines;
Clients.AutoHotKeys := False;
for I := 0 to AMenu.Count - 1 do
begin
AC := Clients.Add;
AC.Caption := AMenu.Items[I].Caption;
// Assign the Tag property to the TMenuItem for reference
AC.Tag := Integer(AMenu.Items[I]);
AC.Action := TContainedAction(AMenu.Items[I].Action);
AC.Visible := AMenu.Items[I].Visible;
// original sample code only created placeholders for submenus. I want to populate
// fully because I need the actions and their keyboard shortcuts to exist.
submenuitem := AMenu.Items[I];
if submenuitem.Count > 0 then
begin
if not bRecurseFlag then
AC.Items.Add // old busted way to work
else
begin
// How do I recursively populate the Action Clients menu?
DoLoadMenu(ActionList, AC.ActionClients, submenuitem, true);
end;
end
else
if ((AMenu.Items[I].Caption <> '') and (AMenu.Items[I].Action = nil) and
(AMenu.Items[I].Caption <> '-')) then
begin
PopulateNodeFromMenuItem( AMenu.Items[I] );
end;
AC.Caption := AMenu.Items[I].Caption;
AC.ImageIndex := AMenu.Items[I].ImageIndex;
AC.HelpContext := AMenu.Items[I].HelpContext;
AC.ShortCut := AMenu.Items[I].ShortCut;
AC.Visible := AMenu.Items[I].Visible;
end;
end;
The most important line above is this one, and it's also the one
that is wrong:
DoLoadMenu(ActionList, AC.ActionClients, submenuitem, true);
If the code is written like that, then I get all the menu items displayed in a flattened form. So I need something like AC.GetMeSubItems.ActionClients in the line above,
but I can't figure out what it is. AC is of type TActionClientItem and is a toolbar button itself also created at runtime.
What I can't figure out for the life of me is, if I need to populate the Actions list and the menu items all at once, recursively, how do I do it? Perhaps my thinking is constrained by the sample code I started out with here. It seems like I'm one line of code away from knowing how to do this.
I have a feeling that I'm just not understanding very well the complex hiearchical relationships of the various classes I'm messing with here.
Update: The following SEEMS to work, but I don't trust myself.
aci := AC.Items.Add;
DoLoadMenu(ActionList, aci.Items, submenuitem, true);
Here's some code that I once wrote or found somewhere and which seems to do what you want to achieve: it is a converter class which copies the items of a Mainmenu to an ActionMainMenubar. It has some issues, but hopefully you can catch the point how it works.
The component assumes that you have a completed MainMenu on a form. You also need a TActionManager and an empty TActionMainMenubar. You create an instance of the TActionbarConverter in the OnCreate event of the MainForm, and assign its properties correspondingly, something like this:
procedure TForm1.FormCreate(Sender: TObject);
begin
with TActionbarConverter.Create(self) do begin
Form := self;
ActionManager := Actionmanager1;
ActionMainMenubar := ActionMainmenubar1;
end;
end;
You can change properties of the ActionManager, or use your own ColorMap to modify the appearance.
I hope that I don't get banned here by posting almost 400 lines of code here (don't know how to upload files...).
unit ActnbarCnv;
interface
uses
Classes, Menus, Forms, ComCtrls, ActnList, ActnMan, ActnMenus,
StdStyleActnCtrls, XPStyleActnCtrls;
type
TActionbarConverter = class(TComponent)
private
FForm : TCustomForm;
FMainMenu : TMainMenu;
FMainMenuToolbar : TToolbar;
FActionManager : TActionManager;
FActionMainMenubar : TActionMainMenubar;
FNewMenuActions : TList;
procedure ActionMainMenuBar_ExitMenuLoop(Sender:TCustomActionMenuBar;
Cancelled: Boolean);
procedure ActionMainMenubar_Popup(Sender:TObject; Item:TCustomActionControl);
procedure SetActionMainMenubar(Value:TActionMainMenubar);
procedure SetActionManager(Value:TActionManager);
procedure SetForm(Value:TCustomForm);
protected
procedure AnalyzeForm;
procedure Loaded; override;
procedure LoadMenu(ActionList: TCustomActionList; Clients: TActionClients;
AMenu: TMenuItem; SetActionList: Boolean = True);
procedure Notification(AComponent: TComponent; Operation: TOperation); override;
procedure ProcessMenu;
procedure UpdateActionMainMenuBar(Menu: TMenu);
public
constructor Create(AOwner:TComponent); override;
destructor Destroy; override;
procedure Update;
published
property Form : TCustomForm read FForm write SetForm;
property ActionManager : TActionManager read FActionManager write SetActionManager;
property ActionMainMenubar : TActionMainMenubar read FActionMainMenubar write SetActionMainMenubar;
end;
//==============================================================================
implementation
//==============================================================================
uses
SysUtils;
//==============================================================================
{ TABMenuAction -
This class is a special ActionBand menu action that stores the TMenuItem
that it is associated with so that if it is executed it can actually call the
TMenuItem.Click method simulating an actual click on the TMenuItem itself. }
type
TABMenuAction = class(TCustomAction)
private
FMenuItem: TMenuItem;
protected
procedure Notification(AComponent: TComponent; Operation: TOperation); override;
public
destructor Destroy; override;
procedure ExecuteTarget(Target: TObject); override;
function HandlesTarget(Target: TObject): Boolean; override;
end;
//------------------------------------------------------------------------------
destructor TABMenuAction.Destroy;
begin
if Assigned(FMenuItem) then FMenuItem.RemoveFreeNotification(Self);
inherited;
end;
//------------------------------------------------------------------------------
procedure TABMenuAction.ExecuteTarget(Target: TObject);
begin
if Assigned(FMenuItem) then FMenuItem.Click;
end;
//------------------------------------------------------------------------------
function TABMenuAction.HandlesTarget(Target: TObject): Boolean;
begin
Result := True;
end;
//------------------------------------------------------------------------------
procedure TABMenuAction.Notification(AComponent: TComponent; Operation: TOperation);
begin
if (Operation = opRemove) and (AComponent = FMenuItem)
then FMenuItem := nil;
end;
//------------------------------------------------------------------------------
// TActionbarConverter
//------------------------------------------------------------------------------
constructor TActionbarConverter.Create(AOwner:TComponent);
begin
inherited Create(AOwner);
FNewMenuActions := TList.Create;
if (AOwner is TCustomForm) then SetForm(TCustomForm(AOwner));
end;
//------------------------------------------------------------------------------
destructor TActionbarConverter.Destroy;
begin
FNewMenuActions.Free;
inherited Destroy;
end;
//------------------------------------------------------------------------------
procedure TActionbarConverter.ActionMainMenuBar_ExitMenuLoop(Sender:TCustomActionMenuBar;
Cancelled: Boolean);
var
I: Integer;
AnAction: TObject;
begin
// Clear the top level menu sub item and add a single dummy item which
// will cause them to be regenerated on the next menu loop. This is done
// because the IDE's menus can be very dynamic and this ensures that the
// menus will always be up-to-date.
for I := 0 to FActionManager.ActionBars[0].Items.Count - 1 do begin
FActionManager.ActionBars[0].Items[I].Items.Clear;
FActionManager.ActionBars[0].Items[I].Items.Add;
end;
// Any menuitems not linked to an action had one dynamically created for them
// during the menu loop so now we need to destroy them
while FNewMenuActions.Count > 0 do begin
AnAction := TObject(FNewMenuActions.Items[0]);
AnAction.Free;
FNewMenuActions.Delete(0);
end;
end;
//------------------------------------------------------------------------------
procedure TActionbarConverter.ActionMainMenubar_Popup(Sender:TObject;
Item: TCustomActionControl);
begin
// If the tag is not zero then we've already populated this submenu...
if Item.ActionClient.Items[0].Tag <> 0 then exit;
// ...otherwise this is the first visit to this submenu and we need to
// populate the actual ActionClients collection.
if Assigned(TMenuItem(Item.ActionClient.Tag).OnClick) then
TMenuItem(Item.ActionClient.Tag).OnClick(TMenuItem(Item.ActionClient.Tag));
Item.ActionClient.Items.Clear;
TMenuItem(Item.ActionClient.Tag).RethinkHotkeys;
LoadMenu(FActionManager, Item.ActionClient.Items, TMenuItem(Item.ActionClient.Tag), False);
end;
//------------------------------------------------------------------------------
procedure TActionbarConverter.AnalyzeForm;
var
i : integer;
begin
FMainMenu := nil;
FMainMenuToolbar := nil;
if Assigned(FForm) then begin
for i:=0 to FForm.ComponentCount-1 do
if (FForm.Components[i] is TMainMenu) then begin
FMainMenu := FForm.Components[i] as TMainMenu;
break;
end;
for i:=0 to FForm.ComponentCount-1 do
if (FForm.Components[i] is TToolbar) then begin
FMainMenuToolbar := FForm.Components[i] as TToolbar;
if FMainMenuToolbar.Menu = FMainMenu
then break
else FMainMenuToolbar := nil;
end;
end;
ProcessMenu;
end;
//------------------------------------------------------------------------------
procedure TActionbarConverter.Loaded;
begin
AnalyzeForm;
end;
//------------------------------------------------------------------------------
procedure TActionbarConverter.LoadMenu(ActionList: TCustomActionList;
Clients: TActionClients; AMenu: TMenuItem; SetActionList: Boolean = True);
{ This method dynamically builds the ActionBand menu from an existing
TMenuItem. }
var
I: Integer;
AC: TActionClientItem;
begin
AMenu.RethinkHotkeys;
AMenu.RethinkLines;
// Use the existing hotkeys from the TMenuItem
Clients.AutoHotKeys := False;
for I := 0 to AMenu.Count - 1 do begin
AC := Clients.Add;
AC.Caption := AMenu.Items[I].Caption;
// Assign the Tag property to the TMenuItem for reference
AC.Tag := Integer(AMenu.Items[I]);
AC.Action := TContainedAction(AMenu.Items[I].Action);
AC.Visible := AMenu.Items[I].Visible;
// If the TMenuItem has subitems add an ActionClient placeholder.
// Submenus are only populated when the user selects the parent item of the
// submenu.
if AMenu.Items[I].Count > 0 then
AC.Items.Add // Add a dummy indicating this item can/should be dynamically built
else
if (AMenu.Items[I].Caption <> '')
and (AMenu.Items[I].Action = nil)
and (AMenu.Items[I].Caption <> '-')
then begin
// The TMenuItem is not connected to an action so dynamically create
// an action.
AC.Action := TABMenuAction.Create(self); //Application.MainForm);
AMenu.Items[I].FreeNotification(AC.Action);
TABMenuAction(AC.Action).FMenuItem := AMenu.Items[I];
FNewMenuActions.Add(AC.Action);
AC.Action.ActionList := FActionManager;
AC.Action.Tag := AMenu.Items[I].Tag;
TCustomAction(AC.Action).ImageIndex := AMenu.Items[I].ImageIndex;
TCustomAction(AC.Action).HelpContext := AMenu.Items[I].HelpContext;
TCustomAction(AC.Action).Visible := AMenu.Items[I].Visible;
TCustomAction(AC.Action).Checked := AMenu.Items[I].Checked;
TCustomAction(AC.Action).Caption := AMenu.Items[I].Caption;
TCustomAction(AC.Action).ShortCut := AMenu.Items[I].ShortCut;
TCustomAction(AC.Action).Enabled := AMenu.Items[I].Enabled;
TCustomAction(AC.Action).AutoCheck := AMenu.Items[I].AutoCheck;
TCustomAction(AC.Action).Checked := AMenu.Items[I].Checked;
TCustomAction(AC.Action).GroupIndex := AMenu.Items[I].GroupIndex;
AC.ImageIndex := AMenu.Items[I].ImageIndex;
AC.ShortCut := AMenu.Items[I].ShortCut;
end;
AC.Caption := AMenu.Items[I].Caption;
AC.ImageIndex := AMenu.Items[I].ImageIndex;
AC.HelpContext := AMenu.Items[I].HelpContext;
AC.ShortCut := AMenu.Items[I].ShortCut;
AC.Visible := AMenu.Items[I].Visible;
end;
end;
//------------------------------------------------------------------------------
procedure TActionbarConverter.Notification(AComponent: TComponent;
Operation: TOperation);
begin
inherited;
if (Operation = opRemove) then
if (AComponent=FForm) then SetForm(nil);
end;
//------------------------------------------------------------------------------
procedure TActionbarConverter.ProcessMenu;
begin
if FMainMenu <> nil then begin
if FMainMenutoolbar <> nil then begin
FMainMenutoolbar.Menu := nil;
FMainMenutoolbar.Visible := false;
end;
FForm.Menu := nil;
if FActionManager=nil then begin
FActionManager := TActionManager.Create(self);
// FActionManager.Style := XPStyle;
end else
FActionManager.Actionbars.Clear;
FActionManager.Images := FMainMenu.Images;
FActionManager.Actionbars.Add;
if FActionMainMenubar=nil then begin
FActionMainMenubar := TActionMainMenubar.Create(self);
if (FMainMenuToolbar <> nil)
then FActionMainMenubar.Parent := FMainMenuToolbar.Parent
else FActionMainMenubar.Parent := FForm;
end;
FActionMainMenubar.ActionManager := FActionManager;
FActionMainMenubar.OnPopup := ActionMainMenubar_Popup;
FActionMainMenubar.OnExitMenuLoop := ActionMainMenubar_ExitMenuLoop;
UpdateActionMainMenubar(FMainMenu);
end;
end;
//------------------------------------------------------------------------------
procedure TActionbarConverter.SetActionMainmenubar(Value:TActionMainMenubar);
begin
if Value=nil then begin
FActionMainMenubar := TActionMainMenubar.Create(self);
if FMainmenuToolbar <> nil
then FActionMainMenubar.Parent := FMainMenuToolbar.Parent
else FActionMainMenubar.Parent := FForm;
end else begin
FActionMainMenubar.Free;
FActionMainMenubar := value;
end;
Update;
end;
//------------------------------------------------------------------------------
procedure TActionbarConverter.SetActionManager(value:TActionManager);
begin
if Value = nil then begin
FActionManager := TActionManager.Create(self);
FActionManager.Style := XPStyle;
end else begin
FActionManager := value;
// if FMainMenu <> nil then FActionManager.Images := FMainMenu.Images;
end;
Update;
end;
//------------------------------------------------------------------------------
procedure TActionbarConverter.SetForm(value:TCustomForm);
begin
if FForm <> Value then begin
FForm := Value;
AnalyzeForm;
end;
end;
//------------------------------------------------------------------------------
procedure TActionbarConverter.Update;
begin
AnalyzeForm;
end;
//------------------------------------------------------------------------------
procedure TActionbarConverter.UpdateActionMainMenuBar(Menu: TMenu);
{ This routine should probably also check for Enabled state although it would
be very wierd to have a top level menu disabled. }
function RefreshItems: Boolean;
var
I: Integer;
begin
Result := FMainMenu.Items.Count <> FActionManager.ActionBars[0].Items.Count;
if not Result then
for I := 0 to FMainMenu.Items.Count - 1 do
if AnsiCompareText(
FMainMenu.Items[I].Caption,
FActionManager.ActionBars[0].Items[I].Caption ) <> 0
then begin
Result := True;
break;
end;
end;
begin
if not (csLoading in FActionManager.ComponentState) and RefreshItems
then begin
// Clear any existing items and repopulate the TActionMainMenuBar
FActionManager.ActionBars[0].Items.Clear;
FActionManager.ActionBars[0].ActionBar := nil;
LoadMenu(FActionManager, FActionManager.ActionBars[0].Items, FMainMenu.Items);
FActionManager.ActionBars[0].ActionBar := FActionMainMenuBar;
// Update the size of the main menu
with FActionMainMenuBar do
SetBounds(
0,
0,
Controls[ControlCount-1].BoundsRect.Right + 2 + FActionMainMenuBar.HorzMargin,
Height
);
end;
end;
end.
We have a combo box with more than 100 items.
We want to filter out the items as we enter characters in combo box. For example if we entered 'ac' and click on the drop down option then we want it to display items starting with 'ac' only.
How can I do this?
Maybe you'd be happier using the autocompletion features built in to the OS. I gave an outline of how to do that here previously. Create an IAutoComplete object, hook it up to your combo box's list and edit control, and the OS will display a drop-down list of potential matches automatically as the user types. You won't need to adjust the combo box's list yourself.
To expand on Rob's answer about using the OnChange event, here is an example of how to do what he suggests.
procedure TForm1.FormCreate(Sender: TObject);
begin
FComboStrings := TStringList.Create;
FComboStrings.Add('Altair');
FComboStrings.Add('Alhambra');
FComboStrings.Add('Sinclair');
FComboStrings.Add('Sirius');
FComboStrings.Add('Bernard');
FComboStrings.Sorted := True;
ComboBox1.AutoComplete := False;
ComboBox1.Items.Text := FComboStrings.Text;
ComboBox1.Sorted := True;
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
FreeAndNil(FComboStrings);
end;
procedure TForm1.ComboBox1Change(Sender: TObject);
var
Filter: string;
i: Integer;
idx: Integer;
begin
// Dropping down the list puts the text of the first item in the edit, this restores it
Filter := ComboBox1.Text;
ComboBox1.DroppedDown := True;
ComboBox1.Text := Filter;
ComboBox1.SelStart := Length(Filter);
for i := 0 to FComboStrings.Count - 1 do
if SameText(LeftStr(FComboStrings[i], Length(ComboBox1.Text)), ComboBox1.Text) then
begin
if ComboBox1.Items.IndexOf(FComboStrings[i]) < 0 then
ComboBox1.Items.Add(FComboStrings[i]);
end
else
begin
idx := ComboBox1.Items.IndexOf(FComboStrings[i]);
if idx >= 0 then
ComboBox1.Items.Delete(idx);
end;
end;
My brief contribution working with objects in the combobox:
procedure FilterComboBox(Combo: TComboBox; DefaultItems: TStrings);
function Origin: TStrings;
begin
if Combo.Tag = 0 then
begin
Combo.Sorted := True;
Result := TStrings.Create;
Result := Combo.Items;
Combo.Tag := Integer(Result);
end
else
Result := TStrings(Combo.Tag);
end;
var
Filter: TStrings;
I: Integer;
iSelIni: Integer;
begin
if(Combo.Text <> EmptyStr) then
begin
iSelIni:= Length(Combo.Text);
Filter := TStringList.Create;
try
for I := 0 to Origin.Count - 1 do
if AnsiContainsText(Origin[I], Combo.Text) then
Filter.AddObject(Origin[I], TObject(Origin.Objects[I]));
Combo.Items.Assign(Filter);
Combo.DroppedDown:= True;
Combo.SelStart := iSelIni;
Combo.SelLength := Length(Combo.Text);
finally
Filter.Free;
end;
end
else
Combo.Items.Assign(DefaultItems);
end;
You can handle the combo box's OnChange event. Keep a master list of all items separate from the UI control, and whenever the combo box's edit control changes, adjust the combo box's list accordingly. Remove items that don't match the current text, or re-add items from the master list that you removed previously.
As Rob already answered, you could filter on the OnChange event, see the following code example. It works for multiple ComboBoxes.
{uses}
Contnrs, StrUtils;
type
TForm1 = class(TForm)
ComboBox1: TComboBox;
ComboBox2: TComboBox;
procedure FormCreate(Sender: TObject);
procedure ComboBoxChange(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
FComboLists: TList;
procedure FilterComboBox(Combo: TComboBox);
end;
implementation
{$R *.dfm}
procedure TForm1.ComboBoxChange(Sender: TObject);
begin
if Sender is TComboBox then
FilterComboBox(TComboBox(Sender));
end;
procedure TForm1.FilterComboBox(Combo: TComboBox);
function Origin: TStrings;
begin
if Combo.Tag = 0 then
begin
Combo.Sorted := True;
Result := TStringList.Create;
Result.Assign(Combo.Items);
FComboLists.Add(Result);
Combo.Tag := Integer(Result);
end
else
Result := TStrings(Combo.Tag);
end;
var
Filter: TStrings;
I: Integer;
begin
Filter := TStringList.Create;
try
for I := 0 to Origin.Count - 1 do
if AnsiStartsText(Combo.Text, Origin[I]) then
Filter.Add(Origin[I]);
Combo.Items.Assign(Filter);
Combo.SelStart := Length(Combo.Text);
finally
Filter.Free;
end;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
FComboLists := TObjectList.Create(True);
// For Each ComboBox, set AutoComplete at design time to false:
ComboBox1.AutoComplete := False;
ComboBox2.AutoComplete := False;
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
FComboLists.Free;
end;
I programming with adodb/dbgo and try to use this code:
procedure TfrMain.dbeNoMejaKeyPress(Sender: TObject; var Key: Char);
begin
dmWarbam.TblTrans_temp.Filtered := False;
dmWarbam.TblTrans_temp.Filter := 'ID_ITEM = ' + QuotedStr(dbeNoMeja.Text);
dmWarbam.TblTrans_temp.Filtered := True;
end;
and
procedure TfrMain.dbeNoMejaChange(Sender: TObject);
begin
dmWarbam.TblTrans_temp.Filtered := False;
dmWarbam.TblTrans_temp.Filter := 'ID_ITEM = ' + QuotedStr(dbeNoMeja.Text);
dmWarbam.TblTrans_temp.Filtered := True;
end;
But none of above can work, when i press key on dbeNoMeja it didn't filter but instead the dataset inserting broken/incomplete data to database.
Can someone give me some example that working (full code)
If the dbedit is connected to the same table as the one you want to filter you have a problem, because the table goes into the dsEdit state once you start entering text.
Use a normal TEdit, and append a wildcard (*) to the string in the filter
dmWarbam.TblTrans_temp.Filter := 'ID_ITEM = ' + QuotedStr(edtNoMeja.Text+'*');
Code example adapted from Delphi-NeftalĂ. Nice and simple!
procedure TForm1.Edit1Change(Sender: TObject);
begin
// incremental search
ClientDataSet1.Locate('FirstName', Edit1.Text, [loCaseInsensitive, loPartialKey]);
Exit;
// actual data filtering
if (Edit1.Text = '') then begin
ClientDataSet1.Filtered := False;
ClientDataSet1.Filter := '';
end
else begin
ClientDataSet1.Filter := 'FirstName >= ' + QuotedStr(Edit1.Text);
ClientDataSet1.Filtered := True;
end;
end;
Setting ClientDataSet's provider to ADO DB (in your case):
Path := ExtractFilePath(Application.ExeName) + 'Data.MDB';
// Exist the MDB?
if FileExists(path) then begin
ClientDataSet1.ProviderName := 'DSProvider';
ADOQ.Open;
ClientDataSet1.Active := True;
ADOQ.Close;
ClientDataSet1.ProviderName := '';
lbldata.Caption := ExtractFileName(path);
Exit;
end;
I found a good solution in Expert Exchange,
unit dbg_filter_u;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
Grids, DBGrids, DBTables, Db, StdCtrls;
type
TForm1 = class(TForm)
Table1: TTable;
DataSource1: TDataSource;
Query1: TQuery;
DBGrid1: TDBGrid;
cbFilterBox: TComboBox; //a hidden combobox (Style = csDropDownList)
procedure Table1AfterOpen(DataSet: TDataSet);
procedure Table1AfterPost(DataSet: TDataSet);
procedure DBGrid1TitleClick(Column: TColumn);
procedure cbFilterBoxChange(Sender: TObject);
procedure cbFilterBoxClick(Sender: TObject);
procedure cbFilterBoxExit(Sender: TObject);
private
Procedure FillPickLists(ADBGrid : TDBGrid);
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.DFM}
//For Accessing some Protected Methods
type TCDBGrid = class(TCustomDBGrid);
//Storing the Values into the Picklist-Propertys of the asscociated Columns,
//this may cost time depending on the amount of the dataset
Procedure TForm1.FillPickLists(ADBGrid : TDBGrid);
const
SQL_Text = 'Select Distinct %s From %s';
var
q : TQuery;
i : integer;
Begin
If (Assigned(ADBGrid)) and
(Assigned(ADBGrid.Datasource)) and
(Assigned(ADBGrid.Datasource.DataSet)) Then
Begin
If (ADBGrid.Datasource.DataSet is ttable) Then
begin
q := TQuery.Create(self);
try
try
q.DatabaseName := TTable(ADBGrid.Datasource.DataSet).DataBaseName;
for i := 0 to ADBGrid.Columns.Count - 1 do //for each column
begin
if ADBGrid.Columns[i].Field.FieldKind = fkData then //only physical fields
begin
ADBGrid.Columns[i].ButtonStyle := cbsNone; //avoid button-showing
ADBGrid.Columns[i].PickList.Clear;
q.Close;
q.SQL.text := Format(SQL_Text,[ADBGrid.Columns[i].Field.FieldName,TTable(ADBGrid.Datasource.DataSet).TableName]);
q.Open;
While not q.eof do
begin
ADBGrid.Columns[i].PickList.Add(q.Fields[0].AsString);
q.next;
end;
q.close;
end;
end;
finally
q.free;
end;
except
raise;
end;
end else
Raise exception.Create('This Version works only for TTables');
end else
Raise Exception.Create('Grid not properly Assigned');
end;
//Initial-Fill
procedure TForm1.Table1AfterOpen(DataSet: TDataSet);
begin
FillPickLists(DBGrid1);
end;
//Refill after a change
procedure TForm1.Table1AfterPost(DataSet: TDataSet);
begin
FillPickLists(DBGrid1);
end;
//Show a Dropdownbox for selecting, instead the title on Titleclick
procedure TForm1.DBGrid1TitleClick(Column: TColumn);
var
ARect : Trect;
DummyTC : TColumn;
begin
If column.PickList.Count > 0 then
begin
cbFilterbox.Items.Assign(column.PickList);
ARect := TCDBGrid(Column.Grid).CalcTitleRect(Column,0,DummyTC);
cbfilterBox.top := Column.Grid.Top+1;
cbfilterBox.left := Column.Grid.left+Arect.Left+1;
cbFilterbox.Width := Column.Width;
cbFilterBox.Tag := Integer(Column); //Store the columnPointer
cbFilterBox.Show;
cbFilterBox.BringToFront;
cbFilterBox.DroppedDown := True;
end;
end;
//Build up the Filter
procedure TForm1.cbFilterBoxChange(Sender: TObject);
begin
cbFilterBox.Hide;
if cbFilterBox.Text <> TColumn(cbFilterBox.Tag).Title.Caption then
begin
Case TColumn(cbFilterBox.Tag).Field.DataType of
//Some Fieldtypes
ftstring :
TTable(TDBGrid(TColumn(cbFilterBox.Tag).Grid).Datasource.Dataset).Filter :=
TColumn(cbFilterBox.Tag).Field.FieldName+' = '+QuotedStr(cbFilterBox.Text);
ftInteger,
ftFloat :
TTable(TDBGrid(TColumn(cbFilterBox.Tag).Grid).Datasource.Dataset).Filter :=
TColumn(cbFilterBox.Tag).Field.FieldName+' = '+cbFilterBox.Text;
end;
TTable(TDBGrid(TColumn(cbFilterBox.Tag).Grid).Datasource.Dataset).Filtered := True;
end;
end;
//some Hiding-events
procedure TForm1.cbFilterBoxClick(Sender: TObject);
begin
cbFilterBox.Hide;
end;
procedure TForm1.cbFilterBoxExit(Sender: TObject);
begin
cbFilterBox.Hide;
end;
end.