Getting a reference to an object inside the Delphi IDE - delphi

This follows on from my answer to this Q:
Can I change the display format for strings in the watch list?
It turns out that at some point between D7 and XE3, the implementation of the IDE's Watch Window changed from using a TListView to a TVirtualStringTree.
Although I posted an update to my answer that works with XE4 by ignoring the VST and getting the watch value from the clipboard, I'd still like to be able to get the watch value from the VST if I can. I think I know how to do that
once I have a reference to the VST but the problem is that my attempt to get one fails.
Following is an MCVE of the code I'm using in my custom package. Hopefully, what it does is self-explanatory. The problem is that the code in the block
if WatchWindow.Components[i] is TVirtualStringTree then begin
[...]
end;
never executes, DESPITE the classname "TVirtualStringTree" appearing in Memo1. Obviously the component with that classname fails the "is" test. I'm guessing that the reason is that the TVirtualTreeView compiled into the IDE is a different version that the one I'm using, v.5.3.0, which is the nearest predecessor I could find to XE4.
So, my question is, is that the likely explanation, and is there anything I can do about it? I suspect if someone can flourish the version of TVirtualStringTree that was used for XE4 from a hat, that might solve my problem.
type
TOtaMenuForm = class(TForm)
Memo1: TMemo;
procedure FormCreate(Sender: TObject);
private
WatchWindow : TForm;
VST : TVirtualStringTree;
end;
procedure TOtaMenuForm.FormCreate(Sender: TObject);
var
i : Integer;
S : String;
begin
WatchWindow := Nil;
VST := Nil;
// Iterate the IDE's forms to find the Watch Window
for i := 0 to Screen.FormCount - 1 do begin
S := Screen.Forms[i].Name;
if CompareText(S, 'WatchWindow') = 0 then begin
WatchWindow := Screen.Forms[i];
Break;
end;
end;
Assert(WatchWindow <> Nil);
if WatchWindow <> Nil then begin
Memo1.Lines.Add('Looking for VST');
for i := 0 to WatchWindow.ComponentCount - 1 do begin
Memo1.Lines.Add(IntToStr(i) + ':' + WatchWindow.Components[i].ClassName);
if WatchWindow.Components[i] is TVirtualStringTree then begin
VST := TVirtualStringTree(WatchWindow.Components[i]);
Memo1.Lines.Add('found VST');
Break;
end;
end;
if VST = Nil then
Memo1.Lines.Add('VST not found');
end;
end;
Btw, I realise that solutions that depend of implementational details of IDE are likely to be fragile, but this is just for amusement (I liked the challenge of getting string data out of a component that goes out of its way to avoid storing any).

May be You can try to use only published properties of embedded into IDE TVirtualStringTree implementation through RTTI methods to do what you want?

Related

Procedure to find component in new unit

I try to create new unit Ado_Op , in this unit i try to create a procedure like this :
procedure CloseAllTables ();
Var I : Integer; T : TADOTable;
begin
for I := 1 to ComponentCount-1 do
if Components[i] is TADOTable then
begin
T := FindComponent(Components[i].Name) as TADOTable;
T.Close;
end;
T.Destroy;
end;
Error :
ComponentCount inaccessible.
Note : I'm using Delphi 10 Seattle.
The compiler error you report is just the beginning of your problems. There are quite a few more. I see the following problems, with item 1 being the one noted in the question:
You need to supply an object on which to refer to the properties ComponentCount and Components[].
You are erroneously using one based indexing.
You needlessly call FindComponent to find the component that you already have.
You call Destroy once only, on whichever object you found last. Or on an uninitialized variable if you don't find any. The compiler should warn of this, and I do hope you have warnings and hints enabled, and heed them.
Based on the comments you are trying to call the Close method on each table owned by a form. Do that like so:
procedure CloseAllTables(Owner: TComponent);
var
i: Integer;
begin
for i := 0 to Owner.ComponentCount-1 do
if Owner.Components[i] is TADOTable then
TADOTable(Owner.Components[i]).Close;
end;
If you wish to destroy all of these components too, which I doubt, then you would need to run the loop in descending order. That's because when you destroy an component, it is removed from its owners list of components. That code would look like this, assuming that there was no need to call Close on an object that is about to be destroyed.
procedure DestroyAllTables(Owner: TComponent);
var
i: Integer;
begin
for i := Owner.ComponentCount-1 downto 0 do
if Owner.Components[i] is TADOTable then
Owner.Components[i].Free;
end;

