How to update the Delphi Object Inspector? - delphi

Following on from this question I recently posted: Can a Component Editor be executed on multiple components?
I have created a ComponentEditor for a new component that when executed shows a TOpenDialog to select a configuration File. When a File is loaded I read the data and copy the values to the calling component (which is Component as this is a TComponentEditor).
There are no problems at all, except that the Object Inspector is not updating to reflect the newly changed values - It only updates when clicking back on the component in the Designer.
It might not seem like such a big a deal, but I need the Object Inspector to update itself somehow so that I can see the properties have changed successfully (without having to switch focus back to the control).
So, is there some way of letting Delphi know that it should update/refresh the Object Inspector? I

After modifying the component as needed, your component editor needs to call the IDesigner.Modified() method, eg:
procedure TMyComponentEditor.ExecuteVerb(Index: Integer);
var
Dlg: TOpenDialog;
begin
...
Dlg := TOpenDialog.Create(nil);
try
...
if Dlg.Execute then
begin
...
Designer.Modified;
end;
finally
Dlg.Free;
end;
...
end;

Related

Function to recreate a TForm in Delphi

I need a procedure that recreates a form.
The reason being is that I have forms with many different components. These component values (edit box text, checkbox checked or not, etc) are saved inside onhide and loaded again isnide onshow. This makes sure all user settings are retained between runs of the program.
The problem comes when they make a change (intentionally or otherwise) that leads to problems. I want to be able to "reset" the form back to the default settings when the application is first installed.
I can create a reset button that runs this code
FormName.free;
DeleteFile('FormNameSettings.ini');
FormName:=TFormName.Create(Application);
FormName.show;
That does what is required. The form is closed, clears the settings file (so states are not restored when it shows again) and then recreates the form. The form now has the original default settings.
My problem is trying to get that code into a function that I can call easily from multiple forms.
procedure ResetForm(form:tform;filename:string);
begin
form.free;
if fileexists(filename)=true then deletefile(filename);
<what goes here to recretae the form by the passed tform?>
end;
Can anyone help get that ResetForm procedure working? Latest Delphi 11.
To return the newly created form we actually need a var parameter for the form, but that alone is not very elegant, because one cannot pass a derived form class to a var parameter of type TForm and has to do a hard cast to please the compiler. Even using a function that returns a TForm is not much better as the result is most likely assigned to a variable of a derived form class and that would also be rejected by the compiler.
Thanks to generics we can write some code that overcomes these restrictions. As standalone generic procedures or functions are not supported in Delphi, we wrap it inside a record declaration:
type
TFormUtils = record
public
class procedure ResetForm<T: TForm>(var form: T; const filename: string); static;
end;
We also need to save some information about the form for later use:
the owner of the form
is the form currently showing
This allows to recreate the form.
class procedure TFormUtils.ResetForm<T>(var form: T; const filename: string);
begin
var formOwner := form.Owner;
var formShowing := form.Showing;
form.free;
if fileexists(filename) then
deletefile(filename);
form := T.Create(formOwner);
if formShowing then
form.Show;
end;

Delphi Bookmark Error: E2003 Undeclared identifier 'TBookmark'

