Odd behaviour when adding a toolbutton to the delphi ide - delphi

I was trying out some things and wanted to make a delphi IDE extension.
My basic idea was expanding the ToDo list feature that is currently in the IDE.
Step one was adding a toolbutton to the IDE which would open a form showing the todo items.
But I noticed some weird things that I hopefully caused myself since that would mean it can be easily fixed.
I am adding my toolbutton to the CustomToolbar, which is the one with the blue questionmark (see screenshot later)
The thing that happens: I install my package and the button is added with the correct image, right next to the existing button.
Now I close the modal form with the installed packages and then the blue questionmark changes.
Don't mind the icon I used, I will use a different one eventually but ok.
So basicly the existing item changes to my own icon but disabled for some reason. And I can't figure out why this happens.
As suggested in the guide I found online I used a TDatamodule to implement my code.
My code:
procedure TDatamoduleToDoList.Initialize;
var
LResource, LhInst: Cardinal;
begin
LhInst := FindClassHInstance(Self.ClassType);
if LhInst > 0 then
begin
LResource := FindResource(LhInst, 'icon', RT_Bitmap);
if LResource > 0 then
begin
FBMP := Vcl.Graphics.TBitmap.Create;
FBMP.LoadFromResourceName(LhInst, 'icon');
end
else
DoRaise('Resource not found');
end
else
DoRaise('HInstance Couldn''t be found');
FToDoAction := TTodoAction.Create(Self);
FToDoAction.Category := actionCat;
FToDoAction.ImageIndex := FIntaServices.ImageList.Add(FBMP, nil);
FToDoAction.Name := 'my_very_own_action_man';
end;
procedure TDatamoduleToDoList.DataModuleCreate(Sender: TObject);
begin
//Create extension
if Supports(BorlandIDEServices, INTAServices, FIntaServices) then
begin
Initialize;
if FToDoAction <> nil then
FCustBut := TSpeedButton(FIntaServices.AddToolButton(sCustomToolBar, 'CstmToDoList', FToDoAction))
else
DoRaise('Initialize failed');
end
else
DoRaise('Something went wrong');
end;
DoRaise is my own procedure that simply destroys all of my objects and raises an exception, did this to prevent mem leaks in the ide.
But, I think, I don't do anything weird but yet this problem occurs.
So I'm hoping someone here might have done something simular and sees the error in my code.
Thanks in advance.
P.s. if you need any more info or see the rest of the unit let me know and ill put the entire unit on github or something like that.
Edit:
Thanks to #Uwe Raabe I managed to solve this problem.
The problem was found in the comments of INTAServices.AddImages
AddImages takes all the images from the given image list and adds them
to the
main application imagelist. It also creates an internal mapping array from the
original image indices to the new indices in the main imagelist. This
mapping is used by AddActionMenu to remap the ImageIndex property of the
action object to the new ImageIndex. This should be the first method
called when adding actions and menu items to the main application window.
The return value is the first index in the main application image list of
the first image in the source list. Call this function with an nil
image list to clear the internal mapping array. Unlike the AddImages function from
the ancestor interface, this version takes an Ident that allows the same base index
to be re-used. This is useful when the IDE implements demand-loading of
personalities so that the images will only get registered once and the same image
indices can be used.
The solution eventually was adding my image to a local imagelist which was added to the imagelist of IntaServices
Code:
procedure TDatamoduleToDoList.DataModuleCreate(Sender: TObject);
begin
//Create extension
if Supports(BorlandIDEServices, INTAServices, FIntaServices) then
begin
Initialize;
if FToDoAction <> nil then
begin
FCustBut := TSpeedButton(FIntaServices.AddToolButton(sCustomToolBar, 'CstmToDoList', FToDoAction));
FToDoAction.ImageIndex := FIntaServices.AddImages(FImages);//This is the fix
end
else
DoRaise('Initialize failed');
end
else
DoRaise('Something went wrong');
end;

You are not supposed to fiddle around with the INTAServices.ImageList directly. Instead use either INTAServices.AddMasked or INTAServices.AddImages (in case you have a local imagelist in your datamodule).
You can safely use the INTAServices.ImageList to be connected to your controls, but you should neither Add nor Delete the images in it directly.

Related

How can I remove a PCB Object in Altium PCB Library using Altium scripting systems?