How to work around Delphi 10's bug with TList<_AnyDynamicArrays_>?

I stumbled upon a bug in Delphi 10 Seattle Update 1. Lets take the following code :
procedure TForm1.Button1Click(Sender: TObject);
begin
//----------We crash here----------------
FList.Items[0] := SplitString('H:E', ':');
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
FList := TList<TStringDynArray>.Create;
FList.Add(SplitString('H:E', ':'));
FList.Items[0] := SplitString('H:E', ':');
end;
At first glance, it would appear that TList<T> doesn't properly manage the lifetime of the dynamic arrays it contains, but then again, it works just fine if compiled in 64 bits, it only crash in 32 bits (I understand it doesn't mean the bug isn't present in 64 bits...).
Note that SplitString was used because if was the first function returning a dynamic array that came to my mind. The original problem was encountered with TList<TBookmark> which exhibits the same problem.
It is possible to work around the bug rewriting the procedure Button1Click like this :
procedure TForm1.Button1Click(Sender: TObject);
var MyArray : TStringDynArray;
begin
MyArray := FList.Items[0];
FList.Items[0] := SplitString('H:E', ':');
//----------Yeah! We don't crash anymore!-----------
end;
But going around all my applications modifying them to work around this bug would not really be my prefered option. I'd much prefer find the offending routine and patch it in-memory if possible.
If anyone encountered this problem and found a workaround, I'd be grateful. Otherwise, I'll post mine when/if I find a proper workaround.
Also, please comment if the problem is still present in Berlin.
After all, the bug was still there in 64 bits. It didn't crash for TStringDynArray, but it did for other dynamic array types.
The source of the problem is found in the following code in Generics.Collections :
procedure TListHelper.DoSetItemDynArray(const Value; AIndex: Integer);
type
PBytes = ^TBytes;
var
OldItem: Pointer;
begin
OldItem := nil;
try
CheckItemRangeInline(AIndex);
TBytes(OldItem) := PBytes(FItems^)[AIndex];
PBytes(FItems^)[AIndex] := TBytes(Value);
FNotify(OldItem, cnRemoved);
FNotify(Value, cnAdded);
finally
DynArrayClear(OldItem, FTypeInfo); //Bug is here.
end;
end;
What happen is, the wrong TypeInfo is passed to DynArrayClear. In the case of a TList<TStringDynArray>, TypeInfo(TArray<TStringDynArray>) is passed instead of TypeInfo(TStringDynArray). From what I can tell, the proper call is:
DynArrayClear(OldItem, pDynArrayTypeInfo(NativeInt(FTypeInfo) + pDynArrayTypeInfo(FTypeInfo).Name).elType^);
The procedure being private makes it complicated to intercept. I did so using the fact that record helper can still access the private section of records in Delphi 10. I guess it will be more complicated for Berlin's users.
function TMyHelper.GetDoSetItemDynArrayAddr: TDoSetItemDynArrayProc;
begin
Result := Self.DoSetItemDynArray;
end;
Hopefully, Embarcadero will fix it someday...

Acrobat Reader ActiveX Access Violation on form close

