Friends,
Need to screenshot of the all desktop WITHOUT MY FORM and load in TImage.
Success in Windows XP, 7 - with just ALPHABLEND = TRUE + SCREENSHOT PROCEDURE.
But same code does not work in Windows 8 - capture all screen INCLUDING THE FORM.
I know the problem is related to AERO - DWM.EXE - success using pssuspend.exe (sysinternals) - suspending winlogon.exe and killing dwm.exe
Someone could tell me how to capture all desktop without my form also in Windows 8?
prntscr.com/314rix - SUCESS IN WIN7
prntscr.com/314tj7 - FAILED IN WIN8
prntscr com/31502u - SUSPEND WINLOGON.EXE and KILL DWM.EXE IN WIN8
www sendspace com/file/b5oxhb - SOURCE CODE
// FORM -> ALPHABLEND -> TRUE
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, ExtCtrls,
Clipbrd;
type
TForm1 = class(TForm)
Button1: TButton;
Image1: TImage;
ScrollBox1: TScrollBox;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure ScreenShot(DestBitmap: TBitmap);
var
DC: HDC;
begin
DC:=GetDC(GetDesktopWindow);
try
DestBitmap.Width:=GetDeviceCaps(DC, HORZRES);
DestBitmap.Height:=GetDeviceCaps(DC, VERTRES);
BitBlt(DestBitmap.Canvas.Handle,0,0,DestBitmap.Width,DestBitmap.Height,DC,0,0,SRCCOPY);
finally
ReleaseDC(GetDesktopWindow, DC);
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
ScreenShot(Image1.Picture.Bitmap);
end;
end.
If you want to take a screenshot without your window appearing: hide the window before taking the screenshot:
procedure TForm1.Button1Click(Sender: TObject);
var
desktop: TGraphic;
fDisable: BOOL;
begin
{
Capture a screenshot without this window showing
}
//Disable DWM transactions so the window hides immediately
if DwmApi.DwmCompositionEnabled then
begin
fDisable := True;
OleCheck(DwmSetWindowAttribute(Self.Handle, DWMWA_TRANSITIONS_FORCEDISABLED, #fDisable, sizeof(fDisable)));
end;
try
//Hide the window
Self.Hide;
try
//Capture the desktop
desktop := CaptureDesktop;
finally
//Re-show our window
Self.Show;
end;
finally
//Restore animation transitions
if DwmApi.DwmCompositionEnabled then
begin
fDisable := False;
DwmSetWindowAttribute(Self.Handle, DWMWA_TRANSITIONS_FORCEDISABLED, #fDisable, sizeof(fDisable));
end;
end;
//Save the screenshot somewhere
desktop.SaveToFile('d:\temp\ss.bmp');
end;
With the magic happening in:
function CaptureDesktop: TGraphic;
const
CAPTUREBLT = $40000000;
SM_XVIRTUALSCREEN = 76;
SM_YVIRTUALSCREEN = 77;
SM_CXVIRTUALSCREEN = 78;
SM_CYVIRTUALSCREEN = 79;
var
nDesktopWidth, nDesktopHeight: Integer;
tmpBmp: TBitmap;
hwndDesktop: HWND;
dcDesktop: HDC;
begin
Result := nil;
{
GetWindowRect(GetDesktopWindow)
is completely wrong. It will intentionally return only the rectangle of the primary monotor. See MSDN.
}
{ Cannot handle dpi virtualization
//Get the rect of the entire desktop; not just the primary monitor
ZeroMemory(#desktopRect, SizeOf(desktopRect));
for i := 0 to Screen.MonitorCount-1 do
begin
desktopRect.Top := Min(desktopRect.Top, Screen.Monitors[i].Top);
desktopRect.Bottom := Max(desktopRect.Bottom, Screen.Monitors[i].Top + Screen.Monitors[i].Height);
desktopRect.Left := Min(desktopRect.Left, Screen.Monitors[i].Left);
desktopRect.Right := Max(desktopRect.Right, Screen.Monitors[i].Left + Screen.Monitors[i].Width);
end;
//Get the size of the entire desktop
nDesktopWidth := (desktopRect.Right - desktopRect.Left);
nDesktopHeight := (desktopRect.Bottom - desktopRect.Top);
}
//Also doesn't handle dpi virtualization; but is shorter and unioning rects
nDesktopWidth := GetSystemMetrics(SM_CXVIRTUALSCREEN);
nDesktopHeight := GetSystemMetrics(SM_CYVIRTUALSCREEN);
tmpBmp:= TBitmap.Create;
try
tmpBmp.Width := nDesktopWidth;
tmpBmp.Height := nDesktopHeight;
//dcDesktop := GetDC(0); //
hwndDesktop := GetDesktopWindow;
dcDesktop := GetDC(hwndDesktop); //GetWindowDC(0) returns the DC of the primary monitor (not what we want)
if dcDesktop = 0 then
Exit;
try
if not BitBlt(tmpBmp.Canvas.Handle, 0, 0, nDesktopWidth, nDesktopHeight, dcDesktop, 0, 0, SRCCOPY or CAPTUREBLT) then
Exit;
finally
ReleaseDC(0, dcDesktop);
end;
except
tmpBmp.Free;
raise;
end;
// CaptureScreenShot(GetDesktopWindow, Image, false);
Result := tmpBmp;
end;
The screen with the app running:
And the saved screenshot:
Note: Any code released into public domain. No attribution required.
Related
I wrote a program that can identify outdated software in a Windows System and update them by interacting with the user.
It has a Software Updater Program which displays a System Tray Icon and show Balloon Tips about Available / Downloading Updates and Software installed in the System.
The problem is It can't show multiple Balloon Tips when each task is processing by it. Such as, when an update is available for a Software, it should remember user showing a balloon like An update for Software Name is available. and when user choose to download and minimize it to system tray again, the balloon tip should again show something like Updates are downloading...Click to view the Progress of Downloads.
However I like to know how can I do this by using only one System Tray Icon?
Can I use the NIM_MODIFY Flag again and again to change the Balloon Tip according to the current state of the Program?
I searched about this and I found some examples, but for Visual Studio and C++.
That's how I tried to show Multiple Tips when the Program is running:
unit MainForm-1;
...
const
NIF_INFO = $10;
NIF_MESSAGE = 1;
NIF_ICON = 2;
NOTIFYICON_VERSION = 3;
NIF_TIP = 4;
NIM_SETVERSION = $00000004;
NIM_SETFOCUS = $00000003;
NIIF_INFO = $00000001;
NIIF_WARNING = $00000002;
NIIF_ERROR = $00000003;
NIN_BALLOONSHOW = WM_USER + 2;
NIN_BALLOONHIDE = WM_USER + 3;
NIN_BALLOONTIMEOUT = WM_USER + 4;
NIN_BALLOONUSERCLICK = WM_USER + 5;
NIN_SELECT = WM_USER + 0;
NINF_KEY = $1;
NIN_KEYSELECT = NIN_SELECT or NINF_KEY;
TRAY_CALLBACK = WM_USER + $7258;
PNewNotifyIconData = ^TNewNotifyIconData;
TDUMMYUNIONNAME = record
case Integer of
0: (uTimeout: UINT);
1: (uVersion: UINT);
end;
TNewNotifyIconData = record
cbSize: DWORD;
Wnd: HWND;
uID: UINT;
uFlags: UINT;
uCallbackMessage: UINT;
hIcon: HICON;
szTip: array [0..127] of Char;
dwState: DWORD; /
dwStateMask: DWORD;
szInfo: array [0..255] of Char;
DUMMYUNIONNAME: TDUMMYUNIONNAME;
szInfoTitle: array [0..63] of Char;
dwInfoFlags: DWORD;
end;
type
MainForm-1 = class(TForm)
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
IconData: TNewNotifyIconData;
procedure SysTrayIconMessageHandler(var Msg: TMessage); message TRAY_CALLBACK;
procedure AddSysTrayIcon;
procedure ShowBalloonTips;
procedure DeleteSysTrayIcon;
public
end;
var
MainForm-1: TForm;
implementation
uses
ShellAPI...,.....,;
procedure MainForm-1.SysTrayIconMessageHandler(var Msg: TMessage);
begin
case Msg.lParam of
WM_MOUSEMOVE:;
WM_LBUTTONDOWN:;
WM_LBUTTONUP:;
WM_LBUTTONDBLCLK:;
WM_RBUTTONDOWN:;
WM_RBUTTONUP:;
WM_RBUTTONDBLCLK:;
NIN_BALLOONSHOW:;
NIN_BALLOONHIDE:;
NIN_BALLOONTIMEOUT:
NIN_BALLOONUSERCLICK:;
end;
end;
procedure MainForm-1.AddSysTrayIcon;
begin
IconData.cbSize := SizeOf(IconData);
IconData.Wnd := AllocateHWnd(SysTrayIconMessageHandler);
IconData.uID := 0;
IconData.uFlags := NIF_ICON or NIF_MESSAGE or NIF_TIP;
IconData.uCallbackMessage := TRAY_CALLBACK;
IconData.hIcon := Application.Icon.Handle;
IconData.szTip := 'Software Updater is running';
if not Shell_NotifyIcon(NIM_ADD, #IconData) then
ShowMessage('System Tray Icon cannot be created.');
end;
procedure MainForm-1.DisplayBalloonTips;
var
TipInfo, TipTitle: string;
begin
IconData.cbSize := SizeOf(IconData);
IconData.uFlags := NIF_INFO;
if ssHelperState = UpdatesAvailable then TipInfo := 'Updates are available to the programs installed on your Computer' + ' Click to see details.';
if ssHelperState = UpdatesDownloading then TipInfo := 'Updates are downloading in the background. Click to view the details.';
strPLCopy(IconData.szInfo, TipInfo, SizeOf(IconData.szInfo) - 1);
IconData.DUMMYUNIONNAME.uTimeout := 2500;
if ssHelperState = UpdatesAvailable then TipTitle := 'Updates are Available...';
if ssHelperState = UpdatesDownloading then TipTitle := 'Downloading the Updates...';
strPLCopy(IconData.szInfoTitle, TipTitle, SizeOf(IconData.szInfoTitle) - 1);
IconData.dwInfoFlags := NIIF_INFO;
Shell_NotifyIcon(NIM_MODIFY, #IconData);
{Following code is for testing purpose.}
IconData.DUMMYUNIONNAME.uVersion := NOTIFYICON_VERSION;
if not Shell_NotifyIcon(NIM_SETVERSION, #IconData) then
ShowMessage('Setting the Version is Failed.');
end;
procedure MainForm-1.DeleteSysTrayIcon;
begin
DeallocateHWnd(IconData.Wnd);
if not Shell_NotifyIcon(NIM_DELETE, #IconData) then
ShowMessage('Unable to delete System Tray Icon.');
end;
procedure MainForm-1.FormCreate(Sender: TObject);
begin
AddSysTrayIcon;
ShowBalloonTips;
end;
procedure MainForm-1.FormDestroy(Sender: TObject);
begin
DeleteSysTrayIcon;
end;
...
end.
But, this is failing and I keep getting the same Balloon Tip (First One) again and again when the Program is running.......
I don't know how to use NIN_BALLOONSHOW and NIN_BALLOONHIDE Flags correctly. So, Thanks in Advance for Your Important Help.
Why are you declaring everything manually? Delphi 2009 already has declarations for the Shell_NotifyIcon() API. They are in the ShellAPI unit. It declares just about everything you are trying to use, except for the uVersion field (that was added in Delphi 2010). You are not using the guidItem and hBalloonIcon fields, so let's not worry about them here. The uTimeout field exists, and since it is wrapped in a union with uVersion, the data size does not change, so you can just use uTimeout when you want to use uVersion (or you can define your own union and type-cast the field, but that is overkill). You certainly do not need to redeclare the entire API.
You are reusing the same IconData variable each time you call Shell_NotifyIcon(), which is fine, but you are not clearing the szTip and szInfoTitle fields if your helper state is not UpdatesAvailable or UpdatesDownloading, so the tray icon keeps displaying the last tip/balloon you have set. You need to clear those fields when you don't need tips/balloons anymore.
NIN_BALLOONSHOW and NIN_BALLOONHIDE are not flags. They are notifications that are sent to your tray icon's registered HWND. To receive the notifications, you need to fill in the Wnd and uCallbackMessage fields and enable the NIF_MESSAGE flag.
Also, you need to handle the WM_TASKBARCREATED message. If Explorer gets restarted for any reason (crashes, or is killed by the user), the Taskbar gets re-created, so you have to re-add your tray icon again.
Also, make sure your message handler passes any unhandled window messages to DefWindowProc(), or you can lock up the system, or at least your app.
And lastly, Delphi 2009 is a Unicode version of Delphi, but there are some sections of your code that are not handling Unicode correctly. Specifically, when populating szTip and szInfoTitle using StrPLCopy(), you need to use Length() instead of SizeOf(). The copy is expressed in number of characters, not number of bytes.
With that said, try something more like this:
unit MainForm1;
interface
uses
..., ShellAPI;
type
eHelperState = (Idle, UpdatesAvailable, UpdatesDownloading);
MainForm = class(TForm)
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
TaskbarCreatedMsg: UINT;
IconData: NOTIFYICONDATA;
IconAdded: Boolean;
ssHelperState: eHelperState;
procedure SysTrayIconMessageHandler(var Message: TMessage);
procedure AddSysTrayIcon;
procedure ShowBalloonTips;
procedure DeleteSysTrayIcon;
procedures SetHelperState(NewState: eHelperState);
...
end;
var
MainForm: TForm;
implementation
const
TRAY_CALLBACK = WM_USER + $7258;
{$IF RTLVersion < 21}
NOTIFYICON_VERSION_4 = 4;
{$IFEND}
procedure MainForm.FormCreate(Sender: TObject);
begin
TaskbarCreatedMsg := RegisterWindowMessage('TaskbarCreated');
IconData.cbSize := SizeOf(IconData);
IconData.Wnd := AllocateHWnd(SysTrayIconMessageHandler);
IconData.uID := 1;
AddSysTrayIcon;
end;
procedure MainForm.FormDestroy(Sender: TObject);
begin
DeleteSysTrayIcon;
DeallocateHWnd(IconData.Wnd);
end;
procedure MainForm.AddSysTrayIcon;
begin
IconData.uFlags := NIF_ICON or NIF_MESSAGE or NIF_TIP;
IconData.uCallbackMessage := TRAY_CALLBACK;
IconData.hIcon := Application.Icon.Handle;
StrLCopy(IconData.szTip, 'Software Updater is running', Length(IconData.szTip));
IconAdded := Shell_NotifyIcon(NIM_ADD, #IconData);
if not IconAdded then
begin
ShowMessage('Unable to add System Tray Icon.');
Exit;
end;
if CheckWin32Version(5, 0) then
begin
IconData.{$IF RTLVersion >= 21}uVersion{$ELSE}uTimeout{$IFEND} := NOTIFYICON_VERSION_4;
if not Shell_NotifyIcon(NIM_SETVERSION, #IconData) then
ShowMessage('Unable to set version for System Tray Icon.');
end;
end;
procedure MainForm.DisplayBalloonTips;
var
Tip, InfoText, InfoTitle: string;
begin
if not IconAdded then Exit;
case ssHelperState of
UpdatesAvailable: begin
Tip := 'Updates are Available. Click to see details.';
InfoText := 'Updates are available to the programs installed on your Computer. Click to see details.';
InfoTitle := 'Updates are Available';
end;
UpdatesDownloading: begin
Tip := 'Downloading Updates. Click to see details.';
InfoText := 'Updates are downloading in the background. Click to see details.';
InfoTitle := 'Downloading Updates';
end;
else
Tip := 'Software Updater is running';
end;
IconData.uFlags := NIF_TIP or NIF_INFO;
StrPLCopy(IconData.szTip, Tip, Length(IconData.szTip));
StrPLCopy(IconData.szInfo, InfoText, Length(IconData.szInfo));
StrPLCopy(IconData.szInfoTitle, InfoTitle, Length(IconData.szInfoTitle));
IconData.uTimeout := 2500;
IconData.dwInfoFlags := NIIF_INFO;
if not Shell_NotifyIcon(NIM_MODIFY, #IconData) then
ShowMessage('Unable to update System Tray Icon.')
end;
procedure MainForm.DeleteSysTrayIcon;
begin
if IconAdded then
begin
IconAdded := False;
if not Shell_NotifyIcon(NIM_DELETE, #IconData) then
ShowMessage('Unable to delete System Tray Icon.');
end;
end;
procedures MainForm.SetHelperState(NewState: eHelperState);
begin
if ssHelperState <> NewState then
begin
ssHelperState := NewState;
DisplayBalloonTips;
end;
end;
procedure MainForm.SysTrayIconMessageHandler(var Message: TMessage);
begin
if Message.Msg = TRAY_CALLBACK then
begin
case LOWORD(Message.LParam) of
WM_MOUSEMOVE: begin
//...
end;
WM_LBUTTONDBLCLK,
NIN_BALLOONUSERCLICK: begin
// display status window...
end;
WM_CONTEXTMENU,
NIN_KEYSELECT,
NIN_SELECT: begin
// display popup menu at coordinates specified by Msg.WParam...
end;
NIN_BALLOONSHOW:;
NIN_BALLOONHIDE:;
NIN_BALLOONTIMEOUT:;
end;
end
else if (Message.Msg = TaskbarCreatedMsg) and (TaskbarCreatedMsg <> 0) then
begin
IconAdded := False;
AddSysTrayIcon;
DisplayBalloonTips;
end
else begin
Message.Result := DefWindowProc(IconData.Wnd, Message.Msg, Message.WParam, Message.LParam);
end;
end;
...
end.
I have the following code, all the code needs to do is go through a list of vehicles and remove the spaces in each registration but before changing it, it should check to make sure the ammended registration doesn't exist. The following code is what I am using:
unit Main;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, Menus, cxLookAndFeelPainters, StdCtrls, cxButtons, Gauges, DB,
DBTables, StrUtils;
type
TfrmMain = class(TForm)
prgTotal: TGauge;
btnStart: TcxButton;
tblVeh: TTable;
tblVehRegNo: TStringField;
procedure btnStartClick(Sender: TObject);
private
procedure OpenTable(pTable: TTable);
procedure CloseTable(pTable: TTable; pPost: Boolean);
{ Private declarations }
public
{ Public declarations }
end;
var
frmMain : TfrmMain;
lvRegLst : TStringList;
lvTblSize : Integer;
lvOrigReg : String;
lvNewReg : String;
lvTest : integer;
implementation
{$R *.dfm}
procedure TfrmMain.btnStartClick(Sender: TObject);
begin
btnStart.Enabled := False;
lvRegLst := TStringList.Create;
// Open Tables
tblVeh.Open;
tblVeh.First;
// Set progress
prgTotal.MinValue := 0;
lvTblSize := tblVeh.RecordCount;
prgTotal.MaxValue := tblVeh.RecordCount;
btnStart.Caption := 'Parsing Registration Numbers...';
// Conversion
while not tblVeh.Eof do
begin
lvRegLst.Add(tblVehRegNo.AsString);
tblVeh.Next;
prgTotal.AddProgress(1);
Application.ProcessMessages;
end;
tblVeh.First;
lvTest := lvRegLst.Count;
prgTotal.Progress := 0;
btnStart.Caption := 'Removing Spaces...';
while not tblVeh.Eof do
begin
lvOrigReg := tblVehRegNo.AsString;
lvNewReg := AnsiReplaceStr(lvOrigReg,' ','');
if lvRegLst.IndexOf(lvNewReg) = -1 then
begin
tblVeh.Edit;
tblVehRegNo.AsString := lvNewReg;
prgTotal.AddProgress(1);
tblVeh.Post;
end;
tblVeh.Next;
prgtotal.AddProgress(1);
Application.ProcessMessages;
end;
// Close Tables
tblVeh.Edit;
tblVeh.Post;
tblVeh.Close;
btnStart.Caption := '&Start Conversion';
btnStart.Enabled := True;
end;
I have stepped through the code and all looks fine and it successfuly changes the registration against the vehicle but when looking at the table afterwards it's not made any changes.
The issue was with the database itself, it turns out 'RegNo' is the only key field so it's the default index. As my conversion was running through it was changing registrations which moved the 'cursor' and skipped over a number of registrations.
I have added another index for the purpose of this conversion but making around 50-60 passes over their data would have eventually sorted out all of the registrations.
Thank you for all of the help.
I am doing just for fun a virtual desktop to play Magic The Gathering with friends. I am using Delphi 2010. The cards are represented in the application by TImage components (loading PNG files of the cards loaded from a database). The point here is that in MTG a very common thing to do is to tap a card (rotating it 90ยบ degrees to right). There is a simple way to do this? I really don't need the "animation", just the card rotated once is clicked (animation would be nice though). The game should work simultaneously with many cards and they can be moved anywhere in the form. I am thinking in having the image of the card tapped and untapped in the database but this may be an overkill if there is a nice and efficient way to rotate the cards.
Any ideas?
The old-skool way of doing this is with PlgBlt.
procedure RotateBitmap90CW(b1,b2:TBitmap);
var
x,y:integer;
p:array[0..2] of TPoint;
begin
x:=b1.Width;
y:=b1.Height;
b2.Width:=y;
b2.Height:=x;
p[0].X:=y;
p[0].Y:=0;
p[1].X:=y;
p[1].Y:=x;
p[2].X:=0;
p[2].Y:=0;
PlgBlt(b2.Canvas.Handle,p,b1.Canvas.Handle,0,0,x,y,0,0,0);
end;
Or you can leave the TImage and use e.g. TPaintBox and GDI+ library. GDI+ has the RotateFlip method directly for doing this. Using the GDI+ Library for Delphi it would look like:
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, ExtCtrls, ActiveX, GDIPOBJ, GDIPAPI;
type
TForm1 = class(TForm)
Button1: TButton;
PaintBox1: TPaintBox;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure Button1Click(Sender: TObject);
procedure PaintBox1Paint(Sender: TObject);
private
FImage: TGPImage;
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.FormCreate(Sender: TObject);
var
Stream: IStream;
BlobStream: TMemoryStream;
begin
BlobStream := TMemoryStream.Create;
try
// assuming the BlobStream here has a valid image loaded from a database
Stream := TStreamAdapter.Create(BlobStream);
FImage := TGPImage.Create(Stream);
finally
BlobStream.Free;
end;
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
FImage.Free;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
FImage.RotateFlip(Rotate90FlipNone);
PaintBox1.Invalidate;
end;
procedure TForm1.PaintBox1Paint(Sender: TObject);
begin
with TGPGraphics.Create(PaintBox1.Canvas.Handle) do
try
DrawImage(FImage, 0, 0);
finally
Free;
end;
end;
end.
Such an overkill, doesn't it :-?
You could use a TPaintBox instead of a TImage and use SetWorldTransform with a rotation matrix to draw the tapped card. I use StretchDrawRotated for this:
procedure XForm_SetRotation(out AXForm: TXForm; AAngle: Extended; ACenter: TPoint);
var
SinA, CosA: Extended;
begin
SinCos(AAngle, SinA, CosA);
AXForm.eM11 := CosA;
AXForm.eM12 := SinA;
AXForm.eM21 := -SinA;
AXForm.eM22 := CosA;
AXForm.eDx := (ACenter.X - (CosA * ACenter.X)) + ((SinA * ACenter.Y));
AXForm.eDy := (ACenter.Y - (SinA * ACenter.X)) - ((CosA * ACenter.Y));
end;
procedure StretchDrawRotated(ACanvas: TCanvas; const ARect: TRect; AAngle: Extended; ACenter: TPoint; AGraphic: TGraphic);
var
XForm, XFormOld: TXForm;
GMode: Integer;
begin
GMode := SetGraphicsMode(ACanvas.Handle, GM_ADVANCED);
try
if GetWorldTransform(ACanvas.Handle, XFormOld) then
try
XForm_SetRotation(XForm, AAngle, ACenter);
SetWorldTransform(ACanvas.Handle, XForm);
ACanvas.StretchDraw(ARect, AGraphic);
finally
SetWorldTransform(ACanvas.Handle, XFormOld);
end;
finally
SetGraphicsMode(ACanvas.Handle, GMode);
end;
end;
You can also use the Graphics32 library or just this function I grabbed some time ago from CodeCentral:
{by Gustavo Daud (Submited on 21 May 2006 )
Use this method to rotate RGB and RGB Alpha 'Portable Network Graphics' Images using a smooth antialiased algorithm in order to get much better results.
Note: Part of this code was based on JansFreeware code [http://jansfreeware.com/]
This is only possible when using the 1.56 library version.}
{Smooth rotate a png object}
procedure SmoothRotate(var aPng: TPNGImage; Angle: Extended);
{Supporting functions}
function TrimInt(i, Min, Max: Integer): Integer;
begin
if i>Max then Result:=Max
else if i<Min then Result:=Min
else Result:=i;
end;
function IntToByte(i:Integer):Byte;
begin
if i>255 then Result:=255
else if i<0 then Result:=0
else Result:=i;
end;
function Min(A, B: Double): Double;
begin
if A < B then Result := A else Result := B;
end;
function Max(A, B: Double): Double;
begin
if A > B then Result := A else Result := B;
end;
function Ceil(A: Double): Integer;
begin
Result := Integer(Trunc(A));
if Frac(A) > 0 then
Inc(Result);
end;
{Calculates the png new size}
function newsize: tsize;
var
fRadians: Extended;
fCosine, fSine: Double;
fPoint1x, fPoint1y, fPoint2x, fPoint2y, fPoint3x, fPoint3y: Double;
fMinx, fMiny, fMaxx, fMaxy: Double;
begin
{Convert degrees to radians}
fRadians := (2 * PI * Angle) / 360;
fCosine := abs(cos(fRadians));
fSine := abs(sin(fRadians));
fPoint1x := (-apng.Height * fSine);
fPoint1y := (apng.Height * fCosine);
fPoint2x := (apng.Width * fCosine - apng.Height * fSine);
fPoint2y := (apng.Height * fCosine + apng.Width * fSine);
fPoint3x := (apng.Width * fCosine);
fPoint3y := (apng.Width * fSine);
fMinx := min(0,min(fPoint1x,min(fPoint2x,fPoint3x)));
fMiny := min(0,min(fPoint1y,min(fPoint2y,fPoint3y)));
fMaxx := max(fPoint1x,max(fPoint2x,fPoint3x));
fMaxy := max(fPoint1y,max(fPoint2y,fPoint3y));
Result.cx := ceil(fMaxx-fMinx);
Result.cy := ceil(fMaxy-fMiny);
end;
type
TFColor = record b,g,r:Byte end;
var
Top, Bottom, Left, Right, eww,nsw, fx,fy, wx,wy: Extended;
cAngle, sAngle: Double;
xDiff, yDiff, ifx,ify, px,py, ix,iy, x,y, cx, cy: Integer;
nw,ne, sw,se: TFColor;
anw,ane, asw,ase: Byte;
P1,P2,P3:Pbytearray;
A1,A2,A3: pbytearray;
dst: TPNGImage;
IsAlpha: Boolean;
new_colortype: Integer;
begin
{Only allows RGB and RGBALPHA images}
if not (apng.Header.ColorType in [COLOR_RGBALPHA, COLOR_RGB]) then
raise Exception.Create('Only COLOR_RGBALPHA and COLOR_RGB formats' +
' are supported');
IsAlpha := apng.Header.ColorType in [COLOR_RGBALPHA];
if IsAlpha then new_colortype := COLOR_RGBALPHA else
new_colortype := COLOR_RGB;
{Creates a copy}
dst := tpngobject.Create;
with newsize do
dst.createblank(new_colortype, 8, cx, cy);
cx := dst.width div 2; cy := dst.height div 2;
{Gather some variables}
Angle:=angle;
Angle:=-Angle*Pi/180;
sAngle:=Sin(Angle);
cAngle:=Cos(Angle);
xDiff:=(Dst.Width-apng.Width)div 2;
yDiff:=(Dst.Height-apng.Height)div 2;
{Iterates over each line}
for y:=0 to Dst.Height-1 do
begin
P3:=Dst.scanline[y];
if IsAlpha then A3 := Dst.AlphaScanline[y];
py:=2*(y-cy)+1;
{Iterates over each column}
for x:=0 to Dst.Width-1 do
begin
px:=2*(x-cx)+1;
fx:=(((px*cAngle-py*sAngle)-1)/ 2+cx)-xDiff;
fy:=(((px*sAngle+py*cAngle)-1)/ 2+cy)-yDiff;
ifx:=Round(fx);
ify:=Round(fy);
{Only continues if it does not exceed image boundaries}
if(ifx>-1)and(ifx<apng.Width)and(ify>-1)and(ify<apng.Height)then
begin
{Obtains data to paint the new pixel}
eww:=fx-ifx;
nsw:=fy-ify;
iy:=TrimInt(ify+1,0,apng.Height-1);
ix:=TrimInt(ifx+1,0,apng.Width-1);
P1:=apng.scanline[ify];
P2:=apng.scanline[iy];
if IsAlpha then A1 := apng.alphascanline[ify];
if IsAlpha then A2 := apng.alphascanline[iy];
nw.r:=P1[ifx*3];
nw.g:=P1[ifx*3+1];
nw.b:=P1[ifx*3+2];
if IsAlpha then anw:=A1[ifx];
ne.r:=P1[ix*3];
ne.g:=P1[ix*3+1];
ne.b:=P1[ix*3+2];
if IsAlpha then ane:=A1[ix];
sw.r:=P2[ifx*3];
sw.g:=P2[ifx*3+1];
sw.b:=P2[ifx*3+2];
if IsAlpha then asw:=A2[ifx];
se.r:=P2[ix*3];
se.g:=P2[ix*3+1];
se.b:=P2[ix*3+2];
if IsAlpha then ase:=A2[ix];
{Defines the new pixel}
Top:=nw.b+eww*(ne.b-nw.b);
Bottom:=sw.b+eww*(se.b-sw.b);
P3[x*3+2]:=IntToByte(Round(Top+nsw*(Bottom-Top)));
Top:=nw.g+eww*(ne.g-nw.g);
Bottom:=sw.g+eww*(se.g-sw.g);
P3[x*3+1]:=IntToByte(Round(Top+nsw*(Bottom-Top)));
Top:=nw.r+eww*(ne.r-nw.r);
Bottom:=sw.r+eww*(se.r-sw.r);
P3[x*3]:=IntToByte(Round(Top+nsw*(Bottom-Top)));
{Only for alpha}
if IsAlpha then
begin
Top:=anw+eww*(ane-anw);
Bottom:=asw+eww*(ase-asw);
A3[x]:=IntToByte(Round(Top+nsw*(Bottom-Top)));
end;
end;
end;
end;
apng.assign(dst);
dst.free;
end;
Graphics32 library was already mentioned there above.
http://graphics32.org/documentation/Docs/Units/GR32_Transforms/Classes/TAffineTransformation/Methods/Rotate.htm
http://graphics32.org
I'd like to mention one more good library, Vampyre Imaging
http://galfar.vevb.net/imaging/doc/imaging.html
http://ImagingLib.sf.net/
I have ID of the process. This process is an application which have a main window.
I am trying to close this application by sending WM_CLOSE to its main window.
I am searching its main window by using EnumWindows.
The problem is, that this application which I try to close, does not close always.
It is multithreaded application. Notepad and Calc are always closing when I use the same method which is presented below. But I am not sure if it is working properly cause it returns me many handles to the same window, even for Calc.exe.
Is it possible that thread is taking a handle to window and then this handle somehow become corrupted? Or maybe I should not use GetWindowThreadProcessId(hHwnd,pPid) but some other function in the callback?
I am out of ideas, would be grateful for any help. Thanks.
Code snippet:
unit Unit22;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
type
TForm22 = class(TForm)
edtprocID: TEdit;
lblEnterProcessID: TLabel;
btnCloseProcessWindow: TButton;
lblStatus: TLabel;
procedure btnCloseProcessWindowClick(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
type
THandleAndHWND = record
ProcID: THandle;
WindowHandle: HWND;
end;
var
Form22: TForm22;
var
HandleAndHWNDArray: array of THandleAndHWND;
HandeIndex, lp: Integer;
implementation
{$R *.dfm}
function EnumProcess(hHwnd: HWND; lParam : integer): boolean; stdcall;
var
pPid : DWORD;
begin
//if the returned value in null the
//callback has failed, so set to false and exit.
if (hHwnd=0) then
begin
result := false;
end else
begin
GetWindowThreadProcessId(hHwnd,pPid);
Inc(HandeIndex);
HandleAndHWNDArray[HandeIndex].ProcID := pPid;
HandleAndHWNDArray[HandeIndex].WindowHandle := hHwnd;
Result := true;
end;
end;
procedure TForm22.btnCloseProcessWindowClick(Sender: TObject);
var
ProcID: Cardinal;
i, LastError: Integer;
begin
HandeIndex := -1;
ProcID := StrToInt(edtprocID.Text);
SetLength(HandleAndHWNDArray, 3000);
EnumWindows(#EnumProcess,lp);
for i := 0 to HandeIndex do //After EnumWindows HandleIndex is above 500 despite the fact that I have like 10 openned windows max
begin //That means that EnumWindows was called 500 times?
if HandleAndHWNDArray[i].ProcID = ProcID then //search for process equal to procces ID given by the user
begin
//if we have a processID then we have a handle to its main window
if PostMessage(HandleAndHWNDArray[i].WindowHandle, WM_CLOSE, 0, 0) then
begin
lblStatus.Caption := 'message posted!';
end else
begin
LastError := GetLastError;
lblStatus.Caption := Format('Error: [%d] ' + SysErrorMessage(LastError), [LastError]);
end;
Exit;
end;
end;
end;
end.
Have a look in this Knowledge Base Article here on how to close another application as cleanly as possible. You are doing it right so far. The Article suggests that you
first post WM_CLOSE to all windows of the application (since you cannot know for sure which one is the main).
wait with a timeout and if the timeout elapses
kill the application using TerminateProcess
I agree.
This question already has answers here:
Closed 12 years ago.
Possible Duplicate:
How can I handle a keyboard shortcut when my program isn't active?
hello guys
i need to make a delphi program that creates a short cut key that works outside the delphi app. for example: when i press ctrl+1 it pastes a certain text. When i press ctrl+2, another text, and so on. It really helps my work. I managed to make a delphi application that does that, but it only works inside that app. i want it to work in all windows applications as long as my app is open ( and minimized). Can anyone help me out? I'm pretty new at delphi, i'm trying to learn.
I tried this code which someone recommended to me but it doesn't work. it does nothing. what did i do wrong?
unit Unit3;
interface
uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, Clipbrd;
type
TForm17 = class(TForm)
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
{ Private declarations }
HotKey1 : Integer;
HotKey2 : Integer;
procedure WMHotKey(var Msg: TWMHotKey); message WM_HOTKEY;
public
{ Public declarations }
end;
var
Form17: TForm17;
implementation
{$R *.dfm}
{ TForm17 }
procedure TForm17.FormCreate(Sender: TObject);
const
MOD_CONTROL = $0002;//0x0002
begin
// Register Ctrl + 1 hotkey
HotKey1 := GlobalAddAtom('Hotkey1');
RegisterHotKey(Handle, HotKey1, MOD_CONTROL, Ord('1'));
// Register Ctrl + 2 hotkey
HotKey2 := GlobalAddAtom('Hotkey2');
RegisterHotKey(Handle, HotKey2, MOD_CONTROL, Ord('2'));
end;
procedure TForm17.FormDestroy(Sender: TObject);
begin
//unregister the hotkeys
UnRegisterHotKey(Handle, HotKey1);
GlobalDeleteAtom(HotKey1);
UnRegisterHotKey(Handle, HotKey2);
GlobalDeleteAtom(HotKey2);
end;
procedure TForm17.WMHotKey(var Msg: TWMHotKey);
begin
if Msg.HotKey = HotKey1 then
begin
ShowMessage('Ctrl + 1 was pressed');
Clipboard.AsText := 'This is my own text!';
end
else
if Msg.HotKey = HotKey2 then
begin
ShowMessage('Ctrl + 2 was pressed');
Clipboard.AsText := 'This is my own text!';
end;
end;
end.
You need to use RegisterHotKey and UnregisterHotKey from the Win32 API, they are very straightforward to use.
Also, you may find useful ShortCutToKey(), which returns the key code and shift state of a Delphi shortcut.
PS: Don't forget to check the return value of RegisterHotKey(), since it will fail if the hotkey is already registered by other application.
Edit: sorry, I though that you were using another WM_MESSAGE, since first you posted the code as plain text and I only scanned through it...
I think that the problem with your code is that your are using GlobalAddAtom for the ID key, but you only need to use an unique ID inside your app (the docs for the function say that you need to use GlobalAddAtom only for a shared DLL). Try using just this:
const
ID_HOTKEY1=0;
ID_HOTKEY2=1;
procedure TYourForm.FormCreate(Sender: TObject);
begin
if not RegisterHotKey(Handle,ID_HOTKEY1,MOD_CONTROL,Ord('1'))
then Application.MessageBox('Error registering hot key 1','Error',MB_ICONERROR);
if not RegisterHotKey(Handle,ID_HOTKEY2,MOD_CONTROL,Ord('2'))
then Application.MessageBox('Error registering hot key 2','Error',MB_ICONERROR);
end;
procedure TYourForm.FormDestroy(Sender: TObject);
begin
UnregisterHotKey(Handle,ID_HOTKEY1);
UnregisterHotKey(Handle,ID_HOTKEY2);
end;
procedure TYourForm.WMHotKey(var Msg: TWMHotKey);
begin
Application.MessageBox(PChar(IntToStr(Msg.HotKey)),'Hotkey ID',MB_OK);
end;
Also, the MOD_CONTROL and related constants are already defined by Delphi, you don't need to redefine them.
andrei, check this sample code to paste a text in a external application using a hotkey.
the code show two options
1) sending the Ctrl+V combination to the focused window
2) sending a WM_PASTE message
function GetFocusedHandle: THandle;
var
ActiveHWND : THandle;
FocusedThread : DWORD;
begin
Result:=0;
ActiveHWND := GetForegroundWindow;
FocusedThread := GetWindowThreadProcessID(ActiveHWND, nil) ;
try
if AttachThreadInput(GetCurrentThreadID, FocusedThread, true) then
Result := GetFocus;
finally
AttachThreadInput(GetCurrentThreadID, FocusedThread, false) ;
end;
end;
procedure TForm17.WMHotKey(var Msg: TWMHotKey);
var
FocusWindowHwnd : THandle;
begin
if Msg.HotKey = HotKey1 then //option 1
begin
Clipboard.AsText := 'Text from Ctrl + 1 Hotkey';//Assign the text to the clipboard
//send the Ctrl + V combination to the current focused window
keybd_event(VK_CONTROL, 0, 0, 0);
keybd_event(Ord('V'), 0, 0, 0);
end
else
if Msg.HotKey = HotKey2 then //option 2
begin
FocusWindowHwnd:=GetFocusedHandle; //get the handle to the focused window
if FocusWindowHwnd<>0 then
begin
Clipboard.AsText := 'Text from Ctrl + 2 Hotkey';//Assign the text to the clipboard
SendMessage(FocusWindowHwnd,WM_PASTE,0,0);//send the WM_PASTE message
end;
end;
end;