I have a working setup.exe that runs 'full' and 'upgrade' installations from a proxy server that elevates the user to admin only during execution of setup.exe.
While the setup.exe successfully creates an uninstall icon, the user does not have sufficient privileges to execute an uninstall. :(
So, it is desirable and efficient for me to submit a single setup.exe to the proxy server that offers the users a single point of reference.
How can I add a 3rd selection to 'uninstall'?
My initial thoughts are to trick the [Types] section:
[Types]
Name: "full"; Description: "Full Install"
Name: "upg"; Description: "Upgrade Only"
Name: "del"; Description: "Uninstall"
But which event do I code in to start the uninstall process?
Is InitializeSetup() too early? Meaning the user hasn't selected anything yet.
EDIT: After much searching and prompts from TLama, I have something working.
// code snippet...
Function GetUninstallString(): String;
var
sUnInstPath: String;
sUnInstallString: String;
begin
sUnInstPath := ExpandConstant('Software\Microsoft\Windows\CurrentVersion\Uninstall\{#emit SetupSetting("AppId")}_is1');
sUnInstallString := '';
if not RegQueryStringValue(HKLM, sUnInstPath, 'UninstallString', sUnInstallString) then
RegQueryStringValue(HKCU, sUnInstPath, 'UninstallString', sUnInstallString);
Result := sUnInstallString;
end;
Function Do_UnInstall(sUninstallString: String): Integer;
var
ri: Integer;
begin
// Return Values:
// 0 -> uninstall string = empty, new install?
// 1 -> successfully executed UnInstallString
// -1 -> error executing UnInstallString
Result := 0; // Default value
if sUnInstallString <> '' then begin
sUnInstallString := RemoveQuotes(sUnInstallString);
if Exec(sUnInstallString, '/SILENT /NORESTART /SUPPRESSMSGBOXES','', SW_HIDE, ewWaitUntilTerminated, ri) then
Result := 1
else
Result := -1;
end else;
end;
Function NextButtonClick(CurPageID: Integer): Boolean;
begin
Result := True;
case CurPageID of
wpSelectComponents:
begin
if WizardSetupType(False) = 'del' then // Uninstall and exit!
begin
OK2Unins := True
if Do_UnInstall(sUnins) = 1 then
Result := False;
//ExitProcess(0); This fails. Prompts user with a cancel message.
WizardForm.Close; // This trigger CancelButtonClick() code...
end;
end;
wpSelectTasks:
begin
end;
wpReady:
begin
end;
end;
end;
Procedure CancelButtonClick(CurPageID: Integer; var Cancel, Confirm: Boolean);
// Predefined Wizard page identifiers
// wpWelcome = 1;
// wpLicense = 2;
// wpPassword = 3;
// wpInfoBefore = 4;
// wpUserInfo = 5;
// wpSelectDir = 6;
// wpSelectComponents = 7;
// wpSelectProgramGroup = 8;
// wpSelectTasks = 9;
// wpReady = 10;
// wpPreparing = 11;
// wpInstalling = 12;
// wpInfoAfter = 13;
// wpFinished = 14;
begin
if CurPageID = wpSelectComponents then
Confirm := not OK2Unins;
end;
Related
I have created a REST server in Delphi using WebBroker. My intention is to use it as a label printer. A client prepares and sends a JSON request detailing the printer name, Fast Report & variables. The server reads the JSON, creates a tFrxReport object loads the requisite report and populates the variables.
This all works admirably, except it will not print to a physical printer. If I select OneNote as my destination, the label is saved to the desktop. If I select a network attached printer, no label emerges.
I have tried PrintOptions.ShowDialog:=True The print dialog shows, indicating the correct printer, but it does not print.
If anyone has any experience, could you point me in the right direction please?
function processJson(itm : sat; jtr : tJsonTextReader): sat;
var
idx : integer;
//itm : sat; // simple array type [idx, 'val1', 'val2']
begin
setlength(itm,0);
idx:=0;
while jtr.Read do
begin
if jtr.TokenType = tJsonToken.PropertyName then
begin
setlength(itm, length(itm)+1);
itm[idx].st_idx := idx;
itm[idx].st_code := jtr.Value.ToString; // property name
jtr.Read;
itm[idx].st_desc := jtr.Value.AsString; // property value
inc(idx);
end;
end;
processJson := itm;
end;
function getPrinterInfo(pnam: string):printinfo_type;
var
ptr : printinfo_type;
idx : integer;
begin
ptr.idx := -1; //default printer
ptr.name := trim(pnam);
for idx := 0 to Printer.Printers.Count - 1 do
if AnsiContainsText(Printer.Printers[idx], ptr.name) then
ptr.idx := idx;
result := ptr;
end;
procedure Ttfdq.tfdqactLabelAction(Sender: TObject; Request: TWebRequest;
Response: TWebResponse; var Handled: Boolean);
var
post : simpleArray_type;
pdx, idx, iitm : integer;
jtr : tJsonTextReader;
sr : tStringReader;
pish : string;
fr : tFrxReport;
thePtr : printinfo_type;
itm : sat;
tstprt : boolean;
begin
d.myHost := 'http://' + Request.host + ':' + intToStr(Request.ServerPort);
d.hostIP := Request.host;
d.Request := Request;
d.Response := Response;
d.remAddr := Request.RemoteAddr;
post := explode(Request.Content);
tstprt := false;
pdx := isset(post, 'json');
pish:='';
if (pdx >=0) then
begin
sr := tStringReader.Create(post[pdx].st_desc);
jtr := tJsonTextReader.Create(sr);
while jtr.read do
begin
if jtr.TokenType = tJsonToken.StartObject then
itm := processJson(itm, jtr);
end;
if fileexists(itm[2].st_desc) then
begin
thePtr := getPrinterInfo(itm[1].st_desc);
fr := tFrxReport.Create(nil);
fr.LoadFromFile(itm[2].st_desc);
// pre load any vars so report does not fail
for idx := 0 to fr.Variables.Count-1 do
fr.Variables.Items[iitm].Value := frText('');
for idx := 4 to High(itm) do
begin
pish := pish + 'index of '+itm[idx].st_code+' = '+ intToStr (fr.Variables.IndexOf(itm[idx].st_code))+'<br>';
iitm := fr.Variables.IndexOf(itm[idx].st_code);
if iitm > -1 then
fr.Variables.Items[iitm].Value := frText(itm[idx].st_desc);
end;
if fr.PrepareReport then
begin
//fr.ShowPreparedReport;
fr.PrintOptions.Printer := thePtr.name;
fr.PrintOptions.PrnOutFileName := 'Trace Label';
fr.PrintOptions.ShowDialog := tstprt;
fr.ShowProgress := tstprt;
fr.Print;
end;
fr.Free;
end;
Response.Content := pish ;
end
else
begin
Response.Content := '<html>' +
'<head><title>Label List</title></head>' +
'<body>This is only used by print serve clients</p>'+
'</body>' +
'</html>';
end;
end;
The problem lies here:
fr.PrintOptions.PrnOutFileName := 'Trace Label';
I erroneously thought that would add a description in the print queue. What it actually did is send the report into limbo :)
I'm having trouble with InputQuery/InputBox on Delphi XE2.
The input area is out of place (should be under text).
Is there a way to re-align it before making my own input form?
Thank you!
InputQuery() is not designed to be used in this manner. The prompt text is meant to be a short label displayed to the left of the text field (similar to TLabeledEdit). It is not designed to display instructions above the prompts, like you are attempting. This situation would be much better handled by simply creating your own custom Form using whatever controls and layouts you want. For instance, using TDateTimePicker for dates and times, TCheckBox or TRadioGroup to indicate repeats, etc.
However, that being said, InputQuery() is implemented using a custom VCL TForm, so it is technically possible to accomplish what you are trying to achieve. You can use the TScreen.OnActiveFormChange event to gain access to the Form object when it becomes visible, and then you can manipulate it however you want. For example:
procedure TMyForm.ActiveFormChanged(Sender: TObject);
var
Form: TCustomForm;
Prompt: TLabel;
Edit: TEdit;
Ctrl: TControl;
I, J, ButtonTop: Integer;
begin
Form := Screen.ActiveCustomForm;
if (Form = nil) or (Form.ClassName <> 'TInputQueryForm') then Exit;
for I := 0 to Form.ControlCount-1 do
begin
Ctrl := Form.Controls[i];
if Ctrl is TLabel then
begin
Prompt := TLabel(Ctrl);
end
else if Ctrl is TEdit then
begin
Edit := TEdit(Ctrl);
end;
end;
Edit.SetBounds(Prompt.Left, Prompt.Top + Prompt.Height + 5, Prompt.Width, Edit.Height);
Form.ClientWidth := (Edit.Left * 2) + Edit.Width;
ButtonTop := Edit.Top + Edit.Height + 15;
J := 0;
for I := 0 to Form.ControlCount-1 do
begin
Ctrl := Form.Controls[i];
if Ctrl is TButton then
begin
Ctrl.SetBounds(Form.ClientWidth - ((Ctrl.Width + 15) * (2-J)), ButtonTop, Ctrl.Width, Ctrl.Height);
Form.ClientHeight := Ctrl.Top + Ctrl.Height + 13;
Inc(J);
end;
end;
end;
procedure TMyForm.DoSomething;
var
value: string;
begin
Screen.OnActiveFormChange := ActiveFormChanged;
try
InputQuery('Enter New Schedule', 'Format: <Second> <Minute> <Hour> <Day_of_the_Month> <Month_of_the_Year> <Day_of_the_Week> <Year>.'#10'Use * for repeating cycles. ex: 0 0 7 * * * * (trigger at 7AM everyday)', value);
finally
Screen.OnActiveFormChange := nil;
end;
end;
Alternatively:
class procedure TScreenEvents.ActiveFormChanged(Sender: TObject);
var
Form: TCustomForm;
Instructions: TLabel;
Ctrl: TControl;
I, J, K, Offset: Integer;
begin
Form := Screen.ActiveCustomForm;
if (Form = nil) or (Form.ClassName <> 'TInputQueryForm') then Exit;
for I := 0 to Form.ControlCount-1 do
begin
Ctrl := Form.Controls[I];
if Ctrl is TLabel then
begin
Instructions := TLabel.Create(Form);
Instructions.Parent := Form;
Instructions.Caption := 'Format: <Second> <Minute> <Hour> <Day_of_the_Month> <Month_of_the_Year> <Day_of_the_Week> <Year>.'#10'Use * for repeating cycles. ex: 0 0 7 * * * * (trigger at 7AM everyday)';
Instructions.SetBounds(Ctrl.Left, Ctrl.Top, Instructions.Width, Instructions.Height);
Offset := Instructions.Top + Instructions.Height + 5;
Form.ClientWidth := Instructions.Width + (Instructions.Left * 2);
K := 0;
for J := 0 to Form.ControlCount-1 do
begin
Ctrl := Form.Controls[J];
if Ctrl <> Instructions then
begin
Ctrl.Top := Ctrl.Top + Offset;
if Ctrl is TEdit then
begin
Ctrl.Width := (Form.ClientWidth - Ctrl.Left - Instructions.Left);
end
else if Ctrl is TButton then
begin
Ctrl.Left := (Form.ClientWidth - (Ctrl.Width + 5) * (2-K));
Inc(K);
end;
end;
end;
Form.ClientHeight := Form.ClientHeight + Offset;
Break;
end;
end;
end;
procedure TMyForm.DoSomething;
var
value: string;
begin
Screen.OnActiveFormChange := ActiveFormChanged;
try
InputQuery('Enter New Schedule', 'Value', value);
finally
Screen.OnActiveFormChange := nil;
end;
end;
I have a problem which occurs only in a very small customer range and I would like to ask if you might give me a hint where the problem might be. The program works for 98% of the customers. Alas, it is not possible that I work with the customers to debug the issue, because their knowledge of Windows and computers is very basic. It is also not possible that I send multiple versions of the product to them, since they don't even know how to install software (the admins do all the stuff).
First of all, I translate all RT_STRING resources on-the-fly, so that the language-switching in the program also affects hardcoded stuff like "Yes", "No", "Cancel" etc., which would only be possible by compiling 2 EXE files.
The code (I have tried to left away as much unnecessary stuff as possible, but since I don't know where the problem is, I provided as much details for the bug as possible):
The ony-the-fly resource translation
procedure TranslateResources;
var
i: integer;
s: string;
{$IF NOT Declared(FILE_ATTRIBUTE_NOT_CONTENT_INDEXED)}
const
FILE_ATTRIBUTE_NOT_CONTENT_INDEXED = $2000;
{$IFEND}
begin
// I copy all resources in a dummy DLL (without code), because
// 1) The resources are the only thing we need when changing the resource module
// 2) If the EXE code/debug sections are too long, BeginUpdateResource() will ruin the performance heavily
FTempFile := IncludeTrailingPathDelimiter(GetTempDirectory) + GetRandomString(8)+'.dll';
// Transfers all resources from ParamStr(0) into the dummy DLL at FTempFile
ReGenerateResourceFile(FTempFile);
// if necessary, remove readonly flag
SetFileAttributes(PChar(FTempFile), FILE_ATTRIBUTE_OFFLINE or
FILE_ATTRIBUTE_NOT_CONTENT_INDEXED or
FILE_ATTRIBUTE_TEMPORARY );
for i := 0 to Length(RTLResStringTranslationArray)-1 do
begin
s := Translate(RTLResStringTranslationArray[i].TranslationID);
if s <> '' then
begin
// Translate the string
UpdateResString(RTLResStringTranslationArray[i].ResStrDescriptor.Identifier, s);
end;
end;
LoadNewResourceModule(FTempFile):
end;
procedure ReGenerateResourceFile(OutputFile: string);
var
hUpd: Cardinal;
rs: TResourceStream;
fs: TFileStream;
begin
// As template we use a dummy DLL which contains no code.
// We will implement all resources from ParamStr(0) into it, before we translate the strings.
rs := TResourceStream.Create(HInstance, 'DUMMYDLL', 'DLL');
fs := TFileStream.Create(OutputFile, fmCreate or fmOpenWrite);
try
fs.CopyFrom(rs, rs.Size)
finally
rs.Free;
fs.Free;
end;
// Transfer resources from our EXE into the dummy DLL file
hUpd := BeginUpdateResource(PChar(OutputFile), true);
try
EnumResourceTypes(hInstance, #_enumResTypesProc, hUpd);
finally
EndUpdateResource(hUpd, false)
end;
end;
// This is based on reinit.pas from Borland's RichEdit example; slightly modified
function LoadNewResourceModule(PatchedFile: string): LongInt;
var
NewInst: Longint;
CurModule: PLibModule;
begin
Result := 0;
// Win95: "Initialization routine failed"
// NewInst := LoadLibrary(PChar(PatchedFile));
NewInst := LoadLibraryEx(PChar(PatchedFile), 0, LOAD_LIBRARY_AS_DATAFILE);
CurModule := LibModuleList;
Result := 0;
while CurModule <> nil do
begin
if CurModule.Instance = HInstance then
begin
if CurModule.ResInstance <> CurModule.Instance then
FreeLibrary(CurModule.ResInstance);
// Win95: ERangeError
CurModule^.ResInstance := NewInst;
Result := NewInst;
Exit;
end;
CurModule := CurModule.Next;
end;
end;
// Based on http://stackoverflow.com/questions/1498658/modifying-a-string-in-resource-of-an-exe
// Modified
procedure UpdateResString(const AStringIdent: Integer; const ANewString: WideString);
var
ResData, TempData: TWordArray;
iSection, iIndexInSection: Integer;
i, iLen, iSkip, iPos: Integer;
begin
// Calculate the resource string area and the string index in that area
iSection := AStringIdent div 16 + 1;
iIndexInSection := AStringIdent mod 16;
ResData := ReadSectionCached(iSection);
// Calculate the position of the string
iLen := Length(ANewString);
iPos := 0;
for i := 0 to iIndexInSection do
begin
if iPos > High(ResData) then
begin
SetLength(ResData, iPos + 1);
ResData[iPos] := 0;
end;
if i <> iIndexInSection then
begin
iSkip := ResData[iPos] + 1;
Inc(iPos, iSkip);
end;
end;
// Put data behind strings into TempData
iSkip := 1{size} + ResData[iPos];
SetLength(TempData, Length(ResData) - (iPos + iSkip));
if Length(TempData) > 0 then
begin
CopyMemory(#TempData[0], #ResData[iPos + iSkip], Length(TempData)*SizeOf(TempData[0]));
end;
SetLength(ResData, iPos + (iLen + 1{size}) + Length(TempData));
// Overwrite string
ResData[iPos] := iLen;
Inc(iPos);
if iLen > 0 then
begin
CopyMemory(#ResData[iPos], #ANewString[1], iLen*SizeOf(ANewString[1]));
Inc(iPos, iLen);
end;
// Append TempData after our new string
if Length(TempData) > 0 then
begin
CopyMemory(#ResData[iPos], #TempData[0], Length(TempData)*SizeOf(TempData[0]));
end;
CacheSet(iSection, ResData);
end;
type
TGlobalData = record
GlobalPtr: Pointer;
Length: integer;
end;
function LoadResourcePtr(hModule: HMODULE; restype, resname: PChar; wIDLanguage: WORD): TGlobalData;
var
hFind, hRes: THandle;
begin
result.GlobalPtr := nil;
result.Length := -1;
hFind := Windows.FindResourceEx(hModule, restype, resname, wIDLanguage);
if hFind = 0 then RaiseLastOSError;
hres := Windows.LoadResource(hModule, hFind);
if hres = 0 then RaiseLastOSError;
result.GlobalPtr := Windows.LockResource(hres);
result.Length := Windows.SizeofResource(hModule, hFind);
end;
function _enumResLangsProc(hmodule: HMODULE; restype, resname: PChar; wIDLanguage: WORD;
lParam: LongInt): BOOL; stdcall;
var
rs: TGlobalData;
begin
rs := LoadResourcePtr(hmodule, restype, resname, wIDLanguage);
UpdateResource(lParam, restype, resname, wIDLanguage, rs.GlobalPtr, rs.Length);
result := true;
end;
function _enumResNamesProc(hmodule: HMODULE; restype, resname: PChar;
lParam: LongInt): BOOL; stdcall;
begin
EnumResourceLanguages(hmodule, restype, resname, #_enumResLangsProc, lParam);
result := true;
end;
function _enumResTypesProc(hmodule: HMODULE; restype: PChar;
lParam: LongInt): BOOL; stdcall;
begin
EnumResourceNames(hmodule, restype, #_enumResNamesProc, lParam);
result := true;
end;
{$R '..\dummydll\dummydll.RES'}
Then I use a wait form:
unit Wait;
interface
uses
...
type
TWaitForm = class(TForm)
...
end;
var
WaitForm: TWaitForm;
implementation
{$R *.dfm}
...
end;
The wait form will be called by dynamically showing the form:
procedure ShowWaitForm;
begin
...
{ I use my own _CreateForm function because it solves many workarounds for
juicy stuff like half-modal windows (which can be hidden without user action),
miscellaneous deadlocks etc. and to allow the form to be shown in a shared PAS file
without the requirement to add it to every DPR file where the WaitForm API is used. }
WaitForm := _CreateForm(TWaitForm, {Application.MainForm}AParent) as TWaitForm;
WaitForm.Show;
...
end;
function _CreateForm(InstanceClass: TCustomFormClass; AParent: TCustomForm): TCustomForm;
var
LOwner: TComponent;
begin
if Assigned(AParent) then
begin
LOwner := AParent;
end
else if Assigned(Application) then
begin
LOwner := Application;
end
else
begin
LOwner := nil;
end;
result := InstanceClass.Create(LOwner);
end;
The error message at 2% of the customers:
Resource TWaitForm was not found
However, other forms are working.
There are 2 theories I can think of:
1) Did the resource translation corrupt the DLL file / part of the RCData section? (Maybe a bug in the WinAPI's UpdateResource ?)
2) Is there a problem with the dynamic showing of the wait form (since other "static" forms are shown?)
I use the following helper for preventing forms moving of screen and it is most of the time working OK. But if I open a wsNormal form in a MDI app then the form might show up of the area where it is supposed to be. I can then just move it a bit and then the unit here takes over and moves it in place.
My question is now: how can I either prevent this from happening or send a message to the form saying it is moving so the unit her can do its job.
unit U_FormsMove;
interface
uses
Messages, Windows, Forms;
{$M+}
type
TForm = class(Forms.TForm)
private
protected
procedure WMMoving(var message : TWMMoving); message WM_MOVING;
published
public
end;
implementation
function GetMovementArea: TRect;
var
MovementRect: TRect;
begin
if Application.MainForm.FormStyle = fsMDIForm then
Windows.GetWindowRect(Application.MainForm.ClientHandle, MovementRect)
else
SystemParametersInfo(SPI_GETWORKAREA, 0, #MovementRect, 0);
if MovementRect.Top < 150 then
MovementRect.Top := 150;
MovementRect.Top := MovementRect.Top + 5;
MovementRect.Left := MovementRect.Left + 5;
MovementRect.Right := MovementRect.Right - 5;
MovementRect.Bottom := MovementRect.Bottom - 5;
Result := MovementRect;
end;
{ TFormHelper }
procedure TForm.WMMoving(var Message: TWMMoving);
var
rec: ^TRect;
wrk: TRect;
begin
wrk := GetMovementArea;
rec := Pointer(Message.DragRect);
if rec^.Left < wrk.Left then
begin
rec^.Right := rec^.Right - (rec^.Left - wrk.Left);
rec^.Left := wrk.Left;
end
else if rec^.Right > wrk.Right then
begin
rec^.Left := rec^.Left - (rec^.Right - wrk.Right);
rec^.Right := wrk.Right;
end;
if rec^.Top < wrk.Top then
begin
rec^.Bottom := rec^.Bottom - (rec^.Top - wrk.Top);
rec^.Top := wrk.Top;
end
else if rec^.Bottom > wrk.Bottom then
begin
rec^.Top := rec^.Top - (rec^.Bottom - wrk.Bottom);
rec^.Bottom := wrk.Bottom;
end;
end;
end.
These days you see a lot of software displaying message windows in the right bottom corner of the active screen for a few seconds or until a close button is clicked (f.i. Norton does this after it has checked a download).
I would like to do this using Delphi 7 (and if possible Delphi 2010, since I am slowly migrating my code to the latest version).
I found some posts here on SO regarding forms not receiving focus, but that's only one part of the problem. I'm thinking also on how to determine the exact position of this message window (knowing that f.i. a user may have put his taskbar to the right of the screen.
Thx in advance.
UPDATE 26 Jan, 10: Starting from the code of drorhan I created the following form (in Delphi 7) which works whether the taskbar is displayed at the bottom, the right, the left or the top of the schreen.
fPopupMessage.dpr:
object frmPopupMessage: TfrmPopupMessage
Left = 537
Top = 233
AlphaBlend = True
AlphaBlendValue = 200
BorderStyle = bsToolWindow
Caption = 'frmPopupMessage'
ClientHeight = 48
ClientWidth = 342
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'MS Sans Serif'
Font.Style = []
OldCreateOrder = False
OnClose = FormClose
OnCreate = FormCreate
DesignSize = (
342
48)
PixelsPerInch = 96
TextHeight = 13
object img: TImage
Left = 0
Top = 0
Width = 64
Height = 48
Align = alLeft
Center = True
Transparent = True
end
object lblMessage: TLabel
Left = 72
Top = 8
Width = 265
Height = 34
Alignment = taCenter
Anchors = [akLeft, akTop, akRight, akBottom]
AutoSize = False
Caption = '...'
Font.Charset = DEFAULT_CHARSET
Font.Color = clNavy
Font.Height = -11
Font.Name = 'Verdana'
Font.Style = [fsBold]
ParentFont = False
Transparent = True
WordWrap = True
end
object tmr: TTimer
Enabled = False
Interval = 3000
OnTimer = tmrTimer
Left = 16
Top = 16
end
end
and
fPopupMessage.pas
unit fPopupMessage;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, ExtCtrls, ImgList;
type
TfrmPopupMessage = class(TForm)
tmr: TTimer;
img: TImage;
lblMessage: TLabel;
procedure FormCreate(Sender: TObject);
procedure tmrTimer(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
private
{ Private declarations }
bBeingDisplayed : boolean;
function GetPopupMessage: string;
procedure SetPopupMessage(const Value: string);
function GetPopupCaption: string;
procedure SetPopupCaption(const Value: string);
function TaskBarHeight: integer;
function TaskBarWidth: integer;
procedure ToHiddenPosition;
procedure ToVisiblePosition;
public
{ Public declarations }
procedure StartAnimationToHide;
procedure StartAnimationToShow;
property PopupCaption: string read GetPopupCaption write SetPopupCaption;
property PopupMessage: string read GetPopupMessage write SetPopupMessage;
end;
var
frmPopupMessage: TfrmPopupMessage;
procedure DisplayPopup( sMessage:string; sCaption:string = '');
implementation
{$R *.dfm}
const
DFT_TIME_SLEEP = 5; // the speed you want to show/hide.Increase/descrease this to make it faster or slower
DFT_TIME_VISIBLE = 3000; // number of mili-seconds the form is visible before starting to disappear
GAP = 2; // pixels between form and right and bottom edge of the screen
procedure DisplayPopup( sMessage:string; sCaption:string = '');
begin
// we could create the form here if necessary ...
if not Assigned(frmPopupMessage) then Exit;
frmPopupMessage.PopupCaption := sCaption;
frmPopupMessage.PopupMessage := sMessage;
if not frmPopupMessage.bBeingDisplayed
then begin
ShowWindow( frmPopupMessage.Handle, SW_SHOWNOACTIVATE);
frmPopupMessage.Visible := True;
end;
frmPopupMessage.StartAnimationToShow;
end;
procedure TfrmPopupMessage.FormCreate(Sender: TObject);
begin
img.Picture.Assign(Application.Icon);
Caption := '';
lblMessage.Caption := '';
bBeingDisplayed := False;
ToHiddenPosition();
end;
procedure TfrmPopupMessage.FormClose(Sender: TObject; var Action: TCloseAction);
begin
tmr.Enabled := False;
Action := caHide;
bBeingDisplayed := False;
end;
function TfrmPopupMessage.TaskBarHeight: integer; // this is just to get the taskbar height to put
// my form in the correct position
var
hTB: HWND;
TBRect: TRect;
begin
hTB := FindWindow('Shell_TrayWnd', '');
if hTB = 0 then
Result := 0
else
begin
GetWindowRect(hTB, TBRect);
if TBRect.Top = 0 // tray bar is positioned to the left or to the right
then
Result := 1
else
Result := TBRect.Bottom - TBRect.Top;
end;
end;
function TfrmPopupMessage.TaskBarWidth: integer; // this is just to get the taskbar height to put
// my form in the correct position
var
hTB: HWND;
TBRect: TRect;
begin
hTB := FindWindow('Shell_TrayWnd', '');
if hTB = 0 then
Result := 0
else
begin
GetWindowRect(hTB, TBRect);
if TBRect.Left = 0 // tray bar is positioned to the left or to the right
then
Result := 1
else
Result := TBRect.Right - TBRect.Left
end;
end;
procedure TfrmPopupMessage.ToHiddenPosition;
begin
Self.Left := Screen.Width - TaskbarWidth - Self.Width - GAP;
Self.Top := Screen.Height - TaskBarHeight;
end;
procedure TfrmPopupMessage.ToVisiblePosition;
begin
Self.Left := Screen.Width - TaskBarWidth - Self.Width - GAP;
Self.Top := Screen.Height - Self.Height - TaskBarHeight - GAP;
end;
procedure TfrmPopupMessage.StartAnimationToShow;
var
i: integer;
begin
if bBeingDisplayed
then
ToVisiblePosition()
else begin
ToHiddenPosition();
for i := 1 to Self.Height+GAP do
begin
Self.Top := Self.Top-1;
Application.ProcessMessages;
Sleep(DFT_TIME_SLEEP);
end;
end;
tmr.Interval := DFT_TIME_VISIBLE;
tmr.Enabled := True;
bBeingDisplayed := True;
end;
procedure TfrmPopupMessage.StartAnimationToHide;
var
i: integer;
begin
if not bBeingDisplayed then Exit;
for i := 1 to Self.Height+GAP do
begin
Self.Top := Self.Top+1;
Application.ProcessMessages;
Sleep(DFT_TIME_SLEEP);
end;
bBeingDisplayed := False;
Visible := False;
end;
procedure TfrmPopupMessage.tmrTimer(Sender: TObject);
begin
tmr.Enabled := False;
StartAnimationToHide();
end;
function TfrmPopupMessage.GetPopupMessage: string;
begin
Result := lblMessage.Caption;
end;
procedure TfrmPopupMessage.SetPopupMessage(const Value: string);
begin
lblMessage.Caption := Value;
end;
function TfrmPopupMessage.GetPopupCaption: string;
begin
Result := frmPopupMessage.Caption;
end;
procedure TfrmPopupMessage.SetPopupCaption(const Value: string);
begin
frmPopupMessage.Caption := Value;
end;
end.
To be used as in my test form with two buttons:
procedure TfrmMain.button1Click(Sender: TObject);
begin
DisplayPopup('Message displayed at ' + FormatDateTime('ddd mmm yy zzz', Now),'My Program');
beep;
end;
procedure TfrmMain.button2Click(Sender: TObject);
begin
DisplayPopup('Another message displayed at ' + FormatDateTime('hh:nn zzz', Now),'My Program');
end;
The message form will display the application icon, but I will probably add a TImageList and add a property to pass an image index so I can display different icons. I will also use the TcxLabel from the Dev.Express components as this will provide verticle positionting, but the above unit can be used as is.
I tested this with Delphi 7 and Windows XP. If anyone uses this unit with another version of Delphi and/or Windows Vista or Windows 7, please tell me if this unit will work there too.
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
procedure FormCreate(Sender: TObject);
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.FormCreate(Sender: TObject);
function TaskBarHeight: integer; // this is just to get the taskbar height to put
// my form in the correct position
var
hTB: HWND;
TBRect: TRect;
begin
hTB := FindWindow('Shell_TrayWnd', '');
if hTB = 0 then
Result := 0
else
begin
GetWindowRect(hTB, TBRect);
Result := TBRect.Bottom - TBRect.Top;
end;
end;
begin
Self.Left := Screen.Width - Self.Width;
Self.Top := Screen.Height-Self.Height-TaskBarHeight;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
i: integer;
TimeSleep: integer;
begin
TimeSleep := 5; // the speed you want to show/hide.Increase/descrease this to make it faster or slower
for i := 1 to Self.Height do
begin
Self.Top := Self.Top+1;
Sleep(TimeSleep);
end;
// now let's show it again(use this as code as the show code)
for i := 1 to Self.Height do
begin
Self.Top := Self.Top-1;
Sleep(TimeSleep);
end;
end;
end.
via http://www.experts-exchange.com/Programming/Languages/Pascal/Delphi/Q_25043483.html
Try using the TJvDesktopAlert component wich is included in the JVCL, you can find an example in jvcl\examples\JvDesktopAlert\JvDesktopAlertDemo.dpr
(source: agnisoft.com)
What you are searching for are Balloon Tips in a System Tray. For general WinAPI here's a nice tutorial for it, that you shouldn't have problems translating to Delphi.
You can find some ready to use code for balloon tips in Delphi here.
A nice implementation is available here.
You can check where is Taskbar:
uses ShellAPI;
//...
Var AppBar: TAppbarData;
//...
begin
FillChar(AppBar, sizeof(AppBar), 0);
AppBar.cbSize := Sizeof(AppBar);
if ShAppBarMessage(ABM_GETTASKBARPOS, AppBar) <> 0 then
begin
//AppBar.rc is TRect
end;
end;
And then show your form...
You could use Growl for Windows - I don't think there is a Delphi library for it yet, but you can control it via UDP messages, so any network library should do.
TMsnPopUpNotify
http://www.torry.net/vcl/forms/appearence/tmsnpopup.zip
Check out Snarl, similar to Growl for Windows, but I have found to be better.
There is a Pas file to easily interface, and the way it works is very simple, with just sending windows messages.
http://fullphat.net/
It also allows the end user some amount of control of which messages to see, duration before fading, etc.