My Delphi application has a form that uses the Acrobat Reader ActiveX control for viewing pdfs. When I use the control's functions (LoadFile, gotoNextPage, gotoPreviousPage, gotoFirstPage, gotoLastPage), then close the form, I get the following error: "Access violation at address 6AF5703C. Read of address 6AF5703C". When I run the app, but do not use the control's functions, and then close the form, the app will exit without error.
Anyone know of a fix or workaround for this issue?
My app is written using Delphi 5 (legacy app). I have Adobe Acrobat Reader DC v15.016.20045 installed.
As I said in a comment to Zam, with the current version downloaded today of Acrobat Reader DC , I get the exact same error as you.
Please try this code and let us know whether it avoids the error for you, because it certainly works for me and there is no AV, either in the FormClose or afterwards.
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
var
Ref : Integer;
begin
Ref := AcroPdf1.ControlInterface._AddRef;
AcroPdf1.Src := '';
AcroPdf1.Free;
AcroPdf1 := Nil;
end;
This is my FormCreate, which contains my only other code.
procedure TForm1.FormCreate(Sender: TObject);
begin
AFileName := 'd:\aaad7\pdf\printed.pdf';
AcroPdf1.src := AFileName;
AcroPdf1.setZoom(200); // <- this line is to exercise the
// ControlInterface to provoke the AV on shutdown
end;
I have absolutely no idea why my FormClose avoids the AV problem, and before anybody else says so, yes, it looks mad to me, too! Hardly something that deserves the name "solution", but maybe it will suggest a proper solution to someone who knows more about COM and Ole controls than I do.
I originally included the Ref := AcroPdf1._AddRef just as an experiment. I noticed that after it, Ref's value was 9. After AcroPdf1.Src := '', calling AcroPdf1._Release in the debugger evaluator returned a value of 4. I was about to see if the AV was avoided by forcing the RefCount down by repeatedly calling _Release but then Presto!, there was no AV after my first trace into FormClose exited.
Update: I have not tested the following exhaustively, but this simplified FormClose also avoids the AV, on my system at any rate:
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
var
Ref : Integer;
begin
Ref := AcroPdf1.ControlInterface._AddRef;
end;
Obviously, omitting the assignment to Ref shouldn't make any difference.
I'm using Delphi 10 Seattle on 64-bit Win10, btw.
The better solution is to edit the TPDF Object in "AcroPDFLib_Tlb.pas"
Just add the proper destructor to the Code to free the OLE Object:
Declaration
Type
TAcroPDF = class(TOleControl)
...
public
destructor Destroy; override; // <- New Line
...
end;
Implementation
destructor TAcroPDF.Destroy;
begin
FIntf := NIL;
inherited;
end;

Incompatible types: 'TFormStyle' and 'TTeeFontStyle'

I have written a code with Delphi 2009 and updated my CodeGear Delphi to XE2. It compiled perfectly with Delphi 2009, but now it doesn't ! It gives me this error instead :
[DCC Error] Incompatible types: 'TFormStyle' and 'TTeeFontStyle'!
I tried creating a new Vcl Forms Application and wrote the command that generates this error :
Form1.FormStyle := FsNormal;
and it compiled perfectly too,I don't know why is this happening, although I believe there's nothing wrong with my syntax, please help, thanks.
This is the code that is not compiling :
procedure TForm1.ApplicationEvents1Message(var Msg: tagMSG;
var Handled: Boolean);
begin
begin
KeyPreview := True;
case Msg.message of
WM_KEYDOWN:
if Msg.wParam = 27 then
begin
form1.Menu:=mainmenu1;
fullscreen1.Checked:=false;
form1.formstyle:=fsnormal;
form1.BorderStyle:=bssizeable;
end
else
if msg.wParam=VK_f5 then
begin
browser.Navigate(memo2.Text);
end;
end;
end;
end;
There is name conflict with some TeeChart module, which is in "use" clause. You can write full-qualified identificator name to resolve this problem:
formstyle := Vcl.Forms.fsnormal;
P.S. Note that I deleted "form1." qualifier also. Normally it is not very useful in the form method body, and sometimes even harmful (imagine that you have multiple instances of TForm1)
In addition to the answer of MBo, I think it is better to use:
Self.formstyle := Vcl.Forms.fsnormal;
When you have multiple instances of TForm1, this will always adjust the instance you are using at that moment.
Qualify the value with the particular enum type that it comes from:
Form1.FormStyle := TFormStyle.fsNormal;
Or even:
Form1.FormStyle := Vcl.Forms.TFormStyle.fsNormal;