I am writing a Delphi Altium script to remove all tracks in TopOverLay inside the PCB Library.
When running the script though, nothing happens (the tracks are not removed).
I don't know why. Can you please help me?
Here below is my code :
procedure RemoveTrackObject;
Var
MyComponent : IPCB_LibComponent;
MyTrack : IPCB_Track;
Iterator : IPCB_GroupIterator;
DeleteList : TInterfaceList;
TrackTemp : IPCB_Track;
i : Integer;
begin
MyComponent := PCBServer.GetCurrentPCBLibrary.CurrentComponent;
//////////////////////////////////////////////////////////////////////
Iterator := MyComponent.GroupIterator_Create;
Iterator.AddFilter_ObjectSet(Mkset(eTrackObject));
Iterator.AddFilter_LayerSet(Mkset(eTopOverLay));
DeleteList := TInterfaceList.Create;
try
MyTrack := Iterator.FirstPCBObject;
While MyTrack <> nil do
begin
DeleteList.Add(MyTrack);
MyTrack := Iterator.NextPCBObject;
end;
finally
MyComponent.GroupIterator_Destroy(Iterator);
end;
try
PCBServer.PreProcess;
for i := 0 to DeleteList.Count - 1 do
begin
TrackTemp := DeleteList.Items[i];
MyComponent.RemovePCBObject(TrackTemp);
end;
finally
PCBServer.PostProcess;
DeleteList.Free;
end;
Client.SendMessage('PCB:Zoom', 'Action=Redraw' , 255, Client.CurrentView);
end;
AFAIU In Altium dephiscript API InterfaceList have specific uses: holding non-PCB objects & passing to external dll functions & letting the receiving fn destroy the list.
You don't really need one here.
The PcbLib does have some strange behaviour around deleting from selected/focused footprint etc.
I think the problem is caused by the Pcb Editor not allowing objects to be deleted from the current focused component/footprint.
The history around this issue points to solutions involving moving focus away from the required component..
You can't complete the delete process while the List still contains the object reference.
Use a While loop, after RemovePCBObject(), remove object ref from the List (remove the List Item). Then when the While loop terminates you have zero items in List.
Might help refresh or look & feel to use some of these fn calls:
CurrentLib.Board.GraphicallyInvalidate;
CurrentLib.Navigate_FirstComponent;
CurrentLib.Board.ViewManager_FullUpdate;
CurrentLib.Board.GraphicalView_ZoomRedraw;
CurrentLib.RefreshView;

Cannot Destroy Dynamically created Menu Item in Delphi

