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