STEROWANIE OBCYM PROGRAMEM Z POZIOMU WŁASNEGO POPRZEZ WYSYŁANIE KOMUNIKATÓW.
W tym artykule pokażę sposób na sterowanie teoretycznie dowolnym programem poprze wysyłanie do niego komunikatów z naszego własnego programu. Zadanie jest w praktyce bardzo proste do zrealizowania. Żeby sterować obcym programem należy mieć do niego uchwyt, uchwyt do programu można pobrać gdy zna się nazwę klasy programu i tutaj napotykamy na pierwszy problem! Jak i skąd pobrać nazwę klasy programu, można się tutaj posłużyć kodem podanym przeze mnie w poradzie 'Wyliczanie okien' aczkolwiek nie będzie to zbyt wygodne dlatego polecam program WinID wersja freeware. Po uruchomieniu program monitoruje wszystkie uruchomione w systemie programy, wystarczy wskazać wskaźnikiem myszy belkę tytułową wybranego programu, a WinDI wyświetli o nim szczegółowe informacje w tym nazwę klasy, co więcej po najechaniu na dowolny element w programie, wskaże nam on nazwę klasy tego obiektu co również będzie nam potrzebne.
![]() |
Program podaje nam również uchwyt do programu (HWND) co
znacznie upraszcza sprawę, jednak numer uchwytu programu zmienia się przy każdym
jego uruchomieniu, więc staje się on bezużyteczny, dlatego uchwyt do programu
będziemy pobierać sobie sami na pomocą funkcji FindWindow podając jej jako
argument nazwę klasy programu, natomiast uchwyt do elementów programu takich jak
przyciski, menu kontekstowe itp. będziemy pobierać za pomocą funkcji
FindWindowEx przekazując jej jako argument uchwyt do programu i nazwę klasy
obiektu. Żeby sobie wszystko uprościć stworzymy specjalną funkcję, nazwę ją
Navigate, która wyeliminuje konieczność powtarzania kodu, a żeby było jeszcze
łatwiej stworzymy trzy podobne funkcje o takiej samej nazwie (Navigate)
wykorzystując przeładowanie funkcji, w ten sposób możliwe będzie sterowanie
zarówno samym programem jak i obiektami w nim się znajdującymi:
| // Plik źródłowy np. Unit1.cpp //Funkcje sterujące - przeładowanie funkcji. void Navigate(String ClassName, UINT Msg, WPARAM wParam, LPARAM lParam) { HWND h = FindWindow(ClassName.c_str(), 0); SendMessage(h, Msg, wParam, lParam); } //-------------------------------- void Navigate(String ClassName, String SubClassName, UINT Msg, WPARAM wParam, LPARAM lParam) { HWND h = FindWindow(ClassName.c_str(), 0); HWND h2; if(h > 0) h2 = FindWindowEx(h, 0, SubClassName.c_str(), ""); SendMessage(h2, Msg, wParam, lParam); } //-------------------------------- void Navigate(String ClassName, String SubClassName, String Title, UINT Msg, WPARAM wParam, LPARAM lParam) { HWND h = FindWindow(ClassName.c_str(), 0); HWND h2; if(h > 0) h2 = FindWindowEx(h, 0, SubClassName.c_str(), Title.c_str()); SendMessage(h2, Msg, wParam, lParam); } //-------------------------------- |
W przedstawionym
kodzie wykorzystywana jest funkcja SendMessage, która wysyła komunikat do
programu na podstawie uchwytu do programu, jest to pierwszy argument, drugi
argument to rodzaj komunikatu. Dostępne komunikaty można znaleźć w pomocy BCB po
wpisaniu hasła WM_ . Trzeci i czwarty argument to parametry komunikatu.
Na początek posłużymy się bardzo prostym przykładem, w celu lepszego zrozumienia
problemu i wyślemy komunikat do obiektu RichEdit w naszym własnym programie,
czyli tym z którego będzie wysyłany komunikat, wpiszemy literę A do okna edycji:
| // Plik źródłowy np. Unit1.cpp //-------------------------------- void __fastcall TForm1::Button1Click(TObject *Sender) { SendMessage(RichEdit1->Handle, WM_CHAR, 'A', 1); RichEdit1->SetFocus(); } //-------------------------------- |
Komunikatem
odpowiedzialnym za wpisywanie znaków do pola edycji jest WM_CHAR i ta zasada
dotyczy wszystkich programów. Teraz zrobimy coś znacznie trudniejszego,
uruchomimy z pozimu naszego programu Notatnik, a następnie wydamy mu polecenia
zamknięcia się:
| // Plik źródłowy np. Unit1.cpp //-------------------------------- void __fastcall TForm1::Button1Click(TObject *Sender) { //Uruchom Notatnik char sysdir[_MAX_PATH]; GetSystemDirectory(sysdir, _MAX_PATH); WinExec(((AnsiString)sysdir + "\\notepad.exe").c_str(), SW_NORMAL); } //-------------------------------- void __fastcall TForm1::Button2Click(TObject *Sender) { Navigate("Notepad", WM_CLOSE, 0, 0); // Zamykanie notatnika } //-------------------------------- |
Jak widać
wykorzystałem tutaj funkcję sterującą Navigate przekazując jej jako pierwszy
argument nazwę klasy programu Notatnik (klasa Notepad), następnie komunikat
WM_CLOSE nakazujący programowi zakończenie działania. Jeżeli wprowadzimy do
Notatnika jakiś tekst to przed zamknięciem zostanie wyświetlony monit o
zapisanie pliku, żeby tego uniknąć można wysłać do programu komunikat WM_DESTROY
nakazujący mu bezwzględne zamknięcie:
| // Plik źródłowy np. Unit1.cpp //-------------------------------- void __fastcall TForm1::Button3Click(TObject *Sender) { Navigate("Notepad", WM_DESTROY, 0, 0); // Zamykanie notatnika poprzez zniszczenie } //-------------------------------- |
Znacznie trudniej
jest przywołać jakąś funkcję obiektu w programie, ponieważ trzeba się odwoływać
do klasy tegoż obiektu poprze klasę programu, ale funkcja potrzebna do
realizacji tego zadania została już przez nas stworzona na samym początku i nosi
ona również nazwę Navigate (przeładowanie funkcji), ale pobiera o jeden argument
więcej, ten dodatkowy argument to nazwa klasy obiektu, w kolejnych przykładach
wprowadzimy litery do pola edycji oraz przywołamy menu kontekstowe tego pola:
| // Plik źródłowy np. Unit1.cpp //-------------------------------- void __fastcall TForm1::Button4Click(TObject *Sender) { //Przywołanie menu kontekstowego okna edycji Navigate("Notepad", "Edit", WM_CONTEXTMENU, 0, 20); } //-------------------------------- void __fastcall TForm1::Button5Click(TObject *Sender) { //Wpisywanie liter pojedynczo do okna edycji Navigate("Notepad", "Edit", WM_CHAR, 'C', 1); Sleep(500); //to można sobie darować Navigate("Notepad", "Edit", WM_CHAR, 'B', 1); } //-------------------------------- |
Nazwa klasy
pola edycji to Edit, tak jest w przypadku większości programów, ale nie musi to
być powszechnie obowiązującą zasadą, zawsze można to sprawdzić w programie WinID
wskazując myszą pole edycji Notatnika. Komunikat przywołujący menu kontekstowe
to WM_CONTEXTMENU, natomiast komunikat wpisujący znaki to WM_CHAR. Teraz
przyszła kolej na wpisanie jakiegoś tekstu do pola edycji. Niestety napotykamy
tutaj pewną trudność ponieważ brak komunikatu wpisującego więcej niż jeden znak,
dlatego należy każdy znak wprowadzać oddzielnie, jednak wywoływanie funkcji
Navigate dla każdej litery, przy dłuższych ciągach znaków byłoby bardzo
uciążliwe, moim pomysłem na rozwiązanie problemu jest wprowadzanie znaków za
pomocą pętli, jednak w takim przypadku należy przekazywać do funkcji nie tyle
znak ile jego wartość w liczbie, dlatego wykorzystamy tutaj funkcję CONVERT,
która była przedstawiana w poradzie
Zamiana liczb na litery i odwrotnie.
Teraz zadanie staje się bardzo proste w realizacji i efektowne w wykonaniu:
| // Plik źródłowy np. Unit1.cpp //-------------------------------- int CONVERT(String litera) { for(int i = -113; i <= 255; i++) { if(litera == CHAR(i)) return i; } return 0; } //-------------------------------- void __fastcall TForm1::Button6Click(TObject *Sender) { //Wpisywanie tekstu do okna edycji String tekst = "Przykładowy tekst do wpisania. Jak widać rozpoznaje polskie znaki" " \"Ćć Śś Źź Żż Ąą Ęę Óó itp.\"\nA nawet zawiaja linie.\n\nRobi przerwy" "\n\r i wcięcia,\r\r\r\r i dłuższe przerwy z wcięciem." "\n\nJEDNAK PRZY DŁUGICH TEKSTACH KOD MOŻE WYKONYWAĆ SIĘ BARDZO DŁUGO." "\n\rChyba że zmodyfikujesz pętle lub przerobisz algorytm:" "\nint CONVERT(String liter)\n{\r for(int i = -113; i <= 255; i++)" "\n {\n if(litera == CHAR(i))\n return i;\n }\n return 0;\n}"; for(int i = 1; i <= tekst.Length(); i++) { String tmp = tekst.SubString(i, 1); WORD a = CONVERT(tmp); Navigate("Notepad", "Edit", WM_CHAR, (WPARAM)a, 1); } } //-------------------------------- |
Przyszła kolej na
zaznaczanie tekstu w polu edycji, normalnie to zadanie jest z reguły realizowane
za pomocą myszki, dlatego ja również posłużę się myszką, a właściwie tylko
komunikatami wysyłanymi gdy myszka zaznacza tekst, te komunikaty to wciśnięcie
lewego klawisza myszy WM_LBUTTONDOWN, przesunięcie wskaźnika myszy do wybranej
pozycji WM_MOUSEMOVE, oraz zwolnienie lewego klawisza myszy WM_LBUTTONUP:
| // Plik źródłowy np. Unit1.cpp //-------------------------------- void __fastcall TForm1::Button7Click(TObject *Sender) { //Zaznaczanie tekstu myszką int x = Canvas->TextWidth("a"); Navigate("Notepad", "Edit", WM_LBUTTONDOWN, 0, 1); Navigate("Notepad", "Edit", WM_MOUSEMOVE, 0, x*20); Navigate("Notepad", "Edit", WM_LBUTTONUP, 0, 1); } //-------------------------------- |
Komunikat
WM_MOUSEMOVE wysyłany jest z parametrem określającym jak daleko wskaźnik myszy
ma się przesunąć, w przykładzie zastosowałem przelicznik uwzględniający rozmiar
czcionki i przesuwający kursor myszy o 20 znaków. Teraz kolej na zaznaczenie
całej zawartości pola edycji, najprościej będzie to zrobić przywołując polecenia
menu Zaznacz wszystko, jednak żeby dobrać się do tego polecenia należy znać jego
numer, do tego celu bardzo przydatny okazuje się program
Resource Hacker,
pozwalający zajżeć do pliki *.exe programu i zobaczyć jaki numer ma to
polecenia. W przypadku Notatnika to polecenia nosi numer 25, ale nie jest to
regułą, komunikat który należy wysłać to WM_COMMAND, z tym, że komunikat
wysyłamy do programu, a nie do pola edycji:
| // Plik źródłowy np. Unit1.cpp //-------------------------------- void __fastcall TForm1::Button8Click(TObject *Sender) { //Zaznacza całą zawartość pola Edit poprzez wywołanie polecenia Zaznacz wszystko z menu //Kod polecenia: 25 //Do sprawdzania kodu polecenia dla programu polecam program Resource Hacker Navigate("Notepad", WM_COMMAND, 25, 0); } //-------------------------------- |
Innym sposobem do
zaznaczania całego tekstu może być wykorzystanie komunikatu o przesunięciu
myszki, ale nie działa to całkiem prawidłowo, niemniej zademonstruję:
| // Plik źródłowy np. Unit1.cpp //-------------------------------- void __fastcall TForm1::Button9Click(TObject *Sender) { //Zaznacza całą zawartość pola Edit za pomoca myszki Navigate("Notepad", "Edit", WM_LBUTTONDOWN, 0, 0); Navigate("Notepad", "Edit", WM_MOUSEMOVE, 0, 1000000000); Navigate("Notepad", "Edit", WM_LBUTTONUP, 0, 1); } //-------------------------------- |
Skoro wiadomo już
jak wszystko zaznaczyć to można stworzyć funkcję usuwającą zawartość pola
edycji, służy do tego komunikat WM_CLEAR:
| // Plik źródłowy np. Unit1.cpp //-------------------------------- void __fastcall TForm1::Button10Click(TObject *Sender) { //Usuwanie całej zawartości pola Edit poprzez zaznaczenie i skasowanie Button18Click(Sender); // Odwołanie do zdarzenie OnClick przycisku Button8 w którym odbywa się zaznaczenie tekstu. Navigate("Notepad", "Edit", WM_CLEAR, 0, 0); } //-------------------------------- |
Kolejnym krokiem jest obsługa schowka, pokaże jak wykorzystać polecenie wklej wysyłając komunikat WM_PASTE, może to być dobry sposób na wprowadzanie tekstu do pola edycji, przedstawię również polecenie wytnij obsługiwane poprzez komunikat WM_CUT:
| // Plik źródłowy np. Unit1.cpp //-------------------------------- void __fastcall TForm1::Button11Click(TObject *Sender) { //Wklejanie zawartości schowka //Najpierw skopiuj jakiś tekst do schowka Navigate("Notepad", "Edit", WM_PASTE, 0, 0); } //-------------------------------- void __fastcall TForm1::Button12Click(TObject *Sender) { //Wytnij i wklej Button18Click(Sender); // Odwołanie do zdarzenie OnClick przycisku Button8 w którym odbywa się zaznaczenie tekstu. Navigate("Notepad", "Edit", WM_CUT, 0, 0); Sleep(2000); // to opóźnienie jest zbędne, jednak pozwala zobaczyć jak to działa Navigate("Notepad", "Edit", WM_PASTE, 0, 0); } //-------------------------------- |
Jeżeli chodzi o
Notatnik to sprawa powinna być już prosta, pokaże jeszcze tylko jak przywołać
kilka poleceń menu:
| // Plik źródłowy np. Unit1.cpp //-------------------------------- void __fastcall TForm1::Button13Click(TObject *Sender) { //Zapisywanie zawartości Notatnika Navigate("Notepad", WM_COMMAND, 3, 0); } //-------------------------------- void __fastcall TForm1::Button14Click(TObject *Sender) { //Wstawianie daty i godziny do pola edycji Navigate("Notepad", WM_COMMAND, 26, 0); } //-------------------------------- void __fastcall TForm1::Button15Click(TObject *Sender) { //Przywołanie okna o programie Navigate("Notepad", WM_COMMAND, 65, 0); } //-------------------------------- |
Czas na coś
trudniejszego, tym razem wykorzystamy Kalkulator i spróbujemy wykonać w nim
mnożenie. Zadanie jest trochę trudniejsze ponieważ tym razem musimy naciskać
myszką przyciski, problem w tym, że w programie Kalkulator klasy wszystkich
przycisków noszą nazwę Button, coś je jednak wyróżnia i jest to nazwa samego
przycisku, czyli to co się na nim znajduje, jednym słowem właściwość Caption, w
programie WinID widnieje ona jako właściwość Title. Więc do przycisków będziemy
odwoływać się podając nazwę klasy programu (SciCalc), nazwę klasy przycisku
(Button) i nazwę samego przycisku, funkcję potrzebną do realizacji tego zadania
stworzyliśmy na samym początku tego artykułu i nosi ona tą samą nazwę Navigate,
pobiera jednak jeden argument więcej i jest to właśnie nazwa obiektu, w
poniższym przykładzie pokaże jak uruchomić i zamknąć Kalkulator, jak wykonać
mnożenie, i jak wyczyścić zawartość pola edycji:
| // Plik źródłowy np. Unit1.cpp //-------------------------------- void __fastcall TForm1::Button16Click(TObject *Sender) { //Uruchamianie Kalkulatora char sysdir[_MAX_PATH]; GetSystemDirectory(sysdir, _MAX_PATH); WinExec(((AnsiString)sysdir + "\\calc.exe").c_str(), SW_NORMAL); } //-------------------------------- void __fastcall TForm1::Button17Click(TObject *Sender) { //Zamykanie Kalkuratora Navigate("SciCalc", WM_CLOSE, 0, 0); } //-------------------------------- void __fastcall TForm1::Button18Click(TObject *Sender) { //Wykonywanie mnożenia w Kalkulatorze Navigate("SciCalc", "Button", "7", WM_LBUTTONDOWN, 0, 1); Navigate("SciCalc", "Button", "7", WM_LBUTTONUP, 0, 1); Navigate("SciCalc", "Button", "*", WM_LBUTTONDOWN, 0, 1); Navigate("SciCalc", "Button", "*", WM_LBUTTONUP, 0, 1); Navigate("SciCalc", "Button", "8", WM_LBUTTONDOWN, 0, 1); Navigate("SciCalc", "Button", "8", WM_LBUTTONUP, 0, 1); Navigate("SciCalc", "Button", "=", WM_LBUTTONDOWN, 0, 1); Navigate("SciCalc", "Button", "=", WM_LBUTTONUP, 0, 1); } //-------------------------------- void __fastcall TForm1::Button19Click(TObject *Sender) { //Kasowanie zawartości Kalkulatora Navigate("SciCalc", "Button", "C", WM_LBUTTONDOWN, 0, 1); Navigate("SciCalc", "Button", "C", WM_LBUTTONUP, 0, 1); } //-------------------------------- |
Na zakończenie
przykład uruchomienia programu Outlook Express, oraz sposób na sterowanie
konsolą wiersza poleceń:
| // Plik źródłowy np. Unit1.cpp //-------------------------------- void __fastcall TForm1::Button20Click(TObject *Sender) { //Uruchamianie Outlooka char sysdir[_MAX_PATH]; GetSystemDirectory(sysdir, _MAX_PATH); String outlook = ExtractFileDrive((AnsiString)sysdir) + "\\Program Files\\Outlook Express\\msimn.exe"; WinExec(outlook.c_str(), SW_NORMAL); Sleep(5000); // Oczekiwanie na uruchomienie Outlooka - 5 sekund. Navigate("Outlook Express Browser Class", WM_COMMAND, 40465, 0); } //-------------------------------- void __fastcall TForm1::Button21Click(TObject *Sender) { //Uruchamianie wiersza poleceń i wyświetlanie listy katalogów char sysdir[_MAX_PATH]; GetSystemDirectory(sysdir, _MAX_PATH); WinExec(((AnsiString)sysdir + "\\cmd.exe").c_str(), SW_NORMAL); Sleep(1000); Navigate("ConsoleWindowClass", WM_CHAR, 'D', 1); Navigate("ConsoleWindowClass", WM_CHAR, 'I', 1); Navigate("ConsoleWindowClass", WM_CHAR, 'R', 1); Navigate("ConsoleWindowClass", WM_CHAR, VK_RETURN, 1); } //-------------------------------- void __fastcall TForm1::Button22Click(TObject *Sender) { //Zamyka wiersz poleceń Navigate("ConsoleWindowClass", WM_CHAR, 'E', 1); Navigate("ConsoleWindowClass", WM_CHAR, 'X', 1); Navigate("ConsoleWindowClass", WM_CHAR, 'I', 1); Navigate("ConsoleWindowClass", WM_CHAR, 'T', 1); Navigate("ConsoleWindowClass", WM_CHAR, VK_RETURN, 1); } //-------------------------------- |
Zaprezentowane tutaj przykłady nie wykorzystują wszystkich możliwości, pokazują tylko sposób na wykorzystanie komunikatów do sterowania programami. Liczba komunikatów możliwych do wykorzystania nie jest duża. Przeprowadzałem próby z komunikatami WM_KEYDOWN i WM_KEYUP, jednak bez sukcesu, wydaje się, że funkcja SendMessage nie obsługuje tych komunikatów.
Możemy skorzystać z dwóch bardzo podobnych funkcji SendMessage i PostMessage. Różnica między tymi funkcjami polega na tym, że SendMessage wysyła komunikat do uchwytu (HWND) i czeka na obsłużenie komunikatu, po czym zwraca rezultat, podczas gdy funkcja PostMessage umieszcza komunikat w kolejce komunikatów i natychmiast kończy swoje działanie.
W przypadku
SendMessage mamy pewność, że komunikat zostanie wykonany - jeżeli jest to
możliwe, lub nie - jeżeli pojawią się nieprzewidziane okoliczności, ale funkcja
zawsze o tym powiadomi.
Funkcja PostMessage powiadamia tylko o wysłaniu komunikatu i umieszczeniu
go w kolejce komunikatów jakie dochodzą do aplikacji, potem kończy działanie i
nie sprawdza czy komunikat został wykonany. Ma to swoje wady, komunikat może
czekać w kolejce w nieskończoność, może zostać pominięty i nigdy nie wykonany.
Opracował: Cyfrowy Baron
UMIESZCZANIE OKNA OBCEGO PROGRAMU W PROGRAMIE WŁASNYM.
Ten artykuł stanowi niejako ciąg dalszy, artykułu zamieszczonego wyżej, ponieważ traktuje o wpływaniu na obcy program z poziomu własnego programu. Tym razem zajmiemy się zmianą rodzica, ale nie dla obiektów w naszym własnym programie, lecz dla obcych programów uruchomionych w systemie. Pisząc o zmianie rodzica mam na myśli umieszczenia okna obcego programu w naszym własnym programie. W przykładzie umieszczę okno programu saper (popularna gra obecna we wszystkich wersjach Windows) w swoim własnym programie, najlepiej ilustruje to załączony obrazek:
|
|
![]() |
Tak włączony
program nie będzie się minimalizował do paska zadań, lecz do naszego programu.
Do zmiany rodzica okna programu służy funkcja SetParent, w BCB można
znaleźć kilka tych funkcji występujących pod tą samą nazwą, lecz
wykorzystujących przeciążenie funkcji, są to:
Nas interesuje ta ostatnia, ponieważ dwie poprzednie działają tylko w obrębie naszego programu. Specyfikacja tej funkcji jest następująca:
HWND
SetParent(
HWND hWndChild, // uchwyt do okna, któremu
zmieniamy rodzica
HWND hWndNewParent // uchwyt do okna, który ma się stać nowym
rodzicem
);
By możliwym stała
się zmiana rodzica należy znać numer uchwytu dla okna programu można to
sprawdzić używając programu
WinID o
którym pisałem w artykule wyżej, jednak jak się szybko przekonamy po każdym
uruchomieniu ten numer się zmienia, ale sami możemy utworzyć sobie uchwyt do
programu jeśli znamy nazwę jego klasy, lub nazwę okna. Nazwę klasy można
sprawdzić za pomocą programu WinID, a nazwa okna to co się wyświetla na belce
tytułowej programu. W przykładzie dla odmiany posłużę się nazwą okna programu, a
nie nazwą klasy chociaż w przypadku programu Saper nazwa jego klasy i nazwa okna
są takie same. Do pobrania uchwytu do programu posłuży nam funkcja FindWindow,
a oto prosty przykład na umieszczenie programu Saper w naszym własnym programie
na obiekcie Panel1:
| // Plik źródłowy np. Unit1.cpp //-------------------------------- void __fastcall TForm1::Button1Click(TObject *Sender) { HWND sap = FindWindow(0, "Saper"); if(sap > 0) { ::SetParent(sap, Panel1->Handle); MoveWindow(sap, 2, 2, 256, 319, true); } } //-------------------------------- |
W
przykładzie pojawiła się jeszcze jedna funkcja o której nie wspominałem
MoveWindow, określa ona położenie i rozmiar "dziecka" (Child) - w
przykładzie programu Saper na powierzchni "rodzica" (Parent) - w przykładzie na
obiekcie Panel1. Funkcja pobiera uchwyt do okna, któremu ma zmienić położenie i
rozmiar, kolejne cztery pobierane argumenty to odpowiednio położenie w poziomie,
położenie w pionie, szerokość i wysokość, ostatni argument określa, czy ma
nastąpić przerysowanie rodzica, czy nie, czyli wywoływany jest komunikat
WM_PAINT. W przykładzie jawnie określiłem rozmiar okna programu Saper podając
szerokość 256 i wysokość 319 ponieważ znam wymiary tego programu, co jednak
jeżeli chcemy, żeby po zmianie rodzica okno programu zachowało swoje oryginalne
wymiary, otóż trzeba by je odczytać, może posłużyć do tego funkcja GetWindowRect
pobierająca położenie okna od lewej i prawej strony, od góry i od dołu, funkcja
nie pobiera wymiarów okna, czyli szerokości i wysokości, można to jednak łatwo
obliczyć odejmując od prawego marginesu, margines lewy i od dolnego
marginesu, margines górny.
Przedstawię teraz bardziej złożony przykład, który zmieni
rodzica dla programu Saper, ale nie zmieni jego wymiarów, dodatkowo program
zostanie wyśrodkowany w poziomie na obiekcie Panel1. Żeby możliwym była zmiena
rodzica dla okna programu, ten program musi być uruchomiony, dlatego dodam do
przykładu jeszcze funkcje uruchamiające program.
Po zamknięciu naszego programu, okno programu Saper powinno zostać ponownie
przywrócone na pulpit, służy do tego ta sama funkcja SetParent, tylko zmienią
się parametry. Przywrócenie okna programu na pulpit może odbywać się przy
zamknięciu naszego programu, a więc w zdarzeniu OnCloseQuery. Przywracając oknu
programu jako rodzica ponownie pulpit, trzeba do funkcji SetParent przekazać ten
sam uchwyt, który posłużył nam do zmiany rodzica, dlatego obiekt HWND zostanie
tym razem zadeklarowany jako globalny w pliku nagłówkowym w sekcji private lub
public. Jeżeli chcemy po zamknięciu naszego programu i zwolnieniu okna obcego
programu, zamknąć również ten program, należy wysłać do niego komunikat
WM_DESTROY za pomocą funkcji SendMessage:
| // Plik nagłówkowy np. Unit1.h //-------------------------------- private: HWND saper;
|
| // Plik źródłowy np. Unit1.cpp //-------------------------------- void __fastcall TForm1::FormShow(TObject *Sender) { char sysdir[_MAX_PATH]; GetSystemDirectory(sysdir, _MAX_PATH); //pobieranie ścieżki dostępu do katalogu systemowego System32 WinExec(((AnsiString)sysdir + "\\winmine.exe").c_str(), SW_NORMAL); //uruchamianie programu z katalogu systemowego saper = FindWindow(0, "Saper"); if(saper > 0) { ::SetParent(saper, Panel1->Handle); tagRECT tr; GetWindowRect(saper, &tr); int x = tr.right - tr.left; int y = tr.bottom - tr.top; int c = (Panel1->Width - 4 - x)/2; MoveWindow(saper, c, 17, x, y, false); } } //-------------------------------- void __fastcall TForm1::FormCloseQuery(TObject *Sender, bool &CanClose) { ::SetParent(saper, HWND_DESKTOP); //przywrócenie okna na pulpit SendMessage(saper, WM_DESTROY, 0, 0); //zamknięcie programu Saper } //-------------------------------- |
Na zakończenie
tego artykułu coś na odwrót, czyli wrzucimy obiekt Panel1 na pulpit:
| // Plik źródłowy np. Unit1.cpp //-------------------------------- void __fastcall TForm1::Button1Click(TObject *Sender) { ::SetParent(Panel1->Handle, HWND_DESKTOP); } //-------------------------------- |
Jeszcze jeden
przykład na umieszczenie naszego programu w obcym programie, w przykładzie
będzie to przeglądarka Internet Explorer, w jednym zdarzeniu program zostanie
wrzucony do IE, a w drugim przywrócony ponownie na pulpit:
| // Plik źródłowy np. Unit1.cpp //-------------------------------- void __fastcall TForm1::Button1Click(TObject *Sender) { char sysdir[_MAX_PATH]; GetSystemDirectory(sysdir, _MAX_PATH); String pf = ExtractFileDrive((AnsiString)sysdir) + "\\Program Files\\Internet Explorer\\Iexplore.exe"; WinExec(pf.c_str(), SW_NORMAL); Sleep(200); ::SetParent(this->Handle, FindWindow("IEFrame", 0)); MoveWindow(this->Handle, 5, 80, Width, Height, true); } //-------------------------------- void __fastcall TForm1::Button2Click(TObject *Sender) { ::SetParent(this->Handle, HWND_DESKTOP); } //-------------------------------- |
W ten sam sposób
można przerzucać obce sobie programy za pośrednictwem własnego, np. można
wrzucić program Saper do programu Kalkulator:
| // Plik źródłowy np. Unit1.cpp //-------------------------------- void __fastcall TForm1::Button1Click(TObject *Sender) { HWND saper = FindWindow("Saper", 0); HWND calc = FindWindow("SciCalc", 0); ::SetParent(saper, calc); MoveWindow(saper, 0, 0, 200, 200, true); } //-------------------------------- |
Opracował: Cyfrowy Baron