Delphi - Komunikaty II
Sun, 11 April 2004
Część: #2
Wstęp
Witam wszystkich po długiej przerwie (z mojej strony). Fanów przepraszam, wrogów zapraszam ;).
W pierwszej części artykułu "Delphi - komunikaty" obiecałem Wam, że wyjdzie następna, opatrzona numerkiem "2". Tak też się stało, więc zapraszam wszystkich programistów na małą wycieczkę po świecie windowsoskich komunikatów. Z mojego punktu widzenia będzie ona nieco ciekawsza od poprzedniej, gdyż dzisiaj pokaże kilka sztuczek z tej dziedziny.
Ostatnio jedynie przechwytywaliśmy komunikaty - czas by coś wysłać. Napiszemy proste programy, które będą umiały zamknąć inną aplikację jedynie znając jej nazwę. Będzie też coś dla zaawansowanych.
Wykorzystanie
Przykładów wykorzystania komunikatów jest wiele. Dziś skupimy się na tym co można osiągnąć wysyłając komunikat do innej aplikacji.
Wielu programistów na pewnym stopniu "rozwoju" staje przed problemem kontrolowania innych aplikacji z poziomu swojego programu. Do tego właśnie służą komunikaty. Za chwilę sprawimy, że nasza aplikacja zniknie. Potem zrobimy to samo z Microsoftoskim Kalkulatorem, na koniec pokażę coś fajnego.
Do wysyłania komunikatów służą trzy metody. Są to: SendMessage, PostMessage i Perform.
SendMessage
SendMessage jest funkcją. Zwraca ona wynik swojego działania. Po wysłaniu komunikatu, funkcja SendMessage czeka na jego wykonanie, a następnie zwraca wynik swojego działania.
SendMessage(hWnd: HWND; Msg: Cardinal; WParam: integer; LParam: integer);
gdzie odpowiednio:
hWnd - uchwyt okna do którego wysyłamy komunikat
Msg - komunikat właściwy ;) tzn. nazwa komunikatu np. WM_CLOSE
WParam - dodatkowa informacja (dane) dotycząca meldunku. Jest to WordParameter, czyli parametr 16-bitowy jeszcze z czasów 16-bitowych Windowsów.
LParam - zawiera dodatkowe informacje (dane) dotyczące meldunku. Jest to LongintParameter, czyli parametr 32-bitowy
PostMessage
PostMessage nie zwraca żadne wyniku swojego działania. Nie czeka ona na wykonanie wysłanego komunikatu.
PostMessage(hWnd: HWND; Msg: Cardinal; WParam: integer; LParam: integer);
gdzie odpowiednio:
hWnd - uchwyt okna do którego wysyłamy komunikat
Msg - nazwa komunikatu np. WM_CLOSE
WParam - dodatkowa informacja (dane) dotycząca meldunku. Jest to WordParameter, czyli parametr 16-bitowy jeszcze z czasów 16-bitowych Windowsów.
LParam - zawiera dodatkowe informacje (dane) dotyczące meldunku. Jest to LongintParameter, czyli parametr 32-bitowy
Perform
Służy do wysyłania komunikatów w obrębie naszej aplikacji. Za pomocą tej metody nie wyślemy komunikatu nigdzie indziej. Zauważmy, że nie ma ona parametru pozwalającego określić uchwyt aplikacji do której wysyłamy komunikat, dlatego też metoda Perform przyjmuje za domyślny uchwyt, uchwyt naszej aplikacji, czyli Application.Handle; Składnia Perform prezentuje się następująco:
Perform(Msg: Cardinal; WParam: integer; LParam: integer);
gdzie odpowiednio:
Msg - nazwa komunikatu np. WM_CLOSE
WParam - dodatkowa informacja (dane) dotycząca meldunku. Jest to WordParameter, czyli parametr 16-bitowy jeszcze z czasów 16-bitowych Windowsów.
LParam - zawiera dodatkowe informacje (dane) dotyczące meldunku. Jest to LongintParameter, czyli parametr 32-bitowy
Wysyłamy pierwszy komunikat
Od czego zaczniemy? Od zamykania okna? Dobra. Za chwilę sprawimy, że okno naszego programu zniknie! Nie próbujcie robić tego w domu ;) Zanim jednak zaczniemy na dobre pisać programy wysyłające komunikaty, jestem zmuszony zapodać wam parę przykładowych nazw komunikatów i objaśnić do czego tak właściwie służą. Jak już pewnie zdążyliście zauważyć, komunikaty zaczynają się charakterystycznym przedrostkiem (?) WM. Mamy więc: WM_CLOSE, WM_LBUTTONDOWN, WMRBUTTONDOWN, WM_UNDO, WM_DESTROY jak i wiele, wiele innych komunikatów czekających tylko na odkrycie. Znajdują się one w katalogu %katalog z delphi%\Source\Rtl\Win\Messages.pas , czyli np. w moim wypadku byłoby to: E:\Program Files\Borland Delphi 6\Source\Rtl\Win\Messages.pas . Macie tam zbiór wszystkich komunikatów. Jeszcze jedna rada : Wątpię, żeby ktokolwiek znał je wszystkie - po prostu - nie znacie komunikatu - zerkacie - i już wiecie, nie potrzeba się tego uczyć na pamięć zwłasza, że nie ma to najmniejszego sensu bo i tak cały czas plik macie w zasięgu ręki (jest niezbędny aby nasza aplikacja mogła otrzymywać i wysyłać komunikaty). I jeszcze jedno: gdy aplikacja nie obsługuje żadnych komunikatów jest jak warzywo ;) - nie dopuśćcie więc do tego!
Piszemy program
- Uruchamiamy środowisko Delphi.
- Na formularzu umieszczamy komponent Button (klikamy na palecie wybierając komponent, a następnie na formularzu)
- Gdy już na formularzu znajduje się przycisk, klikamy nań dwukrotnie otwierając tym samym edytor kodu (dostępny również przez naciśnięcie F12)
Pomiędzy słowa:
begin end;
wpisujemy następującą instrukcję:Perform(WM_CLOSE,0,0);
Prawda, że łatwe? No jasne! Dlaczego wybrałem z tych trzech funkcji akurat Perform ? Ponieważ nie potrzeba wysyłać komunikatu poza naszą aplikację. Mamy za zadanie jedynie zakończyć działanie naszej aplikacji - nic więcej.
Tak. No właśnie. Ale co zrobić, żeby zamknąć inną aplikację, np. Kalkulator dołączany standardowo do Windowsów? Jest także sposób i na to, popatrzcie:
- Uruchamiamy środowisko Delphi.
- Na formularzu umieszczamy komponent Button (klikamy na palecie wybierając komponent, a następnie na formularzu)
- Gdy już na formularzu znajduje się przycisk, klikamy nań dwukrotnie otwierając tym samym edytor kodu (dostępny również przez naciśnięcie F12)
Przed słówkiem
begin
wpisujemy słowo:var
umieszczając następnie pod nim deklarację następującej zmiennej:uchwyt: HWND;
- Pomiędzy słówka
begin end;
wpisujemy instrukcję:uchwyt := FindWindow(nil,'Kalkulator'); SendMessage(uchwyt,WM_CLOSE,0,0);
- Całość powinna wyglądać następująco :
procedure TForm1.Button1Click(Sender: TObject); var uchwyt: HWND; begin uchwyt := FindWindow(nil, 'Kalkulator'); SendMessage(uchwyt, WM_CLOSE, 0, 0); end;
Już zabieram się do tłumaczenia!
Najpierw deklarowana jest zmienna uchwyt, która będzie przechowywała uchwyt okna kalkulatora (każde okno w Windowsie ma swój uchwyt - to jest jakby identyfikator danego okna).
Następnie (po begin) do zmiennej uchwyt przypisywany jest za pomocą funkcji FindWindow() właśnie uchwyt okna kalkulatora. Następnie za pomocą funkcji SendMessage wysyłany jest komunikat zamknięcia ( WM_CLOSE ) do programu kalkulator. Zaraz, zaraz - powiecie - ale, jak działa funkcja...
...FindWindow
Funkcja FindWindow() prezentuje się następująco:
FindWindow(lpClassName: PChar; lpWindowName: PChar);
gdzie odpowiednio:
lpClassName - nazwa klasy do której należy okno
lpWindowName - nazwa okna (w naszym wypadku jest to 'Kalkulator';
O ile lpClassName nie będzie nam potrzebne (należy wpisać nil, ponieważ nie znamy jej) to lpWindowName spełnia tutaj ważną rolę. Otóż po podaniu nazwy okna 'Kalkulator' jako parametru, funkcja FindWindow zwraca nam uchwyt okna programu kalkulator. Sprytne? No, tak, ale wypadałoby jeszcze wysłać komunikat, który "powie" Kalkulatorowi, żeby się zamknął ;). Znając już najważniejsze parametry, wywołujemy funkcję SendMessage lub PostMessage i podstawiamy znane nam wartości. Gdy nie znamy jakiegoś parametru, wpisujemy w to miejsce 0 lub nil, w zależności czy dany parametr jest typu integer czy PChar.
Jeszcze raz komunikaty...
Komunikaty to dobry sposób również na komunikację w zakresie własnego programu. Mało osób wie, że można stworzyć również własne komunikaty. Tak! Delphi udostępnia nam i taką możliwość. Chcecie zobaczyć jak? Czytajcie dalej.
Dzięki własnoręcznie stworzonym komunikatom możemy przesyłać różnoraki informacje wewnątrz własnego programu. Nie jest to aż niezbędne do pisania programów, ale dowiedzieć się czegoś nowego zawsze można.
Aby napisać obsłużyć własny komunikat musimy po słówku uses, ale jeszcze przed słówkiem type (nie pytajcie dlaczego ;?) dopisać taką linię:
const
Moj_komunikat = WM_USER + 100;
Następnie, do sekcji private dodajemy deklarację nowej procedury obsługującej komunikat:procedure MojKomunikat(var msg: TMessage); message Moj_komunikat;
a w sekcji implementation dodajemy nową procedurę i jej kod (kod który zostanie wywołany po wywołaniu komunikatu).
Procedura ta będzie pokazywała dwie tabliczki. Jedna będzie informowała, że udało się wywołanie własnego komunikatu, a druga wyświetli przekształcone na litery parametry dostarczone wraz z komunikatem (są to kody ASCII liter, a funkcja Chr() służy do zmiany kodów ASCII na litery np. dużej literze A w kodzie ASCII odpowiada kod 65). Którym literom odpowiadają poszczególne kody można łatwo sprawdzić korzystając ze specjalnych programów, tablic, lub po prostu na ślepo wstukując kody ( trzymając lewy Alt i wstukując kody na klawiaturze numerycznej ).
procedure TForm1.MojKomunikat(var msg : TMessage);
begin
ShowMessage('Procedura zgłaszająca się po wywołaniu komunikatu "Moj_komunikat"');
ShowMessage(Chr(Msg.WParam)+Chr(Msg.LParam));
end;
No tak, ale to jeszcze nie wszystko!
Umieśćcie na formularzu przycisk i dwukrotnie kliknijcie na niego w celu przeniesienia się do edytora kodu. W procedurze OnClick naszego przycisku wprowadźcie instrukcję, która będzie wywoływała procedurę MojKomunikat z parametrami 64,116 . Są to kody ASCII liter "@" i "t":
Perform(Moj_komunikat, 64, 116);
W efekcie otrzymamy dwie tabliczki. Jedną z informacją, a drugą ... z napisem "@t". Teraz możecie już uruchomić program ( F9 ).
Działa? Na pewno!
Cały kod prezentuje się następująco:
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
const
Moj_komunikat = WM_USER + 1987;
type
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
procedure MojKomunikat(var msg: TMessage); message Moj_komunikat;
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.MojKomunikat(var msg : TMessage);
begin
ShowMessage('Procedura zgłaszająca sie po wywołaniu komunikatu "Moj_komunikat"');
ShowMessage(Chr(Msg.WParam)+Chr(Msg.LParam));
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
Perform(Moj_komunikat,64,116);
end;
end.
Jak zamknąć inną aplikację znając jej ścieżkę?
Ok, ale obiecałem, że pokaże jeszcze jeden trick. Tym razem dla nieco bardziej zaawansowanych.
Jeżeli znasz ścieżkę uruchomionego programu to możesz zamknąć ją. Jak? Pokazuje to poniższy kod. Aha, do listy modułów uses musisz dodać słowo "TLHelp32".
var
PHandle, FHandle: THandle;
Process:TProcessEntry32;
Done, Next: Boolean;
EXE : String; // ścieżka programu
begin
EXE := 'C:\Windows\Pulpit\prog.exe';
FHandle := CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,0);
Process.dwSize := Sizeof(Process);
Next := Process32First(FHandle,Process);
while Next do
begin{ jesli sciezka dostepu sie zgadza }
if AnsiLowerCase(Process.szExeFile) = AnsiLowerCase(EXE) then
begin
PHandle:=OpenProcess(PROCESS_TERMINATE, False,
Process.th32ProcessID);
{ to probujemy zabic aplikacje }
Done := TerminateProcess(PHandle,0);
if not Done then
MessageBox(Handle, 'Błąd', 'Błąd', MB_OK);
end;
Next := Process32Next(FHandle,Process);
end;
CloseHandle(FHandle);
end;
Zakończenie
Czym jednakże byłoby zakończenie bez przydatnej rady na przyszłość ? ;)
Mam dobrą wiadomość dla koderów edytorów tekstu i wszelkiego rodzaju innych paści operujących na tekście i komponencie Memo czy RichEdit. Można bowiem wysłać do kontrolki komunikat, aby cofnęła jedna wykonaną czynność. Niby nic, ale zawsze coś!
Aby skonstruować takie jednopoziomowe polecenie Cofnij należy, jak już wspomniałem, dostarczyć komponentowi Memo1 odpowiedni komunikat - jaki ? WM_UNDO się kłania.
Umieśćcie na formie komponent Memo i komponent Button
Komponent Button będzie służył do cofania ostatniej czynności w komponencie Memo ;)
Dwukrotnie kliknijcie na komponent Button w przekonaniu, że otworzy się edytor kodu.
Jeśli edytor kodu się nie otworzył - patrz: punkt 3 ;)
Pomiędzy słowa:
begin
end;
wpiszcie następującą instrukcję:
PostMessage(Memo1.Handle, WM_UNDO, 0, 0);
Polecenie Undo gotowe!
Gotowa procedura na polecenie Undo powinna wyglądać tak:
procedure TForm1.Button1Click(Sender: TObject);
begin
PostMessage(Memo1.Handle, WM_UNDO, 0, 0);
end;
i nadszedł w końcu czas na...
Zakończenie właściwe
I tym oto miłym akcentem zakańczam część drugą artykułu o meldunkach (inaczej komunikatach). Życzę wielu sukcesów w pisaniu coraz lepszych programów, działających nie tylko na komunikatach. Do artykułu dodaję archiwum z kodem źródłowym do zamykania kalkulatora;). Pozdrawiam wszystkich czytelników zarówno tych którzy dotrwali do końca, jak i tych którzy skorzystali nieco wcześniej z funkcji "<back".
Podobne artykuly:
- Delphi - Komunikaty I
- Delphi - Klient-serwer na TCP/IP
- KillAd
- Delphi - Budowa modułu
- Delphi - Piszemy własny odtwarzacz multimedialny
- Delphi - Piszemy prosty edytor tekstu
- Delphi - Jak pisać?
Skomentuj
Komentarze czytelników
-
- Disip
- Wed, 2 June 2010, 15:31
- I ponownie ja :)
Takie myślę dość zasadnicze pytanie:
w jaki sposób wysłać komunikat WM_LBUTTONDOWN lub BM_CLICK tak, by można było określić w które konkretnie miejsce ma kliknąć?
mouse_event odpada, gdyż tutaj potrzebny jest kursor ustawiony nad miejscem kliknięcia...
Czy da się jakoś w parametrach SendMessage ustalić współrzędne kliknięcia?
Pozdrawiam
Odp: Źle myślisz, że poprzez mouse_event() nie da się tego zrobić. Wszak ta funkcja do tego m.in. służy (właściwie to głównie do tego). Zobacz:
mouse_event(MOUSEEVENTF_ABSOLUTE or MOUSEEVENTF_LEFTDOWN, 500, 500, 0, 0);
mouse_event(MOUSEEVENTF_ABSOLUTE or MOUSEEVENTF_LEFTUP, 500, 500, 0, 0);
Gdzie (500,500) to punkt w którym chcemy kliknąć.