Rozdział 12 Schowek Schowek Microsoft Windows pozwala przenosić dane między programami. Jest to stosunkowo prosty mechanizm, nie wymagający dużego wysiłku przy two- rzeniu programów, które pobierają dane ze Schowka i umieszczają je w nim. Z Windows 98 i Windows NT dostarczany jest program podgląd Schowka, poka- zujący aktualną zawartość Schowka. Wiele programów, działających na dokumentach lub innych danych, zawiera menu Edycja (Edit) z opcjami Wytnij (Cut), Kopiuj (Copy) i Wklej (Paste). Gdy użytkownik wybiera Wytruj lub Kopiuj, program przenosi swoje dane do Schowka. Dane mają określony format, zwykle jest to tekst, bitmapa (prostokątna tablica bitów odpowiadających pikselom wyświetlanego obszaru) lub metaplik (binar- ny zbiór poleceń rysowania). Gdy użytkownik wybierze w menu opcję Wklej, program sprawdza, czy w Schowku znajdują się jakieś dane, których może użyć, i jeśli tak jest, zabiera je do siebie. Programy nie powinny przenosić danych do lub ze Schowka bez wyraź,nego po- lecenia użytkownika. Na przykład użytkownik, który wykona w programie ope- rację Wytnij lub Kopiuj ([Ctrl+X] lub [Ctrl+C]) powinien mieć pewność, że dane pozostaną w Schowku aż do momentu, gdy uruchomi następną operację Wytnij lub Kopiuj. Jak zapewne pamiętasz, menu Edit zostało zaimplementowane w wersjach pro- gramu POPPAD z rozdziałów 10 i 11. Użyto tam jednak zwykłego wysyłania komunikatów do kontrolki edycji. W większości przypadków nie jest to wystar- czające - zamiast tego musisz sam wywołać funkcje przemieszczania danych Schowka. W tym rozdziale skoncentrujemy się na przenoseniu tekstu do i ze Schowka. W kolejnych rozdziałach zobaczysz, jak używa się go z bitmapami (rozdziały 14, 15 i 16) i metaplikami (rozdział 18). Proste zastosowanie Schowka Zaczniemy od kodu umożliwiającego przeniesienie danych do Schowka (Cut i Co- py) i uzyskanie dostępu do danych umieszczonych w nim wcześniej (Paste). Standardowe formaty danych Schowka Windows obsługuje różne predefiniowane formaty danych Schowka. Ich identy- fikatory rozpoczynają się przedrostkiem CD i zdefiniowane są w pliku WINU- SEft.H. 514 Część I: Podstawy Poniżej wymienione są trzy typy danych tekstowych, które można przechowy- wać w Schowku, oraz dodatkowy typ ustawień lokalnych. ł CF TEXT Ciąg znaków z zestawu ANSI zakończony znakiem NULL i za- wierający na końcu każdego wiersza znak powrotu karetki i nowego wiersza. Dane, które mają być przeniesione do Schowka, zapisane są w bloku pamięci - przenoszony jest uchwyt do tego bloku. (Wkrótce omówimy ten mechanizm). Blok pamięci staje się własnością Schowka - program, który utworzył ten blok, nie powinien go więcej używać. ł CF OEMTEXT Blok pamięci zawierający dane tekstowe (podobny do CF TEXT), ale używający zestawu znaków OEM. Programy Windows zwy- kle nie muszą używać tego typu. Staje się on istotny przy wykorzystaniu Schowka z programami MS-DOS uruchomionymi w oknie. ł CF-UNICODETEXT Blok pamięci zawierający tekst Unicode. Podobnie jak w CF TEXT każdy wiersz zakończony jest znakiem powrotu karetki i nowego wiersza, a znak NULL (dwa bajty zerowe) wskazuje koniec danych. CF UNI- CODETEXT jest obsługiwany tylko w Windows NT. ł CF LOCALE Uchwyt do identyfikatora ustawień lokalnych związanych z tek- stem Schowka. Dostępne są dwa dodatkowe formaty Schowka, koncepcyjnie zgodne z forma- tem CF TEXT (tzn. opierają się na tekście), ale ich dane nie są zakończone zna- kiem NULL - formaty te definiują własny koniec danych. Dzisiaj są one rzadko używane: ł CF SYLK Blok pamięci zawierający dane w formacie Symbolic Link Microso- ftu. Format ten jest używany do wymiany danych między programami Excel, Pr Chart i Multiplan Microsoftu. Jest to format ASCII, w którym każdy wiersz zakończony jest znakiem powrotu karetki i nowego wiersza. ł CF DIF Blok pamięci zawierający dane w formacie Data Interchange Format (DIF). Jest to format zalecany przez Software Arts do używania przy przeno- szeniu danych do arkusza kalkulacyjnego VisiCalc, również oparty na ASCII z wierszami zakończonymi znakiem powrotu karetki i nowego wiersza. Następujące formaty Schowka używane są z bitmapami, które są tablicami bi- tów odpowiadających pikselom na urządzeniu wyjściowym. Bitrnapy i związa- ne z nimi formaty Schowka omówione zostaną dokładniej w rozdziałach 14 i 15. ł CF BITMAP Bitmapa zależna od urządzenia. Mapa ta jest przenoszona do Schowka za pomocą jej uchwytu. Program, po przekazaniu uchwytu bitmapy do Schowka, nie powinien dalej go używać. , hGl CF DIB Blok pamięci definiujący bitmapę niezależną od urządzenia, tak jak opisano to w rozdziale 15. Blok pamięci zaczyna się od struktury informacyj- nej bitmapy, po której następuje opcjonalna tabela kolorów i bity. ł CF PALETTE Uchwyt palety kolorów. Typ używany głównie w połączeniu z CD DIB do definiowania palety kolorów używanej przez bitmapę niezależną od urządzenia. Rozdział 12: Schowek 515 Dane bitmapy można także przechowywać w formacie standardu przemysłowe- go TIFF: ł CF TIFF Blok pamięci zawierający dane typu Tag Image File Format (TIFF). Jest to format zaprojektowany przez firmę Microsoft, Aldus Corporation i Hew- lett-Packard oraz kilku innych wytwórców sprzętu komputerowego. Format ten opisany jest na stronie internetowej Hewletta-Packarda. Dostępne są także dwa formaty metaplików, które omówię dokładniej w rozdziale 18. Metaplik jest zbiorem poleceń rysowania przechowanych w formacie binar- nym: ł CF METAFILEPICT - obraz typu metaplik oparty na starym formacie obsłu- gi metaplików w Windows ł CF ENHMETAFILE - uchwyt rozszerzonego metapliku obshzgiwanego w 32- bitowych wersjach Windows. Istnieje także kilka innych formatów Schowka przeznaczonych do różnych za- stosowań: ł CF PENDATA - używany w połączeniu z programami obsługującymi pióro świetlne w Windows ł CF WAVE - plik dźwiękowy (wave) ł CF_RIFF - dane multimedialne w formacie Resource Interchange File Format ł CF HDROP - lista plików używana w połączeniu z programami obshzgują- cymi funkcje przeciągnij i upuść (ang. drag and drop). Przydzielanie pamięci Gdy program przenosi coś do Schowka, musi zarezerwować blok pamięci i prze- kazać go Schowkowi. Gdy potrzebowaliśmy przydzielić pamięć we wcześniej- szych programach w tej książce, używaliśmy po prostu funkcji malloc, która do- starczana jest w standardowej bibliotece C. Ponieważ jednak bloki pamięci prze- chowywane w Schowku są wspólnie użytkowane przez różne aplikacje urucho- mione w Windows, funkcja malloc nie jest odpowiednia do tego zadania. Zamiast tego musimy skorzystać z funkcji przydzielania pamięci zaprojektowa- , nych w czasach, gdy system Windows używał 16-bitowej architektury pamięci w trybie rzeczywistym. Funkcje te są wciąż dostępne i można ich używać, ale rzad- ko jest to potrzebne. Chcąc przydzielić blok pamięci za pomocą Windows API, możesz napisać: hGlobal = GlobalAlloc (uiFlags, dwSize> : Funkcja pobiera dwa parametry: opcjonalny ciąg flag i rozmiar w bajtach przy- dzielanego bloku pamięci. Funkcja zwraca uchwyt typu HGLOBAL, nazywany uchwytem globalnego bloku pamięci lub uchwytem globalnym. Jeśli zwrócona zostanie wartość NLTLL, oznacza to, że nie ma wystarczająco dużo wolnej pamięci, aby przydzielić blok. Mimo że każdy z parametrów funkcji GlobalAlloc jest zdefiniowany nieco inaczej, obydwa są 32-bitowymi liczbami typu unsigned integer. Jeśli ustawisz pierwszy parametr na zero, użyjesz w ten sposób flagi GMEM FIXED. W takim wypadku 516 Część I: Podstawy uchwyt globalny zwrócony przez GlobalAlloc będzie w rzeczywistości wskaźni- kiem przydzielonego bloku pamięci. Możesz użyć także flagi GMEM ZERoINIT, jeżeli chcesz, aby każdy bajt w blo- ku pamięci, był początkowo ustawiony na zero. Flaga GPTR zgodnie z definicją w plikach nagłówkowych Windows jest połączeniem flag GMEM FIXED i GMEM ZEROINIT: - Ildefine GPTR (GMEMFIXED GMEM-ZEROINIT) Dostępna jest także funkcja realokująca: hGlobal = GlobalReAlloc (hGlobal, dwSize, uiFlags) ; Możesz wykorzystać flagę GMEM ZEROINIT, aby wyzerować nowe bajty przy powiększaniu bloku pamięci. Poniższa funkcja zwraca rozmiar bloku pamięci: hGl dwSize = GlobalSize (hGlobal) : Natomiast ta funkcja zwalnia blok pamięci: GlobalFree (hGlobal) ; W 16-bitowych wersjach Windows odradzano używania flagi GMEM FIXED, po- nieważ system operacyjny nie mógł przenieść bloku w pamięci fizycznej. W wer- sjach 32-bitowych nie stanowi to problemu, ponieważ flaga GMEM_FIXED zwra- ca adres wirtualny, dzięki czemu system operacyjny może przenieść blok w pamięci fizycznej zmieniając tabelę stron. Programistom w 16-bitowych wersjach Windows zalecano zamiast tego używanie w funkcji GlobalAlloc flagi GMEM MOVEABLE. (Zauważ, że większość słowników zaleca pisownię "movable" zamiast "moveable" - ja także ją preferuję w niektórych sytuacjach). W plikach nagłówkowych zdefi- niowany jest także identyfikator zerujący przenośną pamięć: Ildefine GHND (GMEM MOVEABLE GMEM ZEROINIT) Flaga GMEM MOVEABLE pozwala Windows przenieść blok w pamięci wirtu- alnej. Nie musi to oznaczać, że blok ten zostanie przemieszczony także w pamię- ci fizycznej, ale może zmienić się adres, którego aplikacja używa do zapisu i od- czytu z tego bloku. Mimo że flaga GMEM MOVEABLE była ważna w 16-bitowej wersji Windows, teraz jest znacznie mniej użyteczna. Jeśli jednak aplikacja często przydziela, re- alokuje i zwalnia bloki pamięci o różnych rozmiarach, jej wirtualna przestrzeń hGl c adresowa może stać się pofragmentowana. Z tego powodu może zabraknąć wol- nych adresów pamięci wirtualnej. Jeżeli napotkasz taki problem, możesz użyć pamięci przenośnej w pokazany poniżej sposób. pGlc Najpierw zdefiniuj wskaźnik (na przykład na typ int) i zmienną typu GLOBAL- HANDLE: int * p ; for GLOBALHANDLE hGlobal ; Następnie przydziel pamięć, na przykład: hGlobal = GlobalAlloc (GHND, 1024) ; Glob Podobnie jak w przypadku każdego innego uchwytu w Windows, nie zastana- wiaj się, co oznacza ta liczba. Po prostu ją zapisz. Gdy zechcesz uzyskać dostęp do tego bloku pamięci, wpisz: Rozdział 12: Schowek 517 p = (int *) GlobalLock (hGlobal) : Powyższe wywołanie żamienia uchwyt na wskaźnik. Kiedy blok pamięci jest zablokowany, Windows ustala adres w pamięci wirtualnej. Nie może być on wtedy przemieszczany. Gdy dostęp do bloku nie będzie już potrzebny, wywołaj: GlobalUnlock (hGlobal> ; Instrukcja ta pozwala systemowi Windows swobodnie przemieszczać blok w pa- mięci wirtualnej. Aby proces ten przeprowadzić w zupełności poprawnie (i zy- skać uznanie programistów pierwszych wersji Windows), powinieneś zabloko- wać i odblokować blok pamięci w ciągu trwania jednego komunikatu. Kiedy chcesz zwolnić pamięć, zamiast ze wskaźnikiem wywołaj GlobalFree z uchwytem. Jeśli nie masz aktualnie do niego dostępu, użyj funkcji: hGlobal = GlobalHandle (p) : Możesz wiele razy zablokować blok pamięci przed jego odblokowaniem. Windows zarządza licznikiem zablokowań i aby blok mógł być przemieszczony, każda zało- żona blokada musi zostać wcześniej odblokowana. Gdy system ten przemieszcza blok w pamięci wirtualnej, nie musi kopiować bajtów z jednego miejsca w drugie - wystarcza do tego manipulacja na tabelach stron pamięci. W 32-bitowych wersjach Windows jedynym powodem przydzielania pamięci przenośnej do użytku przez program jest chęć uniknięcia fragmentacji pamięci wirtualnej. Pamięć przenośną należy również stosować przy operacjach z użyciem Schowka. Przydzielając pamięć dla Schowka, powinieneś raczej używać funkcji GlobalAlloc z flagami GMEM MOVEABLE i GMEM SHARE. Flaga GMEM SHARE powo- duje, że blok pamięci dostępny jest także dla innych aplikacji Windows. Przenoszenie tekstu do Schowka Załóżmy, że chcesz przenieść ciąg znaków ANSI do Schowka. Masz wskaźnik (nazwany pString) do tego ciągu i chcesz przenieść iLength znaków, które mogą, ale nie muszą być zakończone znakiem NULL. Najpierw musisz użyć funkcji GlobalAlloc, aby przydzielić blok pamięci o odpo- wiednim rozmiarze, w którym zapisany zostanie ciąg znaków. Zostaw przestrzeń na końcowy znak NULL: hGlobal = GlobalAlloc (GHND GMEM SHARE, iLength + 1) : Wartość hGlobal wyniesie NULL, jeżeli nie zostanie przydzielona pamięć dla blo- ku. jeśli operacja powiedzie się, zablokuj blok, aby uzyskać do niego wskaźnik: pGlobal = GlobalLock (hGlobal) ; Skopiuj ciąg znaków do globalnego bloku pamięci: for (i = 0 ; i < wLength ; i++) *pGlobal++ = *pString++ ; Nie musimy dodawać końcowego znaku NULL, ponieważ flaga GHND funkcji GlobalAlloc zeruje cały blok podczas przydzielania pamięci. Odblokuj blok: GlobalUnlock (hGlobal) ; Teraz masz uchwyt bloku globalnego, który odwołuje się do bloku zawierające- go tekst zakończony znakiem NULL. Aby przenieść ten tekst do Schowka, otwórz Schowek i wyczyść jego zawartość: 518 Czść I: Podstawy Roz OpenClipboard (hwnd) EmptyClipboard () ; Przekaż Schowkowi uchwyt pamięci używając identyfikatora CF TEXT i zamknij go: SetClipboardData (CF TEXT, hGlobal) ; CloseClipboard () ; T p e To wszystko. Poniżej wymienione są niektóre zasady dotyczące tego procesu: ł Wywołuj funkcje OpenClipboard i CloseClipboard podczas przetwarzania jed- nego komunikatu. Nie pozostawiaj Schowka otwartego dłużej, niż jest to po- pGl trzebne. ł Nie przekazuj Schowkowi zablokowanego uchwytu pamięci. str ł Po wywołaniu funkcji SetClipboardData nie używaj więcej przekazanego bloku pamięci. Nie należy on już do programu i powinieneś traktować jego uchwyt whi jako nieważny. jeśli wciąż potrzebny jest ci dostęp do tych danych, utwórz ich kopię lub odczytaj je ze Schowka (w sposób opisany w następnym podrozdzia- G 1 0 le). Do bloku możesz odwoływać się między wywołaniami SetClipboardData C1 0 i CloseClipboard, ale nie używaj uchwytu globalnego, który przekazałeś do funk- cji SetClipboardData. Funkcja ta również zwraca uchwyt globalny, którego mo- żesz użyć. Aby uzyskać dostęp do pamięci, zablokuj ten uchwyt. Przed wy- wołaniem CIoseClipboard odblokuj go. t Pobieranie tekstu ze Schowka Pobranie tekstu ze Schowka jest tylko trochę bardziej skomplikowane niż przenie- sienie do niego tekstu. Najpierw musisz sprawdzić, czy Schowek w ogóle zawiera dane w formacie CF TEXT. Najprostszym sposobem jest użycie wywołania: bAvailable = IsClipboardFormatAvailable (CF TEXT) ; Funkcja ta zwraca wartość TRUE, jeśli Schowek zawiera dane CF TEXT. Użyli- śmy jej w programie POPPAD2 z rozdziału 10, aby sprawdzić, czy opcja Paste w menu Edit powinna być udostępniona czy szara. IsClipboardAvailable jest jedną z kilku funkcji, których możesz użyć bez wcześniejszego otwierania Schowka. Je- żeli jednak później otworzysz Schowek, aby pobrać tekst, powinieneś ponownie sprawdzić (używając tej samej funkcji lub jednej z innych metod), czy dane typu CF TEXT wciąż znajdują się w Schowku. Chcąc pobrać tekst, najpierw otwórz Schowek: OpenClipboard (hwnd) ; Uzyskaj uchwyt globalnego bloku pamięci, odwohxjąc się do tekstu: hGlobal = GetClipboardData (CF TEXT) ; Uchwyt ten będzie mieć wartość NULL, jeśli Schowek nie zawiera danych w for- macie CF TEXT. Jest to jedna z metod sprawdzania, czy zawiera on tekst. Jeżeli funkcja GetClipboardData zwróci NULL, zamknij Schowek bez wykonywania do- datkowych czynności. Uchwyt, który uzyskujesz od funkcji GetClipboardData, nie należy do programu, tylko do Schowka. Uchwyt ten jest ważny tylko między wywołaniami GetClipboardData Rozdział 12: Schowek 519 i CloseClipboard. Nie możesz zwolnić go ani zmienić danych, do których się od- wołuje. Jeśli dostęp do danych będzie ci potrzebny później, powinieneś wykonać kopię bloku pamięci. Poniżej pokazana jest jedna z metod kopiowania danych do programu. Po prostu przydziel wskaźnik do bloku o takim samym rozmiarze jak blok danych Schowka: pText = (char *) malloc (GlobalSize (hGlobal)) : Jak pamiętasz, hGlobal był uchwytem globalnym uzyskanym od funkcji GetClipbo- ardData. Teraz zablokuj uchwyt, aby uzyskać wskaźnik do bloku pamięci Schow- ka: pGlobal = GlobalLock (hGlobal) ; Następnie po prostu skopiuj dane: strcpy (pText. pGlobal) : lub użyj prostego kodu: while (*pTEXT++ = *pGlobal++) : Odblokuj blok przed zamknięciem Schowka: GlobalUnlock (hGlobal) : CloseClipboard () : Teraz masz wskaźnik nazwany pText, który odwołuje się do kopii tekstu należą- cej do programu. Otwieranie i zamykanie Schowka Schowek może być otwarty jednorazowo tylko dla jednego programu. Celem wywołania OpenClipboard jest zabezpieczenie jego zawartości przed zmianami, gdy program go używa. Funkcja OpenClipboard zwraca wartość boolowską wska- zującą, czy otwarcie Schowka powiodło się. Nie zostanie otwarty, jeśli inna apli- kacja nie zamknęła go poprawnie. Gdyby wszystkie programy otwierały i zamy- kały Schowek tak szybko, jak jest to możliwe, w odpowiedzi na żądanie użyt- kownika, prawdopodobnie nigdy nie doświadczyłbyś trudności związanych z jego otwieraniem. Jednak w świecie nieuprzejmych, wielozadaniowych programów, takie proble- my mogą się pojawić. Nawet jeśli program nie stracił fokusu wejściowego mię- dzy umieszczeniem czegoś w Schowku a użyciem opcji Paste, nie należy zakła- dać, że dane umieszczone w nim nadal tam są. W tym czasie dostęp do Schowka mógł uzyskać proces uruchomiony w tle. Zwróć także uwagę na bardziej subtelny problem dotyczący okien komunikatu: jeżeli nie możesz przydzielić wystarczająco dużo pamięci, aby skopiować coś do Schowka, możesz wyświetlić okno komunikatu. Jeśli jednak nie jest ono modal- ne w systemie, użytkownik może przełączyć się do innej aplikacji podczas jego wyświetlania. Powinieneś albo wyświetlać modalne okno komunikatu, albo za- mknąć Schowek przed wyświetleniem komunikatu. Trudności możesz napotkać także wtedy, gdy pozostawisz otwarty Schowek i wy- świetlisz okno dialogowe. Pola edycji w oknach dialogowych wykorzystują Scho- wek do wycinania i wstawiania tekstu. 520 Część I, Podstawy ' Ro Schowek i Unicode Dotąd omawiałem używanie Schowka wyłącznie z tekstem ANSI (jeden bajt na znak). Format ten jest stosowany po użyciu identyfikatora CF TEXT. Być może, zastanawiasz się jednak, jak korzystać z identyfikatorów CF OEMTEXT i CF UNI- CODETEXT. Mam dobre wiadomości: wystarczy wywołać funkcje SetClipboardData i GetClip- boardData z wybranym formatem tekstu, a Windows automatycznie wykona w Schowku potrzebne konwersje. Jeśli na przykład w Windows NT program uży- wa funkcji SetClipboardData z typem danych Schowka CF TEXT, może także wy- wołać funkcję GetClipboardData z identyfikatorem CF OEMTEXT. Analogicznie można na przykład przekonwertować dane typu CF OEMTEXT na CF TEXT. W Windows NT konwersja może być wykonywana pomiędzy formatami CF UM- CODETEXT, CF TEXT i CF OEMTEXT. Program powinien wywoływać funkcję SetClipboardData, używając formatu, który jest dla niego najbardziej wygodny. Analogicznie funkcja GetClipboardData powinna być wywoływana z formatem wymaganym przez program. Jak zapewne wiesz, programy w tym podręczniku są tak napisane, że mogą być kompilowane z lub bez identyfikatora UNICODE. Jeśli twoje programy używają identyfikatora UNICODE, a CF TEXT jest niezde- finiowany, powinieneś zaimplementować kod, który będzie wywoływał funkcje SetClipboardData i GetClipboardData z identyfikatorem CF UNICODETEXT. Program CLIPTEXT, pokazany na rysunku 12-1, przedstawia jeden ze sposobów, w jaki można to zrobić. CLIPTEXT.C /* CLIPTEXT.C - Schowek i tekst (c) Charles Petzold, 1998 */ ilinclude #include "resource.h" LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ; ili fdef UNICODE lldefine CF_TCHAR CF_UNICODETEXT TCHAR sz0efaultText[] = TEXT ("Default Text - Unicode Version") ; TCHAR szCaption[] = TEXT ("Clipboard Text Transfers - Unicode Version") ; ilel se define CF_TCHAR CF_TEXT TCHAR szDefaultText[7 = TEXT ("Default Text - ANSI Version") ; TCHAR szCaption[] = TEXT ("Clipboard Text Transfers - ANSI Version") ; endi f int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, Rozdział 12: Schowek 521 PSTR szCmdLine, int iCmdShow) ( static TCHAR szAppNameC] = TEXT ("ClipText") ; HACCEL hAccel ; HWND hwnd : I MSG ms9 ' WNDCLASS wndclass ; wndclass.style = CS_HREDRAW CS VREDRAW ; wndclass.lpfnWndProc = WndProc ; wndclass.cbClsExtra = 0 ; wndclass.cbWndExtra = 0 ; wndclass.hInstance = hInstance ; wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ; wndclass.hCursor = LoadCursor (NULL, IDC ARROW) ; wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE BRUSH) ; wndclass.lpszMenuName = szAppName ; wndclass.lpszClassName = szAppName ; k; F.. if (!RegisterClass (&wndclass)) ( Messa9eBox (NULL, TEXT ("This program requires Windows NT!"), szAppName, MBICONERROR) ; return 0 ; } hwnd = CreateWindow (szAppName, szCaption, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW USEDEFAULT, NULL, NULL, hInstance, NULL) ; ShowWindow (hwnd, iCmdShow) ; UpdateWindow (hwnd) ; hAccel = LoadAccelerators (hInstance, szAppName) ; while (GetMessage (&msg, NULL, 0, 0)) ( if (!TranslateAccelerator (hwnd, hAccel, &msg)) ( TranslateMessage (&msg) ; DispatchMessage (&msg) ; } } return msg.wParam ; } LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) I I k. static PTSTR pText ; BOOL bEnable ; HGLOBAL hGlobal ; HDC hdc ; PTSTR pGlobal ; PAINTSTRUCT ps ; RECT rect ; ;, I switch (message) .:I i;, ; ":: Część I: Podstawy (ciąg dalszy ze strony 521) ( case WM_CREATE: SendMessage (hwnd, WM COMMAND, IDM EDIT RESET, 0) ; return 0 ; case WM_INITMENUPOPUP: EnableMenuItem ((HMENU) wParam, IDM EDIT_PASTE, IsClipboardFormatAvailable (CF TCHAR) ? MFENABLED : MF GRAYED) ; bEnable = pText ? MFENABLED : MF GRAYED ; EnableMenuItem ((HMENU) wParam, IDM_EDIT CUT, bEnable) ; EnableMenuItem ((HMENU) wParam, IDM_EDITCOPY, bEnable) ; EnableMenuItem ((HMENU) wParam, IDM EDIT CLEAR, bEnable) ; break ; case WM COMMAND: switch (LOWORD (wParam)) ( case IDM_EDIT_PASTE: OpenClipboard (hwnd) ; if (hGlobal = GetClipboard0ata (CF TCHAR)) f pGlobal = GlobalLock (hGlobal) ; if (pText) ( free (pText) ; pText = NULL ; } pText = malloc (GlobalSize (hGlobal)) ; lstrcpy (pText, pGlobal) ; InvalidateRect (hwnd, NULL, TRUE) ; ) CloseClipboard () ; return 0 ; case IDM_EDIT_CUT: case IDM_EDIT COPY: if (!pText) return 0 ; hGlobal = GlobalAlloc (GHND GMEM_SHARE, (lstrlen (pText) + 1) * sizeof (TCHAR)) ; pGlobal = GlobalLock (hGlobal) ; // lstrcpy (pGlobal, pText) ; GlobalUnlock (hGlobal) ; ir lli r OpenClipboard (hwnd) ; EmptyClipboard () ; // SetClipboardData (CF TCHAR, hGlobal) ; // CloseClipboard (> ; if (LOWORD (wParam) = IDMEDITCOPY) return 0 ; BEG // przypadek IDM EDIT CUT zostawiamy nieopracowany Rozdział 12: Schowek 523 case IDM_EDIT_CLEAR: if (pText) ( free (pText) : pText = NULL ; 1 InvalidateRect (hwnd, NULL, TRUE) ; I return 0 : case IDM_EDIT_RESET: if (pText) free (pText) : pText = NULL ; pText = malloc ((lstrlen (szDefaultText) + 1) * sizeof (TCHAR)) ; lstrcpy (pText, szDefaultText) ; InvalidateRect (hwnd, NULL, TRUE) : return 0 ; break ; case WM PAINT: hdc = BeginPaint (hwnd, &ps) : GetClientRect (hwnd, &rect) : if (pText != NULL) DrawText (hdc, pText, -1, &rect. DT EXPANDTABS DT WORDBREAK) : EndPaint (hwnd, &ps) ; return 0 ; case WM DESTROY: if (pText) . free (pText) : PostOuitMessage (0) : return 0 ; 1 return DefWindowProc (hwnd, message, wParam, lParam) : ) CLIPTEXT.RC (fragmenty) //Microsoft Developer Studio generated resource script. inctude "resource.h" include "afxres.h" ////l////////////////////////////////////////////////////////////////////// // Menu CLIPTEXT MENU DISCARDABLE BEGIN POPUP "&Edit" 524 Część I: Podstawy (ciąg dalszy ze strony 523) BEGIN MENUITEM "Cu&t\tCtrl+X", IDM_EDIT_CUT MENUITEM "&Copy\tCtrl+C", IDM EDIT_COPY MENUITEM "&Paste\tCtrl+V", IDM EDIT_PASTE MENUITEM "De&lete\tDel", IDMEDIT_CLEAR MENUITEM SEPARATOR MENUITEM "&Reset", IDM EDIT RESET END END /////////////////////////////////////////////////////////////////////////// // Accelerator CLIPTEXT ACCELERATORS DISCARDABLE BEGIN "C", IDM_EDIT_COPY, VIRTKEY, CONTROL, NOINVERT "V", IDMEDIT_PASTE, VIRTKEY, CONTROL, NOINVERT VK_DELETE, IDM_EDIT_CLEAR, VIRTKEY, NOINVERT "X", IDM EDIT CUT, VIRTKEY, CONTROL, NOINVERT END RESOURCE.H (fragmenty) // Microsoft Developer Studio generated include file. // Used by ClipText.rc tidefine IDM_EDIT_CUT 40001 4ldefine IDM_EDIT_COPY 40002 tldefine IDM_EDIT_PASTE 40003 ttdefine IDM_EDIT_CLEAR 40004 tldefine IDM EDIT RESET 40005 Rysunek 12-1. Program CLIPTEXT Celem jest tutaj uruchomienie wersji Unicode i ANSI programu w Windows NT i zaobserwowanie, jak Schowek wykonuje konwersje między tymi zestawami znaków. Zwróć uwagę na instrukcję #ifdef na początku pliku CLIPT'EXT.C. Jeśli zdefiniowany jest identyfikator UNICODE, wtedy CF TCHAR (nazwa ogólne- go, utworzonego przeze mnie formatu danych Schowka) równy jest CF_UNICO- DETEXT. W przeciwnym razie CF_TCHAR zgodny jest z CF TEXT. Wywołania funkcji IsClipboardFormatAvailable, GetClipboardData i SetClipboardData w dalszej części programu używają nazwy CF TCHAR do określenia typu danych. Po uruchomieniu programu (lub gdy wybierzesz opcję Reset z menu Edit) zmienna pText będzie zawierała wskaźnik do ciągu znaków Unicode "Default Text - Uni- code version" w wersji Unicode programu lub do ciągu "Default Text - ANSI version" w drugiej wersji. Możesz użyć poleceń Cut i Copy, aby przenieść ciąg tekstowy do Schowka, albo poleceń Cut i Delete, aby usunąć ciąg z programu. Polecenie Paste kopiuje dowolną zawartość tekstową Schowka do pText. Ciąg pText jest wyświetlany w obszarze roboczym programu podczas przetwarzania komu- nikatu WM PAINT. Rozdział 12: Schowek 525 Jeżeli najpierw wybierzesz polecenie Copy z wersji Unicode programu CLIPTEXT, a następnie polecenie Paste z drugiej wersji, tekst został nie przekonwertowany z Unicode na ANSI. Analogicznie, jeśli wybierzesz odwrotne polecenia, tekst zo- stanie przekonwertowany z ANSI na Unicode. Poza standardowymi zastosowaniami Schowka Wiemy już, że przeniesienie tekstu ze Schowka wymaga czterech wywołań po przygotowaniu danych. OpenClipboard (hwnd) ; EmptyClipboard () ; SetClipboardData (iFormat, hGlobal) ; CloseClipboard () ; Natomiast uzyskanie dostępu do tych danych wymaga trzech wywołań: OpenClipboard (hwnd) ; hGlobal = GetClipboardData (iFormat) ; [pozostaie wiersze programuJ CloseClipboard () ; Możesz wykonać kopię danych ze Schowka lub użyć ich w inny sposób między wywołaniami GetClipboardData i CloseClipboard. Takie rozwiązanie jest wystarcza- jące w większości przypadków, ale Schowek można wykorzystać także w spo- sób bardziej zaawansowany. Używanie kilku elementów danych Gdy otwierasz Schowek, aby umieścić w nim dane, musisz wywołać funkcję Emp- tyClipboard, która powoduje, że Windows zwalnia lub usuwa jego zawartość. Nie możesz niczego dodać do istniejącej zawartości Schowka. W takim rozumieniu prze- chowuje on tylko jeden element danych naraz. Pomiędzy wywołaniami EmptyClip- board i CIoseCliboard możesz jednak wywołać kilka razy funkcję SetClipboardData, używając za każdym razem różnego formatu danych. Jeśli chcesz na przykład przechować w Schowku krótki ciąg tekstowy, możesz zapisać go w metapliku lub bitmapie. W ten sposób udostępniasz ten ciąg nie tylko programom, które mogą odczytać tekst ze Schowka, ale także tym, które mogą odczytać metapliki i bitmapy. Oczywiście, programy te nie będą w stanie w prosty sposób rozpo- znać, czy metaplik lub bitmapa rzeczywiście zawiera ciąg znaków. Jeżeli chcesz utworzyć kilka uchwytów Schowka, możesz dla każdego z nich wywołać funk- cję SetClipboardData: OpenClipboard (hwnd) ; EmptyClipboard () ; SetClipboardData (CF_TEXT, hGlobalText) ; SetClipboardData (CF_BITMAP, hBitmap) ; SetClipboardData (CF METAFILEPICT, hGlobalMFP , CloseClipboard () ; Gdy w Schowku znajdą się te trzy formaty danych, funkcja IsClipboardFormatA- vailable zwróci wartość TRUE dla argumentów CF TEXT, CF BITMAP i CF ME- TAFILEPICT. Program może uzyskać dostęp do tych uchwytów przez wywoła- nie: 526 Część I: Podstawy hGlobalText = GetClipboardData (CF TEXT) ; lub hBitmap = GetClipboardData (CF BITMAP) ; lub hGlobalMFP = GetClipboardData (CF METAFILEPICT) ; Przy kolejnym wywołaniu EmptyClipboard Windows zwolni lub usunie wszyst- kie trzy uchwyty używane przez Schowek. Nie używaj tej techniki, aby umieścić w Schowku różne formaty tekstu, różne formaty bitmapy lub różne formaty metapliku. Korzystaj tylko z jednego forma- tu tekstu, jednego formatu bitmapy i jednego formatu metapliku. Jak wspomina- łem, Windows dokonuje konwersji formatu CF TEXT na CF OEMTEXT i CF UM- CODETEXT. Wykonywana jest także konwersja między formatami CF BITMAP i CF DIB oraz między CF METAFILEPICT i CF ENHMETAFILE. Program może sprawdzić, jakie formaty danych przechowywane są w Schowku, otwierając go i wywołując funkcję EnumClipboardFormats. Rozpocznij od ustawienia zmiennej iFormat na 0: IFormat = 0 ; OpenClipboard (hwnd) ; Następnie wykonaj kolejne wywołania EnumCIipboardFormats, zaczynając od war- tości 0. Funkcja ta zwróci dodatnią wartość iFormat dla każdego formatu prze- chowywanego aktualnie w Schowku. Gdy zwróci 0, będzie to oznaczało, że prze- tworzyłeś wszystkie formaty: while (iFormat = EnumClipboardFormats (iFormat)) ł. Clogiczny d1a każdej wartoci iFormat] } CloseClipboard () ; Liczbę różnych formatów dostępnych aktualnie w Schowku możesz uzyskać wywołując: iCount = CountClipboardFormats () : Opóźnione przenoszenie Gdy umieszczasz dane w Schowku, tworzysz w rzeczywistości ich kopię i prze- kazujesz mu uchwyt globalnego bloku pamięci, zawierającego tę kopię. Dla bar- dzo dużych elementów danych technika ta powoduje niepotrzebne zużycie pa- mięci. Jeśli użytkownik nie zamierza umieścić danych w innym programie, pa- mięć będzie zajęta, aż do momentu, gdy zostaną one zastąpione innymi. Problemu tego można uruknąć stosując technikę nazwaną opóźnionym przeno- szeniem, w której program przekazuje dane dopiero wtedy, gdy zażąda ich inna aplikacja. Zamiast przekazywać systemowi Windows uchwyt danych, możesz użyć wartości NULL w wywołaniu SetClipboardData: OpenClipboard (hwnd) ; EmptyClipboard () ; SetClipboard (iFormat, NULL) ; CloseClipboard () ; Rozdział 12: Schowek 527 Możesz wpisać kilka wywołań SetClipboardData używając różnych wartości iFor- mat. W niektórych możesz wykorzystać wartość NULL, a w pozostałych - praw- dziwe uchwyty. Dotychczasowa część jest prosta, ale w tym punkcie proces staje się bardziej skom- plikowany. Gdy inny program wywołuje funkcję GetClipboardData, Windows sprawdza, czy uchwyt danego formatu jest równy NULL. jeżeli tak nie jest, Win- dows wysyła komunikat do właściciela Schowka (twojego programu) z pytaniem o prawdziwy uchwyt danych. Program musi wtedy dostarczyć ten uchwyt. Właściciel Schowka to w rzeczywistości ostatnie okno, które umieściło dane w Schowku. Gdy program wywołuje funkcję OpenClipboard, Windows zachowu- je uchwyt wymagany przez tę funkcję. Uchwyt identyfikuje okno, które otwo- rzyło Schowek. Po odebraniu wywołania EmptyClipboard Windows nadaje temu oknu status nowego właściciela Schowka. Program używający opóźnionego przenoszenia musi przetworzyć w procedurze okna trzy komunikaty: WMRENDERFORMAT, WMRENDERALLFORMATS i WM-DESTROYCLIPBOARD. Windows wysyła procedurze okna komunikat WM-RENDERFORMAT, gdy inny program wywołuje funkcję GetClipboardData. Wartość wParam jest wymaganym formatem. Gdy przetwarzasz komunikat WM-RENDERFORMAT, nie otwieraj i nie usuwaj zawartości Schowka. Po pro- stu utwórz globalny blok pamięci dla formatu określonego przez wParam, prze- nieś do niego dane i wywołaj funkcję SetClipboardData z odpowiednim formatem i uchwytem globalnym. Musisz oczywiście zachować te informacje w programie, aby poprawnie skonstruować dane podczas przetwarzania komunikatu WM-RENDERFORMAT. Kiedy inny program wywołuje funkcję EmptyClipboard, Windows wysyła do programu komunikat WM DESTROYCLIPBOARD. Mówi on, że informacje potrzebne do skonstruowania danych Schowka nie są już po- trzebne. Nie jesteś już jego właścicielem. Jeśli program zakończy działanie, będąc właścicielem Schowka, a Schowek za- wiera uchwyty danych o wartości NULL, ustawione przez funkcję SetClipboard- Data, odbierzesz komunikat WM-RENDERALLFORMATS. Powinieneś otworzyć Schowek, usunąć jego zawartość, umieścić dane w globalnych blokach pamięci, dla każdego formatu wywołać funkcję SetClipboardData i zamknąć go. Komuni- kat WM RENDERALLFORMATS jest jednym z ostatnich, które odbiera proce- dura okna. Po nim następuje WM-DESTROYCLIPBOARD (ponieważ przetwo- rzyłeś wszystkie dane) i na koniec zwykły komunikat WM DESTROY. Jeżeli program może przekazywać do Schowka tylko jeden format danych (na przykład tekst), możesz połączyć przetwarzanie komunikatów WM-RENDERAL- LFORMATS i WM RENDERFORMAT. Kod będzie wtedy wyglądał podobnie do poniższego: case WMRENDERALLFORMATS : OpenClipboard (hwnd) ; EmptyClipboard () ; , // nie powiodło się case WM_RENDERFORMAT : fumieszczenie tekstu w globalnym bloku pamięci] SetClipboardData (CF TEXT, hGlobal) ; 528 Część I: Podstawy if (message == WM_RENDERALLFORMATS) CloseClipboard () ; return 0 ; Jeśli program używa kilku typów danych, komunikat WMRENDERFORMAT należy przetwarzać tylko dla formatu wymaganego przez wParam. Nie należy przetwarzać komunikatu WMDESTROYCLIPBOARD, chyba że przeszkadza on programowi uzyskać informacje potrzebne do skonstruowania danych. Własne formaty danych Do tej pory zajmowaliśmy się tylko standardowymi formatami danychSchowka zdefiniowanymi przez Windows. Możesz jednak użyć Schowka do przechowa- nia własnych formatów danych. Wiele procesorów tekstów używa tej techniki do przechowania tekstu z informacją o czcionce i formatowaniu. Na pierwszy rzut oka pomysł ten może wydawać się bezsensowny. Jeśli zada- niem Schowka jest przenoszenie danych między aplikacjami, dlaczego mieliby- śmy umieszczać w nim dane wykorzystywane tylko przez jeden program? Od- powiedź jest prosta: Schowek służy również do przenoszenia danych wewnątrz jednego programu (lub między kilkoma jego kopiami), który, oczywiście, właści- wie rozpoznaje swój własny format. Dostępne jest kilka metod wykorzystania własnych formatów danych. Najłatwiej- sza używa danych w jednym ze standardowych formatów Schowka (tzn. tekst, bitmapa lub metaplik), ale mających znaczenie tylko dla twojego programu. W takim przypadku w wywołaniach SetClipboardData i GetClipboardData używa się jednej z następujących wartości wFormat: CF DSPTEXT, CF DSPBITMAP, CF DSPMETAFILEPICT lub CF DSPENHMETAFILE. Litery DSP to skrót od ang. display (wyświetlanie). Formaty te pozwalają Schowkowi Windows wyświetlać dane jako tekst, bitmapę lub metaplik. Jeśli jednak inny program wywoła funkcję GetClipboardData przy użyciu zwykłe- go formatu CF_TEXT, CF BITMAP, CF DIB, CF METAFILEPICT lub CF_ENH- METAFILE, nie uzyska danych. Jeżeli użyjesz jednego z tych formatów, aby umieścić dane w Schowku, musisz zastosować taki sam format, aby te dane pobrać. Skąd jednak wiemy, czy dane pochodzą z kopii twojego programu czy z innego programu, używającego jedne- go z tych formatów? Jednym ze sposobów jest sprawdzenie właściciela Schowka przez wywołanie: hwndClipOwner = GetClipboardOwner () : Następnie możesz uzyskać nazwę klasy okna dla tego uchwytu okna: TCHAR szClassName [32] ; Cpozostaie wiersze programu7 GetClassName (hwndClipOwner, szClassName, 32) ; Jeśli nazwa klasy jest taka sama jak programu, wtedy dane zostały umieszczone w Schowku przez inną kopię twojego programu. Innym sposobem jest użycie własnych formatów za pomocą flagi CF OWNER- DISPLAY. Globalny uchwyt pamięci w funkcji SetClipboardData równy jest NULL: Rozdział 12: Schowek 529 SetClipboardData (CF OWNERDISPLAY, NULL) ; Metody tej używają niektóre procesory tekstu, aby wyświetlić sformatowany tekst w obszarze roboczym podglądu Schowka dostarczanego z Windows. Oczywiście podgląd Schowka nie wie, że wyświetla sformatowany tekst. Z używaniem flagi CF OWNERDISPLAY przez procesor tekstu związane jest wyświetlenie obszaru roboczego podglądu Schowka. Ponieważ globalny uchwyt pamięci równy jest NULL, program wywołujący funkcję SetClipboardData z formatem CF OWNERDISPLAY (właściciel Schowka) musi prze- tworzyć komunikaty opóźnionego przenoszenia wysyłane przez Windows do wła- ściciela Schowka oraz pięć innych, dodatkowych, które wysyła podgląd Schowka: ł WM SKCBFORMATNAME Podgląd Schowka wysyła ten komunikat do wła- ściciela Schowka, aby uzyskać nazwę formatu danych. Parametr lParam jest wskaźnikiem bufora, a wParam - maksymalną liczbą znaków dla tego bufora. Właściciel Schowka musi skopiować nazwę formatu Schowka do tego bufora. ł WM SIZECLIPBOARD Komunikat ten mówi właścicielowi Schowka, że zmie- nił się rozmiar obszaru roboczego podglądu Schowka. Parametr wParam jest uchwytem tego podglądu, a lParam - wskaźnikiem do struktury RECT, za- wierającą nowy rozmiar. Jeżeli struktura RECT zawiera same zera, podgląd Schowka jest zminimalizowany lub zniszczony. Mimo że można uruchomić tylko jedną kopię danego podglądu Schowka Windows, inne takie podglądy także mogą przesłać ten komunikat do właściciela Schowka. Obsługa kilku podglądów Schowka nie jest dla niego niemożliwa (zważywszy na to, że wPa- ram identyfikuje poszczególne podglądy), ale nie jest także łatwa. ł WM PAINTCLIPBOARD Komunikat ten mówi właścicielowi Schowka, aby od- świeżył obszar roboczy podglądu Schowka. Ponownie wParam jest uchwytem do okna tego podglądu. Parametr IParam jest uchwytem gtóbalnym do struktu- ry PAV'TSTRUCT. Właściciel Schowka może zablokować uchwyt i uzyskać go do kontekstu urządzenia podglądu Schowka za pomocą pola hdc tej struktury. ł WM HSCROLLCLIPBOARD i WMVSCROLLCLIPBOARD Komunikaty te in- formują właściciela Schowka, że użytkownik użył pasków przewijania pod- glądu Schowka. Parametr wParam jest uchwytem okna tego podglądu, mniej znaczące słowo lParam jest żądaniem przewijania, a bardziej znaczące - po- zycją miniatury, jeśli mniej znaczące słowo jest równe SB THUMBPOSITION. Przetwarzanie tych komunikatów może wydawać się niewiele warte. Ma jednak pewne zalety dla użytkownika: kopiując tekst z procesora tekstów do Schowka, będzie czuł się pewniej, jeśli zobaczy, że tekst jest sformatowany również w ob- szarze roboczym Schowka. Kolejnym sposobem korzystania z własnych formatów danych Schowka jest za- rejestrowanie własnej nazwy formatu. Nazwę formatu podajesz systemowi Win- dows, a on zwraca programowi numer, którego używa się jako parametru w funk- cjach SetCliboardData i GetCliboardData. Programy używające tej metody także kopiują dane w jednym ze standardowych formatów. Rozwiązanie to umożliwia podglądowi Schowka wyświetlanie danych w obszarze roboczym (z uniknięciem kłopotów występujących przy CF OWNERDISPLAY) i pozwala innym progra- mom kopiować dane ze Schowka. 530 Część I: Podstawy Załóżmy, że napisaliśmy program do grafiki wektorowej, który kopiuje dane do Schowka w formacie bitmapy, metapliku i we własnym zarejestrowanym forma- cie. Podgląd Schowka wyświetli metaplik i bitmapę. Inne programy, mogące od- czytać metapliki i bitmapy ze Schowka, także obsłużą te formaty. Jeżeli jednak nasz program potrzebuje odczytać dane ze Schowka, skopiuje je we własnym formacie, ponieważ zawiera on prawdopodobnie więcej informacji niż bitmapa czy metaplik. Program rejestruje nowy format Schowka przez wywołanie: iFormat = RegisterClipboardFormat (szFormatName) ; Wartość iFormat mieści się w zakresie od 0xC000 do OxFFFF. Podgląd Schowka (lub program odbierający wszystkie bieżące formaty w Schowku przez wywoła- nie EnumClipboardFormats) może uzyskać nazwę ASCII formatu przez wywoła- nie: GetClipboardFormatName (iFormat, psBuffer, iMaxCount) ; Windows kopiuje maksymalnie iMaxCount znaków do psBuffer. Programiści używający tej metody do kopiowania danych do Schowka mogą udokumentować nazwę formatu i rzeczywisty format danych. Kiedy program stanie się populamy, inne programy będą mogły kopiować dane ze Schowka w tym formacie. Tworzenie podglądu Schowka Program powiadamiany o zmianach zawartości Schowka nazywany jest pod- glądem Schowka. Dostarczany jest z Windows, ale równie dobrze możesz napi- sać swój własny program. Podgląd Schowka powiadamiany jest o zmianach za- wartości Schowka przez komunikaty przekazywane do procedury okna pod- glądu. Łańcuch podglądu Schowka Jednocześnie można uruchomić w Windows dowolną liczbę podglądów Schow- ka i wszystkie z ruch można powiadomić o zmianach jego zawartości. Z perspek- tywy tego systemu istnieje jednak tylko jeden podgląd Schowka, który nazywam aktualnym podglądem Schowka". Windows zarządza tylko jednym uchwytem " do identyfikacji aktualnego podglądu Schowka i tylko do tego okna wysyła ko- munikaty, gdy zmieni się zawartość Schowka.Aplikacje tego podglądu stanowią część łańcucha podglądu Schowka i wszystkie mogą odbierać komunikaty, które Windows wysyła do podglądu bieżącego. Gdy program zarejestruje się jako pod- gląd Schowka, staje się podglądem bieżącym. Windows nadaje mu uchwyt okna podglądu Schowka, który poprzednio był podglądem bieżącym. Program zapi- suje ten uchwyt. Po odebraniu przez program komunikatu podglądu Schowka, przesyła go do procedury okna programu, który umieszczony jest jako następny w łańcuchu tego podglądu. Rozdział 12: Schowek 531 Funkcje i komunikaty podglądu Schowka Program może stać się częścią łańcucha podglądu Schowka wywołując funkcję SetC- lipboardViewer. jeśli podstawowym zadaniem programu ma być działanie jako pod- gląd Schowka, może wywołać tę funkcję podczas przetwarzania komunikatu WMCREATE. Funkcja zwraca uchwyt okna podglądu Schowka, który poprzed- nio był bieżącym. Program powinien zapisać ten uchwyt w zmiennej statycznej: static HWND hwndNextUiewer ; Cpozostałe wiersze programuJ case WM_CREATE : Cpozostaie wiersze programuJ hwndNextViewer = SetClipboardViewer (hwnd) ; Jeżeli twój program jako pierwszy ma stać się podglądem Schowka podczas sesji Windows, zmienna hwndNextViewer powinna być równa NULL. Windows wysyła komunikat WM DRAWCLIPBOARD do bieżącego podglądu Schowka (tzn. ostatniego okna, które zrejestrowało sięjako jego podgląd) za każdym razem, gdy zmieni się zawartość Schowka. Każdy program w łańcuchu podglądu Schowka powinien używać funkcji SendMessage, aby przekazać ten komunikat do następnego jego podglądu. Ostatru program w tym łańcuchu (tzn. pierwsze okno, które zarejestrowało się jako podgląd Schowka) będzie przechowywać wartość hwnd- NextViewer równą NULL. Jeśli wartość hwndNextViewer jest równa NULL, program kończy działanie bez wysyłania komunikatu do innego programu. (Nie pomyl ze sobą komunikatów WMDRAWCLIPBOARD i WMPAINTCL'BOARD. Komuni- kat WMPAINTCL'BOARD jest wysyłany przez podgląd Schowka do programów, które używają formatu CF OWNERDISPLAY. Natomiast komunikat WMDRAWC- LIl'BOARD jest wysyłany przez Windows do bieżącego podglądu Schowka). Najprostszym sposobem przetworzenia komunikatu WM DRAWCLIPBOARD jest wysłanie go do następnego w łańcuchu podglądu Schowka (chyba że war- tość hwndNextViewer jest równa NULL) i uaktualnienie obszaru roboczego twoje- go okna: case WM_DRAWCLIPBOARD : if (hwndNextViewer) SendMessage (hwndNextViewer. message, wParam, lParam) ; InvalidateRect (hwnd, NULL, TRUE) ; return 0 ; Podczas przetwarzania komunikatu WMPAINT możesz odczytać zawartość Schowka używając zwykłych wywołań OpenClipboard, GetClipboardData i CIoseClip- board. Gdy program chce usunąć się z łańcucha podglądu Schowka, musi wywołać funk- cję ChangeClipboardChain. Funkcja ta wymaga uchwytu okna programu, który wy- cofuje się z łańcucha podglądu Schowka i uchwytu okna następnego w kolejno- ści jego podglądu: ChangeClipboardChain (hwnd, hwndNextUiewer) ; Kiedy program wywohxje funkcję ChangeClipboardChain, Windows wysyła komu- nikat WM CHANGECBCHAIN do bieżącego podglądu Schowka. Parametr wPa- ram jest uchwytem okna, które usuwa się z łańcucha (tzn. pierwszym parame- 532 Część I: Podstawy trem funkcji ChangeClipboardChain), a parametr IParam jest uchwytem okna pod- glądu Schowka następującego w łańcuchu za podglądem usuwanym (tzn. dru- gim parametrem funkcji ChangeClipboardChain). Gdy program odbierze komunikat WM CHANGECBCHAIN, musi sprawdzić, czy parametr wParam jest równy wartości hwndNextViewer, którą wcześniej zapi- sałeś. Jeśli tak jest, musisz ustawić hwndNextViewer na IParam. Operacja ta zapew- nia, że do okna usuwającego się z łańcucha nie zostaną wysłane komunikaty 4VMDRAWCLIPBOARD. Jeżeli wParam nie jest równy hwndNextViewer, a hwnd- NextViewer nie jest równy NULL, wyślij komunikat do następnego podglądu Schowka: case WM_CHANGECBCHAIN : if ((HWND) wParam = hwndNextViewer) hwndnNextViewer = (HWND) lParam else if (hwndNextViewer) SendMessage (hwndNextViewer, message, wParam, lParam) ; return 0 ; W rzeczywistości nie ma potrzeby używania instrukcji else if, która sprawdza, czy zmienna hwndNextViewer nie zawiera wartości NULL. Wartość NULL dla tej zmiennej oznaczałaby, że program wykonujący kod jest ostatnim podglądem w łańcuchu. W takim przypadku komunikat nigdy nie powinien pojawić się tak daleko. Jeśli podczas zamykania program nadal znajduje się w łańcuchu podglądu Schow- ka, powinien zostać z niego usunięty. Można to wykonać przetwarzając komuni- kat WM DESTROY przez wywołanie funkcji ChangeClipboardChain: case WM_DESTROY : ChangeClipboardChain (hwnd, hwndNextViewer) ; PostOuitMessage (0) ; return 0 ; Windows dostarcza także funkcję, która pozwala programowi uzyskać uchwyt okna pierwszego podglądu Schowka: hwndViewer = GetClipboardViewer () ; Zwykle funkcja ta nie jest potrzebna. Jeżeli bieżący Schowek nie jest ustawiony, funkcja zwraca wartość NULL. Poniższy przykład ilustruje działanie łańcucha Schowka. Gdy Windows jest uru- chamiany po raz pierwszy, bieżący podgląd Schowka ma wartość NULL: Bieżgcy podglgd Schowka: NULL Program, którego uchwytem okna jest hwndl, wywołuje funkcję SetClipboardVie- wer. Funkcja zwraca wartość NULL, która staje się w programie wartością hwnd- NextViewer: Bieżący podglgd Schowka: hwndl Następny podglgd za hwndl: NULL Drugi program, którego uchwytem okna jest hwnd2, także wywołuje funkcję Set- CipboardViewer. Zwraca ona uchwyt hwndl: Rozdział 12: Schowek 533 Bieżgcy podglgd Schowka: hwnd2 Następny podglgd za hwnd2: hwndl Nastgpny podglgd za hwndl: NULL
Trzeci program (hwnd3), a następnie czwarty (hwnd4) również wywołują funkcję SetClipboardViewer, która zwraca odpowiednio hwnd2 i hwnd3: I Bieżgcy podglgd Schowka: hwnd4 Następny podglgd za hwnd4: hwnd3 i :'. Następny podglgd za hwnd3: hwnd2 Następny podglgd za hwnd2: hwndl Następny podglgd za hwndl: NULL Gdy zawartość Schowka zmieni się, Windows wysyła komunikat WM DRAWCLIP- BOAIZD do hwnd4, hwnd4 wysyła komunikat do hwnd3, hwnd3 - do hwnd2, hwnd2 - do hwndl, a hwndl kończy ten proces. Jeżeli hwnd2 zdecyduje usunąć się ze Schowka, wywołując funkcję: ChangeClipboardChain (hwnd2, krwndl) : to Windows wyśle do hwnd4 komunikat WM CHANGECBCHAIN z parametrem wParam równym hwnd2 i parametrem IParam równym 1. Ponieważ następnym podglądem Schowka za hwnd4 jest hwnd3, hwnd4 wysyła komunikat do hwnd3. Następnie hwnd3 zauważa, że wParam jest równy następnemu podglądowi (hwnd2), ustawia swój następny podgląd Schowka na równy IParam (hwndl) i koń- czy ten proces. Operacja została wykonana. Łańcuch podglądu Schowka wyglą- da teraz następująco: Bieżgcy podglgd Schowka: hwnd4 ' Następny podglgd za hwnd4: hwnd3 Następny podglgd za hwnd3: hwndl Następny podglgd za hwndl: NULL Prosty podgląd Schowka Podglądy Schowka nie muszą być aż tak rozbudowane jak ten dostarczany z Win- dows. Podgląd Schowka może wyświetlać na przykład tylko jeden format da- nych. Program CLIPVIEW, pokazany na rysunku 12-2, jest podglądem Schowka, który wyświetla tylko format CF TEXT. CLIPVIEW.C i* CLIPVIEW.C - Prosty podgląd Schowka (c) Charles Petzold, 1998 */ ilinclude LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ; 534 Część 1: Podstawy (ciąg dalszy ze strony 533) int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) ( static TCHAR szAppName[] = TEXT ("ClipView") ; HWND hwnd ; MSG msg ; WNDCLASS wndclass ; wndclass.style = CS_HREDRAW CS VREDRAW ; wndclass.lpfnWndProc = WndProc ; wndclass.cbClsExtra = 0 ; wndclass.cbWndExtra = 0 ; wndclass.hInstance = hInstance ; wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ; wndclass.hCursor = LoadCursor (NULL, IDC ARROW) ; wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITEBRUSH) ; wndclass.lpszMenuName = NULL ; wndclass.lpszClassName = szAppName ; if (!RegisterClass (&wndclass)) ( MessageBox (NULL, TEXT ("This program requires Windows NT!"), szAppName, MBICONERROR) ; return 0 ; ) hwnd = CreateWindow (szAppName, TEXT ("Simple Clipboard Viewer (Text Only)"), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW USEDEFAULT, CW_USEDEFAULT, CW USEDEFAULT, NULL, NULL, hInstance, NULL) ; ShowWindow (hwnd, iCmdShow) ; UpdateWindow (hwnd) ; while (GetMessage (&msg, NULL, 0, 0)) ( TranslateMessage (&msg) ; DispatchMessage (&msg) ; return msg.wParam ; ) LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) ? static HWND hwndNextViewer ; HGLOBAL hGlobal ; HDC hdc ; . PTSTR pGlobal ; PAINTSTRUCT ps ; RECT rect ; switch (messa9e) ( case WMCREATE: Rozdział 12: Schowek 535 hwndNextViewer = SetClipboardViewer (hwnd) ; return 0 ; case WM_CHANGECBCHAIN: if ((HWND) wParam == hwndNextViewer) hwndNextViewer = (HWND) lParam ; else if (hwndNextUiewer) SendMessage (hwndNextUiewer, message, wParam, lParam) ; return 0 ; case WM_DRAWCLIPBOARD: if (hwndNextViewer) SendMessage (hwndNextViewer, message, wParam, lParam) ; InvalidateRect (hwnd, NULL, TRUE) ; return 0 ; case WM_PAINT: hdc = BeginPaint (hwnd, &ps) ; GetClientRect (hwnd, &rect) ; OpenClipboard (hwnd) ; ifdef UNICODE hGlobal = GetClipboardData (CF UNICODETEXT) ; else hGlobal = GetClipboardData (CF TEXT) ; #endif if (hGlobal != NULL) pGlobal = (PTSTR) GlobalLock (hGlobal) ; DrawText (hdc, pGlobal, -1, &rect, DT EXPANDTABS) ; GlobalUnlock (hGlobal) ; 1 CloseClipboard () ; EndPaint (hwnd, &ps) ; return 0 ; case WM_DESTROY: ChangeClipboardChain (hwnd, hwndNextViewer) ; PostOuitMessage (0) ; return 0 ; return DefWindowProc (hwnd, message, wParam, lParam) ; J Program CLIPVIEW przetwarza komunikaty WMCREATE, WMCHANGECB- CHAIN, WM DRAWCLIPBOARD i WMDESTROY w omówiony wcześniej spo- sób. Komunikat WMPAIN'T otwiera po prostu Schowek i używa funkcji GetC- IipboardData z formatem CF TEXT: Jeśli funkcja zwróci globalny uchwyt pamię- ci, program CLIPVIEW zablokuje go i użyje funkcji DrawText, aby wyświetlić tekst w obszarze roboczym. Podgląd Schowka, obsługujący niestandardowe formaty danych (np. podgląd Schowka dostarczany z Windows), musi wykonywać dodatkowe zadania, takie 536 Część I: Podstawy jak wyświetlanie nazw wszystkich formatów dostępnych w Schowku. Można to zrobić wywołując funkcje EnumClipboardFormats i uzyskując nazwy niestandar- dowych formatów za pomocą funkcji GetClipboardFormatName. Podgląd Schow- ka, używający formatu CF OWNERDISPLAY, aby wyświetlić dane, musi prze- słać następujące cztery komunikaty do właściciela Schowka: WM PAIN'TCLIPBOARD M VSCROLLCLIPBOARD WM SIZECLIPBOARD WM HSCROLLCLIPBOARD Jeżeli chcesz napisać taki podgląd Schowka, musisz uzyskać uchwyt okna wła- ściciela Schowka, stosując funkcję GetClipboardOwner, i wysłać do tego okna po- wyższe komunikaty w momencie odświeżania obszaru roboczego podglądu Schowka.