How to add persistence to the Delphi Docking example

Although I realise that in addition to the included Delphi docking demo there are other and better docking libraries available such as the Developer Express Library and the JVCL Docking Library, but for a specific demonstration project I am restricted to use only the Delphi built-in capability (despite some of the noted flaws).
My question relates to adding persistence to the docking state. I see from examining Controls.pas that TDockTree is the default dock manager and it has Stream I/O routines. Digging around on SO and in various forums though I cant see how anyone has called these routines. I've tried loading and saving to a file from the relevant Create and OnDrop events but I'm stabbing in the dark. I am happy saving and restoring form sizes and states but am struggling with the concepts of what I should be saving. Would any kind person give me a starting place?
I'm using Delphi XE3, so all (?) things are possible!
Many thanks.
I'm using Toolbar 2000 from J. Russels. It is providing panels, toolwindow's and toolbar's.
That one provides functions like TBRegSavePositions and TBRegSavePositions to store the user customization into registry.
Loading a "view" get's easily done by on code line:
TBRegLoadPositions(self, HKEY_CURRENT_USER, c_BaseUserRegKey);
in this case self is my form.
You can load and save your docking configuration with the LoadFromStream and SaveToStream methods by storing the data in a string.
Therefore, the following methods are required:
save the current docking configuration to a string
load the current docking configuration from a string
Here is some code to do this:
function GetDockString(const AManager: IDockManager): AnsiString;
var
LStream: TMemoryStream;
begin
LStream := TMemoryStream.Create();
try
AManager.SaveToStream(LStream);
SetLength(Result, 2 * LStream.Size);
BinToHex(LStream.Memory, PAnsiChar(Result), LStream.Size);
finally
FreeAndNil(LStream);
end;
end;
procedure ReadDockString(const ADockString: AnsiString; const AManager: IDockManager);
var
LStream: TMemoryStream;
begin
LStream := TMemoryStream.Create();
try
LStream.Size := Length(ADockString) div 2;
HexToBin(PAnsiChar(ADockString), LStream.Memory, LStream.Size);
LStream.Position := 0;
AManager.LoadFromStream(LStream);
finally
FreeAndNil(LStream);
end;
end;
I've used such methods in an application to create dockable windows, but the vcl provides only a very basic user experience. You can do something about it, but it is hard to test and debug - I already spent too much time to use and override TCustDockDragObject and TCaptionedTabDockTree, so I would recommend using a docking framework.
Here is a minimal example which creates two forms and reads a docking configuration.
TForm1 = class(TForm)
procedure FormCreate(Sender: TObject);
procedure FormDblClick(Sender: TObject);
private
FPanel: TPanel;
end;
Implementation:
procedure TForm1.FormCreate(Sender: TObject);
var
LWindow: TForm;
const
LDockExample = '0000080000000000000000000000000000000000000000000000000100000000000000000B0000004368696C6457696E646F77FFFFFFFF';
begin
FPanel := TPanel.Create(Self);
FPanel.Align := alTop;
FPanel.Height := 300;
FPanel.DockSite := true;
FPanel.Parent := Self;
LWindow := TForm.CreateNew(Self);
LWindow.Name := 'ChildWindow';
LWindow.DragKind := dkDock;
LWindow.BoundsRect:=Rect(10, 10, 400, 400);
LWindow.Color := clGreen;
LWindow.Show;
ReadDockString(LDockExample, FPanel.DockManager);
end;
procedure TForm1.FormDblClick(Sender: TObject);
begin
ShowMessage(GetDockString(FPanel.DockManager));
end;

Resources