artykuły

Delphi - Komunikaty II

21:45
nie, 11 kwiecień 2004
Druga część popularnej serii artykułów objaśniająca zagadnienie komunikatów systemowych (nie mylić z oknami dialogowymi), ich funkcjonowanie i wykorzystywanie. W drugiej części czytelnik dowiaduje się jak wysyłać komunikaty w obrębie własnej aplikacji oraz jak przesyłać je do innych programów. W artykule zostają zaprezentowane ciekawe sposoby wykorzystania tego interfejsu.

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

  1. Uruchamiamy środowisko Delphi.
  2. Na formularzu umieszczamy komponent Button Komponent typu TButton (klikamy na palecie wybierając komponent, a następnie na formularzu)
  3. 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:

  1. Uruchamiamy środowisko Delphi.
  2. Na formularzu umieszczamy komponent Button Komponent typu TButton (klikamy na palecie wybierając komponent, a następnie na formularzu)
  3. 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łówkiembeginwpisujemy słowo:varumieszczając następnie pod nim deklarację następującej zmiennej:uchwyt: HWND;
  4. Pomiędzy słówkabegin end;wpisujemy instrukcję: uchwyt := FindWindow(nil,'Kalkulator'); SendMessage(uchwyt,WM_CLOSE,0,0);
  5. 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;
Źródło: http://www.4programmers.net na FAQ (pozycja 58)

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".

12345
Delphi - Komunikaty II Autor opinii: Czytelnicy, data przesłania: 5

Podobne artykuly:

Skomentuj

Aby zamieścić komentarz, proszę włączyć JavaScript - niestety roboty spamujące dają mi niezmiernie popalić.






Komentarze czytelników

    • Disip
    • śro, 2 czerwiec 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ąć.
Dexter