Loading resources at design time? - resources

Is it possible to load a resource at design time?
I am making a speed button component, and I want to automatically load a new image from the resource whenever the button size changes. It already works properly at run time, but at design time, after I set the resource name property, it does not show any icon.
I can draw a default rectangle in place of the icon if it is not possible, but it would have been nice to display my icons at design time as well.
function TPngSpeedButton.LoadIcon(ResName: String): Boolean;
var hI: HICON;
Ico: TIcon;
ISize: Integer;
Png: TPngImage;
begin
Result:= False;
if ResName = '' then Exit;
ISize:= Height - 7 - Round(Height * 0.15);
Png:= TPngImage.Create; Ico:= TIcon.Create;
try
if LoadIconWithScaleDown(HInstance, PChar(ResName), ISize, ISize, hI) = S_OK then begin
Ico.Handle:= hI;
ConvertToPng(Ico, Png);
SetPngImage(Png);
Result:= True;
end;
finally
Png.Free; Ico.Free;
end;
end;

You can not load icons at design-time from your application resource, since at that time the application executable doesn't even exist as you haven't compiled it yet.
Now, what you might be able to do is create a resource-based dynamic link library (resource DLL) which you compile separately. This way, you would be able to access the DLL resources even at design-time, similar to how the Delphi IDE is already accessing some system resources.
If you don't want to deal with additional DLLs, then put your icons into one or more ImageLists, since images from ImageLists are available both at run-time as well at design-time.

Related

Use an Excel .xlsx sheet as calculator (function) in Delphi?

I have a fairly complex Excel sheet made by an external company. It uses several formulas, lookups etc.
From Delphi (version 6 or 7) , is it possible to have this sheet open in the background, allow a user to input a few variables, then pass these on to excel and have the calculated result returned to the delphi program ?
I know how to open excel and read the entire sheet using ADO, but here I need to send data both ways, by reference of range or coordinate.
This could be done as a unit written in Delphi, but the logic is already done in Excel, and it's simpler to write such calculations in Excel.
Simplified example:
procedure do_it;
var
v1: integer;
v2: integer;
begin
v1 := strtoint(inp1.text); // operator provided numbers
v2 := strtoint(inp2.text);
theresult.caption := get_excel_value(v1, v2); // get back result from excel formulas
end;
function get_excel_value(v1, v2: integer) : string;
begin
// pseudocode
myExcel.range('A3').text := v1;
myExcel.range('A4').text := v2;
myExcel.recalculate; // update excel sheet on demand
result := myExcel.range('A10').text;
end;
Is it correct to assume that I need to be looking in the direction of Excel COM or Excel OLE ?
If possible to do - can the excel sheet call and use macros internally (invisible to Delphi) ?
Microsoft Office (Word, Excel and others) are applications which can be fully automated from another application. Everything you can do by hand can also be done programmatically from another application and of course from your Delphi application.
To ease the automation, Delphi is delivered with non-visual components which are "wrapper" around the underlying COM objects and exposes the same properties, methods and events as the COM interface.
Delphi has several sets of components for working with several Office versions. There are components for Office 2000, Office XP and Office 2010. 2010 version is available from Delphi XE2. Of course there are more Office versions, but don’t worry: Microsoft has carefully made his interfaces upward compatible. For example, if you use the Office 2000 components, you can still automate all Office versions from 2000 to the latest. The only restriction is that you cannot easily use new features if you use the old component.
Actually, it is better to use the older components which are capable of doing what you need to do!
This is because the compatibility in Microsoft office API goes upward and not downward.
Since there are several sets of components, you must make sure the correct version is installed.
o see the installed packages, launch Delphi and go to the menu / Component / Install Packages. You see a long list of all installed design time packages with a checkbox telling that the package is available or not.
Locate “Microsoft Office 2000 Sample Automation Server Wrapper Components” (Or Office XP) and check the checkbox in front of the one you plan to use. Click OK and verify that your component palette now include a tab “Servers”.
As an example, we will create a sample application to insert a sentence at the end of a Word document. This is quick and easy!
Create a new VCL forms application, drop a TWordApplication and a TButton on the form. Then add the code below as the button’s OnClick handler.
The quickest way to locate it is to enter WordApplication in the component palette search tool.
TForm1.Button1Click(Sender: TObject);
begin
WordApplication1.Connect;
WordApplication1.Visible := TRUE;
WordApplication1.Selection.EndOf(wdStory, wdMove);
WordApplication1.Selection.Text := 'Delphi Rocks !' + #13;
WordApplication1.Selection.EndOf(wdStory, wdMove);
WordApplication1.Disconnect;
end;
Adding to fpiette's good answer:
Another way to do it is without the components, but instead to directly create an Excel COM object on the fly in the code. Example:
uses
ComObj, Variants;
var
V: Variant;
Square: Double; // could also be of type string
begin
try
// Get a running Excel instance, if there is one
V := GetActiveOleObject('Excel.Application');
except
on EOleSysError do // Probably Excel was not running
begin
// Create a new Excel instance
V := CreateOleObject('Excel.Application');
V.Visible := True; // Optional
end;
end;
//
// Do some work with Excel - a few examples
//
// If no workbook is open, then create one
if VarIsClear(V.ActiveWorkbook) then
begin
V.Workbooks.Add;
end;
V.ActiveSheet.Cells[4, 1].Value := 7; // Put a value into cell A4
V.ActiveSheet.Cells[5, 1].Formula := '=A4 * A4'; // Put a formula into A5
V.Calculate; // Normally not needed, but in case the user has chosen manual calculation
Square := V.ActiveSheet.Cells[5, 1].Value; // Read the value of A5
end;
It should be noted, even if perhaps obvious, that all of this works only if Excel is installed on the computer where your Delphi program is run.