Hey I wanted to use a TBookmark as a varialbe in my Form. I got it running in another Form and it is working there.
But in the new Form I get the Error.. I guess I have to include something in the uses statement but I cant remember what it was. Here is the code TBookmark is underlined in red so thats where the error sits.
procedure TForm4.FormCreate(Sender: TObject);
var test : string;
var selectedRow, rows : TBookmark;
begin
rows := Form1.DBGrid1.DataSource.DataSet.GetBookmark;
Form1.DBGrid1.SelectedRows.CurrentRowSelected := True;
Form1.DBGrid1.DataSource.DataSet.GotoBookmark(rows);
test := Form1.DBGrid1.DataSource.DataSet.FieldByName('name').AsString;
ShowMessage(test);
end;
end.
Your Form4 needs to Use the DB unit, because that's where TBookMark is declared.
Btw, what is in Form1's unit is irrelevant to this. The only relevant thing is that Form4's unit has to Use DB. What happens is that when the compiler tries to compile your Form4 unit, it needs to be able to find the definition of TBookMark, and that is in the standard DB.Pas unit along with lots of other dataset-related stuff. The same is true of any other identifier (or its class) that the compiler encounters in your project's source code.
99% of problems like this can be solved by doing a "Search | Find in Files" through Dephi's source code folders (and your project's folder if it's one of yours) to identify where the "undeclared" or missing item is declared.
Update So, you've got this code, which I'll assume is in your uForm4.Pas unit.
procedure TForm4.FormCreate(Sender: TObject);
var
test : string;
var
selectedRow, rows : TBookmark;
begin
rows := Form1.DBGrid1.DataSource.DataSet.GetBookmark;
Form1.DBGrid1.SelectedRows.CurrentRowSelected := True;
Form1.DBGrid1.DataSource.DataSet.GotoBookmark(rows);
test := Form1.DBGrid1.DataSource.DataSet.FieldByName('name').AsString;
ShowMessage(test);
end;
You want to be able to do something with the Name value that's shown in the current row of
DBGrid1 on Form1. There's nothing particularly wrong with the way you've done it, just that
it's long-winded, error-prone and invites problems like the one you've having with
TBookMark.
The point is that somewhere in your project, maybe in your uForm1.Pas unit, you know,
I don't, there must be a TDataSet-descendant (like TFDQuery, TAdoQuery or TTable) that is
specified in the DataSet property of Form1's DataSource1. For the sake of argument, lets'
say that the dataset component is FDQuery1 on Form1 and you want to get the Name field value
from the current row in DBGrid1.
To get that Name value, you don't actually need the bookmarks your code is using. The way
a TDBGrid works, the currently-selected row in the grid is always the current row in the
dataset component. So you could simply write
procedure TForm4.FormCreate(Sender: TObject);
var
test : string;
begin
test := Form1.FDQuery1.FieldByName('name').AsString;
ShowMessage(test);
end;
because you don't need to go through the rigmarole of Form1.DBGrid1.DataSource.DataSet to get to it.
Now, to explain another little mystery, how come your code would work fine if it was in uForm1.Pas
but you get the Undeclared Identifier: TBookMark error why you try the same code in uForm4.Pas
unit? Well, if you've ever watched the top of a source code file as it's being saved, you'll notice that
Delphi automatically adds, to the Uses list at the top, the units which contain any of the
components you've added to the form since its last save. So adding a TDataSource to the form would add
the DB unit to the Uses list, because that's where TDataSource is declared and so is TBookMark. Which
is why Delphi could compile Form1's code without the error, whereas when you try to mention a TBookMark
to uForm4, you need to add it to the unit's Uses list unless you add a component (like TDataSource)
to Form4 which will cause it to automatically add DB to the Uses list if it isn't already there. Mystery
solved.

CHM file not displaying correctly when Delphi VCL style active