Firstly, yes I have looked all over the net and still cannot seem to destroy dynamically created menu items. Using Delphi XE. I create the items thus (for the purposes of the exercise SubMenuName is 'Test1':
MenuItemCreated := TMenuItem.Create(PopupMenu1);
MenuItemCreated.Caption:= SubMenuCaption
MenuItemCreated.Hint := SubMenuHint;
MenuItemCreated.Name := SubMenuName;
MenuItemCreated.OnClick := SubMenuClick;
MenuItemCreated.AutoHotkeys := maManual;
MySubMenu.Add(MenuItemCreated);
There is no issue using the sub-menu(s) created. The procedure SubMenuClick works as it should, and I identify the correct subMenu item so no issues there. What I then do is an application logout which is supposed to free the dynamically created sub-menus using this code (although I have tried many variations):
// Get rid of the menu items created
While MySubMenu.Count > 0 do
begin
Itemtodelete := MySubMenu.Items[0];
FreeandNil(ItemtoDelete);
end;
I have put in showmessage() debug lines that show the component names of the menu items being freeandnil'd and they are what I'd expect, ie. 'Test1' and any others I've created. I then log back in to my application (which was still running, but with me logged out). The software then tries to recreate the same sub menus with the same names (as nothing has changed as far as my application is concerned and they were previously disposed of (supposedly)). I immediately get the exception raised:
Error: A component Named Test1 already exists
I am at a complete loss as to how to dispose of the submenu items so that I can recreate them later with the same names.
Any help greatly appreciated.
Thanks,
KB
You did not say it, so I have to assume that MySubMenu is a MenuItem of PopupMenu1. If not please clarify.
To delete items from MySubMenu in order to recreate them again later, it's easyest to call the Clear method:
procedure TForm5.Button2Click(Sender: TObject);
begin
MySubMenu.Clear;
end;
which deletes all menu items of MySubMenu and frees their memory.
In order to recreate the items later, you can not use Delete() or Remove(), without also freeing the memory because they do not free the memory of the items. This is documented in help:
http://docwiki.embarcadero.com/Libraries/XE7/en/Vcl.Menus.TMenuItem.Delete
http://docwiki.embarcadero.com/Libraries/XE7/en/Vcl.Menus.TMenuItem.Remove
With these methods you must free the memory yourself, before you recreate the menu items. But then, it's not necessary to even call Delete or Remove, you can just simply Free the items:
procedure TForm5.Button2Click(Sender: TObject);
var
mi: TMenuItem;
begin
while MySubMenu.Count > 0 do
begin
mi := MySubMenu.Items[0];
mi.Free;
end;
end;
There's no need to call FreeAndNil.
This last option looks very much as yours, with which you had problems when recreating the menu items. I can't reproduce the error except when using Delete() or Remove() without freeing.
Since the Popup menu owns the items, you do not Free it. Instead of FreeAndNil use MySubMenu.Delete(0) OR more appropriately MySubMenu.Items.Clear instead of the entire While routine.
On App shutdown the popup menu will clear it, there's no need to do it manually unless you're rebuilding the menu.

Missing functionality of added Control in another application

I have read this article how to add a button to another application. When the Button is added to the parent application, everything seems OK, but when this Button is added to another app called Labform (TLabForm), the code after click is not executed. I created also a descendant to implement simple behavior after click, but no success:
TButton2 = class (TButton)
public
procedure Click; override;
end;
procedure TButton2.Click;
begin
inherited;
MessageBox(ParentWindow, 'Hello', 'Window', MB_OK);
end;
procedure TForm1.btn1Click(Sender: TObject);
var
Button2 : TButton2 ;
Hand: THandle;
begin
// Hand:= FindWindow('TLabForm', 'Labform'); // button added, but SHOWS NO message after click
Hand:= FindWindow('TForm1', 'Form1'); // button added, and SHOWS message after click
if Hand <> 0 then
begin
Button2 := TButton2.Create(self);
Button2.ParentWindow := hand;
Button2.BringToFront;
end
else
ShowMessage('handle not found');
end;
How to solve it?
thanx
Whilst it is technically possible to do what you want, it is excruciatingly difficult. Raymond Chen wrote about this at some length. The executive summary:
Is it technically legal to have a parent/child or owner/owned relationship between windows from different processes? Yes, it is technically legal. It is also technically legal to juggle chainsaws.
So, you are attempting something with difficulty akin to juggling chainsaws. Unless you have a deep understanding of Win32 you've got no chance of succeeding.
So, if you want to modify the GUI of an existing process, and it's not tractable to do so with code in a different process, what can you do? Well, it follows that you need to execute code inside the target process.
That's easy enough to do with DLL injection. Inject a DLL into the process and modify it's UI from that DLL. Still not trivial. You'll have the best chance of success if you subclass a window by replacing the existing window procedure with one of your own. That will allow you to run your UI modification code in the UI thread.

Delphi7 CustomImageList problem

I've run into the following problem:
My Delphi7 program runs smoothly on most computers running WinXP/Vista/7 BUT on some older Windows XP installs (only a few) I'm getting the following problem:
I have a system image list, and I'm adding my own icons to a copy of the system image list. Upon adding my icons I get an "Invalid image size." EInvalidOperation error.
Here is the code in question:
function GetSystemLargeIconsList: TCustomImageList;
// This gets the system image list.
var
SysIL: HImageList;
SFI: TSHFileInfo;
MyImages: TCustomImageList;
begin
SysIL := SHGetFileInfo('', 0, SFI, SizeOf(SFI),
SHGFI_SYSICONINDEX or SHGFI_LARGEICON);
if SysIL <> 0 then begin
MyImages:=TCustomImageList.Create(nil);
// Assign the system list to the component
MyImages.Handle := SysIL;
// The following prevents the image list handle from being
// destroyed when the component is.
MyImages.ShareImages := TRUE;
Result:=MyImages;
end;
end;
var
DocumentImgList: TCustomImageList;
IconToAdd: TIcon;
begin
DocumentImgList:=GetSystemLargeIconsList;
Documents.LargeImages:=DocumentImgList;
Documents.SmallImages:=DocumentImgList;
IconToAdd:=TIcon.Create;
DocumentListIcons.GetIcon(0, IconToAdd);
DocumentImgList.AddIcon(IconToAdd); ----> this is the line of the exception
To make the problem worse, I'm using the TPngImageList component, but according to the code, it just seems to call the standard Delphi function:
if TObject(Self) is TPngImageList
then if Image = nil
...
else begin
Patch := FindMethodPatch('AddIcon');
if Patch <> nil
then begin
Patch.BeginInvokeOldMethod;
try
Result := TCustomImageList(Self).AddIcon(Image); ----> this is where the exception happens
finally
Patch.FinishInvokeOldMethod;
end;
end
else Result := -1;
end;
I've recently found out that on one of the computers that have this problem, either uxtheme.dll or explorer.exe has been patched with some Windows-skinning program.
So I suppose that somebody or a program is hacking the system image list in a way that is making my Delphi program crash.
Any ideas on how to fix this?
Thanks!
One thing you could try would be to load your icon into a separate tBitmap, then resize it before adding it into the image list.

Create an exact copy of TPanel on Delphi5

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.

Resources