Inno Setup: Modify icon and title of specific error message

I have a program that should only be installed on 64-bit architectures. So I added:
ArchitecturesAllowed = x64
In my Inno Setup file to prevent this.
The problem is that the message displayed is a bit too "violent". It displays "error" in title and a big red cross.
I've found that I can change content of this message (with OnlyOnTheseArchitectures message), but no way to modify icon and title without impacting other error message.
And I do not see what step can match...
Is there a way to make it like an information box?
There's no generic way to modify any standard Inno Setup message box.
You generally have to re-implement the function on your own. What is not always possible.
For your specific case, you are lucky, as you can easily implement your own custom check for 64-bit system using the IsWin64 function from the InitializeSetup event function.
And display your own custom message box using the MsgBox function.
function InitializeSetup(): Boolean;
begin
Result := True;
if not IsWin64 then
begin
MsgBox('This cannot be installed on 32-bit system.', mbInformation, MB_OK);
Result := False;
end;
end;

Setup Programs created using Inno Setup Compiler doesn't display Minimize Animation

My Problem is why Inno Setup Compiler (Unicode or ANSI) and any Setups made by it don't minimize showing a nice Minimizing Animation like in Other Windows Programs?
It displays a very basic Minimize Animation..........Why that?
I know Borland Delphi as Inno Setup Compiler's Compiler, but Borland Delphi doesn't have such a bad Minimize Animation...........It minimizes normally as Windows System Windows minimize (such as Explorer, Computer, Control Panel).................
I also noticed that the Windows Installer Creater Nullsoft Scriptable Install System - NSIS and Setups made using it are also minimizing well like I said.
How can I resolve this problem?
UPDATED QUESTION
I also added a code to play that nice Zooming Minimize / Restore Animation can be seen in Many Windows Applications on Inno Setup's WizardForm, But when I click the WizardForm's Minimize Button after adding this code to Inno Setup Compiler's Source Code, the Nice Zooming animation not plays and it never can be minimized using it, it only can be minimized using Taskbar button after adding this code. So it means this code not working or anything else wrong...........Why this is not working???
The Code I Added to unit WizardForm:
interface
uses
Windows;
type
TTrayZoom = class(TObject)
private
class function GetTrayRect: TRect;
class procedure DoZoom(const Wnd: HWND; const Src, Dest: TRect);
public
class procedure ZoomToTray(const Wnd: HWND);
class procedure ZoomFromTray(const Wnd: HWND);
end;
implementation
class procedure TTrayZoom.DoZoom(const Wnd: HWND; const Src, Dest: TRect);
begin
DrawAnimatedRects(Wnd, IDANI_CAPTION, Src, Dest);
end;
class function TTrayZoom.GetTrayRect: TRect;
var
TaskbarWnd, TrayWnd: HWND;
begin
TaskbarWnd := FindWindow('Shell_TrayWnd', nil);
TrayWnd := FindWindowEx(TaskbarWnd, 0, 'TrayNotifyWnd', nil);
GetWindowRect(TrayWnd, Result);
end;
class procedure TTrayZoom.ZoomFromTray(const Wnd: HWND);
var
WndRect: TRect;
begin
GetWindowRect(Wnd, WndRect);
DoZoom(Wnd, GetTrayRect, WndRect);
end;
class procedure TTrayZoom.ZoomToTray(const Wnd: HWND);
var
WndRect: TRect;
begin
GetWindowRect(Wnd, WndRect);
DoZoom(Wnd, WndRect, GetTrayRect);
end;
And I called TTrayZoom.ZoomToTray from if WMSysCommand..... = SCMINIMIZE and called TTrayZoom.ZoomFromTray from if WMSysCommand..... = SCRESTORE with the setting HWND parameter to WizardForm.Handle.
But those codes never works, I even don't know if they're get called or not. :(
What is the problem playing this Zooming Animation in this WizardForm?
I'd say there are two issues.
The animation is shown for windows that have a task bar button. The wizard form does not have a task bar button.
The task bar button of the installer belongs to a hidden main window.
Historically the installers had full screen background gradient blue windows. Even Inno Setup supported that.
While that background window is no longer enabled by default (the WindowVisible directive defaults to No in modern versions of Inno Setup), it still exists and owns the task bar button.
Inno Setup is built using an ancient version of Delphi that likely does not play nicely with the minimize feature.
Generally, I'd say you should file a feature request/bug report to get this fixed.

Inno Setup Change AppName based on component(s) selected

I need the installer to show different AppName based on (un)selected components. I tried this:
[Setup]
AppName={code:GetAppName}
AppVersion=1.0
AppVerName=Dagon Video Tools
AppId=Dagon Video Tools
DefaultDirName={sd}\Games\Dagon Video Tools
[Code]
function GetAppName(Value: string): string;
var
CurPageID: Integer;
Begin
Result := 'Dagon Video Tools'
if (CurPageID=wpSelectComponents) and IsComponentSelected('Slasher') and not IsComponentSelected('Frankenstein') then
begin
Result := 'Dagon Slasher';
end;
if (CurPageID=wpSelectComponents) and IsComponentSelected('Frankenstein') and not IsComponentSelected('Slasher') then
begin
Result := 'Dagon Frankenstein';
end;
if (CurPageID=wpSelectComponents) and IsComponentSelected('Slasher') and IsComponentSelected('Frankenstein') then
begin
Result := 'Dagon Video Tools';
end;
End;
But, as you can guess, this doesn't work. Is this script incomplete or should it be done in a different way altogether?
The AppName directive value is resolved (= your GetAppName is called) immediately after the InitializeSetup (if any) finishes. That is a long before the user is able to change the components.
So you cannot make AppName depend on the selected components.
Some uses of the AppName could be overridden with a custom value though, but not all. See below.
Though, as I know that your question is actually about a setup type, you can do this:
Create custom "type" page (like a menu) as the very first one.
Once the user selects the "type", restart the installer with a custom switch (e.g. /APPTYPE=slasher) and exit.
Once the installer is (re-)run with the /APPTYPE, you know from the beginning, what component/type you are installing and hence you can set the AppName normally.
Of course, you skip the custom "type" page.
This is actually a way simpler to implement. The only drawback is that the setup window is "recreated" after the user selects the "type".
This is the original response in case you do not want to use the above solution.
First, your implementation of the GetAppName is wrong. You are using an uninitialized variable CurPageID. And anyway, as mentioned already, the GetAppName is called even before the wizard window is created, so "current page" is irrelevant here.
The correct implementation would be like:
function GetAppName(Value: string): string;
begin
if IsComponentSelected('Slasher') and not IsComponentSelected('Frankenstein') then
begin
Result := 'Dagon Slasher';
end
else
if IsComponentSelected('Frankenstein') and not IsComponentSelected('Slasher') then
begin
Result := 'Dagon Frankenstein';
end
else
begin
Result := 'Dagon Video Tools';
end;
end;
But this still won't make it working in the AppName directive. We will use it in other contexts though later.
Also note that for your specific installer, you should better use the WizardSetupType(false) function instead of the IsComponentSelected.
FinishedLabel
Just override the Inno Setup default text in CurPageChanged(wpFinished):
procedure CurPageChanged(CurPageID: Integer);
var
S: string;
begin
if CurPageID = wpFinished then
begin
S := SetupMessage(msgFinishedHeadingLabel);
StringChange(S, '[name]', GetAppName(''));
WizardForm.FinishedHeadingLabel.Caption := S;
WizardForm.AdjustLabelHeight(WizardForm.FinishedHeadingLabel);
{ Ideally we should shift the FinishedLabel up or down here, }
{ if the height of the header changed. }
{ Note that other messages (msgFinishedLabelNoIcons or msgFinishedRestartLabel) }
{ are used in special situations, so this is not a complete solution. }
S := SetupMessage(msgFinishedLabel);
StringChange(S, '[name]', GetAppName(''));
WizardForm.FinishedLabel.Caption := S;
WizardForm.AdjustLabelHeight(WizardForm.FinishedLabel);
end;
end;
Add/Remove Programs
That's easy. There's the UninstallDisplayName directive for this, which is resolved only during the actual installation, when we already know the selected components. So we can use your (fixed) GetAppName here:
[Setup]
UninstallDisplayName={code:GetAppName}
Are you sure you want to completely remove AppName and all of its components?
You cannot change that. You better use some generic name in the AppName so that this message works for any component.
Or make the message not mention the application name at all:
[Messages]
ConfirmUninstall=Are you sure you want to completely remove this game?
Alternatively remove the message completely:
Replace or customize modal uninstallation windows in Inno Setup
Please wait while AppName is removed from your computer
The same solution as for the WizardForm.FinishedLabel. Just use the UninstallProgressForm.PageDescriptionLabel from the InitializeUninstallProgressForm.
AppName was successfully removed from your computer
Similar as with the "Are you sure you want to completely remove AppName and all of its components?"
Either make the AppName generic. Or disable the message with "silent" mode and implement your own message in the CurUninstallStepChanged(usPostUninstall).
Again, see Replace or customize modal uninstallation windows in Inno Setup.
For a similar discussion, see also Changing AppName and AppDir depending on language in Inno Setup.

List Index Out of Bounds on screen.forms

I do some reporting on a form with reportbuilder.
On the main form I select some items on a grid and then a generate the reports of the items.
I want to do this in a Tthread but i get an error 'List index out of bounds'.
Here is the call stack:
Classes.TList.Get(1244868)
Classes.TList.Get(???)
Forms.TScreen.GetCustomForms(???)
Forms.TApplication.DoActionIdle
Forms.TApplication.Idle(???)
Forms.TApplication.HandleMessage
Forms.TApplication.Run
Seems some form is either not being added to the Screen.Forms
collection in a timely manner or one is being freed from it during the
loop in DoActionIdle.
Any ideas on how to circumvent this problem?
I work on windows XP and delphi 2010.
I Also I've the problem with a test procedure on my application
TForm3 is just a form with no code.
TDebugThread = class(TThread)
protected
procedure Execute; override;
public
constructor Create();
end;
constructor TDebugThread.Create;
begin
FreeOnTerminate := True;
inherited Create(False);
end;
procedure TDebugThread.Execute;
var
oReport: DeBugReport.TForm3;
begin
inherited;
oReport:= DeBugReport.TForm3.Create(Nil);
try
sleep(1000);
finally
oReport.Free;
end;
end;
....
procedure RunThread();
begin
TDebugThread.Create();
end;
Recapitulation:
I have a list of some Intervention on a form. Each detail and resumation of the intervention can I print on 2/5 reports. Therefore I use reports components (reportbuilder) on another form (not visible). The new feature was to multiselect some interventions on the list and set the reports in a folder in pdf format. That's was simple just on each intervention call the reportform and some parameters to change and save into pdf.
But this take to long. The user must wait until the procedure was ended. No problem I set the procedure in a thread. But there I get the error 'List index out of bounds'. ArgggArggg, I was suspected that the reportform (created, to his job and then destroyed) the problem was but hoped that there was another solution. I was thinking to change the TForm into TDataModule. Can I set all the components of the form into the datamodule. I use the TDbGrid to see some values in design. But in the Tdatamodule I can't set a TDBGrid. Ok, I can live without the TDbGrid. So I transformed the TForm into TDataModule.
But the TDataModule is not the answer. There I get the error 'Graphics.OutOfResource' from a TBitmap. I think that the TBitmap is calling from the TppReport. Now I'm done. I'm changing my code all more than 2 days with no result. I leave the TThread for this time.
Let's take a look at TApplication.DoActionIdle:
procedure TApplication.DoActionIdle;
var
I: Integer;
begin
for I := 0 to Screen.CustomFormCount - 1 do
with Screen.CustomForms[I] do
if HandleAllocated and IsWindowVisible(Handle) and
IsWindowEnabled(Handle) then
UpdateActions;
end;
Let's assume that Screen.CustomFormCount and is implemented correctly and always returns the number of items indexed by Screen.CustomForms. In which case the conclusion is that the body of the loop is deleting a form. That is Screen.CustomFormCount is changing during the execution of the loop.
The only way that can happen is if one of the form's action update handlers results in a form being deleted. So, I can't tell you any more than that, but this analysis should lead you to the root cause of the problem.
And the second part of your question is quite simple. You cannot use VCL components outside the main GUI thread.
In fact it is plausible that destroying the VCL form in your thread is what is leading to Screen.CustomFormCount changing during the execution in the GUI thread of TApplication.DoActionIdle.

Resources