I would like to find out the Drive-GUID for a specific drive letter.
I have following code:
Function GetVolumeNameForVolumeMountPoint(lpszVolumeMountPoint: LPCTSTR; lpszVolumeName: LPTSTR; cchBufferLength: DWORD): BOOL; stdcall;
external 'kernel32.dll' name 'GetVolumeNameForVolumeMountPointW';
procedure TForm1.Button1Click(Sender: TObject);
var
Buffer: array[0..50] of AnsiChar;
begin
if GetVolumeNameForVolumeMountPoint('C:\', Buffer, SizeOf(Buffer)) then
begin
showmessage(buffer); // Expected: "\\?\Volume{deadbeef-895e-4a1d-9d64-9b82fa068d76}\"
end
else RaiseLastOSError; // Actual: ERROR_INVALID_NAME (123).
end;
I am getting the error ERROR_INVALID_NAME (123), but I do not know why. What am I doing wrong?
You are calling the Unicode version of the function but passing ANSI text. Thus the first argument you pass is wrongly encoded and hence the error message.
This program demonstrates how it should be done:
{$APPTYPE CONSOLE}
uses
SysUtils, Windows;
function GetVolumeNameForVolumeMountPointA(lpszVolumeMountPoint: PAnsiChar;
lpszVolumeName: PAnsiChar; cchBufferLength: DWORD): BOOL; stdcall;
external 'kernel32.dll';
procedure Main;
var
Buffer: array [0 .. 49] of AnsiChar;
begin
Win32Check(GetVolumeNameForVolumeMountPointA('C:\', Buffer, Length(Buffer)));
Writeln(Buffer);
end;
begin
try
Main;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
Readln;
end.
Since it is clear that you are using a pre-Unicode version of Delphi (otherwise your program would not compile), I have given you ANSI code above. If you ever upgrade to a Unicode version of Delphi then you'd write it like this:
{$APPTYPE CONSOLE}
uses
SysUtils, Windows;
function GetVolumeNameForVolumeMountPointW(lpszVolumeMountPoint: PWideChar;
lpszVolumeName: PWideChar; cchBufferLength: DWORD): BOOL; stdcall;
external 'kernel32.dll';
procedure Main;
var
Buffer: array [0 .. 49] of WideChar;
begin
Win32Check(GetVolumeNameForVolumeMountPointW('C:\', Buffer, Length(Buffer)));
Writeln(Buffer);
end;
begin
try
Main;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
Readln;
end.
Or if you want code that works in all versions of Delphi, you can do this:
{$APPTYPE CONSOLE}
uses
SysUtils, Windows;
const
Win32ImportSuffix = {$IFDEF Unicode}'W'{$ELSE}'A'{$ENDIF};
function GetVolumeNameForVolumeMountPointA(lpszVolumeMountPoint: PAnsiChar;
lpszVolumeName: PAnsiChar; cchBufferLength: DWORD): BOOL; stdcall;
external 'kernel32.dll';
function GetVolumeNameForVolumeMountPointW(lpszVolumeMountPoint: PWideChar;
lpszVolumeName: PWideChar; cchBufferLength: DWORD): BOOL; stdcall;
external 'kernel32.dll';
function GetVolumeNameForVolumeMountPoint(lpszVolumeMountPoint: PChar;
lpszVolumeName: PChar; cchBufferLength: DWORD): BOOL; stdcall;
external 'kernel32.dll' name 'GetVolumeNameForVolumeMountPoint' + Win32ImportSuffix;
procedure Main;
var
Buffer: array [0 .. 49] of Char;
begin
Win32Check(GetVolumeNameForVolumeMountPoint('C:\', Buffer, Length(Buffer)));
Writeln(Buffer);
end;
begin
try
Main;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
Readln;
end.
Related
I had a unit for a Windows (32 bit) service that was built up like this:
unit uSvcBase;
interface
type
TMyServiceBase = class(TService)
procedure ServiceBeforeUninstall(Sender: TService);
procedure ServiceCreate(Sender: TObject);
procedure ServiceStop(Sender: TService; var Stopped: Boolean);
procedure ServiceExecute(Sender: TService);
procedure ServiceStart(Sender: TService; var Started: Boolean);
procedure ServiceAfterInstall(Sender: TService);
private
public
function GetServiceController: TServiceController; override;
end;
var
MyServiceBase: TMyServiceBase;
implementation
{$R *.DFM}
{$R SvcEventLogMessages.res}
procedure ServiceController(CtrlCode: DWord); stdcall;
begin
MyServiceBase.Controller(CtrlCode);
end;
function TMyServiceBase.GetServiceController: TServiceController;
begin
Result := ServiceController;
end;
const
rsServiceMessages =
'SYSTEM\CurrentControlSet\Services\EventLog\Application';
procedure TMyServiceBase.ServiceAfterInstall(Sender: TService);
var
lReg : TRegistry;
lAppName: String;
begin
lReg := TRegistry.create;
try
with lReg do
begin
Rootkey := HKEY_LOCAL_MACHINE;
if OpenKey(rsServiceMessages, False) then
begin
if OpenKey(MyServiceBase.Name, True) then
begin
lAppName := ParamStr(0);
WriteString('EventMessageFile', lAppName);
WriteString('CategoryMessageFile', lAppName);
WriteInteger('CategoryCount', 2);
WriteInteger('TypesSupported', EVENTLOG_ERROR_TYPE OR EVENTLOG_WARNING_TYPE OR EVENTLOG_INFORMATION_TYPE);
CloseKey;
end;
CloseKey;
end; { if OpenKey }
end; { with lReg }
finally
lReg.Free;
end;
end;
Because I needed to make a second service which was largely identical, I decided to make this a 'base' unit that others derive from (you can already see that in the names above):
unit uSvcTasks;
interface
uses
System.SysUtils, System.Classes, uSvcBase;
type
TMyServiceScheduler = class(TMyServiceBase)
procedure ServiceCreate(Sender: TObject);
private
public
end;
var
MyServiceScheduler: TMyServiceScheduler;
implementation
{%CLASSGROUP 'System.Classes.TPersistent'}
{$R *.dfm}
Uses uTypesAlgemeen;
procedure TMyServiceScheduler.ServiceCreate(Sender: TObject);
begin
inherited;
// Set some properties
end;
At design time, the MyServiceScheduler.Name in this descendant differs from the MyServiceBase.Name.
Issue: The AfterInstall now crashed. Trying to use the original code using OpenKey(MyServiceBase.Name was not allowed.
I worked around his by using a property for the name (setting it in the descendant Create), but I do not understand why referencing MyServiceBase.Name in the AfterInstall does not work. Can anyone explain?
Thanks to Uwe Raabe's comments I was able to figure out how to fix this:
The project had an Application.CreateForm(TMyServiceScheduler, MyServiceScheduler) in the project source which initializes MyServiceScheduler, but there was nothing initializing MyServiceBase, so refering to it was illegal.
Replace the reference to MyServiceBase.Name with Name in the AfterInstall(That should've been done anyway).
Move the code for the ServiceController from uSvcBase to uSvcTasks
I use one code for my installation and it work with other setup i have made but now one of my setup give me a runtime error:
Acces violation at adresse 000000 .... Whrite of adresse 000000
If i remove the bass.dll part of the code the setup work, but it the same code from one other working correctly... why it not working this time??
This is the code:
[CustomMessages]
SoundCtrlButtonCaptionSoundOn=Play
SoundCtrlButtonCaptionSoundOff=Stop
[Code]
procedure LoadVCLStyle(VClStyleFile: String); external 'LoadVCLStyleW#files:VclStylesInno.dll stdcall setuponly';
procedure LoadVCLStyle_UnInstall(VClStyleFile: String); external 'LoadVCLStyleW#{#VCLStylesSkinPath}\VclStylesInno.dll stdcall uninstallonly';
procedure UnLoadVCLStyles; external 'UnLoadVCLStyles#files:VclStylesInno.dll stdcall setuponly';
procedure UnLoadVCLStyles_UnInstall; external 'UnLoadVCLStyles#{#VCLStylesSkinPath}\VclStylesInno.dll stdcall uninstallonly';
function InitializeSetup(): Boolean;
begin
ExtractTemporaryFile('Skin.vsf');
LoadVCLStyle(ExpandConstant('{tmp}\Skin.vsf'));
Result := True;
end;
procedure DeinitializeSetup;
begin
UnLoadVCLStyles;
end;
const
BASS_SAMPLE_LOOP = 4;
BASS_ACTIVE_STOPPED = 0;
BASS_ACTIVE_PLAYING = 1;
BASS_ACTIVE_STALLED = 2;
BASS_ACTIVE_PAUSED = 3;
BASS_UNICODE = $80000000;
BASS_CONFIG_GVOL_STREAM = 5;
const
#ifndef UNICODE
EncodingFlag = 0;
#else
EncodingFlag = BASS_UNICODE;
#endif
type
HSTREAM = DWORD;
function BASS_Init(device: LongInt; freq, flags: DWORD;
win: HWND; clsid: Cardinal): BOOL;
external 'BASS_Init#files:bass.dll stdcall';
function BASS_StreamCreateFile(mem: BOOL; f: string; offset1: DWORD;
offset2: DWORD; length1: DWORD; length2: DWORD; flags: DWORD): HSTREAM;
external 'BASS_StreamCreateFile#files:bass.dll stdcall';
function BASS_Start: BOOL;
external 'BASS_Start#files:bass.dll stdcall';
function BASS_Pause: BOOL;
external 'BASS_Pause#files:bass.dll stdcall';
function BASS_ChannelPlay(handle: DWORD; restart: BOOL): BOOL;
external 'BASS_ChannelPlay#files:bass.dll stdcall';
function BASS_SetConfig(option: DWORD; value: DWORD ): BOOL;
external 'BASS_SetConfig#files:bass.dll stdcall';
function BASS_ChannelIsActive(handle: DWORD): DWORD;
external 'BASS_ChannelIsActive#files:bass.dll stdcall';
function BASS_Free: BOOL;
external 'BASS_Free#files:bass.dll stdcall';
var
SoundStream: HSTREAM;
SoundCtrlButton: TNewButton;
procedure SoundCtrlButtonClick(Sender: TObject);
begin
case BASS_ChannelIsActive(SoundStream) of
BASS_ACTIVE_PLAYING:
begin
if BASS_Pause then
SoundCtrlButton.Caption :=
ExpandConstant('{cm:SoundCtrlButtonCaptionSoundOn}');
end;
BASS_ACTIVE_PAUSED:
begin
if BASS_Start then
SoundCtrlButton.Caption :=
ExpandConstant('{cm:SoundCtrlButtonCaptionSoundOff}');
end;
end;
end;
procedure InitializeWizard;
begin
ExtractTemporaryFile('Music.mp3');
if BASS_Init(-1, 44100, 0, 0, 0) then
begin
SoundStream := BASS_StreamCreateFile(False,
ExpandConstant('{tmp}\Music.mp3'), 0, 0, 0, 0,
EncodingFlag or BASS_SAMPLE_LOOP);
BASS_SetConfig(BASS_CONFIG_GVOL_STREAM, 8500);
BASS_ChannelPlay(SoundStream, False);
SoundCtrlButton := TNewButton.Create(WizardForm);
SoundCtrlButton.Parent := WizardForm;
SoundCtrlButton.Left := 8;
SoundCtrlButton.Top := WizardForm.ClientHeight -
SoundCtrlButton.Height - 12;
SoundCtrlButton.Width := 148;
SoundCtrlButton.Caption :=
ExpandConstant('{cm:SoundCtrlButtonCaptionSoundOff}');
SoundCtrlButton.OnClick := #SoundCtrlButtonClick;
end;
end;
function InitializeUninstall: Boolean;
begin
Result := True;
LoadVCLStyle_UnInstall(ExpandConstant('{#VCLStylesSkinPath}\Skin.vsf'));
end;
procedure DeinitializeUninstall();
begin
UnLoadVCLStyles_UnInstall;
end;
Is there ANY way to get pixel color under mouse cursor really FAST? I have a mouse hook and I try to read pixel color during mouse move. Its kind of ColorPicker
Any attempts with getPixel and BitBlt were terribly slow.
UPDATE - ADDED CODE
unit Unit1;
{$mode delphi}{$H+}
interface
uses
Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, ExtCtrls, lclintf, Windows;
type
{ TForm1 }
TForm1 = class(TForm)
pnColor: TPanel;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure ms(var message: tmessage); message WM_USER+1234;
private
{ private declarations }
public
{ public declarations }
end;
var
Form1: TForm1;
DC:HDC;
const WH_MOUSE_LL = 14; //for Lazarus
implementation
{$R *.lfm}
{ TForm1 }
procedure HookMouse(Handle:HWND); stdcall; external 'mhook.dll';
procedure UnHookMouse; stdcall; external 'mhook.dll';
procedure TForm1.FormCreate(Sender: TObject);
begin
//Self.Caption := IntToStr(Self.Height);
Self.Left:= Screen.Monitors[0].WorkareaRect.Right - Self.Width - 18;
Self.Top := Screen.Monitors[0].WorkareaRect.Bottom - Self.Height - 18 - 25; //35 LAZARUS BUG
DC := getDC(0);
HookMouse(Self.Handle);
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
UnHookMouse;
end;
procedure TForm1.ms(var message: tmessage);
var color:TColor;
begin
color := GetPixel(DC, message.WParam, message.LParam); //<-- Extremly slow
//format('%d - %d',[message.LParam, message.WParam]); // Edited
pnColor.Color:=color;
end;
end.
And the DLL
library project1;
{$mode delphi}{$H+}
uses
Windows,
Messages;
var Hook: HHOOK;
hParent:HWND;
function HookProc(nCode: Integer; MsgID: WParam; Data: LParam): LResult; stdcall;
var
mousePoint: TPoint;
begin
//if nCode = HC_ACTION then
//begin
mousePoint := PMouseHookStruct(Data)^.pt;
PostMessage(hParent, WM_USER+1234, mousePoint.X, mousePoint.Y);
//end;
Result := CallNextHookEx(Hook,nCode,MsgID,Data);
end;
procedure HookMouse(Parent: Hwnd); stdcall;
begin
hParent := parent;
if Hook = 0 then Hook:=SetWindowsHookEx(WH_MOUSE_LL,#HookProc,HInstance,0);
end;
procedure UnHookMouse; stdcall;
begin
UnhookWindowsHookEx(Hook);
Hook:=0;
end;
exports
HookMouse, UnHookMouse;
begin
end.
UPDATE 2 - One unit update with 100ms interval
unit Unit1;
{$mode delphi}{$H+}
interface
uses
Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, ExtCtrls, lclintf, Windows;
type
{ TForm1 }
TForm1 = class(TForm)
pnColor: TPanel;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
{ private declarations }
public
{ public declarations }
end;
var
Form1: TForm1;
HookHandle: Cardinal;
DC:HDC;
timer:Long;
const WH_HOOK_LL = 14; //for Lazarus
implementation
{$R *.lfm}
{ TForm1 }
function LowLevelMouseProc(nCode: Integer; wParam: wParam; lParam: lParam): LRESULT; stdcall;
var
point:TPoint;
begin
if (nCode >= 0) then
begin
if(GetTickCount - timer >= 100) then
begin
point:=PMouseHookStruct(lParam)^.pt;
Form1.pnColor.Color := GetPixel(DC,point.X,point.Y);
timer := GetTickCount;
end;
end;
Result := CallNextHookEx(HookHandle, nCode, wParam, lParam);
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
//Self.Caption := IntToStr(Self.Height);
Self.Left:= Screen.Monitors[0].WorkareaRect.Right - Self.Width - 18;
Self.Top := Screen.Monitors[0].WorkareaRect.Bottom - Self.Height - 18 - 25; //35 LAZARUS BUG
DC := GetWindowDC(GetDesktopWindow);
if HookHandle = 0 then
begin
HookHandle := SetWindowsHookEx(WH_HOOK_LL, #LowLevelMouseProc, hInstance, 0);
end;
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
if HookHandle <> 0 then
UnhookWindowsHookEx(HookHandle);
ReleaseDC(GetDesktopWindow(), DC);
end;
end.
I wouldn't personally use a hook for this. I would use e.g. a timer with interval 30ms for instance and use the following code to determine position and color of the current pixel under the mouse cursor (the code will work only on Windows platform as your original code can). I'd use this, as because if your application won't be able to process (low level idle priority though) WM_TIMER messages, I don't think it will be able to process so frequent callbacks from your hook keeping the user interface responsible (to process own main thread messages):
unit Unit1;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, ExtCtrls,
StdCtrls, Windows;
type
{ TForm1 }
TForm1 = class(TForm)
Label1: TLabel;
Panel1: TPanel;
UpdateTimer: TTimer;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure UpdateTimerTimer(Sender: TObject);
private
DesktopDC: HDC;
public
{ public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.lfm}
{ TForm1 }
procedure TForm1.FormCreate(Sender: TObject);
begin
DesktopDC := GetDC(0);
if (DesktopDC <> 0) then
UpdateTimer.Enabled := True;
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
ReleaseDC(GetDesktopWindow, DesktopDC);
end;
procedure TForm1.UpdateTimerTimer(Sender: TObject);
var
CursorPos: TPoint;
begin
if GetCursorPos(CursorPos) then
begin
Label1.Caption := 'Cursor pos: [' + IntToStr(CursorPos.x) + '; ' +
IntToStr(CursorPos.y) + ']';
Panel1.Color := GetPixel(DesktopDC, CursorPos.x, CursorPos.y);
end;
end;
end.
Is there any way to know which verion of Windows we are working on?
I need to set image to TBitButton in Windows XP and no image in Windows7. It should be done automatically.
Check the SysUtils.Win32MajorVersion (in Delphi 7, you'll need to add SysUtils to your uses clause if it's not there already - later versions add it automatically). The easiest way is to assign the Glyph as usual in the IDE, and clear it if you're running on Vista or higher:
if SysUtils.Win32MajorVersion >= 6 then // Windows Vista or higher
BitBtn1.Glyph := nil;
For more info on detecting specific Windows editions and versions, see this post. It hasn't been updated for the latest Windows versions and editions, but it'll get you started. You can also search SO for [delphi] GetVersionEx to see other examples.
This is actually a little project of mine - a drop-in component which provides info of the operating system - even preview it in design-time...
unit JDOSInfo;
interface
uses
Classes, Windows, SysUtils, StrUtils, Forms, Registry;
type
TJDOSInfo = class(TComponent)
private
fReg: TRegistry;
fKey: String;
fMinor: Integer;
fMajor: Integer;
fBuild: Integer;
fPlatform: Integer;
fIsServer: Bool;
fIs64bit: Bool;
fProductName: String;
function GetProductName: String;
procedure SetProductName(Value: String);
procedure SetMajor(Value: Integer);
procedure SetMinor(Value: Integer);
procedure SetBuild(Value: Integer);
procedure SetPlatform(Value: Integer);
procedure SetIs64Bit(const Value: Bool);
procedure SetIsServer(const Value: Bool);
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
published
property Major: Integer read fMajor write SetMajor;
property Minor: Integer read fMinor write SetMinor;
property Build: Integer read fBuild write SetBuild;
property Platf: Integer read fPlatform write SetPlatform;
property ProductName: String read GetProductName write SetProductName;
property IsServer: Bool read fIsServer write SetIsServer;
property Is64Bit: Bool read fIs64bit write SetIs64Bit;
end;
function IsWOW64: Boolean;
function GetOSInfo: TOSVersionInfo;
procedure Register;
implementation
procedure Register;
begin
RegisterComponents('JD Custom', [TJDOSInfo]);
end;
function GetOSInfo: TOSVersionInfo;
begin
FillChar(Result, SizeOf(Result), 0);
Result.dwOSVersionInfoSize := SizeOf(Result);
if not GetVersionEx(Result) then
raise Exception.Create('Error calling GetVersionEx');
end;
function IsWOW64: Boolean;
type
TIsWow64Process = function( // Type of IsWow64Process API fn
Handle: THandle;
var Res: BOOL): BOOL; stdcall;
var
IsWow64Result: BOOL; // result from IsWow64Process
IsWow64Process: TIsWow64Process; // IsWow64Process fn reference
begin
// Try to load required function from kernel32
IsWow64Process:= GetProcAddress(GetModuleHandle('kernel32'),'IsWow64Process');
if Assigned(IsWow64Process) then
begin
// Function is implemented: call it
if not IsWow64Process(GetCurrentProcess, IsWow64Result) then
raise Exception.Create('Bad process handle');
// Return result of function
Result := IsWow64Result;
end else
// Function not implemented: can't be running on Wow64
Result:= False;
end;
constructor TJDOSInfo.Create(AOwner: TComponent);
var
Info: TOSVersionInfo;
Str: String;
begin
inherited Create(AOwner);
fReg:= TRegistry.Create(KEY_READ);
fReg.RootKey:= HKEY_LOCAL_MACHINE;
fKey:= 'Software\Microsoft\Windows NT\CurrentVersion';
fReg.OpenKey(fKey, False);
Info:= GetOSInfo;
fMajor:= Info.dwMajorVersion;
fMinor:= Info.dwMinorVersion;
fBuild:= Info.dwBuildNumber;
fIsServer:= False;
fIs64bit:= False;
fPlatform:= Info.dwPlatformId;
if fMajor >= 5 then begin
//After 2000
if fReg.ValueExists('ProductName') then
Str:= fReg.ReadString('ProductName')
else begin
Str:= 'Unknown OS: '+IntToStr(fMajor)+'.'+IntToStr(fMinor)+'.'+
IntToStr(fBuild)+'.'+IntToStr(fPlatform);
end;
if fReg.ValueExists('InstallationType') then begin
if UpperCase(fReg.ReadString('InstallationType')) = 'SERVER' then
fIsServer:= True;
end;
fIs64bit:= IsWOW64;
if fIs64bit then
Str:= Str + ' 64 Bit';
end else begin
//Before 2000
case fMajor of
4: begin
case fMinor of
0: Str:= 'Windows 95';
10: Str:= 'Windows 98';
90: Str:= 'Windows ME';
end;
end;
else begin
Str:= 'Older than 95';
end;
end;
end;
Self.fProductName:= Str;
end;
destructor TJDOSInfo.Destroy;
begin
if assigned(fReg) then begin
if fReg.Active then
fReg.CloseKey;
fReg.Free;
end;
inherited Destroy;
end;
function TJDOSInfo.GetProductName: String;
begin
Result:= Self.fProductName;
end;
procedure TJDOSInfo.SetProductName(Value: String);
begin
//Do Nothing Here!
end;
procedure TJDOSInfo.SetMinor(Value: Integer);
begin
//Do Nothing Here!
end;
procedure TJDOSInfo.SetMajor(Value: Integer);
begin
//Do Nothing Here!
end;
procedure TJDOSInfo.SetBuild(Value: Integer);
begin
//Do Nothing Here!
end;
procedure TJDOSInfo.SetPlatform(Value: Integer);
begin
//Do Nothing Here!
end;
procedure TJDOSInfo.SetIs64Bit(const Value: Bool);
begin
//Do Nothing Here!
end;
procedure TJDOSInfo.SetIsServer(const Value: Bool);
begin
//Do Nothing Here!
end;
end.
How can I capture the changes made to the clipboard from a Lazarus program in windows. For example, to save a clipboard history to a file.
Thanks,
It's the same in Lazarus as in any Windows development environment. You need to add yourself into the chain of clipboard viewers.
There are many articles on the web describing how to do it. For example:
http://delphi.about.com/od/windowsshellapi/a/clipboard_spy_2.htm
http://www.developer.com/net/csharp/article.php/3359891/C-Tip-Monitoring-Clipboard-Activity-in-C.htm
http://www.radsoftware.com.au/articles/clipboardmonitor.aspx
I have found this and managed to get it work, but forgot to save it and now struggling to figure how i managed to make it work:
unit Unit1;
{$mode delphi}{$H+}
interface
uses
Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs,
Clipbrd, StdCtrls, Windows, Messages;
type
TForm1 = class(TForm)
Button1: TButton;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
FNextClipboardOwner: HWnd; // handle to the next viewer
// Here are the clipboard event handlers
function WMChangeCBChain(wParam: WParam; lParam: LParam):LRESULT;
function WMDrawClipboard(wParam: WParam; lParam: LParam):LRESULT;
public
end;
var
Form1: TForm1;
implementation
{$R *.lfm}
var
PrevWndProc:windows.WNDPROC;
function WndCallback(Ahwnd: HWND; uMsg: UINT; wParam: WParam;
lParam: LParam): LRESULT; stdcall;
begin
if uMsg = WM_CHANGECBCHAIN then begin
Result := Form1.WMChangeCBChain(wParam, lParam);
Exit;
end
else if uMsg=WM_DRAWCLIPBOARD then begin
Result := Form1.WMDrawClipboard(wParam, lParam);
Exit;
end;
Result := CallWindowProc(PrevWndProc, Ahwnd, uMsg, WParam, LParam);
end;
{ TForm1 }
procedure TForm1.FormCreate(Sender: TObject);
begin
PrevWndProc := Windows.WNDPROC(SetWindowLong(Self.Handle, GWL_WNDPROC, PtrInt(#WndCallback)));
FNextClipboardOwner := SetClipboardViewer(Self.Handle);
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
ChangeClipboardChain(Handle, FNextClipboardOwner);
end;
function TForm1.WMChangeCBChain(wParam: WParam; lParam: LParam): LRESULT;
var
Remove, Next: THandle;
begin
Remove := WParam;
Next := LParam;
if FNextClipboardOwner = Remove then FNextClipboardOwner := Next
else if FNextClipboardOwner <> 0 then
SendMessage(FNextClipboardOwner, WM_ChangeCBChain, Remove, Next)
end;
function TForm1.WMDrawClipboard(wParam: WParam; lParam: LParam): LRESULT;
begin
if Clipboard.HasFormat(CF_TEXT) Then Begin
ShowMessage(Clipboard.AsText);
end;
SendMessage(FNextClipboardOwner, WM_DRAWCLIPBOARD, 0, 0); // VERY IMPORTANT
Result := 0;
end;
end.
Above code is from http://wiki.lazarus.freepascal.org/Clipboard and in theory it should work. It compiles and runs but no window pops up when the clipboard content changes. Maybe someone else here have got better eyes to figure out why.
On Vista and later, you should be using AddClipboardFormatListener() instead of SetClipboardViewer().
This working example originally posted on Lazarus Forums by ASerge and Remy: Not reacting to clipboard change - windows
unit ClipboardListener;
{$mode objfpc}{$H+}
interface
uses
Windows, Messages, Classes;
type
{ TClipboardListener }
TClipboardListener = class(TObject)
strict private
FOnClipboardChange: TNotifyEvent;
FWnd: HWND;
class function GetSupported: Boolean; static;
procedure WindowProc(var Msg: TMessage);
public
constructor Create;
destructor Destroy; override;
property OnClipboardChange: TNotifyEvent read FOnClipboardChange
write FOnClipboardChange;
class property Supported: Boolean read GetSupported;
end;
implementation
uses SysUtils, LCLIntf;
var
AddClipboardFormatListener: function(Wnd: HWND): BOOL; stdcall;
RemoveClipboardFormatListener: function(Wnd: HWND): BOOL; stdcall;
procedure InitClipboardFormatListener;
var
HUser32: HMODULE;
begin
HUser32 := GetModuleHandle(user32);
Pointer(AddClipboardFormatListener) :=
GetProcAddress(HUser32, 'AddClipboardFormatListener');
Pointer(RemoveClipboardFormatListener) :=
GetProcAddress(HUser32, 'RemoveClipboardFormatListener');
end;
{ TClipboardListener }
constructor TClipboardListener.Create;
begin
inherited;
if GetSupported then
begin
FWnd := LCLIntf.AllocateHWnd(#WindowProc);
if not AddClipboardFormatListener(FWnd) then
RaiseLastOSError;
end;
end;
destructor TClipboardListener.Destroy;
begin
if FWnd <> 0 then
begin
RemoveClipboardFormatListener(FWnd);
LCLIntf.DeallocateHWnd(FWnd);
end;
inherited;
end;
class function TClipboardListener.GetSupported: Boolean;
begin
Result := Assigned(AddClipboardFormatListener) and
Assigned(RemoveClipboardFormatListener);
end;
procedure TClipboardListener.WindowProc(var Msg: TMessage);
begin
if (Msg.msg = WM_CLIPBOARDUPDATE) and Assigned(FOnClipboardChange) then
begin
Msg.Result := 0;
FOnClipboardChange(Self);
end;
end;
initialization
InitClipboardFormatListener;
end.
unit Unit1;
{$mode objfpc}{$H+}
interface
uses
ClipboardListener, Classes, Forms, StdCtrls;
type
{ TForm1 }
TForm1 = class(TForm)
Memo1: TMemo;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
FListener: TClipboardListener;
procedure ClipboardChanged(Sender: TObject);
end;
var
Form1: TForm1;
implementation
{$R *.lfm}
{ TForm1 }
procedure TForm1.ClipboardChanged(Sender: TObject);
begin
Memo1.Lines.Append(timetostr(Now)+' ['+Clipboard.AsText+']')
// Memo1.Lines.Append('Clipboard changed');
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
FListener := TClipboardListener.Create;
FListener.OnClipboardChange := #ClipboardChanged;
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
FListener.Free;
end;
end.