My Delphi application includes a help file that the user can call from anywhere in the application (well... that is, for all the parts I've written so far...)
It also includes the ability for the user to switch from the regular style to another VCL style from a list.
When no style is applied, the help file displays normally like this :
But as soon as a VCL style is active, the Help file does not display correctly anymore, like this :
Is this due to the way I declare the HelpFile on main Form creation like this (path being a global variable pointing to the main exe folder):
Application.HelpFile := path+'Help\D.R.A.M.A. 2.0 Help.chm';
or is this a known problem that can not be solved ?
SIDE NOTE : the help is called on helpContext should that be important to mention and the HtmlHelpViewer is added to the uses clause.
This answer was taken from https://forums.embarcadero.com/thread.jspa?threadID=227785 and I've confirmed works very well.
Drop a TApplicationEvents component onto the applications main form.
Implement the OnHelp event of that component as this:
function TfmMain.ApplicationEvents1Help(Command: Word; Data: NativeInt; var CallHelp: Boolean): Boolean;
begin
CloseHelpWnd;
Result := ShellExecute(0,'open','hh.exe',
PWideChar('-mapid '+IntToStr(Data)
+' ms-its:'+Application.HelpFile),
nil,SW_SHOW) = 32;
CallHelp := false;
end;
On the main form, implement the CloseHelpWnd method as this:
procedure TfmMain.CloseHelpWnd;
var
HlpWind: HWND;
const
HelpTitle = 'Your help file title';
begin
HlpWind := FindWindow('HH Parent',HelpTitle);
if HlpWind <> 0 then PostMessage(HlpWind,WM_Close,0,0);
end;
You would replace 'Your help file title' with the title of your help file. This is the window caption title when you open the help file directly.
In the FormDestroy event for the main form, include a call to
CloseHelpWnd;
So far we've not seen any issues with the above method, and because we are running the help file in a separate process, it is not affected by the VCL Styles problems evident in Delphi 10.2 Tokyo.
NOTE: It does not have to be the applications main form, but it must be a form that is created before the help system is needed and remains instantiated while the application is running. In our case, we did it on a common resources form and then all programs we rebuilt with the new form had the help problem resolved.
NOTE: You still need to set the Application.HelpFile property as normal, but you don't need to include the HtmlHelpViewer unit in the Uses clause.

how to get the selected text from acropdf component to an edit directly with Delphi 7

I study preparing a dictionary programme with delphi. So far I have solved my problems about Word documents but I've got some problem about PDF documents.
I imported and installed the AcroPdf component with Delphi 7 and I want to get the word (or text) which was selected by dblclicking by user from pdf document which was viewed by the ACROPDF component in Delphi. If I can get it I'll send it the dictionary database directly.
If you help me I'll be glad. Thank you...
Remzi MAKAK
The following shows one way to get the selected text from a Pdf document which is
open in Adobe Acrobat Professional (v.8, English version).
Update The original version of this answer neglected to check the Boolean result of calling MenuItemExecute and specified the wrong argument to it. Both these points are fixed in the updated version of this answer. It turned out that the reason the call to MenuItemExecute was failing was that it is essential to call BringToFront on the Acrobat document before trying to copy the text selected in to to the clipboard.
Create a new Delphi VCL project.
In D7's IDE go to Projects | Import Type Library, and in the Import Type Library pop-up, scroll down until you see something like "Acrobat (Version 1.0) in the list of files, and
"TAcroApp, TAcroAVDoc..." in the Class names box. That is the one you need to import. Click the Create unit button/
In the project's main form file
a. Make sure it USES the Acrobat_Tlb.Pas unit from step 2. You may need to add the path to wherever you saved Acrobat_Tlb.Pas to the SearchPath of your project.
b. Drop a TButton on the form, name it btnGetSel. Drop a TEdit on the form and name it edSelection
Edit the source code of your main form unit as shown below.
Set a debugger breakpoint on Acrobat.MenuItemExecute('File->Copy'); Do not set a breakpoint within the GetSelection procedure as this is likely to defeat the call to BringToFront in it.
Close any running instance of Adobe Acrobat. Check in Task Manager that there are no hidden instances of it running. The reason for these step is to make sure that when you run your app, it "talks" to the instance of Acrobat that it starts, not another one.
Compile and run your app. Once the app and Acrobat are open, switch to Acrobat, select some text, switch back to your app and click the btnGetSel button.
Code:
uses ... Acrobat_Tlb, ClipBrd;
TDefaultForm = class(TForm)
[...]
private
FFileName: String;
procedure GetSelection;
public
Acrobat : CAcroApp;
PDDoc : CAcroPDDoc;
AVDoc : CAcroAVDoc;
end;
[...]
procedure TDefaultForm.FormCreate(Sender: TObject);
begin
// Adjust the following path to suit your system. My application is
// in a folder on drive D:
FFileName := ExtractfilePath(Application.ExeName) + 'Printed.Pdf';
Acrobat := CoAcroApp.Create;
Acrobat.Show;
AVDoc := CoAcroAVDoc.Create;
AVDoc.Open(FileName, FileName); // := Acrobat.GetAVDoc(0) as CAcroAVDoc; //
PDDoc := AVDoc.GetPDDoc as CAcroPDDoc;
end;
procedure TDefaultForm.btnGetSelClick(Sender: TObject);
begin
GetSelection;
end;
procedure TDefaultForm.GetSelection;
begin
// call this once some text is selected in Acrobat
edSelection.Text := '';
if AVDoc.BringToFront then // NB: This call to BringToFront is essential for the call to MenuItemExecute('Copy') to succeed
Caption := 'BringToFront ok'
else
Caption := 'BringToFront failed';
if Acrobat.MenuItemExecute('Copy') then
Caption := 'Copy ok'
else
Caption := 'BringToFront failed';
Sleep(100); // Normally I would avoid ever calling Sleep in a Delphi
// App's main thread. In this case, it is to allow Acrobat time to transfer the selected
// text to the clipboard before we attempt to read it.
try
edSelection.Text := Clipboard.AsText;
except
end;
end;

Can a Component Editor be executed on multiple components?

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.

Resources