Rozdział 14.
Interfejs Win32 i jego wykorzystanie
Paul Gustavson
Chris Winters
Oprogramowanie pośrednie a wywołania funkcji Win32
Krótka historia Windows i jego API
Kategorie funkcji interfejsu Win32
Budowa i działanie aplikacji dla Windows
Praktyczne przykłady użycia funkcji Win32
C++Builder jest narzędziem przeznaczonym do tworzenia 32-bitowych aplikacji przeznaczonych dla systemu Windows. Używając nazwy „Windows”, mamy na myśli systemy z rodziny Windows 9x, Windows Millennium (Me), NT oraz 2000. Co prawda opracowany przez firmę Borland system Kylix umożliwia programistom tworzenie 32-bitowych programów przeznaczonych również dla Linuksa, jednak obecnie główną platformą docelową dla pisanych w C++Builderze aplikacji pozostaje Windows.
Zadaniem systemu C++Builder jest umożliwienie szybkiego i łatwego tworzenia w C++ 32-bitowych aplikacji przeznaczonych dla systemu Windows. Aby ułatwić realizację tego zadania, C++Builder udostępnia programiście funkcje interfejsu programowego systemu Windows, Win32 API (Applications Programming Interface), bądź to bezpośrednio, bądź też poprzez komponenty stanowiące warstwę tzw. oprogramowania pośredniego (ang. middleware). Rozdział ten poświęcimy właśnie prezentacji interfejsu programowego Win32 oraz przykładom jego użycia.
Oprogramowanie pośrednie a funkcje Win32
Firma Borland opracowała już kilka generacji bibliotek klas ułatwiających i przyspieszających tworzenie aplikacji dla Windows. W terminologii inżynierii oprogramowania pojęcie „biblioteki klas” lub „systemu szkieletowego” (ang. resource framework) oznacza system obejmujący strukturę, interfejs, a często również „prefabrykowane” elementy, które projektant może wykorzystać do budowy aplikacji. Spośród bibliotek znanych programistom, korzystającym z produktów firmy Borland, najbardziej godną uwagi jest biblioteka komponentów VCL (Visual Component Library), omówiona szczegółowo w rozdziałach 6. i 7. Biblioteka VCL jest kluczowym elementem opracowanych przez firmę Borland narzędzi do szybkiego tworzenia aplikacji (RAD) - Delphi i C++Buildera; to właśnie ona jest głównym powodem, dla którego określenie RAD jest jak najbardziej prawdziwe. Poprzedniczką VCL była biblioteka OWL (Object Windows Library), przeznaczona dla wcześniejszych wersji systemu Windows. Narzędzia firmy Borland umożliwiają wreszcie korzystanie z biblioteki MFC (Microsoft Foundation Class Library), najpopularniejszej (chyba nie do końca zasłużenie) wśród bibliotek klas przeznaczonych dla Windows.
Niezależnie od tego, czy mamy do czynienia z biblioteką VCL, OWL czy MFC, elementy oprogramowania pośredniego i biblioteki klas mają to samo przeznaczenie - pozwalają zmniejszyć nakład pracy przeznaczony na projektowanie aplikacji dzięki „opakowaniu” wywołań funkcji Win32 w obiekty i metody przeznaczone do wielokrotnego wykorzystania.
Projektując aplikację przeznaczoną dla Windows, warto w większości przypadków przynajmniej rozważyć możliwość użycia oprogramowania pośredniego, „otaczającego” interfejs Win32 - w końcu jego przeznaczeniem jest zmniejszenie wysiłku związanego z projektowaniem aplikacji. Dodatkowo interfejs udostępniany przez biblioteki klas i oprogramowanie pośrednie pozwala na obiektowe traktowanie wywołań Win32 API, którego natura jest bardziej strukturalna. Mimo tych niewątpliwych zalet, w niektórych przypadkach dokładna znajomość „podstaw”, czyli funkcji Win32, okazuje się niezbędna - nie mówiąc już o konieczności ich bezpośredniego (choćby sporadycznego) użycia. Jednym z powodów takiego stanu rzeczy jest fakt, iż bezpośrednie odwołania do funkcji Win32 są szybsze i mniej „pamięciożerne” niż analogiczne operacje realizowane z użyciem oprogramowania pośredniego. Ponieważ to ostatnie jest de facto abstrakcyjnym interfejsem, czy też „otoczką” funkcji Win32, mnogość parametrów zapewniających ową abstrakcyjność może ujemnie wpływać na prędkość wykonywania programu. Warto też zauważyć, iż programiści, wykorzystujący bezpośrednio funkcje API, na ogół lepiej rozumieją mechanizmy współdziałania aplikacji z systemem operacyjnym, innymi aplikacjami i sprzętem. Na szczęście wersje Professional i Enterprise systemu C++Builder zawierają kod źródłowy licznych komponentów biblioteki VCL; podobnie ma się rzecz z większością komponentów VCL, pochodzących od niezależnych producentów. Analiza kodu źródłowego może pomóc projektantowi aplikacji w zrozumieniu wewnętrznych mechanizmów działania funkcji Win32, wywoływanych w obrębie komponentu lub klasy podstawowej.
Prawdopodobnie najważniejszym powodem, dla którego użytkownicy C++Buildera odwołują się bezpośrednio do funkcji Win32, jest brak możliwości zrealizowania danej operacji wyłącznie za pomocą komponentów VCL. Doświadczeni programiści często natrafiają w swojej praktyce na sytuacje, w których możliwości funkcjonalne VCL okazują się niewystarczające. W takich właśnie przypadkach bezpośrednie odwołania do systemu Win32 pozwalają na ogól poradzić sobie z problemem, zapewniając pożądane możliwości i elastyczność. Obiektowa natura biblioteki VCL oraz możliwość wielokrotnego wykorzystywania jej komponentów są ważkimi argumentami, przemawiającymi za jak najczęstszym jej użyciem - o ile tylko jest to możliwe. Jeśli tak nie jest, należy odwołać się do odpowiednich mechanizmów interfejsu programowego Win32.
Krótka historia Windows i jego API
Pełne zrozumienie szerokich możliwości Win32 API wymaga spojrzenia na system Windows i rozwój jego interfejsu programowego z perspektywy historycznej. We wczesnych latach 80. firma Microsoft rozpoczęła prace nad programem o nazwie Interface Manager, zmieniając następnie jego nazwę na Windows. Produkt Microsoftu, podobnie jak dzieło jego konkurenta, firmy Apple Computer, prawdopodobnie bazował na opracowaniach centrum badawczego firmy Xerox w Palo Alto (PARC). Inżynierowie z PARC stworzyli koncepcję „okienkowego” graficznego środowiska roboczego, znanego dziś powszechnie jako graficzny interfejs użytkownika (Graphical User Interface, GUI). Celem Microsoftu stało się zatem opracowanie graficznego środowiska roboczego, zapewniającego łatwość użycia, niezależność prezentacji od platformy sprzętowej oraz wielozadaniowość. Co ciekawe, analogiczny projekt, nazwany Lisa, realizowała również firma Apple, jednak dopiero pojawienie się komputera Macintosh uświadomiło użytkownikom potęgę graficznych środowisk roboczych.
W roku 1985 firma Microsoft zaprezentowała produkt o nazwie Windows 1.0, którego przeznaczeniem było rozszerzenie możliwości funkcjonalnych systemu DOS poprzez wprowadzenie „bardziej graficznych” metod komunikowania się z aplikacjami. Wygląd pierwszej wersji Windows był jednak nadal bardzo „DOS-owy” - okna programów można było co prawda ustawiać obok siebie, jednak nie dało się układać ich kaskadowo. System obsługiwał wielozadaniowość bez wywłaszczania, umożliwiając pracę wielu programów w tym samym obszarze pamięci. Podobne możliwości udostępniało kilka innych produktów przeznaczonych dla komputerów PC, między innymi DESQview firmy Quarterdeck, TopView IBM-a i GEM firmy Digital Research. Charakterystyczną cechą wszystkich tych rozwiązań była możliwość uruchomienia kilku programów i ich przełączania, realizowanego przez system w wyniku polecenia wydanego przez użytkownika (np. naciśnięcia klawiszy). Takie „przełączanie zadań” pozwalało nie tylko korzystać jednocześnie z kilku aplikacji, ale również tworzyło namiastkę wielozadaniowości.
W systemie Windows 1.0 przełączanie zadań realizowane było z użyciem funkcji API GetMessage(); przydzielaniem aplikacjom pamięci (w ramach dostępnych zasobów komputera) zajmowała się inna funkcja API GlobalAlloc(). Obie one dostępne są w API do dzisiaj, chociaż poddano je licznym modyfikacjom i ulepszeniom. W programowaniu aplikacji dla Windows wykorzystywane są one sporadycznie.
W roku 1987 na rynku pojawił się system Windows 2.0, a wraz z nim charakterystyczne ikony i kaskadowo ułożone okna aplikacji. Interfejs programowy tej wersji zapewniał szersze możliwości tworzenia i obsługi okien dialogowych, zaś programy dla DOS-u można było uruchamiać pod nadzorem Windows za pośrednictwem plików PIF. System Windows 2.0 przełamał wreszcie „barierę jednego megabajta”, czyli ograniczenie dostępności pamięci narzucone przez architekturę wcześniejszych wersji DOS-u. Modernizacją wersji 2.0 był system Windows 2.1, jednak pojawienie się w tym czasie procesora Intel 80386 i ulepszenia programowe związane z jego możliwościami zaowocowały kolejną wersją Windows o nazwie Win/386. W konsekwencji, dla odzwierciedlenia nazewnictwa procesorów Intela, reedycja systemu Windows 2.1 zyskała nazwę Win/286. Jednym z najważniejszych ulepszeń związanych z nowym procesorem Intela był nowy mechanizm wielozadaniowości bez wywłaszczania, znacznie przewyższający zastosowane w wersji 1.0 rozwiązanie, oparte cokolwiek na zasadzie wiary na słowo i wymagające użycia funkcji GetMessage(). Przydzielenie poszczególnym aplikacjom zadanego odcinka czasu procesora pozwoliło na pewną automatyzację pracy wielozadaniowej.
Rok 1990 przyniósł użytkownikom system Windows 3.0, a środowiska informatyczne powoli zaczęły dostrzegać produkt Microsoftu. Wersja 3.0 obsługiwała do 16 MB pamięci w trybie rozszerzonym 386, zapewniała lepszą obsługę grafiki i pamięci. Nowa wersja funkcji GlobalAlloc() umożliwiała przydzielanie większych obszarów pamięci na stercie i adresowanie z użyciem przesunięć w segmencie pamięci przydzielonym aplikacji. Wraz z Windows użytkownicy otrzymali programy narzędziowe, jak np. Menedżer plików i Menedżer programów. Pojawiły się także inne biblioteki i rozszerzenia systemu, jak np. zestaw rozszerzeń multimedialnych, obsługiwany do dziś przez bibliotekę dynamiczną Windows Multimedia System (MMSYSTEM.DLL).
W kwietniu 1992 na rynku pojawiła się wersja Windows 3.1, a wraz z nią czcionki TrueType, łączenie i osadzanie obiektów (OLE) oraz rozszerzone funkcje multimedialne, jak np. PlaySound(). Programiści otrzymali w prezencie zestaw predefiniowanych okien dialogowych, upraszczający obsługę rutynowych czynności, jak drukowanie, otwieranie i zapisywanie plików czy wybór kolorów. Większość liczących się producentów sprzętu PC zaczęła dołączać system Windows 3.1 do swoich produktów. Pomimo iż produkowane przez firmę Intel procesory 80386 i 486 posiadały już architekturę 32-bitową, system Windows nadal obsługiwał segmentowany model pamięci z adresowaniem 16-bitowym oraz co najwyżej 16-bitowe liczby całkowite.
Na początku roku 1994 Microsoft zaprezentował Windows for Workgroups (WFW) 3.11 - swoisty „sieciowy system operacyjny dla ubogich”, który szybko zdobył popularność w amerykańskich firmach. Jedną z cech projektowych Windows for Workgroups była możliwość integracji z systemami, których podstawą była technologia NT, o której powiemy za chwilę. Zestaw funkcji, struktur i wywołań zwrotnych zawarty w interfejsie programowym systemów Windows 3.x (oraz WFW) jest finalną wersją 16-bitowego API, znaną dziś jako Win16.
Na krótko przed prezentacją Windows for Workgroups w roku 1993 firma Microsoft przedstawiła system Windows NT (New Technology) 3.1. Jego premiera przeszła praktycznie bez rozgłosu, chociaż to właśnie NT była pierwszą w pełni 32-bitową architekturą Windows. Jądro systemu, wykorzystujące 32-bitowy procesor Intel 486, realizowało pełną wielozadaniowość z wywłaszczaniem, obsługiwało 32-bitowe adresowanie liniowe i 32-bitową arytmetykę całkowitą. Wkrótce potem wprowadzono na rynek interfejs programowy Win32s, umożliwiający programistom tworzenie 32-bitowych aplikacji przeznaczonych dla systemów z rodziny Windows 3.x. „32-bitowość” Win32s była realizowana za pomocą pośredniczącej biblioteki dynamicznej, tłumaczącej 32-bitowe wywołania, pochodzące z aplikacji, na ich 16-bitowe odpowiedniki akceptowane przez jądro systemu operacyjnego. Co prawda właściwy system nadal obsługiwał wyłącznie segmentowany model pamięci z adresowaniem 16-bitowym i takąż arytmetykę całkowitoliczbową, jednak pojawienie się interfejsu Win32s umożliwiło programistom rozpoczęcie na większą skalę prac nad 32-bitowym oprogramowaniem przeznaczonym dla Windows NT i mającym się niebawem ukazać Windows 95.
Ten ostatni został oficjalnie zaprezentowany z wielką pompą w sierpniu 1995 roku. Oprócz całkowicie zmienionego środowiska graficznego, nowy system udostępniał programistom 32-bitowy interfejs programowy, zbliżony do zawartego w Windows NT 3.51. Podobnie jak ten ostatni, Windows 95 obsługiwał wielozadaniowość, wielowątkowość oraz 32-bitowe adresowanie liniowe, oferował natomiast uboższy zestaw możliwości w zakresie bezpieczeństwa i pracy w charakterze serwera. Ponadto (chociaż nie było to na pierwszy rzut oka widoczne) nowe „okienka” w dalszym ciągu związane były z systemem DOS. Największą zaletą Windows 95 była natomiast możliwość wykonywania pod jego nadzorem większości programów stworzonych dla rodziny Windows 3.x (wykorzystujących wywołania interfejsu Win16 i Win32s), co bez wątpienia było niezwykle atrakcyjne dla użytkowników.
Najpoważniejszym zarzutem stawianym systemowi Windows NT był brak zgodności z większością już istniejących aplikacji dla Windows 3.x, przy jednoczesnym ubogim repertuarze programów wykorzystujących funkcje Win32. Typową ilustracją tego stanu rzeczy była ankieta przeprowadzona w czerwcu 1994 roku (na rok przed premierą Windows 95) podczas zorganizowanej przez firmę Borland w Orlando na Florydzie konferencji projektantów oprogramowania Borland Developer's Conference. Badanie miało na celu ustalenie udziału poszczególnych platform w docelowym rynku oprogramowania tworzonego przez ankietowanych. Okazało się, iż większość z nich - kilkuset - oświadczyła, że ich produkty przeznaczone są dla rodziny Windows 3.x. Około 20 osób stwierdziło, że docelową platformą jest dla nich system OS/2, a tylko dwie (spośród kilkuset!) „przyznały się”, że piszą oprogramowanie dla Windows NT. Powodem takiego stanu rzeczy było stosunkowo niewielkie zapotrzebowanie na aplikacje przeznaczone dla tego ostatniego systemu. Dopiero pojawienie się Windows 95, oferującego zarówno interfejs Win32, jak i możliwość uruchamiania aplikacji Win16, zapoczątkowało falę migracji programów 16-bitowych do nowego środowiska Win32. Dość powszechne jest przekonanie, że Windows 95 miał być jedynie rozwiązaniem przejściowym, mającym pobudzić programistów do tworzenia aplikacji Win32, a użytkowników przeciągnąć na stronę Windows NT. Microsoft nie planował utrzymywania Windows 95 „przy życiu” przez dłuższy czas, zamierzając zastąpić go nową wersją Windows NT, lepiej dostosowaną do potrzeb typowego użytkownika. Pierwszym krokiem w tym kierunku była modernizacja środowiska graficznego NT, mająca na celu ujednolicenie go z Windows 95. Powstały w ten sposób system Windows NT 4.0 pojawił się na rynku w sierpniu 1996 i wkrótce szeroko rozpowszechnił się w kręgach biznesu i przemysłu. Niestety, nowa wersja NT pozbawiona była tak pożądanych przez przeciętnego użytkownika możliwości, jak obsługa multimediów (czyli np. gier komputerowych).
Firma Microsoft ostatecznie zdecydowała się na zmodernizowanie Windows 95, uzupełniając go m.in. o możliwość obsługi urządzeń USB i 32-bitowy system plików FAT32. Tak odnowiony system ukazał się na rynku w roku 1997 pod nazwą Windows 95 Original Equipment Manufacturer Service Release 2 (w skrócie Windows 95 OSR2). Wśród nowości udostępnionych przez zaimplementowaną w OSR2 nową wersję Win32 API znalazły się między innymi: możliwość korzystania z elementów specyficznych dla przeglądarki Internet Explorer 3.0, zmodernizowany podsystem obsługi grafiki i multimediów, obejmujący standardy DirectX 2, OpenGL oraz ActiveMovie, a także kilka innych funkcji, w tym narzędzia kryptograficzne oraz funkcja GetDiskFreeSpaceEx(), obsługująca duże dyski twarde. Wersja interfejsu Win32 zawarta w systemie Windows 98 (wprowadzonym na rynek w czerwcu 1998) zawierała już prawie 10 tysięcy funkcji, a nie trzeba chyba nikogo przekonywać, że Microsoft nie zasypiał gruszek w popiele, wciąż rozszerzając możliwości API, publikując poprawki i nowe wersje systemów.
Po Windows 98 pojawiły się dwa kolejne systemy: Windows 98 Second Edition (SE) w maju 1999 oraz Windows Millennium (Me) we wrześniu 2000. Wprowadzenie na rynek systemu Windows 2000 oznaczało jednocześnie koniec dalszego rozwoju rodziny NT, jednak linia Windows 9x jest nadal kontynuowana. Modernizacje Windows 98 wprowadzone w wersji Me obejmują rozszerzenie możliwości w zakresie multimediów, udostępnienie nowych programów narzędziowych oraz wyeliminowanie możliwości uruchomienia w trybie MS-DOS, jednak nowy system nadal tkwi swoimi korzeniami w architekturze Windows 9x. O ile jednak Windows 98/Me i Windows 2000 różnią się zdecydowanie na poziomie jądra systemu, obecna wersja interfejsu Win32 umożliwia swobodne przenoszenie aplikacji pomiędzy systemami Windows 9x, Me, NT oraz 2000. Dzięki temu większość programów przeznaczonych dla Windows Me można bez przeszkód uruchamiać pod nadzorem systemu Windows 2000 i na odwrót - z nielicznymi wyjątkami.
Interfejs programowy systemu Windows nie zmienił się w sposób radykalny, ewoluuje jednak nieustannie. Od chwili swojego powstania w połowie lat 80. (wraz z systemem Windows 1.0) uległ on znacznemu rozszerzeniu, zachowując jednak wiele spośród pierwotnie obecnych w nim funkcji. Nowe technologie w dziedzinie sprzętu oraz rozwiązania programowe wprowadzane przez Microsoft z pewnością będą wymuszały kolejne zmiany i udoskonalenia, należy jednak oczekiwać, że większość dostępnych obecnie funkcji i struktur danych będzie obsługiwana również w przyszłych wersjach Windows. W miarę upływu czasu publikowane są także kolejne informacje o nieudokumentowanych możliwościach systemu, co pozwala na ich wykorzystanie przez programistów. Sukces systemu Windows zależy - i nadal będzie zależał - od dostępności jego interfejsu programowego. Umiejętność wykorzystania tego ostatniego przez projektanta programu pozwala na tworzenie coraz lepszego oprogramowania, co przekłada się z kolei na zadowolenie użytkowników.
Kategorie funkcji interfejsu Win32
Przedstawiony mały wykład z historii miał za zadanie wprowadzenie Czytelników w zagadnienia API i przedstawienie jego zadań. Interfejs programowy Win32 udostępnia funkcje, wywołania zwrotne oraz struktury danych związane z zarządzaniem pracą aplikacji oraz jej integracją ze środowiskiem operacyjnym. Wywołania funkcji API pozwalają aplikacji na korzystanie z systemowych usług wysokiego poziomu, jak np. wyświetlanie okien dialogowych otwarcia lub zapisania pliku czy drukowanie dokumentu. Dostępne są też liczne inne funkcje; liczba elementów API dostępnych w wersji zaimplementowanej w Windows 98, ME i 2000 przekracza już prawdopodobnie 10 tysięcy. Większość podstawowych funkcji zawarto w bibliotekach dynamicznych, znajdujących się w katalogu Windows\ --> System[Author:ts] . Do najważniejszych należą biblioteki implementujące jądro systemu (kernel32.dll), obsługę interfejsu użytkownika (user32.dll) oraz funkcji graficznych GDI (gdi32.dll).
Istotne znaczenie mają również rozszerzenia i biblioteki pomocnicze, wprowadzone przez Microsoft oraz innych producentów oprogramowania w trakcie ewolucji Windows. Na przykład wprowadzając na rynek systemy Windows 3.1 i Windows 95, szefowie firmy z Redmond nie mieli pojęcia, jak ogromną rolę w kształtowaniu przyszłych wersji ich systemu odegra sieć WWW. „Wojna przeglądarek” pomiędzy Netscape i Microsoftem stała się jednym z kluczowych czynników, które wpłynęły na powstanie nowych rozszerzeń API i bibliotek pomocniczych dla Windows.
Biblioteki rozszerzające i pomocnicze obejmują m.in.: bibliotekę SHELL i MMSYSTEM, podsystem Windows Sockets (WinSock), DirectX oraz interfejs programowy Windows Internet Extensions (WinInet). Moduły te nie są niezbędne do poprawnej pracy systemu, jednak oferowany przez nie wachlarz funkcji pozwala tworzyć solidniejsze i bardziej uniwersalne aplikacje. Aby obejrzeć zestaw funkcji udostępnianych przez dowolną bibliotekę dynamiczną, można posłużyć się programem narzędziowym impdef, wchodzącym w skład pakietu C++Builder. Wygenerowany przezeń plik (o rozszerzeniu .def) będzie zawierał nazwy funkcji eksportowanych z danej biblioteki. Oto przykład:
impdef -a user32.def user32.dll
Uzyskany w ten sposób plik tekstowy zawiera listę funkcji bibliotecznych, które można wywoływać z aplikacji. Chociaż katalog Windows\System zawiera mnóstwo plików .dll, tylko nieliczne z nich implementują funkcje Win32. Te ostatnie można na ogół rozpoznać po zawartej w nazwie pliku liczbie „32”. Szczegółowe informacje o danej bibliotece można uzyskać, wykorzystując polecenie Właściwości Eksploratora Windows. Przykład wyświetlonego przezeń okna przedstawiono na rysunku 14.1. Jeśli w zawartych w oknie informacjach uda nam się odszukać nazwę „Microsoft” i skrót „API”, najprawdopodobniej mamy do czynienia z biblioteką Win32.
Rysunek 14.1. Właściwości biblioteki DLL
Przyswojenie sobie wiadomości na temat wszystkich elementów systemu Win32 jest zadaniem niezwykle ambitnym, głównie dlatego, iż każda aktualizacja, zarówno samego systemu, jak i przeglądarki Internet Explorer, może wnosić ze sobą nowe funkcje API, w wielu przypadkach nieudokumentowane. Tym niemniej można pokusić się o określenie kilku zasadniczych obszarów funkcjonalnych API, które prezentujemy na rysunku 14.2. Zapewnia to podstawę do dyskusji na temat elementów składowych interfejsu oraz możliwości, jakie udostępnia on programistom.
Windows management - Zarządzanie aplikacjami i oknami
System services - Usługi systemowe
Graphics Device Interface - Obsługa interfejsu urządzeń graficznych
Multimedia services - Usługi multimedialne
Common controls & dialogs - Standardowe elementy interfejsu użytkownika
Shell features - Funkcje powłoki systemu
International features - Obsługa ustawień regionalnych
Network services - Usługi sieciowe
Rysunek 14.2. Kategorie funkcji interfejsu Win32
Funkcje interfejsu Win32 można podzielić na osiem głównych kategorii:
zarządzanie aplikacjami i oknami (windows management);
usługi systemowe (system services);
obsługa interfejsu urządzeń graficznych (GDI, Graphics Device Interface);
usługi multimedialne (multimedia services);
standardowe elementy interfejsu użytkownika (common controls and dialogs);
funkcje powłoki systemu (shell features);
obsługa ustawień regionalnych (international features);
usługi sieciowe (network services).
Zajmijmy się po kolei każdą z nich.
Zarządzanie oknami
Do tworzenia i zarządzania aplikacjami uruchamianymi w systemie Windows służą funkcje zawarte w bibliotece obsługującej interfejs użytkownika (user32.dll). Realizowane przez nią usługi obejmują: zarządzanie oknami, menu, oknami dialogowymi, okienkami komunikatów, komunikację z klawiaturą i myszą oraz innymi elementami interfejsu użytkownika. Pojęciem kluczowym dla dalszej dyskusji jest tu okno (ang. window). Jest ono swoistym punktem styku, przez który użytkownik porozumiewa się z aplikacją. Każdy program tworzy co najmniej jedno okno, zwane oknem głównym (ang. main window), chociaż oczywiście może utworzyć ich więcej. Zasadniczym przeznaczeniem okna jest przekazywanie i odbieranie informacji od użytkownika. Omawiane tutaj funkcje umożliwiają manipulowanie oknami tworzonymi przez program oraz sterowanie przepływem komunikatów generowanych przez urządzenia wejściowe (klawiatura i mysz), odbieranych przez okno główne i przekazywanych dalej do systemu. Funkcje zarządzania oknami pozwalają także programowi wyświetlać menu, ikony oraz okna dialogowe, służące do przekazywania i odbierania informacji od użytkownika.
Niektóre z popularniejszych funkcji zarządzania oknami zawartych w API zestawiono w tabeli 14.1. Aby móc się do nich odwoływać, należy włączyć do tekstu źródłowego programu plik nagłówkowy windows.h lub zawarty w pakiecie C++Builder plik vcl.h.
Tabela 14.1. Najczęściej używane funkcje zarządzania oknami
Funkcja |
Opis |
CascadeWindows() |
Układa dane okna (lub okna potomne danego okna) kaskadowo. |
CloseWindow() |
Ukrywa (nie likwiduje) dane okno. |
CreateWindow() |
Tworzy okno wyposażone w pasek tytułu (overlapped), wyskakujące (pop-up) lub potomne (child). |
DestroyWindow() |
Likwiduje okno; jej wywołanie powoduje przesłanie do okna systemowego komunikatu WM_DESTROY. |
EnableWindow() |
Blokuje lub odblokowuje odbieranie przez okno komunikatów wysyłanych przez klawiaturę i mysz. |
EnumWindows() |
Identyfikuje wszystkie okna najwyższego poziomu (ang. top level window; są to okna, których rodzicem jest pulpit Windows), przekazując kolejno ich uchwyty do zdefiniowanej przez użytkownika funkcji zwrotnej. |
EnumWindowsProc() |
Odbiera uchwyty okien przekazywane przez EnumWindows() w celu dalszego przetwarzania (funkcja zwrotna definiowana przez programistę). |
FindWindow() |
Zwraca uchwyt okna najwyższego poziomu identyfikowanego nazwą klasy i zawartością paska tytułu okna. |
FindWindowEx() |
Zwraca uchwyt okna identyfikowanego nazwą klasy oraz zawartością paska tytułu; działa podobnie do FindWindow(), jednak umożliwia również zlokalizowanie okna potomnego (typu child). |
GetWindowRect() |
Zwraca współrzędne ekranowe prostokąta, zawierającego dane okno. |
GetWindowText() |
Zwraca tekst zawarty w oknie (zwykle w pasku --> tytułu[Author:ts] ). |
MoveWindow() |
Przesuwa okno lub zmienia jego rozmiary. |
SetWindowText() |
Zmienia tekst zawarty w oknie (zwykle w pasku tytułu). |
ShowWindow() |
Modyfikuje stan widoczności danego okna. Możliwości funkcji ShowWindow() obejmują: maksymalizację, minimalizację, ukrywanie, przywracanie rozmiarów oraz uaktywnianie okien. |
TileWindows() |
Układa dane okna (lub okna potomne danego okna) obok siebie. |
WinMain() |
Definiuje punkt wejścia do aplikacji Win32; wywoływana przez system operacyjny. |
Zestaw funkcji przeznaczonych do zarządzania oknami nie ogranicza się oczywiście do wymienionych powyżej - biblioteka user32.dll w wersji 4.10.2222 zawiera ich aż 648. Do uzyskania pełnej ich listy można wykorzystać wspomniany wcześniej program narzędziowy impdef firmy Borland.
Usługi systemowe
Funkcje określane ogólnie mianem usług systemowych umożliwiają aplikacji zarządzanie i nadzorowanie stanu zasobów, dostęp do plików, folderów oraz urządzeń wejścia-wyjścia, a także rejestrację i obsługę błędów oraz wyjątków. Usługi systemowe obejmują także funkcje wykorzystywane przy tworzeniu programów innych niż „okienkowe”, np. tekstowych aplikacji Win32, usług i sterowników. Większość funkcji systemowych niskiego poziomu (w tym funkcje przeznaczone do obsługi plików, zarządzania pamięcią i innymi zasobami oraz obsługi pracy wielozadaniowej i wielowątkowej) zawarta jest w bibliotece implementującej jądro systemu (kernel.dll). Funkcje jądra wykorzystywane są przez wszystkie programy przeznaczone dla Windows. Przykładowo, jeśli dana aplikacja wymaga dodatkowej pamięci (czy to podczas uruchamiania, czy w trakcie pracy), kieruje odpowiednie żądanie właśnie do jądra systemu. Podstawowe grupy i elementy usług systemowych zestawiono w tabeli 14.2.
Tabela 14.2. Najważniejsze grupy i elementy usług --> systemowych[Author:ts]
Grupa lub element |
Opis |
Atomy |
Obsługa wymiany między aplikacjami łańcuchów tekstowych identyfikowanych liczbami całkowitymi |
Usługi komunikacyjne |
Komunikacja z urządzeniami peryferyjnymi (porty szeregowe, równoległe i modemy) |
Obsługa konsoli |
Obsługa wejścia-wyjścia w trybie tekstowym (dla aplikacji nie korzystających z graficznego interfejsu użytkownika) |
Usługi uruchomieniowe |
Sterowany zdarzeniami system uruchomieniowy (debugger) |
Wejście-wyjście |
Komunikacja pomiędzy aplikacją a sterownikami urządzeń |
Dynamiczna wymiana danych (DDE, Dynamic Data Exchange) |
Wymiana danych pomiędzy aplikacjami. Funkcje DDE są obsługiwane przez bibliotekę interfejsu użytkownika (user32.dll) oraz bibliotekę DDEML (Dynamic Data Exchange Management Library) |
Biblioteki dynamiczne (DLL, Dynamic Link Library) |
Obsługa dynamicznego (w trakcie pracy aplikacji) ładowania bibliotek DLL |
Obsługa komunikatów o błędach |
Wyprowadzanie informacji o błędach (np. funkcje FlashWindow() czy MessageBeep()) |
Rejestracja zdarzeń |
Rejestracja informacji o zdarzeniach w systemowym dzienniku zdarzeń (event log) |
Odwzorowywanie plików |
Odwzorowywanie zawartości plików w pamięci wirtualnej |
Obsługa plików |
Odczytywanie i zapisywanie plików w urządzeniach pamięci masowej |
Uchwyty i obiekty |
Obsługa tworzenia i manipulowania tzw. uchwytami i obiektami, reprezentującymi zasoby systemu i umożliwiającymi ich prawidłowe wykorzystanie |
Usługi systemu pomocy |
Funkcje związane z systemem pomocy Windows. Funkcje te są de facto obsługiwane przez bibliotekę interfejsu użytkownika (user32.dll), jednak Microsoft oficjalnie umieszcza je w grupie usług systemowych |
Komunikacja międzyprocesowa (IPC, Interprocess Communications) |
Mechanizmy umożliwiające wymianę danych pomiędzy procesami. Podsystem IPC obejmuje obsługę odwzorowywania plików, współużytkowania pamięci, potoki (anonimowe i nazwane), tzw. szczeliny wysyłkowe oraz obsługę schowka |
Obsługa „długich” liczb całkowitych |
Całkowitoliczbowa arytmetyka 64-bitowa |
Szczeliny wysyłkowe |
Obsługa tzw. szczelin wysyłkowych (ang. mailslot), umożliwiających jednokierunkową wymianę danych pomiędzy aplikacjami |
Zarządzanie pamięcią |
Obsługa przydzielania i wykorzystania pamięci |
Potoki |
Obsługa potoków (ang. pipe) - mechanizmu IPC pozwalającego procesom na wzajemną wymianę informacji |
Przenośne pliki wykonywalne (PE, Portable Executable) |
Zarządzanie i udostępnianie obrazów w pamięci tzw. przenośnych plików wykonywalnych (funkcje zaimplementowane w bibliotece IMAGEHLP.DLL) |
Zarządzanie energią |
Funkcje i komunikaty związane z podsystemem zarządzania energią |
Procesy i wątki |
Obsługa pracy wielozadaniowej i uruchamiania procesów; tworzenie wątków i procesów potomnych aplikacji oraz zarządzanie nimi |
Rejestr systemowy |
Zarządzanie, odczyt i zapis danych w rejestrze Windows (ang. registry), zawierającym informacje o konfiguracji elementów systemu |
Bezpieczeństwo |
Udostępnianie bądź blokowanie aplikacjom i użytkownikom dostępu do obiektów i zasobów (niektóre funkcje z tej grupy zaimplementowano w bibliotece zaawansowanych rozszerzeń API, advapi32.dll) |
Usługi |
Obsługa zautomatyzowanych procesów (usług, ang. service), w których programy bądź sterowniki urządzeń wykonują swoje zadania automatycznie, bez komunikowania się z użytkownikiem. Zarządzanie usługami realizowane jest przez program Menedżera usług (Service Control Manager, SCM); niektóre funkcje z tej grupy zaimplementowano w bibliotece advapi32.dll) |
Operacje na łańcuchach |
Kopiowanie, porównywanie, sortowanie, formatowanie i konwersja łańcuchów znakowych; manipulowanie danymi typu znakowego |
Obsługa wyjątków strukturalnych |
Niezależna od języka i narzędzia programistycznego obsługa tzw. wyjątków oraz zamykania procesów |
Synchronizacja wątków |
Mechanizmy wykorzystywane przez wątki do sterowania dostępem do zasobów |
Informacje o systemie |
Funkcje udostępniające informacje o systemie (nazwa komputera, użytkownika, wartości zmiennych środowiskowych, dane o procesorze, ustawienia kolorów itd.) |
Komunikaty systemowe |
Obsługa powiadamiania aplikacji i sterowników o zmianach stanu i parametrów urządzeń |
Zamykanie systemu |
Zamykanie sesji użytkownika i kończenie pracy systemu |
Obsługa pamięci taśmowych |
Funkcje obsługi napędów pamięci taśmowych (zapis, odczyt, formatowanie, odczyt informacji o taśmie i napędzie), wykorzystywane przez aplikacje wykonujące kopie zapasowe |
Data i czas |
Ustawianie i odczyt daty i czasu dla plików oraz samego systemu |
Obsługa stacji roboczych i pulpitu |
Obsługa wywołań funkcji GDI32 i USER32 bez względu na bieżące konto użytkownika |
Dostępna w obecnej wersji Windows biblioteka user32.dll zawiera 745 funkcji związanych z działaniem jądra systemu i obsługujących wyżej wspomniane podgrupy funkcjonalne. Pełną listę funkcji systemowych dostępnych w danej wersji biblioteki można uzyskać za pomocą opisanego wcześniej programu narzędziowego impdef. Należy przy tym pamiętać, że niektóre z funkcji systemowych zostały zaimplementowane w innych bibliotekach, m.in. user32.dll, imagehlp.dll czy advapi32.dll.
Interfejs GDI
Interfejs urządzeń graficznych GDI (Graphics Device Interface) zapewnia możliwość rysowania i drukowania zawartości okien. Obsługujące go funkcje, zawarte w bibliotece gdi32.dll, umożliwiają m.in. kreślenie linii, wyprowadzanie tekstu, manipulowanie czcionkami oraz zarządzanie kolorami.
Jednym z podstawowych elementów GDI jest tzw. kontekst urządzenia (ang. device context, DC). Reprezentuje on strukturę danych, obejmującą zestaw obiektów graficznych, ich atrybutów oraz trybów wyprowadzania. Tworzenie kontekstów realizują funkcje GetDC() i CreateDC(); oprócz nich biblioteka GDI definiuje wiele innych funkcji stosowanych intensywnie do obsługi kontekstów urządzeń. Programista ma do dyspozycji siedem typów obiektów GDI, które można wybrać w kontekście urządzenia; zestawiono je w tabeli 14.3.
Tabela 14.3. Obiekty --> GDI[Author:ts]
Obiekt |
Zastosowanie |
Mapa bitowa (bitmap) |
Kopiowanie i przemieszczanie fragmentów zawartości ekranu |
Pędzel (brush) |
Malowanie i wypełnianie obszarów wielokątów, elips i ścieżek |
Czcionka (font) |
Określanie kształtu, rozmiaru i atrybutów znaków wyprowadzanego tekstu |
Paleta (palette) |
Ustalanie zestawu używanych kolorów |
Ścieżka (path) |
Kreślenie i malowanie |
Pióro (pen) |
Kreślenie linii |
Obszar (region) |
Obcinanie, wyznaczanie kształtu i inne operacje związane z obszarem rysowania |
Obecne wersje Windows udostępniają 334 funkcje interfejsu GDI. Pełną listę funkcji zaimplementowanych w bibliotece gdi32.dll można uzyskać za pomocą opisanego wcześniej programu narzędziowego impdef.
Funkcje GDI są niezwykle użyteczne w przypadku wyprowadzania danych graficznych w formie dwuwymiarowej, np. w programach przeznaczonych do użytku biurowego. Większość z nich ma swoje odpowiedniki w postaci klas TImage i TCanvas, dostępnych w bibliotece VCL. Ponieważ VCL-owa implementacja funkcji i obiektów GDI jest bardzo dobrze dopracowana, podczas tworzenia aplikacji w systemie C++Builder nie ma najczęściej potrzeby bezpośredniego odwoływania się do funkcji Windows.
Najpoważniejszym mankamentem interfejsu GDI - niezależnie od tego, czy odwołujemy się do niego bezpośrednio, czy też za pośrednictwem klas VCL - jest (delikatnie mówiąc) mierna wydajność operacji graficznych. Próby wykorzystania wywołań GDI do wyświetlania szybko zmieniających się obrazów często prowadzą zarówno programistów, jak i użytkowników do skrajnej frustracji: prędkość odświeżania wyświetlanego na ekranie obrazka jest mizerna, nawet przy użyciu najlepszych kart graficznych. Na szczęście Microsoft oferuje projektantom alternatywę w postaci interfejsu programowego DirectDraw, wchodzącego w skład pakietu DirectX SDK i zapewniającego znacznie lepsze osiągi w zakresie grafiki dwuwymiarowej. Rozwiązanie to należy polecić wszystkim osobom zainteresowanym tworzeniem wydajnych mechanizmów prezentacji grafiki 2D. Chociaż interfejs DirectX uznawany jest za rozszerzenie Win32 API (obsługiwany jest zarówno w systemach Windows 9x, jak i NT/2000), jego omawianie przekracza zakres niniejszego rozdziału. Więcej informacji na ten temat można natomiast znaleźć w rozdziale 16.
Obsługa multimediów
Elementy prezentacji multimedialnych, jak np. dźwięk i sekwencje wideo, można znaleźć w coraz większej liczbie nowoczesnych aplikacji, co bez wątpienia poszerza możliwości komunikowania się z użytkownikiem. Rozszerzenia multimedialne opracowane przez Microsoft dla systemu Windows obejmują m.in.: bibliotekę Multimedia System Library (mmsystem. --> dll[Author:ts] ), bibliotekę obsługi zapisów wideo Microsoft Video for Windows (msvfw32.dll) oraz bibliotekę obsługi kompresji dźwięku Microsoft Audio Compression Manager Library (msacm32.dll). Chociaż obsługa multimediów nie była początkowo elementem systemu Win32, ewolucja Windows spowodowała konieczność uzupełnienia odpowiednich funkcji. Można wręcz powiedzieć, że współczesne odmiany Windows są z praktycznego punktu widzenia „urządzeniami multimedialnymi” tak samo, jak np. telewizor czy wieża stereo (właściwie należałoby powiedzieć „kombajnami”, gdyż Windows łączy w sobie funkcje wszystkich typowych urządzeń do odtwarzania dźwięku i obrazu - przyp. tłum.). System Windows obsługuje większość urządzeń i formatów zapisu danych multimedialnych, w tym urządzenia MIDI, spróbkowane zapisy dźwięku oraz pliki wideo. Większość podstawowych funkcji multimedialnych skupiono w trzech bibliotekach: mmsystem.dll, msvfw32.dll oraz msacm32.dll, zaś sprawną i stosunkowo prostą obsługę gier oraz odtwarzania dźwięku i obrazu zapewnia multimedialny interfejs programowy DirectX. W tym rozdziale skupimy się na funkcjach udostępnianych przez wspomniane wyżej „systemowe” biblioteki dynamiczne; odpowiadające im pliki nagłówkowe noszą nazwy digitalv.h, mciavi.h, msystem.h, msacm.h, vcr.h i vfw.h. Usługi realizowane przez biblioteki multimedialne opisano w tabeli 14.4 (w nawiasach podano nazwę biblioteki implementującej daną usługę).
Tabela 14.4. Usługi multimedialne w systemie Windows
Usługa |
Opis |
Menedżer kompresji dźwięku (Audio Compression Manager, ACM) |
Udostępnia funkcje systemowe umożliwiające kompresję, dekompresję, filtrowanie i konwersję danych dźwiękowych (msacm32.dll). |
Miksery dźwiękowe |
Umożliwiają sterowanie przepływem danych dźwiękowych z urządzeń rejestrujących i do urządzeń odtwarzających, a także regulację głośności i innych efektów dźwiękowych (mmsystem.dll). |
Funkcje AVICap |
Umożliwiają rejestrację sekwencji wideo; obsługują interfejsy urządzeń rejestrujących obrazy oraz dźwięk w postaci spróbkowanej; zarządzają zapisywaniem strumieni danych wideo na dysku (msvfw32.dll). |
Funkcje i makrodefinicje AVIFile |
Obsługują dostęp do plików w formacie AVI (Audio-Video Interleaved) (msvfw32.dll). |
Funkcje DrawDib |
Umożliwiają szybkie wyświetlanie map bitowych typu DIB, (Device Independent Bitmap) z pominięciem funkcji GDI (msvfw32.dll). |
Obsługa joysticków |
Zapewnia obsługę joysticków i innych dodatkowych urządzeń wejściowych, wykorzystujących bezwzględne układy współrzędnych, jak np. pióra świetlne, ekrany i tabliczki dotykowe (mmsystem.dll). |
Klasa MCIWnd |
Definiuje klasę okna przeznaczoną do sterowania urządzeniami multimedialnymi; umożliwia łatwe dołączanie do aplikacji funkcji zapisu i odtwarzania danych multimedialnych (msvfw32.dll). |
Interfejs sterowania mediami (Media Control Interface, MCI) |
Implementuje niezależną od sprzętu warstwę sterowania odtwarzaniem (odtwarzacze spróbkowanych zapisów dźwięku, odtwarzacze CD, MIDI oraz wideo) i zapisem danych multimedialnych (mmsystem.dll). |
Obsługa plików multimedialnych |
Realizuje buforowany i niebuforowany dostęp do plików multimedialnych oraz obsługę plików w formacie RIFF (Resource Interchange File Format), zawierających m.in. próbki dźwiękowe i sekwencje wideo (mmsystem.dll). |
Zegary |
Udostępniają zegary o wysokiej rozdzielczości, sterujące funkcjami multimedialnymi (mmsystem.dll). |
Interfejs MIDI (Musical Instruments Digital Interface) |
Udostępnia tzw. maper MIDI (MIDI Mapper), tłumaczący i sterujący przepływem komunikatów MIDI oraz realizujący inne usługi związane z tym interfejsem, m.in. identyfikację urządzeń, rejestrowanie, zarządzanie i sterowanie przepływem danych. Usługi MIDI mogą być łączone z funkcjami interfejsu MCI, realizującymi sekwencery MIDI (mmsystem.dll). |
Menedżer kompresji wideo (Video Compression Manager, VCM) |
Realizuje kompresję i dekompresję danych wizyjnych (msvfw32.dll). |
Obsługa dźwięku w postaci spróbkowanej |
Udostępnia funkcje obsługujące odtwarzanie i rejestrację dźwięku w postaci spróbkowanej (mmsystem.dll). |
Trzy wymienione powyżej biblioteki dynamiczne zawierają w sumie ponad 240 funkcji obsługujących interfejs multimedialny. Tak jak poprzednio, do uzyskania pełnej listy funkcji zawartych w plikach mmsystem.dll, msvfw32.dll i msacm32.dll można posłużyć się dostarczanym w pakiecie C++Builder programem narzędziowym impdef. Biblioteki importowe dla omawianych tu plików DLL zawarte są odpowiednio w plikach: winmm.lib, vfw32.lib i msacm32.lib.
Standardowe elementy interfejsu użytkownika
System Windows udostępnia programistom kolekcję --> standardowych [Author:ts] elementów sterujących, zawartą w bibliotece comctl32.dll. Druga biblioteka w omawianej grupie, comdlg32.dll, zawiera z kolei zestaw standardowych okien dialogowych. Wykorzystanie obu bibliotek ułatwia projektantom aplikacji szybkie tworzenie interfejsu użytkownika, zwalniając ich z konieczności żmudnego tworzenia odpowiednich elementów we własnym zakresie. Co prawda C++Builder udostępnia większość standardowych elementów interfejsu użytkownika w postaci odpowiednich komponentów VCL, jednak znajomość zestawu elementów sterujących i okien dialogowych udostępnianych poprzez interfejs Win32 jest dla programisty sprawą dość istotną. Oficjalna liczba elementów zawartych w wersji 4.71 biblioteki comctl32.dll (udostępnionej wraz z przeglądarką Internet Explorer 4.0) wynosi 22. Szczegółowe informacje na ich temat można uzyskać, np. analizując dostarczany w pakiecie C++Builder plik nagłówkowy commctrl.h. Poszczególne elementy opisano w tabeli 14.5 (dla ułatwienia w pierwszej kolumnie podano również angielskie nazwy komponentów).
Tabela 14.5. Standardowe elementy interfejsu użytkownika
Element |
Opis |
Odpowiednik w bibliotece VCL |
Okienko animacji (Animation Control) |
Implementuje prosty odtwarzacz plików AVI (bez możliwości odtwarzania dźwięku). |
TAnimate |
Rozszerzona lista rozwijana ComboBoxEx* |
Implementuje listę rozwijaną, obsługującą elementy graficzne (ikony). |
brak |
Selektor daty i czasu (Date/Time Picker) |
Wyświetla okno służące do wyświetlania i wprowadzania daty i czasu. |
TDateTimePicker |
Lista obsługująca przeciąganie (Drag List Box) |
Implementuje listę, umożliwiającą zmianę kolejności elementów przez przeciąganie. |
brak |
Płaski pasek przewijania (Flat Scroll Bar)* |
Implementuje d --> wuwymiarowy [Author:ts] („płaski”) pasek przewijania (tworzony poprzez wywołanie funkcji InitializeFlatSB()). |
brak |
Nagłówek kolumny (Header Control) |
Umożliwia tworzenie nagłówków kolumn tekstowych lub liczbowych, pozwalających na zmianę szerokości. |
THeaderControl |
Klawisz szybkiego dostępu (Hotkey Control) |
Obsługuje definiowanie klawiszy i kombinacji klawiszy szybkiego dostępu (skrótu). |
THotKey |
Lista obrazków (Image List) |
Implementuje indeksowaną listę obrazków o jednakowych rozmiarach (tworzoną za pomocą wywołania funkcji ImageListCreate()). |
TImageList |
Pole adresu IP* (IP Address) |
Umożliwia łatwe wprowadzanie adresów IP. |
brak |
Widok listy (List View) |
Umożliwia prezentowanie zawartości listy w postaci dużych lub małych ikon, listy lub widoku szczegółowego (ang. report view). |
TListView |
Kalendarz miesięczny (Monthly Calendar) |
Implementuje graficzną reprezentację kalendarza, umożliwiającą przeglądanie i wybieranie dat. |
TMonthlyCalendar |
Strona przewijana (Page Scroller) |
Umożliwia utworzenie przewijalnego okna, zawierającego okno potomne (np. inny element interfejsu), którego rozmiary nie pozwalają wyświetlić go w całości. |
TPageScroller |
Wskaźnik postępu (Progress Bar) |
Implementuje pasek, informujący o stanie zaawansowania operacji. |
TProgressBar |
Arkusz właściwości (Property Sheet) |
Wyświetla (i pozwala modyfikować) grupę właściwości w formie karty wybieranej za pomocą zakładki. |
TPageControl |
Elastyczny pasek narzędzi (Rebar, CoolBar) |
Implementuje pasek narzędzi złożony z „taśm”, zawierających oprócz elementów interfejsu także tło, etykiety oraz uchwyty, umożliwiające zmianę rozmiarów. |
TCoolBar |
Pasek statusu (Status Bar) |
Umożliwia wyświetlanie tekstowych i graficznych informacji o stanie aplikacji (przeważnie ma postać paska „przyklejonego” do krawędzi okna). |
TStatusBar |
Zakładka (Tab Control) |
Umożliwia zdefiniowanie i umieszczenie w oknie karty, wybieranej przez kliknięcie zakładki (podobnie jak np. w katalogu bibliotecznym). |
TTabControl |
Pasek narzędzi (Toolbar) |
Pozwala na grupowanie przycisków w obrębie pojedynczego panelu. |
TToolBar |
Etykietka (Tooltip Control) |
Umożliwia wyświetlanie „dymków” z podpowiedziami po wskazaniu danego komponentu myszą. |
THintWindow |
Suwak (Trackbar) |
Implementuje wyskalowany suwak, pozwalający na zmianę wartości całkowitoliczbowej w zadanym zakresie. |
TTrackBar |
Widok drzewa (Tree View Control) |
Umożliwia wyświetlenie w oknie hierarchicznej struktury (drzewa) elementów, reprezentowanych w postaci opisów i opcjonalnych ikon. |
TTreeView |
Pokrętło (Up-Down Control) |
Implementuje --> przycisk [Author:ts] w formie dwóch strzałek (nieco myląco nazywany pokrętłem), umożliwiający zmianę wartości całkowitoliczbowej. |
TUpDown |
* Elementy wprowadzone w przeglądarce Internet Explorer 4.0
O ile nie zaznaczono inaczej, wymienione powyżej elementy tworzy się za pomocą wywołania funkcji InitCommonControlsEx() lub CreateWindowEx(), przekazując jej stałe odpowiadające żądanym właściwościom. Biblioteka dynamiczna comctl32.dll zawiera łącznie 82 funkcje związane z obsługą omawianych tu (i innych) elementów, dostępnych w większości pod postacią odpowiednich komponentów VCL. Pełną listę funkcji związanych z obsługą standardowych elementów interfejsu użytkownika w systemie Win32 można uzyskać np. za pomocą opisanego wcześniej programu narzędziowego impdef.
Oprócz standardowych elementów sterujących (kontrolek) Microsoft udostępnia programistom także kilka powszechnie używanych okien dialogowych. Obecnie (tj. w wersji 4.00.950 biblioteki comdlg32.dll) jest ich osiem; każde z nich posiada swój VCL-owy odpowiednik w systemie C++Builder.
Tabela 14.6. Odpowiedniki standardowych okien dialogowych Win32 w bibliotece VCL
Okno |
Funkcja Win32 API |
Odpowiednik VCL |
Wybór koloru |
ChooseColor() |
TColorDialog |
Wybór czcionki |
ChooseFont() |
TFontDialog |
Wyszukiwanie tekstu |
FindText |
TFindDialog |
Otwarcie pliku |
--> GetOpenFileName[Author:ts] () |
TOpenDialog |
Zapisanie pliku |
GetSaveFileName() |
TSaveDialog |
Ustawienia wydruku |
PageSetupDlg() |
TPrinterSetupDialog |
Drukowanie |
PrintDlg() |
TPrintDialog |
Wyszukiwanie i zamiana tekstu |
ReplaceText() |
TReplaceDialog |
Więcej informacji na temat standardowych okien dialogowych dostępnych poprzez interfejs Win32 można uzyskać, przeglądając plik commdlg.h, wchodzący w skład pakietu C++Builder, jak również generując plik definicji (.def) dla biblioteki comdlg32. --> dll[Author:ts] .
Elementy i funkcje powłoki systemu
Termin „powłoka” (ang. shell) oznacza w systemie Windows program umożliwiający użytkownikowi grupowanie, uruchamianie i zarządzanie innymi programami. Funkcje udostępniane przez powłokę obejmują m.in.: obsługę mechanizmu „przesuń i upuść”, zarządzanie skojarzeniami (ang. file associations), pozwalającymi automatycznie wyszukiwać i uruchamiać aplikacje, pobieranie ikon z plików itd. Niezwykle obszerne możliwości powłoki realizowane są przez funkcje zawarte w bibliotece shell32.dll, zaś odwoływanie się do nich wymaga włączenia do kodu źródłowego pliku shellapi.h. Podstawowe mechanizmy dostępne przez interfejs powłoki przedstawiono w tabeli 14.7.
Tabela 14.7. Podstawowe funkcje dostępne przez interfejs programowy powłoki
Funkcja lub mechanizm |
Opis |
Mechanizm „przesuń i upuść” |
Umożliwia wybranie pliku lub grupy plików w oknie Eksploratora (także Menedżera plików w systemach Windows 3.x), a następnie przeciągnięcie ich za pomocą myszy i „upuszczenie” w oknie programu akceptującego taki sposób wymiany danych (wymaga to wcześniejszego wywołania funkcji DragAcceptFiles()). Do aplikacji docelowej przesyłany jest komunikat WM_DROPFILES, umożliwiający ustalenie nazw plików i położenia kursora myszy w chwili ich „upuszczenia”. Służą do tego odpowiednio funkcje powłoki DragQueryFile() i DragQueryPoint(). |
Skojarzenia plików |
W zamierzchłych czasach systemów Windows 3.x Menedżer programów udostępniał użytkownikowi funkcję Skojarz i odpowiednie okno dialogowe. Pozwalało ono związać rozszerzenie nazwy (czyli typ) pliku z odpowiednią aplikacją. Począwszy od systemu Windows 95, służy do tego nowe, znacznie obszerniejsze okno Open With (Otwórz za pomocą), zaś informacje o skojarzeniach rozszerzeń i aplikacji zawarte są w rejestrze. Dwukrotne kliknięcie ikony pliku, dla którego zdefiniowano skojarzenie, powoduje automatyczne załadowanie go do odpowiedniego programu. Związane z tym operacje wykonują funkcje API FindExecute() i ShellExecute(). Nazwę i uchwyt aplikacji skojarzonej z danym typem pliku zwraca z kolei funkcja FindExecutable(). Funkcje ShellExecute() i ShellExecuteEx() umożliwiają oprócz otwierania także drukowanie pliku, uruchamiając odpowiednią aplikację na podstawie rozszerzenia nazwy. |
Pobieranie ikon z plików |
Aplikacje, biblioteki dynamiczne i pliki ikon reprezentowane są za pomocą jednej lub kilku ikon. Aby uzyskać uchwyt ikony zawartej w takim pliku, wystarczy wywołać funkcję powłoki ExtractIcon(). |
Obecne wersje systemu Windows udostępniają 120 funkcji powłoki (w tym wiele nieudokumentowanych). Szczegółową ich listę można uzyskać za pomocą programu narzędziowego impdef, wchodzącego w skład pakietu C++Builder. Niektóre z nieudokumentowanych i mniej znanych funkcji powłoki omówimy w dalszej części rozdziału.
Obsługa ustawień regionalnych
Interfejs Win32 zawiera elementy usprawniające projektowanie aplikacji przeznaczonych dla środowisk innych niż anglojęzyczne. Do reprezentowania znaków diakrytycznych oraz symboli (np. technicznych) służy w Windows alfabet Unicode, w którym znaki reprezentowane są w postaci 16-bitowej. Lokalizację programu (tj. tworzenie wersji przeznaczonej na rynek danego kraju) ułatwiają także funkcje obsługi języków (National Language Support, NLS). Mechanizmy obsługi ustawień regionalnych zestawiono w tabeli 14. --> 8[Author:AG] .
Mechanizm |
Opis |
Alfabety użytkownika (End-User Defined Character Sets, EUDC) |
Alfabety użytkownika umożliwiają tworzenie własnych znaków, niedostępnych w standardowych zestawach czcionek monitora i drukarki. System EUDC umożliwia obsługę alfabetów azjatyckich, m.in. chińskiego i japońskiego. |
Edytor metod wprowadzania danych (Input Method Editor, IME) |
Mechanizm ten, zawarty w bibliotece imm32.dll, udostępnia zestaw funkcji, komunikatów i struktur umożliwiających wprowadzanie danych zapisanych w alfabecie Unicode oraz kodowanych dwubajtowo. |
Obsługa języków (National Language Support, NLS) |
Podsystem NLS umożliwia tłumaczenie aplikacji i przystosowywanie ich do wymagań określonych środowisk geograficznych i językowych. |
Unicode i inne alfabety |
Dostępne w Windows mechanizmy obsługi alfabetów, w tym alfabetu Unicode, umożliwiają elastyczne kodowanie znaków, co pozwala łatwo budować aplikacje wielojęzyczne. |
Usługi sieciowe
Funkcje należące do tej grupy umożliwiają wymianę danych pomiędzy programami oraz komunikację pomiędzy komputerami połączonymi za pomocą sieci. Służą one do tworzenia i zarządzania odwołaniami do współużytkowanych zasobów sieciowych, takich jak katalogi i drukarki. W skład podsystemu usług sieciowych wchodzą: Sieć Windows (Windows Networking), interfejs NetBIOS, protokoły Windows Sockets, RAS (Remote Access Service), SNMP (Simple Network Management Protocol) oraz funkcje sieciowej dynamicznej wymiany danych (Network DDE). Opisano je krótko w tabeli 14.9.
Tabela 14.9. Usługi sieciowe w systemie Win32
Element |
Opis |
Sieć Windows (Windows Networking, WNet) |
Udostępnia zestaw niezależnych od sieci funkcji, umożliwiających aplikacji komunikowanie się z urządzeniami sieciowymi. Funkcje te zawarto w bibliotece mpr.dll, tworzącej sieciowy interfejs systemu Win32; ich nazwy rozpoczynają się od przedrostka WNet. |
Zaadaptowane funkcje programu LAN Manager |
Program LAN Manager, zaprojektowany dla systemu OS/2, realizował podstawowe funkcje sieciowego systemu operacyjnego. Obecnie nie jest on już uznawany za element systemu Win32, jednak wiele spośród jego funkcji nadal wchodzi w skład biblioteki netapi32.dll. |
Interfejs NetBIOS |
Interfejs NetBIOS (Network Basic Input/Ouptut System) umożliwia komunikowanie się poprzez sieć z programami uruchomionymi na innych komputerach. Realizujące go funkcje zawarto w pliku netapi32.dll. |
Sieciowa dynamiczna wymiana danych (Network DDE) |
Mechanizm ten umożliwia wymianę danych w protokole DDE (Dynamic Data Exchange) poprzez połączenia sieciowe. Odpowiednie funkcje zaimplementowane są w pochodzącej z systemu Windows for Workgroups bibliotece nddeapi.dll i posiadają przedrostek NDde. |
Dostęp zdalny (RAS) |
Funkcje RAS umożliwiają użytkownikom zdalne łączenie się z siecią komputerową za pośrednictwem serwerów dostępu zdalnego, w tym także poprzez połączenia komutowane (modemowe). Skupiono je w bibliotece rasapi32.dll, a ich nazwy poprzedzone są przedrostkiem --> Ras[Author:ts] . |
Prosty protokół zarządzania siecią (SNMP) |
Protokół ten, oparty na standardowym internetowym protokole SNMP, umożliwia wymianę informacji i poleceń związanych z administrowaniem siecią. Dodatkowe informacje na jego temat można znaleźć w plikach pomocy Windows. |
Interfejs Windows Sockets (WinSock) |
Interfejs ten pośredniczy w wymianie danych z użyciem protokołów TCP i UDP. Jego konstrukcja oparta jest na modelu gniazd (ang. sockets), pochodzącym z systemu UNIX firmy Berkeley Software Distribution. Elementy interfejsu WinSock zawarto w pliku wsock32.dll (WinSock 1.1) i ws2_32.dll (WinSock 2.0). |
Budowa i działanie aplikacji dla Windows
Przedstawiony przed chwilą podział funkcji Win32 stanowi podstawę do zrozumienia budowy API i oferowanych przezeń możliwości. Zanim jednak zagłębimy się w analizę praktycznych przykładów, podamy jeszcze kilka podstawowych reguł, rządzących współpracą programu z systemem operacyjnym Windows. Ich przyswojenie jest niezbędnym krokiem, jaki powinien w pierwszej kolejności wykonać programista, pragnący korzystać z funkcji Win32. Na kolejnych stronach zajmiemy się zatem budową i działaniem programu przeznaczonego dla systemu Windows.
Funkcja WinMain()
Funkcja WinMain(), związana ściśle z podsystemem zarządzania oknami i aplikacjami, jest najważniejszą funkcją programu przeznaczonego dla systemu Win32. Czytelnicy mający pewne doświadczenie z systemem C++Builder, zdążyli już zapewne zlokalizować ją w malutkim pliku źródłowym, znanym jako główny plik źródłowy projektu (project source file). Funkcja WinMain(), należąca formalnie do zestawu funkcji Win32 API, jest obowiązkowym elementem każdego programu przeznaczonego dla Windows. Jest ona wywoływana w momencie uruchamiania programu, stanowiąc dlań „punkt wejścia”. Odpowiednikiem WinMain() w klasycznych aplikacjach tekstowych, tworzonych w ANSI C, jest znana programistom funkcja main(). Główny plik źródłowy projektu, zawierający funkcję WinMain(), jest generowany automatycznie, co w zasadzie zwalnia programistę od zajmowania się szczegółami jej konstrukcji. Zdarzają się jednak przypadki, gdy umiejętność „podrasowania” WinMain() i uzupełnienia jej o dodatkowe możliwości okazuje się --> cenna[Author:ts] .
Wyjaśnienie struktury funkcji WinMain() jest chyba dobrym miejscem do rozpoczęcia dyskusji o zasadach działania Windows i przeznaczonych dlań aplikacji. Deklaracja funkcji WinMain(), przedstawiona poniżej, jest zawsze identyczna, niezależnie od narzędzia użytego do stworzenia programu:
WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
Pierwszy parametr funkcji określa uchwyt modułu identyfikujący daną (właśnie uruchamianą) aplikację. Uchwyt taki można uzyskać za pomocą wywołania funkcji Windows GetModuleHandle(), zaś samymi uchwytami, wykorzystywanymi przez system do zarządzania aplikacjami i procesami, zajmiemy się za chwilę.
Drugi parametr funkcji jest zaszłością z czasów systemu Win16; jego obecność w Win32 API ma zapewne na celu umożliwienie przenoszenia starszych aplikacji 16-bitowych do 32-bitowych wersji Windows. W systemie Win16 parametr ten identyfikował poprzednią kopię (ang. instance) aplikacji, o ile takowa została wcześniej uruchomiona. W Win32 jego wartość wynosi zawsze NULL, bowiem każdy program uruchamiany jest we własnej, chronionej przestrzeni adresowej, a zatem stanowi niezależny proces. Co za tym idzie, w obrębie danej przestrzeni nie mogą jednocześnie pracować dwie kopie procesu, a zatem pojęcie poprzedniej kopii nie ma sensu. Nie oznacza to, że programu nie da się uruchomić w kilku egzemplarzach, jednak każde z takich wywołań utworzy proces niezależny od pozostałych. Zalecanym sposobem sprawdzenia, czy dana aplikacja została uruchomiona uprzednio, jest wykorzystanie w funkcji WinMain() tzw. muteksu (tworzonego za pomocą funkcji --> CreateMutex[Author:ts] ()).
Trzeci parametr funkcji WinMain() jest wskaźnikiem do łańcucha, zawierającego wiersz polecenia, który można wykorzystać do przekazania programowi argumentów wywołania. Wartości przekazane w wierszu polecenia można pobierać na wiele sposobów. Jednym z nich jest wywołanie funkcji Win32 GetCommandLine() i „wyłuskanie” parametrów i ich wartości ze zwróconego przez nią łańcucha. C++Builder udostępnia też prostszą alternatywę w postaci funkcji ParamCount() i ParamStr(), odpowiadających parametrom argc i argv[], znanym z używanej w aplikacjach tekstowych funkcji main().
Czwarty i ostatni parametr funkcji WinMain() określa początkowy stan okna uruchamianej aplikacji. Jego wartości przedstawiono w tabeli 14.10 za elektroniczną dokumentacją Windows SDK.
Tabela 14.10. Stałe określające początkowy stan okna w funkcji WinMain()
Stała |
Znaczenie |
SW_HIDE |
Nakazuje wyświetlenie okna w postaci ukrytej i aktywację innego okna. |
SW_MINIMIZE |
Nakazuje wyświetlenie okna w postaci zminimalizowanej i uaktywnienie --> następnego [Author:ts] okna najwyższego poziomu z systemowej listy okien. |
SW_RESTORE |
Nakazuje uaktywnienie i wyświetlenie okna; jeśli było ono zminimalizowane lub ukryte, zostanie wyświetlone w pierwotnych rozmiarach i położeniu (tak samo, jak stała SW_SHOWNORMAL). |
SW_SHOW |
Nakazuje uaktywnienie okna i wyświetlenie go w bieżącym położeniu. |
SW_SHOWMAXIMIZED |
Wymusza wyświetlenie okna w postaci zmaksymalizowanej. |
SW_SHOWMINIMIZED |
Wymusza wyświetlenie okna w postaci ikony (zminimalizowanej). |
SW_SHOWMINNOACTIVE |
Nakazuje wyświetlenie okna w postaci ikony; pozostawia uprzednio aktywne okno w stanie aktywnym. |
SW_SHOWNA |
Nakazuje wyświetlenie okna w bieżącym stanie; pozostawia uprzednio aktywne okno w stanie aktywnym. |
SW_SHOWNOACTIVATE |
Nakazuje wyświetlenie okna w ostatnio zapamiętanych rozmiarach i położeniu; pozostawia uprzednio aktywne okno w stanie aktywnym. |
SW_SHOWNORMAL |
Nakazuje uaktywnienie i wyświetlenie okna, przywracając jego rozmiary, jeśli uprzednio było zminimalizowane lub zmaksymalizowane (tak samo, jak stała SW_RESTORE). |
Uchwyty okien
Pojęcie uchwytu (ang. handle), wprowadzone przed chwilą przy okazji funkcji WinMain(), jest jednym z kluczowych elementów interfejsu Win32 (i ogólnie programowania w Windows - przyp. tłum.). Uchwyty, będące „wewnętrznie” 32-bitowymi liczbami całkowitymi bez znaku, wykorzystywane są do identyfikacji praktycznie wszystkich zasobów Windows. Przykładami mogą być tu: moduły, procesy, wątki, okna, menu, mapy bitowe, ikony, kursory i obiekty reprezentujące kolory. Użycie uchwytów pozwala na zarządzanie i manipulowanie różnymi obiektami, jak np. oknami czy procesami potomnymi; umożliwia również przekazywanie danych do innych aplikacji dzięki wywołaniom zwrotnym. Z pojęciem uchwytu spotkamy się wielokrotnie w prezentowanych w niniejszym rozdziale przykładach.
Komunikaty
Mechanizm wymiany komunikatów (ang. message) umożliwia przekazywanie danych do obiektów reprezentowanych za pomocą uchwytów. W systemie Windows zdefiniowano liczne komunikaty standardowe, których mogą używać zarówno aplikacje, jak i sam system operacyjny. Przykładem mogą być komunikaty generowane przez system w odpowiedzi na kliknięcie lub przesunięcie kursora myszy czy też zmianę rozdzielczości ekranu. Komunikaty takie przesyłane są do aplikacji, sygnalizując zaistnienie określonych zdarzeń. Zastosowania komunikatów obejmują obsługę danych wejściowych, zmiany ustawień systemu oraz przesyłanie informacji pomiędzy programami. Aby móc skutecznie korzystać z mechanizmu wymiany komunikatów, należy znać uchwyty obiektów, dla których są one przeznaczone.
Funkcje SendMessage() i PostMessage()
Dystrybucja komunikatów może być realizowana na wiele sposobów. Do najpopularniejszych metod przesyłania komunikatów wewnątrz aplikacji należy użycie funkcji SendMessage() i PostMessage(). Pierwsza z nich przesyła dany komunikat do określonego okna (identyfikowanego uchwytem) lub wszystkich okien najwyższego poziomu, nie zwracając sterowania do wywołującego programu, dopóki wysłany komunikat nie zostanie przetworzony przez odbiorcę. Funkcja PostMessage() działa podobnie, jednak nie czeka na przetworzenie komunikatu i natychmiast po jego wysłaniu zwraca sterowanie do wywołującej aplikacji.
Obie funkcje wykorzystują cztery parametry. Są to uchwyt okna docelowego, identyfikator komunikatu (określający jego charakter) oraz dwie 32-bitowe wartości, będące parametrami samego komunikatu i niosące w razie potrzeby dodatkową informację przeznaczoną dla jego odbiorcy (np. wartość danej, adres itp.). Wynik przetworzenia komunikatu, zwracany przez obie funkcje, jest wartością typu LRESULT. Przykładowo zwrócenie przez PostMessage() wartości niezerowej oznacza udane umieszczenie komunikatu w kolejce. Szczegółowe informacje na ten temat można znaleźć w dokumentacji API.
LRESULT SendMessage(
HWND hWnd, // uchwyt okna-odbiorcy
UINT Msg, // identyfikator komunikatu
WPARAM wParam, // pierwszy parametr
LPARAM lParam // drugi parametr
);
Identyfikatory komunikatów
W systemie Windows zdefiniowano ponad 200 stałych, określających identyfikatory komunikatów. Są one intensywnie używane w procesach wymiany --> danych [Author:ts] pomiędzy aplikacjami i systemem operacyjnym. Oprócz identyfikatorów systemowych można także tworzyć własne stałe, przy czym zaleca się użycie do tego celu funkcji RegisterWindowMessage() (podsystem zarządzania oknami), gwarantującej niepowtarzalność definiowanego identyfikatora.
Nazwy identyfikatorów systemowych rozpoczynają się od przedrostka WM_. Kilka popularniejszych identyfikatorów przedstawiono poniżej.
WM_PAINT nakazuje ponowne wyrysowanie (odświeżenie) zawartości okna;
WM_QUIT nakazuje zakończenie pracy aplikacji;
WM_LBUTTONDBLCLK sygnalizuje dwukrotne kliknięcie lewym przyciskiem myszy;
WM_LBUTTONDOWN sygnalizuje wciśnięcie lewego przycisku myszy;
WM_LBUTTONUP sygnalizuje zwolnienie lewego przycisku myszy;
WM_MBUTTONDBLCLK sygnalizuje dwukrotne kliknięcie środkowym przyciskiem --> myszy[Author:ts] ;
WM_MBUTTONDOWN sygnalizuje wciśnięcie środkowego przycisku myszy;
WM_MBUTTONUP sygnalizuje zwolnienie środkowego przycisku myszy;
WM_RBUTTONDBLCLK sygnalizuje dwukrotne kliknięcie prawym przyciskiem myszy;
WM_RBUTTONDOWN sygnalizuje wciśnięcie prawego przycisku myszy;
WM_RBUTTONUP sygnalizuje zwolnienie prawego przycisku myszy;
WM_NCHITTEST sygnalizuje poruszenie kursora myszy, naciśnięcie lub zwolnienie jej przycisku;
WM_KEYDOWN sygnalizuje naciśnięcie klawisza przy zwolnionym klawiszu Alt;
WM_TIMER oznacza „tyknięcie” programowanego zegara systemowego (timera).
Obsługa komunikatów
W celu obsłużenia komunikatów wysyłanych za pomocą funkcji SendMessage() lub PostMessage() aplikacje posługują się na ogół specjalnymi tablicami obsługi komunikatów (mapami komunikatów, ang. event response table). Tablicę taką można utworzyć na kilka sposobów; w systemie C++Builder najczęściej deklaruje się w tym celu zestaw funkcji zwrotnych i mapę komunikatów, umieszczając je w sekcji chronionej deklaracji klasy formularza głównego. Oto przykład:
protected:
// funkcje zwrotne obsługujące komunikaty
void __fastcall Process_wmPaint(TMessage &);
void __fastcall Process_wmQuit(TMessage &);
void __fastcall Process_wmXYZ(TMessage &);
BEGIN_MESSAGE_MAP
MESSAGE_HANDLER(WM_PAINT, TMessage, Process_wmPaint);
MESSAGE_HANDLER(WM_QUIT, TMessage, Process_wmQuit);
MESSAGE_HANDLER(WM_XYZ, TMessage, Process_wmXYZ);
END_MESSAGE_MAP(TForm);
Zawarta w kodzie źródłowym definicja funkcji obsługi komunikatu mogłaby wyglądać następująco:
void __fastcall TForm1::Process_wmXYZ(TMessage Msg)
{
int fromhandle = LOBYTE(LOWORD(Msg.WParam));
int infoid = Msg.LParam;
AnsiString StatusText =
"Odebrano komunikat XYZ\nUchwyt nadawcy :" +
IntToStr(fromhandle) + "\nParametr = " + IntToStr(infoid);
MessageBox(Handle, StatusText, "Obsługa komunikatu", MB_OK);
}
W systemie C++Builder komunikaty opisywane są typem strukturalnym TMessage, mającym następującą postać:
struct TMessage
{
Cardinal Msg;
union
{
struct
{
Word WParamLo;
Word WParamHi;
Word LParamLo;
Word LParamHi;
Word ResultLo;
Word ResultHi;
};
struct
{
int WParam;
int LParam;
int Result;
};
};
};
Komunikaty przesyłane za pomocą funkcji SendMessage() lub PostMessage() zawierają zwykle dodatkowe informacje, umieszczane w parametrach WParam i LParam. Należy pamiętać, że oba parametry mogą zawierać adresy, wskazujące obszar pamięci dostępny dla odbiorcy komunikatu, o ile wykorzystuje on ten sam chroniony obszar pamięci co nadawca (w systemie Win16 poszczególne procesy mogły swobodnie „sięgać” do pamięci innych procesów; Win32 zapewnia ściślejszą ochronę pamięci). Przekazanie adresu (wskaźnika) pozwala udostępnić odbiorcy komunikatu więcej danych, niż dałoby się zawrzeć w 32 bitach parametru LParam.
Praktyczne przykłady użycia funkcji Win32
Najlepszym sposobem na poznanie metod wykorzystania funkcji Win32 w aplikacjach pisanych w C++Builderze jest analiza praktycznych przykładów. Na kolejnych stronach przedstawimy zatem kilka najważniejszych i najpopularniejszych rozwiązań, pozwalających na tworzenie solidnych aplikacji przeznaczonych dla systemu Windows. Zajmiemy się również kilkoma nieudokumentowanymi funkcjami API oraz nowymi elementami wprowadzonymi w systemie Windows 2000.
Uruchomienie programu z innego programu
Możliwość uruchomienia programu z innego programu (utworzenia procesu potomnego) bywa bardzo przydatna. Operację tę można zrealizować za pomocą kilku funkcji Win32, m.in.: WinExec(), LoadModule(), CreateProcess(), ShellExecute() i ShellExecuteEx().
Ulubioną metodą „weteranów” programowania w Windows jest funkcja WinExec(), jednak Microsoft nie zaleca jej używania (dotyczy to również LoadModule()), sugerując w zamian wykorzystanie CreateProcess() (która notabene wywoływana jest wewnętrznie przez obie wspomniane funkcje). WinExec() i LoadModule(), jako funkcje pochodzące jeszcze z czasów systemu Win16, mogą zostać usunięte z przyszłych wersji API.
Inną metodę udostępniają ShellExecute()i ShellExecuteEx(), należące do grupy funkcji powłoki. Określają one sposób wywołania programu na podstawie danych zawartych w rejestrze Windows, przy czym ShellExecuteEx() dysponuje nieco większymi możliwościami niż ShellExecute(). Przykłady użycia funkcji CreateProcess() i ShellExecuteEx() przedstawimy w dalszej części rozdziału.
Wykorzystanie funkcji CreateProcess() do lokalizacji uchwytów okien
Zalecaną przez Microsoft metodą uruchamiania programu z wnętrza innej aplikacji jest wywołanie funkcji CreateProcess(). Niestety, jest to również sposób najbardziej złożony, chociaż posiada kilka znaczących zalet w stosunku do pozostałych rozwiązań. Załóżmy np., że zależy nam nie tylko na uruchomieniu kilku procesów potomnych, ale także zarządzaniu nimi - jeśli np. okno procesu macierzystego zostanie zminimalizowane, okna procesów potomnych powinny zachować się w taki sam sposób; zamknięcie procesu macierzystego powinno spowodować zakończenie pracy procesów potomnych itd. Zarządzanie aplikacjami uruchomionymi jako potomne wymaga ustalenia ich uchwytów, których znajomość pozwoli nam przesyłać odpowiednie komunikaty (systemowe lub własne), sterujące ich działaniem. Można w tym celu wykorzystać właśnie funkcję CreateProcess(), co przedstawiono w poniższym przykładzie, ilustrującym uruchomienie procesu potomnego i ustalenie jego uchwytu:
unsigned long processid = RunProgramEx(progName, cmdLine);
// tu można poczekać kilkaset milisekund
if (processid != 0)
{
HWND winhandle = LookForWindowHandle(processid);
GetWindowText(winhandle, windowtitle, 80);
MessageBox(NULL, windowtitle, "Uruchomiono proces potomny", MB_OK);
}
Użyta tutaj funkcja RunProgramEx() jest zdefiniowaną przez programistę funkcją, służącą do uruchomienia zadanej aplikacji (poprzez wywołanie CreateProcess()) i zwracającą uchwyt utworzonego procesu. Druga funkcja, LookForWindowHandle(), została również zdefiniowana w kodzie programu, a jej zadaniem jest przejrzenie listy aktywnych okien i znalezienie tego, które posiada zadany identyfikator. Po zlokalizowaniu okna można już uzyskać jego uchwyt. Definicje obu funkcji zawierają kilka wywołań API, które opiszemy poniżej.
GetWindowText() to funkcja Win32 zwracająca nagłówek (tekst zawarty w pasku tytułu) okna. Uzyskany w ten sposób tekst wyświetlany jest za pomocą innej funkcji API, MessageBox(), w celu powiadomienia użytkownika o uruchomieniu nowego programu.
Przyjrzyjmy się wzmiankowanej powyżej funkcji RunProgramEx(), „opakowującej” wywołanie CreateProcess():
Wydruk 14.1. Funkcja RunProgramEx()
unsigned long RunProgramEx (char* file, char* parameters)
{
STARTUPINFO StartupInfo;
ZeroMemory(&StartupInfo, sizeof(STARTUPINFO));
PROCESS_INFORMATION ProcessInfo;
StartupInfo.cb = sizeof(STARTUPINFO);
if(CreateProcess(file,
parameters,
NULL,
NULL,
TRUE,
NORMAL_PRIORITY_CLASS,
NULL,
NULL,
&StartupInfo,
&ProcessInfo))
{
// Uchwyty utworzone przez funkcję CreateProcess() należy zamknąć.
// Można to zrobić w dowolnym momencie, np. teraz.
CloseHandle(ProcessInfo.hProcess);
CloseHandle(ProcessInfo.hThread);
return (unsigned long)ProcessInfo.dwProcessId;
}
return 0;
}
Definicja funkcji RunProgramEx() zawiera kilka odwołań do API, w tym wywołania funkcji ZeroMemory(), CreateProcess() i CloseHandle(). Kluczowym punktem jest tu wywołanie CreateProcess(). Jak sama nazwa wskazuje, funkcja ta tworzy nowy proces, uruchamiając zadany plik wykonywalny i tworząc dlań nowy wątek. Nazwę pliku i argumenty wywołania przekazujemy jako parametry funkcji RunProgramEx(). Struktura StartupInfo, przekazana jako parametr wywołania funkcji CreateProcess(), zawiera informacje, określające sposób utworzenia głównego okna uruchamianej aplikacji. Do zainicjalizowania zawartości struktury służy wywołanie funkcji API ZeroMemory(), wypełniającej zadany blok pamięci zerami. Typ STARTUPINFO, określający zawartość struktury, opisano szczegółowo w dokumentacji API.
Wywołanie funkcji CreateProcess() powoduje umieszczenie informacji w polach struktury ProcessInfo informacji o utworzonym procesie i jego głównym wątku. Typ strukturalny PROCESS_INFORMATION, definiujący postać struktury ProcessInfo, przedstawiono poniżej:
typedef struct _PROCESS_INFORMATION {
HANDLE hProcess;
HANDLE hThread;
DWORD dwProcessId;
DWORD dwThreadId;
} PROCESS_INFORMATION;
Identyfikator procesu, zwracany przez funkcję RunProgramEx(), pochodzi z pola dwProcessId struktury. Wykorzystujemy go do określenia uchwytów okien należących do uruchomionej aplikacji, co realizuje funkcja LookForWindowHandle():
HWND LookForWindowHandle(unsigned long processid)
{
if (!EnumWindows((WNDENUMPROC)GetWinHandle,processid))
return swProcess;
else
return 0;
}
LookForWindowHandle() to kolejne „opakowanie”, wykorzystujące funkcję Win32 EnumWindows(). Przegląda ona iteracyjnie wszystkie okna najwyższego poziomu i wywołuje dla nich funkcję zwrotną GetWinHandle(), identyfikowaną pierwszym parametrem (zobacz wydruk 14.2).
Wydruk 14.2. Identyfikacja okna z użyciem funkcji GetWinHandle()
HWND swProcess;
BOOL CALLBACK GetWinHandle(HWND hwnd, unsigned long hproc)
{
char windowtitle[80];
char classname[80];
GetWindowLong(hwnd, GWL_HINSTANCE);
unsigned long dwProcessId; // adres identyfikatora procesu
GetWindowThreadProcessId(
hwnd,
&dwProcessId); // adres identyfikatora procesu
if (dwProcessId != hproc)
return true;
GetWindowText( hwnd, windowtitle, 80);
if (windowtitle[0] == NULL)
return true;
GetClassName(hwnd, classname, 80);
int ptr = strcmp(classname, "TApplication");
if (ptr == 0)
return true;
swProcess = hwnd;
return FALSE; // koniec przeglądania - uchwyt znaleziony
}
Funkcja ta ustala identyfikator procesu na podstawie uchwytu utworzonego przezeń okna, zwracając informację o dopasowaniu lub niedopasowaniu do wywołującej ją funkcji EnumWindows(). Zwrócenie przez EnumWindows() wartości niezerowej oznacza udane zodentyfikowanie okna, którego uchwyt zostaje zwrócony przez funkcję LookForWindowHandle().
Wykorzystanie funkcji ShellExecuteEx()
Prostszą metodę uruchomienia programu udostępnia funkcja ShellExecuteEx(), wchodząca w skład grupy funkcji powłoki (zobacz wydruk 14.3). Jest ona prostsza w użyciu niż CreateProcess(), nie udostępnia jednak uchwytu utworzonego przez siebie procesu. Jej użycie ilustruje funkcja RunProgram(), przedstawiona poniżej.
Wydruk 14.3. Uruchomienie aplikacji za pomocą funkcji ShellExecuteEx()
int RunProgram (char* file, char* parameters)
{
// Ustal sposób wywołania programu
SHELLEXECUTEINFO execinfo ;
memset (&execinfo, 0, sizeof (execinfo)) ;
execinfo.cbSize = sizeof (execinfo) ;
execinfo.lpVerb = "open" ;
execinfo.lpFile = file;
execinfo.lpParameters = parameters;
execinfo.fMask = SEE_MASK_NOCLOSEPROCESS ;
execinfo.nShow = SW_SHOWDEFAULT ;
// Uruchom program
if (! ShellExecuteEx (&execinfo))
{
char data[100];
sprintf(data, "Nie mogę uruchomić: '%s'", file);
MessageBox(NULL, data, "Błąd!", MB_OK);
return -1;
}
return 0;
}
Parametrem funkcji ShellExecuteEx() jest struktura execinfo typu SHELLEXECUTEINFO. Najważniejsze jej elementy to nazwa polecenia, w naszym przypadku open (otwórz), oraz nazwa pliku (tj. otwieranego dokumentu, nie samego programu - przyp. tłum.). Możliwe jest również przekazanie uruchamianemu programowi dodatkowych argumentów. Funkcje ShellExecute() i ShellExecuteEx() wykorzystują skojarzenia plików, tj. ustalają uruchamiany program na podstawie rozszerzenia nazwy otwieranego pliku. Do funkcji powłoki powrócimy w dalszej części rozdziału.
Podstawy obsługi plikowych operacji wejścia-wyjścia
Obsługa plikowych operacji wejścia-wyjścia, jak np. odczytywania i zapisywania danych na dysku, jest niezwykle istotnym elementem programu. Wymianę danych pomiędzy programem a pamięcią masową realizują takie funkcje Win32, jak: CreateFile(), ReadFile() i WriteFile(). Omówimy je na kilku przykładach.
CreateFile()
Funkcja CreateFile() tworzy obiekt obsługujący wejście-wyjście i zwraca jego uchwyt. Jej zastosowania nie ograniczają się wyłącznie do plików - można jej także używać do tworzenia: potoków, strumieni związanych z konsolami, szczelin wysyłkowych i innych zasobów wykorzystywanych w komunikacji międzyprocesowej (IPC). Obecnie przedstawimy ją w zastosowaniu „plikowym”, jednak po oswojeniu się z jej użyciem warto spróbować wykorzystać ją do tworzenia innych obiektów związanych z IPC.
Deklaracja funkcji CreateFile() jest następująca:
HANDLE CreateFile(
LPCTSTR lpFileName, // wskaźnik do nazwy tworzonego zasobu
DWORD dwDesiredAccess,// tryb dostępu (odczyt, zapis itd.)
DWORD dwShareMode, // tryb współdzielenia zasobu
LPSECURITY_ATTRIBUTES lpSecurityAttributes,// atryb. bezpieczeństwa
DWORD dwCreation, // sposób utworzenia
DWORD dwFlagsAndAttributes, // atrybuty zasobu
HANDLE hTemplateFile // uchwyt szablonu atrybutów
);
Parametr lpFileName wskazuje na zakończony zerem łańcuch, zawierający nazwę tworzonego obiektu; maksymalna długość łańcucha określona jest stałą MAX_PATH i wynosi 260 znaków.
Parametr dwDesiredAccess określa tryb dostępu do obiektu (odczyt, zapis lub sprawdzenie). Dozwolone tryby dostępu opisano w tabeli 14.11.
Tabela 14.11. Tryby dostępu do obiektu tworzonego funkcją CreateFile()
Stała |
Znaczenie |
0 |
Dostęp do sprawdzenia (ang. query) - możliwość sprawdzenia atrybutów urządzenia bez fizycznego odwoływania się do niego |
GENERIC_READ |
Dostęp do odczytu - możliwość odczytywania danych i modyfikacji położenia wskaźnika plikowego. W połączeniu z wartością GENERIC_WRITE umożliwia odczytywanie i zapisywanie danych |
GENERIC_WRITE |
Dostęp do zapisu - możliwość zapisywania danych i modyfikacji położenia wskaźnika plikowego. W połączeniu z wartością GENERIC_READ umożliwia odczytywanie i zapisywanie danych |
Parametr dwShareMode określa tryb współużytkowania tworzonego obiektu. Opisano go w tabeli 14.l2.
Tabela 14.12. Tryby współużytkowania obiektu
Stała |
Znaczenie |
0 |
Blokada współużytkowania pliku |
FILE_SHARE_READ |
Możliwość wielokrotnego otwarcia pliku do odczytu (wartości tej należy użyć, otwierając szczelinę wysyłkową po stronie odbiorcy) |
FILE_SHARE_WRITE |
Możliwość wielokrotnego otwarcia pliku do zapisu |
Parametr lpSecurityAttributes wskazuje strukturę typu SECURITY_ATTRIBUTES, określającą atrybuty bezpieczeństwa tworzonego pliku. Jego użycie ma sens tylko w systemie Windows NT, który obsługuje zabezpieczenia plików i katalogów na woluminach NTFS. W pozostałych przypadkach jest on ignorowany.
Parametr dwCreation określa sposób postępowania w przypadku, gdy tworzony plik już istnieje (lub nie istnieje). Opisano go w tabeli 14.13.
Tabela 14.13. Wartości parametru dwCreation
Stała |
Znaczenie |
CREATE_NEW |
Utworzenie nowego pliku; jeśli plik już istniał, wywołanie funkcji kończy się niepowodzeniem |
CREATE_ALWAYS |
Utworzenie nowego pliku; jeśli plik już istniał, jest niszczony |
OPEN_EXISTING |
Otwarcie istniejącego pliku; jeśli plik nie istniał, wywołanie funkcji kończy się niepowodzeniem |
OPEN_ALWAYS |
Otwarcie pliku; jeśli plik nie istniał, jest tworzony (jak w przypadku użycia wartości CREATE_NEW) |
TRUNCATE_EXISTING |
Otwarcie pliku z usunięciem zawartości (wyzerowaniem długości pliku); jeśli plik nie istniał, wywołanie funkcji kończy się niepowodzeniem. Wymaga użycia stałej GENERIC_WRITE |
Parametr dwFlagsAndAttributes określa atrybuty i opcje tworzenia pliku. Wartości opisanych w tabeli 14.14 można używać w dowolnych kombinacjach, pamiętając, że użycie którejkolwiek z nich automatycznie wyłącza atrybut FILE_ATTRIBUTE_NORMAL.
Tabela 14.14. Atrybuty i opcje tworzenia pliku
Stała |
Znaczenie |
FILE_ATTRIBUTE_ARCHIVE |
Ustawienie atrybutu archiwizacji pliku (atrybut ten używany jest do zaznaczania plików przeznaczonych do archiwizacji lub usunięcia) |
FILE_ATTRIBUTE_NORMAL |
Utworzenie pliku bez atrybutów (stała ta jest ignorowana w przypadku użycia którejkolwiek z pozostałych wartości) |
FILE_ATTRIBUTE_HIDDEN |
Utworzenie pliku ukrytego |
FILE_ATTRIBUTE_READONLY |
Utworzenie pliku tylko do odczytu |
FILE_ATTRIBUTE_SYSTEM |
Utworzenie pliku systemowego (pliki takie powinny być użytkowane przez system operacyjny) |
FILE_ATTRIBUTE_TEMPORARY |
Utworzenie pliku tymczasowego |
FILE_ATTRIBUTE_ATOMIC_WRITE |
Zarezerwowana |
FILE_ATTRIBUTE_XACTION_WRITE |
Zarezerwowana |
FILE_ATTRIBUTE_OFFLINE |
Zaznaczenie pliku jako tymczasowo niedostępnego (dane zostały fizycznie przeniesione na inne urządzenie pamięci masowej) |
FILE_ATTRIBUTE_COMPRESSED |
Utworzenie pliku (lub katalogu) skompresowanego. Dla pliku oznacza to włączenie mechanizmu kompresji danych; dla katalogu - nakazuje domyślne użycie kompresji dla wszystkich tworzonych w jego obrębie plików |
FILE_FLAG_WRITE_THROUGH |
Wymuszenie zapisu do pliku z pominięciem buforów dyskowych (ang. write-through) |
FILE_FLAG_OVERLAPPED |
Otwarcie pliku w trybie dostępu asynchronicznego (ang. overlapped mode). W trybie tym wywołania funkcji: ReadFile(), WriteFile(), ConnectNamedPipe() i TransactNamedPipe(), z natury czasochłonne, wykonywane są asynchronicznie, tj. zwracają natychmiast do wywołującego programu wartość FALSE i ustawiają kod błędu ERROR_IO_PENDING. Właściwe zakończenie wykonywania operacji sygnalizowane jest aktywacją określonego obiektu zdarzenia. Użycie tej stałej wymusza wykonywanie zapisu i odczytu z pliku w trybie asynchronicznym, tj. z użyciem dodatkowej struktury typu OVERLAPPED. W takiej sytuacji położenia w pliku określa się nie za pomocą wskaźnika plikowego, lecz odpowiednich pól struktury OVERLAPPED, przekazywanej w wywołaniach funkcji ReadFile() i WriteFile() |
FILE_FLAG_NO_BUFFERING |
Otwarcie pliku z pominięciem dodatkowych mechanizmów buforujących |
FILE_FLAG_RANDOM_ACCESS |
Otwarcie pliku do dostępu swobodnego |
FILE_FLAG_SEQUENTIAL_SCAN |
Otwarcie pliku do dostępu sekwencyjnego |
FILE_FLAG_DELETE_ON_CLOSE |
Wymuszenie usunięcia pliku natychmiast po zamknięciu wszystkich jego uchwytów (nie tylko uchwytu aktualnie otwieranego) |
FILE_FLAG_BACKUP_SEMANTICS |
Otwarcie lub utworzenie pliku na potrzeby wykonania lub odtworzenia kopii zapasowej |
FILE_FLAG_POSIX_SEMANTICS |
Otwarcie pliku zgodnego z regułami nazewnictwa standardu POSIX |
W przypadku użycia funkcji CreateFile() do otwarcia nazwanego potoku po stronie klienta parametr dwFlagsAndAttributes może zawierać również stałe określające zakres uprawnień serwera do wykonywania operacji w kontekście klienta (parametr ten nosi nazwę Security Quality of Service, SQOS). Użycie w wywołaniu parametru SECURITY_SQOS_PRESENT pozwala na wykorzystanie dodatkowych wartości, opisanych w tabeli 14.15.
Tabela 14.15. Poziomy uprawnień serwera do wykonywania operacji w kontekście klienta
Stała |
Znaczenie |
SECURITY_ANONYMOUS |
Zabrania serwerowi „wcielania się” w klienta. |
SECURITY_IDENTIFICATION |
Umożliwia serwerowi korzystanie z tożsamości i uprawnień klienta, ale zabrania „wcielania się” w niego. |
SECURITY_IMPERSONATION |
Umożliwia serwerowi „udawanie” klienta w obrębie systemu lokalnego. |
SECURITY_DELEGATION |
Umożliwia serwerowi „udawanie” klienta w systemach zdalnych. |
SECURITY_CONTEXT_TRACKING |
Wymusza dynamiczne śledzenie kontekstu bezpieczeństwa klienta; w razie jej braku śledzenie jest statyczne (tj. serwer uzyskuje informację o kontekście bezpieczeństwa klienta tylko jednorazowo - przyp. tłum.). |
SECURITY_EFFECTIVE_ONLY |
Ogranicza dostęp serwera do kontekstu bezpieczeństwa klienta, udostępniając tylko elementy aktywne (domyślnie dostępne są wszystkie elementy). Pozwala to ograniczyć zakres grup i uprawnień, których serwer może użyć do „wcielania się” w klienta. |
Parametr hTemplateFile jest uchwytem tzw. pliku szablonu (ang. template file), zawierającego informację o dodatkowych atrybutach tworzonego pliku. Jest on niedostępny w systemie Windows 95 (można go używać tylko w Windows NT). Plik szablonu musi być otwarty do odczytu (z użyciem stałej GENERIC_READ).
Użycie funkcji CreateFile() jest względnie proste, co widać w poniższym przykładzie. Wywołanie funkcji zwraca uchwyt otwartego pliku.
HANDLE aFile;
aFile = CreateFile("FILE.TXT", GENERIC_READ|GENERIC_WRITE, 0, NULL,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
Plik FILE.TXT jest tu otwierany do odczytu i zapisu, nie może być współużytkowany, przed otwarciem musi już istnieć (w przeciwnym przypadku wywołanie zakończy się niepowodzeniem) i nie ma żadnych atrybutów.
ReadFile()
Funkcja ReadFile() odczytuje blok danych z pliku, począwszy od bieżącego położenia wskaźnika plikowego, a następnie aktualizuje ten ostatni o liczbę odczytanych bajtów. Jeżeli dostęp do pliku realizowany jest w trybie „normalnym” (tj. nie asynchronicznym), do zmiany wartości wskaźnika plikowego można wykorzystać funkcję SetFilePointer(). W przeciwnym przypadku aplikacja musi sama aktualizować wartość wskaźnika, odwołując się do odpowiednich pól struktury wskazywanej przez parametr lpOverlapped (zobacz niżej).
Deklaracja funkcji ReadFile() wygląda następująco:
BOOL ReadFile(
HANDLE hFile, // uchwyt czytanego pliku
LPVOID lpBuffer, // adres bufora docelowego
DWORD nNumberOfBytesToRead, // liczba bajtów do przeczytania
LPDWORD lpNumberOfBytesRead, // adres liczby przeczytanych bajtów
LPOVERLAPPED lpOverlapped // adres struktury danych dla dostępu
// asynchronicznego
);
Parametr hFile jest uchwytem pliku, z którego należy pobrać dane. Odczytane dane umieszczane są w buforze wskazywanym przez parametr lpBuffer, zaś żądaną liczbę bajtów określa się za pomocą parametru nNumberOfBytesToRead.
Parametr lpOverlapped wskazuje na strukturę typu OVERLAPPED, związaną z asynchronicznych dostępem do pliku (w naszych przykładach nie korzystamy z tej metody). Jeżeli podczas otwarcia pliku podano parametr FILE_FLAG_OVERLAPPED, dane zostaną odczytane lub zapisane do pliku na pozycji danej odpowiednim polem struktury lpOverlapped, zaś funkcja (WriteFile() lub ReadFile()) może zwrócić sterowanie do wywołującego jeszcze przed zakończeniem operacji. W takiej sytuacji zwracana jest wartość FALSE, zaś wywołanie funkcji GetLastError() powinno zwrócić kod ERROR_IO_PENDING. Mechanizm ten pozwala na dostęp do pliku „w tle”, umożliwiając aplikacji wykonywanie innych zadań. Zakończenie odczytu lub zapisu sygnalizowane jest aktywacją obiektu zdarzenia, identyfikowanego polem hEvent struktury OVERLAPPED.
Jeżeli podczas otwarcia pliku nie podano stałej FILE_FLAG_OVERLAPPED, a wartość lpOverlapped jest różna od NULL, odczyt lub zapis danych rozpoczyna się od pozycji określonej polami Offset i OffsetHigh struktury OVERLAPPED, zaś funkcja (ReadFile() lub WriteFile()) zwraca sterowanie do wywołującego programu dopiero po zakończeniu całej operacji.
W przypadku powodzenia odczytu ReadFile() zwraca wartość TRUE; w przeciwnym razie - FALSE.
Struktura typu OVERLAPPED zawiera informacje wykorzystywane w asynchronicznych operacjach zapisu i odczytu danych z plików. Opisano ją krótko poniżej; dodatkowe informacje można znaleźć w pliku pomocy C++Buildera, poświęconym interfejsowi Win32.
typedef struct _OVERLAPPED {//o
DWORD Internal;
DWORD InternalHigh;
DWORD Offset;
DWORD OffsetHigh;
HANDLE hEvent;
}OVERLAPPED;
Pola Internal i InternalHigh zarezerwowano do użytku systemu operacyjnego. Pierwsze z nich zawiera zależny od systemu kod stanu, ustawiany w sytuacji, gdy wywołanie funkcji GetOverlappedResult() nie powoduje ustawienia kodu błędu ERROR_IO_PENDING. Zawartość pola InternalHigh jest prawidłowa tylko w przypadku zwrócenia przez funkcję GetOverlappedResult wartości TRUE; określa ona wówczas liczbę bajtów przesłanych w trakcie operacji zapisu lub odczytu.
Pole Offset określa położenie początku zapisywanych lub odczytywanych danych (liczone w bajtach od początku pliku). Wartość tę należy ustawić przed wywołaniem funkcji ReadFile() lub WriteFile(); jest ona ignorowana w przypadku zapisywania i odczytywania danych z potoków oraz urządzeń komunikacyjnych (np. portów szeregowych).
Pole OffsetHigh jest uzupełnieniem Offset i określa bardziej znaczące 32 bity składające się na położenie danych w pliku. Również ono jest ignorowane w przypadku odczytu i zapisu danych z potoków i urządzeń komunikacyjnych.
Pole hEvent zawiera uchwyt obiektu zdarzenia, który ma przyjąć stan aktywny w momencie zakończenia przesyłania danych. Pole to musi zostać odpowiednio zainicjalizowane przez aplikację przed wywołaniem funkcji: ReadFile(), WriteFile(), ConnectNamedPipe() lub TransactNamedPipe().
WriteFile()
Do zapisywania danych w plikach służy funkcja WriteFile(). Położenie zapisywanych danych określa wskaźnik plikowy, aktualizowany po zapisie o liczbę umieszczonych w pliku bajtów. Podobnie jak dla funkcji ReadFile(), w przypadku otwarcia pliku z opcją FILE_FLAG_OVERLAPPED (dla prostoty nie będziemy jej tu używać) odpowiedzialność za aktualizację wskaźnika plikowego spoczywa na aplikacji.
Deklaracja funkcji WriteFile() przedstawia się następująco:
BOOL WriteFile(
HANDLE hFile, // uchwyt zapisywanego pliku
LPCVOID lpBuffer, // adres bufora źródłowego
DWORD nNumberOfBytesToWrite, // liczba bajtów do zapisania
LPDWORD lpNumberOfBytesWritten, // adres liczby zapisanych bajtów
LPOVERLAPPED lpOverlapped // adres struktury danych
// dla dostępu asynchronicznego
);
Podobnie jak poprzednio, parametr hFile jest uchwytem pliku, do którego zapisujemy dane, zaś lpBuffer - adresem bufora, który je zawiera. Wartość nNumberOfBytesToWrite określa liczbę bajtów, które należy zapisać w pliku, zaś lpNumberOfBytesWritten to adres zmiennej, w której zostanie umieszczona liczba faktycznie zapisanych bajtów. Parametr lpOverlapped to znany nam już wskaźnik do struktury typu OVERLAPPED, wykorzystywanej w przypadku otwarcia pliku w trybie asynchronicznym.
Funkcja WriteFile() zwraca wartość TRUE w przypadku pomyślnego wykonania zapisu; w przeciwnym razie zwracana jest wartość FALSE.
Przedstawimy teraz krótki przykład, demonstrujący zapisywanie i odczytywanie danych z pliku. Po uruchomieniu IDE i utworzeniu nowej aplikacji trzeba umieścić na głównym formularzu następujące elementy:
Tabela 14.16. Komponenty głównego formularza programu do zapisu i odczytu pliku
Komponent |
Właściwości |
Form1 |
Left = 235 |
Label1 |
Left = 9 |
Label2 |
Left = 8 |
Label5 |
Name = BufferLabel |
Label3 |
Left = 6 |
Label4 |
Left = 10 |
Edit1 |
Left = 7 |
Button1 |
Left = 13 |
Button2 |
Left = 200 |
Edit2 |
Left = 8 |
Tak utworzony projekt trzeba zapisać pod nazwą readwrite. Modułowi formularza należy nadać nazwę mainform.cpp i wstawić do niego następujące definicje:
Wydruk 14.4. Przykłady użycia funkcji: CreateFile(), WriteFile() i ReadFile()
#include <vcl\vcl.h>
#include <stdio.h>
#pragma hdrstop
#include "mainform.h"
//---------------------------------------------------------------------------
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------
HANDLE hSrc;
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
char buffer[255];
DWORD bytes_written;
DWORD bytes_read;
hSrc = CreateFile(Edit1->Text.c_str(), GENERIC_WRITE, 0, NULL,
CREATE_ALWAYS, FILE_FLAG_RANDOM_ACCESS, 0);
if(hSrc == INVALID_HANDLE_VALUE)
{
Application->MessageBoxA("Błąd otwarcia pliku!", NULL, NULL);
return;
}
strcpy(buffer, Edit2->Text.c_str());
WriteFile(hSrc, buffer, strlen(buffer), &bytes_written, NULL);
CloseHandle(hSrc);
BufferLabel->Caption = buffer;
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button2Click(TObject *Sender)
{
BufferLabel->Caption = "";
char Buffer[256];
DWORD dwRead;
hSrc = CreateFile(Edit1->Text.c_str(), GENERIC_READ, 0, NULL,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
if(hSrc == INVALID_HANDLE_VALUE)
{
Application->MessageBoxA("Błąd otwarcia pliku!", NULL, NULL);
return;
}
if(ReadFile(hSrc, Buffer, 255, &dwRead, NULL) )
{
Buffer[dwRead] = 0;
BufferLabel->Caption = Buffer;
ShowMessage("Odczyt OK");
}
else
{ ShowMessage("Błąd odczytu"); }
CloseHandle(hSrc);
}
Istotnym elementem powyższego kodu jest zmienna hSrc (typu HANDLE), przechowująca uchwyt pliku. Funkcje obsługi zdarzeń, Button1Click() i Button2Click(), można wygenerować automatycznie, klikając dwukrotnie oba przyciski, co spowoduje samoczynne wpisanie odpowiedniego kodu do pliku źródłowego i nagłówkowego modułu (przepisując kod „ręcznie” należy wstawić deklaracje funkcji Button1Click() i Button2Click() do sekcji __published deklaracji klasy TForm1 w pliku mainform.h - przyp. tłum.).
Skompilowanie i uruchomienie naszego program powinno udowodnić, że zapisywanie i odczytywanie danych z pliku nie jest żadną filozofią.
Interfejs Win32 udostępnia programistom znacznie więcej funkcji obsługujących manipulowanie plikami. Warto tu wspomnieć m.in. o funkcjach MoveFile(), DeleteFile() oraz nowych funkcjach powłoki (zwanych skrótowo „SHx”), wprowadzonych w systemach Windows 95 oraz NT 4. Na początek zajmiemy się starszymi rozwiązaniami; w dalszej kolejności zaprezentujemy nowe funkcje powłoki realizujące zbliżone zadania.
MoveFile()
Jak można się domyślać, funkcja MoveFile() pozwala na przenoszenie plików lub katalogów w obrębie woluminu. Niestety, nie można wykorzystać jej do przenoszenia danych pomiędzy woluminami; dzieje się tak dlatego, iż proces „przenoszenia” plików i katalogów sprowadza się do zmiany ich nazw (przenoszenia wpisów w katalogach), zaś same dane pozostają fizycznie w tym samym miejscu.
Deklaracja funkcji MoveFile() jest następująca:
BOOL MoveFile(LPCTSTR lpszExisting, LPCTSTR lpszNew);
Parametr lpszExisting wskazuje na zakończony zerem łańcuch, zawierający nazwę źródłowego pliku lub katalogu; lpszNew to wskaźnik do nazwy nowej lokalizacji (plik lub katalog dany nową nazwą nie może istnieć). Funkcja zwraca wartość TRUE w przypadku powodzenia operacji; w przeciwnym razie zwracana jest wartość FALSE. Oto przykład:
MoveFile("C:\\Moje dokumenty\\Zdjecia",
"c:\\Moje dokumenty\\1999\\Zdjecia");
DeleteFile()
Działanie funkcji DeleteFile() jest analogiczne do znanej z ANSI C/C++ funkcji unlink() i sprowadza się do usunięcia pliku. W przypadku niepowodzenia operacji (plik nie daje się usunąć, jest aktualnie otwarty lub nie istnieje), DeleteFile() zwraca wartość FALSE.
Deklaracja funkcji DeleteFile() jest prosta:
BOOL DeleteFile(LPCSTR lpszFileName)
Parametr lpszFileName wskazuje na zakończony zerem łańcuch, zawierający nazwę usuwanego pliku. A oto przykład wywołania funkcji:
DeleteFile("c:\\Moje dokumenty\\Zdjecia\\impreza.gif");
CopyFile()
Jak sama nazwa wskazuje, funkcja CopyFile() służy do kopiowania plików. Kopiowanie powoduje powielenie atrybutów plików, nie dotyczy to jednak atrybutów bezpieczeństwa.
Deklaracja funkcji CopyFile() wygląda następująco:
bool CopyFile(LPCSTR lpszExistingfile, LPCSTR lpszNewFile,
BOOL bFailIfExisting);
Parametr lpszExsistingFile wskazuje na łańcuch zawierający nazwę kopiowanego pliku; nową nazwę określa parametr lpszNewFile. Parametr bFailIfExisting określa zachowanie funkcji w przypadku, gdy docelowy plik już istnieje. Nadanie mu wartości TRUE powoduje w takiej sytuacji niepowodzenie kopiowania i pozostawienie pliku docelowego w stanie nienaruszonym. Wartość FALSE umożliwia zastąpienie starego pliku kopią. Oto przykład:
CopyFile("plik.txt", "plik1.txt", TRUE);
CreateDirectory()
Wywołanie funkcji CreateDirectory() tworzy nowy katalog. Jeśli system operacyjny obsługuje mechanizmy bezpieczeństwa danych (są one dostępne w systemie Windows NT, nie można zaś z nich korzystać w Windows 95), katalog tworzony przez CreateDirectory() opatrywany jest odpowiednimi atrybutami.
Deklaracja funkcji CreateDirectory() ma następującą postać:
bool CreateDirectory(LPCSTR lpszPath,
LPSECURITYATTRIBUTES lpSecurityAttributes)
Parametr lpszPath wskazuje na zakończony zerem łańcuch, zawierający nazwę nowo tworzonego katalogu, zaś lpSecurityAttributes to wskaźnik do struktury zawierającej atrybuty bezpieczeństwa. Użycie drugiego parametru ma sens tylko w systemach obsługujących mechanizmy bezpieczeństwa danych. Oto przykład:
CreateDirectory("C:\\Moje dokumenty\\Wakacje", NULL);
Magiczne funkcje powłoki
Funkcje powłoki systemów Windows 98 i 2000 są dla programistów niezwykle atrakcyjnym narzędziem. Nowsze funkcje z tej grupy znane są potocznie jako „funkcje SHx”, bowiem nazwy wielu z nich poprzedzone są przedrostkiem SH. Wybrane funkcje - zarówno starsze, jak i wprowadzone w systemie Windows 2000 - opiszemy na kilku kolejnych stronach.
Warto zauważyć, że kilka wprowadzonych ostatnio funkcji zastąpiło starsze wywołania API, oferując większe możliwości. Co prawda w przypadku prostszych operacji nadal można korzystać ze „starych” funkcji, jednak ich nowe odpowiedniki są szybsze i wydajniejsze. Bywają także mocno kłopotliwe w użyciu, ale nikt nie powiedział, że programowanie w Windows jest łatwe…
Komunikacja z przeglądarką WWW
Coraz więcej danych we współczesnych systemach informacyjnych dostępnych jest poprzez sieć WWW. Dlatego też możliwość porozumiewania się aplikacji z systemową przeglądarką WWW okazuje się niekiedy bardzo przydatna. Możliwość taką dają nam właśnie wywołania funkcji Win32, obsługujących powłokę systemu. Przedstawiony poniżej przykład demonstruje użycie funkcji ShellExecute() do uruchomienia przeglądarki i załadowania do niej strony o zadanym adresie URL. Sama ShellExecute() jest nieco okrojoną wersją funkcji ShellExecuteEx(), którą omówiliśmy wcześniej.
int RunBrowser(char* URL)
{
if (! ShellExecute(NULL, "open", URL, NULL, NULL, SW_SHOW))
{
char data[100];
sprintf(data, "Nie mogę otworzyć strony '%s'", URL);
MessageBox(NULL, data, "Błąd!", MB_OK);
return -1;
}
return 0;
}
Drugi parametr funkcji ShellExecute() określa rodzaj wykonywanej operacji, w naszym przypadku otwarcie dokumentu (open). Parametr trzeci opisuje położenie dokumentu. Wewnętrzne mechanizmy powłoki „rozszyfrowują” podany adres URL i wywołują dlań domyślną systemową przeglądarkę WWW.
Funkcja SHFileOperation(), czyli operacje plikowe z dopalaczem
Funkcja SHFileOperation() jest uniwersalnym narzędziem służącym do manipulowania plikami. Nie jest ona co prawda łatwa do opanowania, jednak jej ogromne możliwości, w tym zdolność do kopiowania i przenoszenia większej liczby katalogów, są przedmiotem częstego zainteresowania programistów. O przewadze SHFileOperation() nad starszą funkcją MoveFile() stanowi m.in. możliwość przenoszenia plików i katalogów pomiędzy woluminami; potrafi ona także przenosić zawartość podkatalogów i zmieniać nazwy poszczególnych plików. Elastyczność i duże możliwości sprawiają, że funkcja SHFileOperation() jest szeroko wykorzystywana w systemie Windows 95; przykładem jej użycia może być kopiowanie całych katalogów lub opróżnianie Kosza.
Przykład zamieszczony na wydruku 14.5 ilustruje sposób kopiowania całych grup plików i katalogów za pomocą funkcji SHFileOperation().
Wydruk 14.5. Wykorzystanie funkcji SHFileOperation()
// Struktura typu SHFILEOPSTRUCT, zawierająca dane dla funkcji
// SHFileOperation
SHFILEOPSTRUCT op;
// Wyzeruj strukturę
ZeroMemory(&op, sizeof(op));
String RestoreDir, RestoreToDir;
// Zmienna RestoreDir będzie zawierała adres katalogu źródłowego
RestoreDir = "C:\\Pliki\\*.*";
// Ustal długość łańcucha i dodaj 1 RestoreDir.SetLength(RestoreDir.Length() + 1);
// Zakończ łańcuch zerem
RestoreDir[RestoreDir.Length()] == '\0';
// Dopisz jeszcze jedno zero (łańcuch kończy się dwoma znakami NUL)
RestoreDir.SetLength(RestoreDir.Length() + 1);
RestoreDir [RestoreDir.Length()] =='\0';
// Nazwa katalogu docelowego znajdzie się w zmiennej RestoreToDir.
// Tam umieszczone zostaną kopiowane pliki.
RestoreToDir = "C:\\TEMP";
// Dodaj końcowy lewy ukośnik i dwa znaki NUL
RestoreToDir = RestoreToDir + "\\";
RestoreToDir.SetLength(RestoreToDir.Length() + NULL + NULL);
op.hwnd = 0; // uchwyt okna informacyjnego
op.wFunc = FO_COPY; // kopiujemy pliki
op.pFrom = RestoreDir.c_str(); // katalog źródłowy
op.pTo = RestoreToDir.c_str(); // katalog docelowy
op.fFlags = FOF_FILESONLY; // kopiuj tylko pliki (gdy użyto *.*)
/* Wartość zwracana przez SHFileOperation() oznacza powodzenie lub niepowodzenie. Przypisanie jej do zmiennej copy_done pozwala powiadomić użytkownika o wyniku operacji - wykorzystamy do tego etykiety. */
// Wywołaj SHFileOperation i zapamiętaj zwróconą wartość
int copy_done = SHFileOperation(&op);
if (copy_done == 0)
{
if (op.fAnyOperationsAborted)
{
Label1->Caption = "Kopiowanie przerwane!";
}
else
{
Label1->Caption = "Kopiowanie udane!";
}
}
else
{
Label1->Caption = "Kopiowanie nieudane!";
}
Wykonanie powyższego kodu spowoduje skopiowanie wszystkich plików z katalogu c:\Pliki do katalogu c:\Temp.
Jak widać w poniższym fragmencie, funkcja SHFileOperation() wymaga, by łańcuch zawierający nazwę katalogu docelowego (RestoreDir) był zakończony dwoma zerami. Łatwo o tym zapomnieć, co zwykle kończy się błędem (funkcja zwraca informację o niemożności skopiowania pliku - przyp. tłum.).
// Zakończ łańcuch zerem
RestoreDir[RestoreDir.Length()] = '\0';
// Dopisz jeszcze jedno zero (łańcuch kończy się dwoma znakami NUL)
RestoreDir.SetLength(RestoreDir.Length() + 1);
RestoreDir [RestoreDir.Length()] = '\0';
Należy także pamiętać o zainicjalizowaniu struktury SHFILEOPSTRUCT (w naszym przykładzie zmienna op) przed wywołaniem funkcji SHFileOperation(). Struktura typu SHFILEOPSTRUCT zawiera kilka dość zróżnicowanych elementów; przedstawiono ją poniżej.
typedef struct _SHFILEOPSTRUCT {//shfos
HWND hwnd;
UINT wFunc;
LPCSTR pFrom;
LPCSTR pTo;
FILEOP_FLAGS fFlags;
BOOL fAnyOperationsAborted;
LPVOID hNameMappings;
LPCSTR lpszProgressTitle;
} SHFILEOPSTRUCT, FAR *LPSHFILEOPSTRUCT;
Na pierwszy rzut oka całość wydaje się dość złożona, toteż poświęcimy chwilę na jej objaśnienie.
Pole hwnd zawiera uchwyt okna dialogowego wykorzystywanego do wyświetlania informacji o przebiegu operacji. Pojęcie uchwytu okna jest nam już znane, toteż nie będziemy się tu nad nim rozwodzić.
Pole wFunc zawiera kod żądanej operacji. Dostępne kody operacji zestawiono w tabeli 14.17.
Tabela 14.17. Kody operacji dla funkcji SHFileOperation()
Stała |
Znaczenie |
FO_COPY |
Skopiowanie plików danych zawartością pola pFrom do miejsca danego polem pTo. Pliki można kopiować na inny wolumin |
FO_DELETE |
Usunięcie plików danych zawartością pola pFrom. Zawartość pola pTo jest w tym przypadku ignorowana. Dodatkowe informacje odnośnie przenoszenia plików do Kosza można znaleźć w tabeli 14.18 |
FO_MOVE |
Przeniesienie plików danych zawartością pola pFrom do miejsca określonego polem pTo. Operacja ta polega na fizycznym przeniesieniu zawartości plików, co pozwala przenosić je pomiędzy woluminami |
FO_RENAME |
Zmiana nazw plików danych zawartością pola pFrom |
Pole pFrom to wskaźnik do bufora zawierającego jedną lub więcej nazw plików. W przypadku użycia kilku nazw, należy rozdzielić je zerami (znakami NUL), zaś cała lista musi być zakończona dwoma zerami (wspomniano o tym nieco wyżej). Należy o tym pamiętać w przypadku dokonywania operacji na kilku plikach (znak NUL jest w C standardowym znacznikiem końca łańcucha, co przysparza nieco problemów).
Pole pTo zawiera wskaźnik do bufora przechowującego nazwę pliku lub katalogu docelowego. Użycie opcji FOF_MULTIDESTFILES pozwala na wykorzystanie różnych nazw docelowych dla poszczególnych plików, przy czym i w tym przypadku należy pamiętać o rozdzieleniu ich znakami NUL i zamknięciu całej listy dwoma zerami.
Pole fFlags umożliwia podanie opcji sterujących działaniem funkcji. Może ono zawierać dowolną kombinację stałych podanych w tabeli 14.18.
Tabela 14.18. Opcje sterujące działaniem funkcji SHFileOperation()
Stała |
Wartość |
FOF_ALLOWUNDO |
Nakazuje usuwanie plików w sposób „odwracalny” (o ile to możliwe). |
FOF_CONFIRMMOUSE |
Niezaimplementowana. |
FOF_FILESONLY |
W przypadku użycia specyfikacji *.* (wszystkie pliki) zezwala tylko na kopiowanie plików. |
FOF_MULTIDESTFILES |
Sygnalizuje, że pole pTo zawiera więcej niż jedną nazwę docelową (po jednej nazwie dla każdego pliku źródłowego), a nie nazwę pojedynczego katalogu (wspólną dla wszystkich kopiowanych plików). |
FOF_NOCONFIRMATION |
Wymusza automatyczną odpowiedź twierdzącą na wszelkie pytania zadawane w trakcie operacji. |
FOF_NOCONFIRMMKDIR |
Wyłącza żądania potwierdzenia tworzenia nowych katalogów (o ile w trakcie kopiowania zachodzi taka potrzeba). |
FOF_RENAMEONCOLLISION |
W przypadku, gdy kopia pliku już istnieje, automatycznie zmienia nazwę kopiowanego (przenoszonego lub przemianowywanego) pliku, np. dodając przedrostek „Kopia ...”. |
FOF_SILENT |
Wyłącza wyświetlanie informacji o postępie operacji (tryb „dyskretny”). |
FOF_SIMPLEPROGRESS |
Zabrania wyświetlania nazw kopiowanych plików w oknie informacji o postępie operacji. |
FOF_WANTMAPPINGHANDLE |
Nakazuje zdefiniowanie zawartości struktury wskazywanej uchwytem hNameMappings (używanej wraz z opcją FOF_RENAMEONCOLLISION - przyp. tłum.). Uchwyt ten należy zwolnić poprzez wywołanie funkcji SHFreeNameMappings(). |
Pole fAnyOperationsAborted zawiera informację o przerwaniu operacji. Wartość TRUE oznacza, że działanie funkcji zostało przerwane przez użytkownika przed zakończeniem operacji, zaś wartość FALSE sygnalizuje pomyślne zakończenie procesu.
Pole hNameMappings zawiera uchwyt obiektu przechowującego tablicę odwzorowań nazw plików. Każdy element takiej tablicy przechowuje pierwotną nazwę pliku oraz nazwę po jego skopiowaniu, przeniesieniu lub przemianowaniu. Omawiane pole jest używane wyłącznie w przypadku podania opcji FOF_WANTMAPPINGHANDLE.
Pole lpszProgressTitle przechowuje wskaźnik do łańcucha wyświetlanego w pasku tytułu okna, zawierającego informacje o postępie operacji. Oznacza to możliwość zmiany tytułu okna informacyjnego stosownie do potrzeb - np. zamiast standardowego tytułu „Kopiowanie plików...” można użyć bardziej wymyślnego „Trwa usuwanie plików pana Iksińskiego”.
Pierwszym elementem kodu przedstawionego na wydruku 14.5 jest deklaracja zmiennej op typu SHFILEOPSTRUCT. Utworzoną w ten sposób strukturę najlepiej od razu zainicjalizować za pomocą funkcji ZeroMemory(), przygotowując ją do użycia. Następnie można przystąpić do wypełniania poszczególnych elementów struktury celem odpowiedniego „zaprogramowania” funkcji SHFileOperation().
Do najistotniejszych elementów struktury SHFILEOPSTRUCT należą pola pTo i pFrom. Nadając im wartości, należy pamiętać o konieczności podania listy nazw plików w postaci łańcucha zakończonego dwoma zerami - użycie tylko jednego znaku NUL spowoduje, że SHFileOperation() zachowa się raczej niezgodnie z oczekiwaniami („zgubienie” zera w specyfikacji plików źródłowych spowoduje błąd wykonania; w specyfikacji plików wynikowych - potraktowanie pierwszej nazwy jako nazwy katalogu - przyp. tłum.). Warto pamiętać, że w przypadku użycia więcej niż jednego pliku docelowego nie wystarczy podanie nazw plików w postaci listy zakończonej dwoma zerami - konieczne jest także użycie opcji FOF_MULTIDESTFILES (w przeciwnym przypadku funkcja potraktuje pierwszą nazwę jako nazwę katalogu, ignorując pozostałe - przyp. tłum.).
Po zadeklarowaniu i zainicjalizowaniu struktury oraz wypełnieniu odpowiednich pól pozostaje tylko wywołać funkcję SHFileOperation(). Jej działanie można prześledzić na wydruku 14.5. Należy pamiętać, że może ona nie być dostępna w starszych wersjach systemu Windows 95.
Jedną z ciekawszych nowości, udostępnianych przez funkcję SHFileOperation(), jest usuwanie plików z przenoszeniem ich do Kosza. Prezentowany w poniższym przykładzie komponent DirectoryListBox umożliwia użytkownikowi wybranie katalogu i usunięcie go. Odpowiedni kod przedstawiono na wydruku 14.6.
Wydruk 14.6. Usunięcie całej zawartości katalogu
SHFILEOPSTRUCT op; // struktura danych dla SHFileOperation()
// Wyzeruj strukturę op
ZeroMemory(&op, sizeof(op));
String DelDir; // katalog do usunięcia
// Odczytaj z listy wybrany element
DelDir =
DirectoryListBox1->Items->Strings[DirectoryListBox1->ItemIndex];
DelDir.SetLength(DelDir.Length() + 1);
DelDir[DelDir.Length()] == '\0';
DelDir.SetLength(DelDir.Length() + 1);
DelDir[DelDir.Length()] == '\0';
String BS;
BS = "Za chwilę USUNIESZ cały katalog " + DelDir + "!!! Na pewno?";
int theanswer = Application->MessageBox(BS.c_str(), "Uwaga!", MB_YESNO);
switch(theanswer)
{
case ID_NO:
if(NoNag == 0)
{ ShowMessage("Tak! Przemyśl to jeszcze raz.");} return;
}
// Definiujemy dane dla funkcji SHFIleOperation
// plik źródłowy, docelowy itd...
op.hwnd = 0;
op.wFunc = FO_DELETE; // usuwamy pliki
op.pFrom = DelDir.c_str();
op.fFlags = FOF_ALLOWUNDO; // ta opcja pozwala przenieść pliki
// do Kosza (tylko dla operacji FO_DELETE)
int copy_done = SHFileOperation(&op);
if (copy_done == 0)
{
if (op.fAnyOperationsAborted)
{
ShowMessage("Usuwanie katalogu zostało przerwane. Niektóre pliki mogły zostać przeniesione do Kosza; w razie potrzeby można je stamtąd odtworzyć.");
}
else
{
ShowMessage("Katalog został usunięty!");
}
}
else
{
ShowMessage("Katalog nie został usunięty");
} // No, napracowaliśmy się...
// Ponieważ katalog aktualnie wybrany z listy DirectoryListBox został // usunięty, trzeba przenieść wyróżnienie wyżej,
// w przeciwnym razie będziemy mieli nazwę nieistniejącego katalogu
DirectoryListBox1->Items->Strings[DirectoryListBox1->ItemIndex-1];
// Aktualizuj zawartość listy
DirectoryListBox1->Update();
Uruchomienie powyższego przykładu spowoduje zlokalizowanie i usunięcie wszystkich plików (oraz podkatalogów) zawartych w katalogu wybranym przez użytkownika. Usuwane obiekty nie zostaną jednak bezpowrotnie zlikwidowane, a umieszczone w Koszu. Takie działanie funkcji SHFileOperation() zapewnia wywołanie jej z opcją FOF_ALLOWUNDO w polu fFlags i kodem operacji FO_DELETE w polu wFunc.
Przedstawione tu przykłady prezentują ogromne możliwości funkcji z grupy „SHx”. Oprócz SHFileOperation() grupa ta zawiera inne, równie przydatne funkcje.
Obsługa Kosza
Funkcje powłoki umożliwiają również sprawdzanie stanu Kosza i manipulowanie jego zawartością (np. ustalanie liczby i rozmiaru plików zawartych w Koszu, a nawet jego opróżnianie bez udziału użytkownika). Kosz obsługują wprowadzone w Windows 98 (i dostępne standardowo również w Windows 2000) funkcje powłoki SHEmptyRecycleBin() i SHQueryRecycleBin().
Przykład ich zastosowania pokazano poniżej - przedstawiony tu kod sprawdza liczbę elementów zawartych w Koszu i usuwa je (bez ingerencji użytkownika). Mechanizm taki można wykorzystać np. w programie użytkowym „sprzątającym” dysk.
// struktura danych dla funkcji obsługujących Kosz
SHQUERYRBINFO RCinfo;
RCinfo.cbSize = sizeof(RCinfo);
// Łączny rozmiar obiektów zawartych w Koszu - na początek zerujemy
RCinfo.i64Size = 0;
// Łączna liczba obiektów zawartych w Koszu
RCinfo.i64NumItems = 0;
// Ile obiektów znajduje się w koszu? Odpowiedź w strukturze RCinfo.
if(S_OK != SHQueryRecycleBin("C:\\", &RCinfo))
{
Label1->Caption = "Błąd!";
return;
}
int one = RCinfo.i64NumItems;
int two = RCinfo.i64Size;
// Usuń wszystkie obiekty z Kosza (i powiadom użytkownika!)
SHEmptyRecycleBin(0, 0, SHERB_NOPROGRESSUI);
Label1->Caption = one;
Label2->Caption = two;
Omawiane tu funkcje dostępne są również w systemie Windows 95, pod warunkiem zainstalowania biblioteki shell32.dll w wersji co najmniej 4.00.
Typ strukturalny SHQUERYRBINFO jest bardzo prosty:
typedef struct _SHQUERYRBINFO {
DWORD cbSize;
__int64 i64Size;
__int64 i64NumItems;
} SHQUERYRBINFO, *LPSHQUERYRBINFO;
Pole cbSize powinno zawierać rozmiar struktury w bajtach. Pola i64Size i i64NumItems przechowują odpowiednio łączny rozmiar oraz łączną liczbę wszystkich obiektów zawartych w Koszu.
Wspomniane tu funkcje SHEmptyRecycleBin() i SHQueryRecycleBin() również są bardzo proste w użyciu. Opiszemy je w kolejnych podpunktach.
SHQueryRecycleBin()
Funkcja SHQueryRecycleBin() zwraca informacje o stanie Kosza - liczbę oraz łączny rozmiar zawartych w nim obiektów. Jej deklaracja jest następująca:
HRESULT SHQueryRecycleBin (
LPCTSTR pszRootPath,
LPSHQUERYRBINFO pSHQueryRBInfo
);
Powodzenie wywołania sygnalizowane jest zwróceniem wartości S_OK; w przypadku niepowodzenia funkcja zwraca kod błędu operacji OLE.
Pierwszy parametr, pszRootPath, jest wskaźnikiem do zakończonego zerem łańcucha, zawierającego ścieżkę dostępu do katalogu przechowującego zawartość Kosza. Ścieżka ta musi zawierać co najmniej symbol dysku oraz katalogu głównego, może jednak zawierać również specyfikację podkatalogu (np. c:\\JanBałaganiarz\\Pliki\\Robocze). Użycie tu wartości NULL lub łańcucha pustego powoduje zwrócenie informacji o wszystkich plikach Koszy znajdujących się na dyskach komputera.
Parametr drugi, pSHQueryRBInfo, wskazuje strukturę typu SHQUERYBINFO, w której polach funkcja umieści odczytane informacje. Parametr cbSize, zawierający rozmiar struktury, musi zostać poprawnie ustawiony przed wywołaniem funkcji.
SHEmptyRecycleBin()
Funkcja SHEmptyRecycleBin() pozwala na opróżnienie Kosza. Oto jej deklaracja:
HRESULT SHEmptyRecycleBin (
HWND hwnd,
LPCTSTR pszRootPath,
DWORD dwFlags
);
Zwracana przez nią wartość wynosi S_OK w przypadku powodzenia; w przeciwnym razie zawiera kod błędu operacji OLE (tak samo, jak dla SHQueryRecycleBin()).
Pierwszy parametr funkcji, hwnd, jest uchwytem okna, będącego rodzicem okien dialogowych, które mogą zostać wyświetlone w trakcie opróżniania Kosza. Użycie wartości NULL jest tu dopuszczalne, ale uniemożliwia wyświetlanie informacji o przebiegu operacji.
Parametr pszRootPath pełni tę samą rolę, co w przypadku opisanej powyżej funkcji SHQueryRecycleBin() - określa położenie katalogu przechowującego zawartość Kosza. Łańcuch wskazywany przez pszRootPath musi zawierać co najmniej identyfikator dysku i symbol katalogu głównego, jednak może zawierać także nazwę podkatalogu (np. c:\\Dokumenty\\DoLikwidacji). Użycie łańcucha pustego lub wartości NULL powoduje opróżnienie wszystkich Koszy znajdujących się na dyskach komputera.
Trzeci parametr, dwFlags, określa opcje wywołania i może przyjmować wartości opisane poniżej.
Tabela 14.19. Opcje wywołania funkcji SHEmptyRecycleBin()
Stała |
Znaczenie |
SHERB_NOCONFIRMATION |
Zabrania wyświetlania prośby o potwierdzenie usunięcia zawartości Kosza. |
SHERB_NOPROGRESSUI |
Zabrania wyświetlania okna sygnalizującego postęp operacji. |
SHERB_NOSOUND |
Wyłącza dźwiękową sygnalizację zakończenia operacji. |
Przeglądanie folderów
Kolejną interesującą funkcją powłoki jest SHBrowseForFolder(). Umożliwia ona przeglądanie folderów (katalogów) i wybranie jednego z nich jako folderu docelowego. Zna ją zapewne większość użytkowników Windows, którym zdarzyło się np. instalować sterowniki urządzeń.
Możliwość wybrania folderu docelowego bywa niezwykle przydatna. Działanie funkcji SHBrowseForFolder() zademonstrujemy na przykładzie, który pozwala użytkownikowi wybrać jeden z istniejących już folderów i wyświetla jego nazwę w polu etykiety. Wybrany folder można następnie uczynić domyślnym.
Wybór folderu za pomocą funkcji SHBrowseForFolder()
Konstrukcję przykładu rozpocznij od uruchomienia IDE i umieszczenia na formularzu przycisku i etykiety (położenie obu elementów nie ma znaczenia). Na początku pliku Unit1.cpp (przed dyrektywą #include <vcl.h> wstaw następujące wiersze:
#define NO_WIN32_LEAN_AND_MEAN
#include <shlobj.h>
Za pomocą inspektora obiektów utwórz funkcję obsługi zdarzenia OnClick dla przycisku, umieszczając w jej treści kod podany poniżej. Następnie skompiluj i uruchom aplikację.
String Directory;
BROWSEINFO BrowsingInfo;
char FolderName [MAX_PATH];
LPITEMIDLIST ItemID;
memset(&BrowsingInfo, 0, sizeof(BROWSEINFO));
BrowsingInfo.hwndOwner = Handle;
BrowsingInfo.pszDisplayName = FolderName;
BrowsingInfo.lpszTitle = "Wybierz folder:";
ItemID = SHBrowseForFolder(&BrowsingInfo);
if(ItemID) {
char DirPath [_MAX_PATH]="";
SHGetPathFromIDList(ItemID,DirPath);
/* zapisz ścieżkę do zmiennej DirPath */
Directory = DirPath;
}
Label1->Caption = Directory;
Uruchomienie przykładu pozwala wybrać katalog (folder), którego nazwa jest następnie wyświetlana w polu etykiety.
Oto deklaracja funkcji SHBrowseForFolder():
WINSHELLAPI LPITEMIDLIST WINAPI SHBrowseForFolder(
LPBROWSEINFO lpbi
);
Wartość zwracana przez funkcję wskazuje na identyfikator elementu wybranego z listy katalogów (wartość NULL oznacza anulowanie operacji przez użytkownika). Parametr lpbi jest strukturą typu LPBROWSEINFO, opisanego poniżej.
typedef struct _browseinfo {
HWND hwndOwner;
LPCITEMIDLIST pidlRoot;
LPSTR pszDisplayName;
LPCSTR lpszTitle;
UINT ulFlags;
BFFCALLBACK lpfn;
LPARAM lParam;
int iImage;
} BROWSEINFO, *PBROWSEINFO, *LPBROWSEINFO;
Pole hwndOwner zawiera uchwyt okna będącego właścicielem okna dialogowego wyboru folderu. pidlRoot to wskaźnik do listy identyfikatorów elementów (struktury typu ITEMIDLIST), określającej położenie folderu, od którego ma się rozpocząć przeszukiwanie (drzewo wyświetlane w oknie dialogowym zawiera tylko ten folder i foldery niższego poziomu). Użycie w tym miejscu wartości NULL powoduje wyświetlenie pełnego drzewa folderów, począwszy od pulpitu (folderu głównego w danym systemie).
Typ strukturalny ITEMIDLIST zdefiniowano następująco:
typedef struct _ITEMIDLIST { //idl
SHITEMID mkid; // lista identyfikatorów elementów
} ITEMIDLIST, *LPITEMIDLIST;
typedef const ITEMIDLIST *LPCITEMIDLIST;
Pole pszDisplayName wskazuje na bufor, w którym zostanie zapisana nazwa katalogu wybranego przez użytkownika. Zakłada się, że długość bufora wynosi MAX_PATH (260 znaków, maksymalna długość ścieżki przyjęta w systemie Win32 - przyp. tłum.). Pole lpszTitle jest wskaźnikiem do łańcucha zawierającego nagłówek wyświetlany w oknie dialogowym ponad polem drzewa katalogów; można go użyć np. do przekazania użytkownikowi dodatkowych wskazówek.
Pole uFlags pozwala określić rodzaje wyświetlanych folderów i inne opcje. Może ono zawierać dowolną kombinację wartości podanych w tabeli 14.20.
Tabela 14.20 Opcje wyświetlania folderów
Stała |
Znaczenie |
BIF_BROWSEFORCOMPUTER |
Zezwala na wybranie tylko komputera; wybranie innego elementu z listy blokuje przycisk OK. |
BIF_BROWSEFORPRINTER |
Zezwala na wybranie tylko drukarki; wybranie innego elementu blokuje przycisk OK. |
BIF_BROWSEINCLUDEFILES |
Zezwala na wyświetlanie plików w drzewie katalogów. |
BIF_DONTGOBELOWDOMAIN |
Zabrania wyświetlania folderów sieciowych położonych poniżej danej domeny. |
BIF_RETURNFSANCESTORS |
Zezwala na wybranie wyłącznie elementu nadrzędnego w stosunku do systemu plików; wybranie innego elementu blokuje przycisk OK. |
BIF_RETURNONLYFSDIRS |
Zezwala na wybranie wyłącznie katalogu; wybranie innego elementu blokuje przycisk OK. |
BIF_STATUSTEXT |
Nakazuje umieszczenie w oknie dialogowym pola statusu; zawartość pola może być modyfikowana za pomocą komunikatów wysyłanych do okna przeglądania przez odpowiednią funkcję zwrotną. |
Pole lpfn zawiera wskaźnik do zdefiniowanej w programie funkcji zwrotnej, obsługującej zdarzenia okna dialogowego przeglądania folderów (wartość NULL oznacza brak takiej funkcji). Dodatkowe informacje na ten temat można znaleźć w dokumentacji Win32 API pod hasłem BrowseCallbackProc.
Wartość lParam to parametr przesyłany przez okno do funkcji zwrotnej (o ile jest używana), zdefiniowany przez programistę.
Pole iImage określa graficzną reprezentację wybranego folderu (zawiera ono indeks systemowej tablicy ikon).
Jak widać z przytoczonych tu przykładów, funkcje powłoki (zarówno standardowe, jak i nowe funkcje z grupy SHx) dysponują ogromnymi możliwościami. Ich opisanie to temat na oddzielną książkę; tutaj możemy jedynie zalecić odwołanie się do wchodzącego w skład systemu C++Builder pliku pomocy opisującego interfejs Win32 - i, oczywiście, eksperymentowanie na własną rękę. Ale ostrożnie: potęga funkcji powłoki łatwo może obrócić się przeciwko nam.
Obsługa multimediów
Jak wspomnieliśmy wcześniej, interfejs Win32 udostępnia liczną grupę funkcji i mechanizmów przeznaczonych do obsługi multimediów. Przedstawimy tutaj dwa z nich - odtwarzanie zapisów multimedialnych oraz dokładne odmierzanie czasu.
Odtwarzanie plików multimedialnych
W bibliotece VCL można znaleźć komponent o nazwie TMediaPlayer, służący do odtwarzania i manipulowania zapisami multimedialnymi. TMediaPlayer, stanowiący „otoczkę” zaimplementowanych w bibliotece vfw32.dll funkcji MCIWnd, jest skądinąd bardzo przydatny, jednak bezpośrednie odwołanie do klasy MCIWnd udostępnia kilka dodatkowych możliwości, a przy tym nie jest o wiele bardziej skomplikowane. Przykładem może tu być użycie funkcji MCIWndCreate(), umożliwiającej stosunkowo proste odtwarzanie muzyki z płyt CD, plików w formacie WAV lub sekwencji wideo (zobacz rysunek 14.3). Pełny kod źródłowy opisywanego tu przykładu znajduje się na dołączonej do książki płycie CD-ROM.
Przedstawiony poniżej fragment kodu ilustruje sposób odtworzenia zapisu wideo z wykorzystaniem funkcji Win32 API:
void __fastcall
TForm1::SpeedButtonPlayMMFileUsingWin32Click(TObject *Sender)
{
OpenDialog1->DefaultExt = "AVI";
OpenDialog1->FileName = "*.avi";
if (OpenDialog1->Execute())
{
Form1->Caption = "Odtwarzacz - " +
ExtractFileName(OpenDialog1->FileName);
MCIWndCreate(Panel2->Handle, // uchwyt okna aplikacji
NULL, // uchwyt kopii aplikacji
WS_VISIBLE | WS_CHILD | MCIWNDF_SHOWALL,// styl okna
OpenDialog1->FileName.c_str()); // nazwa pliku
}
}
Funkcja MCIWndCreate() tworzy okno odtwarzacza (klasy MCIWND_WINDOW_CLASS), zawierające przyciski uruchomienia i zatrzymania odtwarzania, suwak określający czas odtwarzania oraz okno prezentujące właściwy obraz (o ile odtwarzamy sekwencję wideo).
Do odtworzenia dźwięku zawartego w pliku WAV można też wykorzystać prostszą funkcję PlaySound(). Oto przykład:
void __fastcall
TForm1::SpeedButtonPlayWaveUsingWin32Click(TObject *Sender)
{
OpenDialog1->DefaultExt = "WAV";
OpenDialog1->FileName = "*.wav";
Form1->Caption = "Odtwarzacz - " +
ExtractFileName(OpenDialog1->FileName);
if (OpenDialog1->Execute())
PlaySound(OpenDialog1->FileName.c_str(), NULL, SND_ASYNC);
}
Rysunek 14.3. Odtwarzacz plików multimedialnych
Funkcja PlaySound(), zawarta w bibliotece mmsystem.dll, jest niezwykle użyteczna. Często zachodzi potrzeba odtworzenia zapisu dźwięku „w tle”, np. przy realizacji efektów dźwiękowych w programie. PlaySound() nie udostępnia elementów sterujących, jak czyni to MCIWndCreate(); odtwarzanie dźwięku rozpoczyna się natychmiast po jej wywołaniu. Jeszcze szersze możliwości odtwarzania i manipulowania dźwiękiem (np. jednoczesne odtwarzanie zapisów z kilku źródeł) zapewniają funkcje DirectSound, zaimplementowane w podsystemie DirectX. Ich omawianie wykracza jednak poza ramy tego rozdziału (informacje na ten temat można znaleźć w rozdziale 16. - przyp. tłum.).
Dokładne odmierzanie czasu
Elementarną obsługę odmierzania czasu zapewnia komponent VCL TTimer, wykorzystujący funkcje pomiaru czasu dostępne w systemie Win32. Do uruchomienia i zatrzymania generowania zdarzeń zegara służą odpowiednio wywołania funkcji SetTimer() i KillTimer(), zaś same zdarzenia (sygnalizowane systemowymi komunikatami WM_TIMER) reprezentowane są przez zdarzenie OnTimer. Wywołanie funkcji SetTimer() nakazuje systemowi operacyjnemu cykliczne wysyłanie komunikatów do wywołującego okna, aż do chwili „zatrzymania” zegara funkcją KillTimer(). Częstotliwość generowania komunikatów ustalana jest parametrem timeout wywołania funkcji SetTimer() (w bibliotece VCL odpowiada mu właściwość Interval komponentu TTimer). Co prawda żądana długość odstępu pomiędzy kolejnymi „tyknięciami” podawana jest z dokładnością jednej milisekundy, jednak praktyczna dokładność zegara systemowego jest o wiele niższa - komunikaty WM_TIMER mogą pojawiać się przed lub po upływie zadanego czasu (zwłaszcza w przypadku dużego obciążenia procesora). Ogólnie rzecz biorąc, na stabilności zegara systemowego lepiej nie polegać. Na szczęście istnieje dokładniejsza alternatywa - jest nią system odmierzania czasu zaimplementowany w podsystemie obsługi multimediów, zapewniający znacznie lepszą dokładność i rozdzielczość niż standardowy mechanizm oparty na funkcji SetTimer().
Rzut oka na zawartość pliku mmsystem.h (dostępny zarówno w pakiecie C++Builder, jak i w plikach bibliotecznych Microsoftu) ujawnia następujące deklaracje:
/* obsługa systemu odmierzania czasu */
WINMMAPI MMRESULT WINAPI timeGetSystemTime(LPMMTIME pmmt, UINT cbmmt);
WINMMAPI DWORD WINAPI timeGetTime(void);
WINMMAPI MMRESULT WINAPI timeSetEvent(UINT uDelay, UINT uResolution,
LPTIMECALLBACK fptc, DWORD dwUser, UINT fuEvent);
WINMMAPI MMRESULT WINAPI timeKillEvent(UINT uTimerID);
WINMMAPI MMRESULT WINAPI timeGetDevCaps(LPTIMECAPS ptc, UINT cbtc);
WINMMAPI MMRESULT WINAPI timeBeginPeriod(UINT uPeriod);
WINMMAPI MMRESULT WINAPI timeEndPeriod(UINT uPeriod);
Do większości zastosowań wystarcza użycie funkcji timeSetEvent() i timeKillEvent(). Przykład prostego programu porównującego dokładność zegara systemowego i multimedialnego przedstawiono na wydruku 14.7; kompletny kod źródłowy można znaleźć w katalogu MMTimer na załączonej do książki płycie CD-ROM. Wynik wykonania programu, ilustrujący rozbieżności pomiędzy obydwoma zegarami, pokazano na rysunku 14.4.
Rysunek 14.4. Rozbieżność pomiędzy zegarem systemowym (komponent TTimer) i multimedialnym
Wydruk 14.7. Demonstracja działania systemu odmierzania czasu z biblioteki MMSYSTEM
#include <vcl.h>
#pragma hdrstop
#include "mmtimer.h"
#include <mmsystem.h>
#include <time.h>
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
MMTimerID = 0;
MMlasttime = 0;
STANlasttime = 0;
clicksMM = 0;
clicksST = 0;
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Timer1Timer(TObject *Sender)
{
unsigned int clocktime = clock();
EditStandardTimer->Text = AnsiString(clocktime - STANlasttime);
STANlasttime = clocktime;
clicksST++;
EditClicksST->Text = clicksST;
}
//---------------------------------------------------------------------------
void __fastcall TForm1::HandleMMTimerEvent()
{
unsigned int clocktime = clock();
EditMMTimer->Text = AnsiString(clocktime - MMlasttime);
MMlasttime = clocktime;
clicksMM++;
EditClicksMM->Text = clicksMM;
//Refresh();
}
//---------------------------------------------------------------------------
void CALLBACK TimerProc(unsigned int uID, unsigned int uMsg, DWORD dwUser,
DWORD dw1, DWORD dw2)
{
Form1->HandleMMTimerEvent();
}
//---------------------------------------------------------------------------
void __fastcall TForm1::SpeedButtonMMTimerClick(TObject *Sender)
{
if (SpeedButtonMMTimer->Down)
{
int Interval = EditInterval->Text.ToIntDef(0);
int Resolution = EditResolution->Text.ToIntDef(0);
// 0 = największa dokładność
if (Timer1->Interval > 0)
{
MMlasttime = clock();
MMTimerID = timeSetEvent(Interval, Resolution, TimerProc, NULL,
TIME_PERIODIC);
}
clicksMM = 0;
}
else
{
timeKillEvent(MMTimerID);
MMTimerID = 0;
}
}
//---------------------------------------------------------------------------
void __fastcall TForm1::SpeedButtonStandardTimerClick(TObject *Sender)
{
if (SpeedButtonStandardTimer->Down)
{
Timer1->Interval = EditInterval->Text.ToIntDef(0);
STANlasttime = clock();
if (Timer1->Interval > 0)
Timer1->Enabled = true;
clicksST = 0;
}
else
{
Timer1->Enabled = false;
}
}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormClose(TObject *Sender,
TCloseAction &Action)
{
if (MMTimerID) timeKillEvent(MMTimerID);
}
Identyfikatory GUID i ich wykorzystanie
Wśród narzędzi udostępnionych programistom przez Microsoft znalazły się także funkcje obsługujące tzw. globalnie niepowtarzalne identyfikatory (Globally Unique Identifier, GUID) - 128-bitowe kody o gwarantowanej niepowtarzalności. Podobnie jak w przypadku odcisków palców, teoretycznie nie istnieje możliwość wystąpienia dwóch jednakowych identyfikatorów GUID. Jest to niezwykle przydatne w przypadku konieczności „etykietowania” danych i obiektów w obrębie aplikacji lub w ramach systemu rozproszonego (w sieci komputerowej). Identyfikator GUID ma postać następującej struktury:
typedef struct _GUID {
DWORD Data1;
WORD Data2;
WORD Data3;
BYTE Data4[8];
} GUID;
Pole Data1 zawiera 32-bitowy klucz, który można wykorzystać np. tak:
unsigned int GetNewID()
{
GUID guid;
::CoCreateGuid(&guid);
return guid.Data1;
}
Definicja powyższej funkcji znajduje się w pliku źródłowym win32_util.cpp, zawartym w katalogu Win32Code na dołączonej do książki płycie CD-ROM. Jej użycie wymaga włączenia do kodu aplikacji pliku nagłówkowego objbase.h lub innego, zawierającego z kolei dyrektywę włączającą plik base.h. Kluczową funkcją Win32, używaną do utworzenia identyfikatora, jest CoCreateGuid(); funkcja GetNewId() zwraca jedynie 32-bitową zawartość pola Data1, która często okazuje się wystarczająca do jednoznacznego zidentyfikowania obiektu. Dla uzyskania całkowitej niepowtarzalności zaleca się jednak użycie pełnego 128-bitowego identyfikatora.
Pobieranie informacji o systemie
Interfejs Win32 udostępnia kilka funkcji pozwalających na odczytanie informacji natury systemowej, jak np. nazwa bieżącego użytkownika i stacji roboczej, ilość dostępnej pamięci i wersja systemu operacyjnego. Zadania te realizują funkcje przedstawione poniżej, stanowiące „otoczki” odpowiednich wywołań API. Deklaracje wykorzystywanych tu funkcji Win32 można znaleźć w pliku winbase.h.
Odczytanie nazwy użytkownika
Przedstawiona poniżej funkcja odczytuje i zwraca nazwę bieżącego użytkownika systemu, wykorzystując w tym celu funkcję API GetUserName(). Parametrami wywołania tej ostatniej są wskaźnik do bufora przeznaczonego na nazwę oraz wskaźnik do zmiennej, w której znajdzie się długość odczytanej nazwy użytkownika. Definicję funkcji można znaleźć w pliku win32_util.cpp, zawartym w katalogu Win32Code na dołączonej do książki płycie CD-ROM.
AnsiString LoginUserName()
{
AnsiString Name;
DWORD size = MAX_PATH+1;
char name[MAX_PATH+1];
name[0] = '\0'; // zeruj długość nazwy
GetUserName(name, &size);
Name = AnsiString(name);
return Name;
}
Odczytanie nazwy komputera
Kolejny przykład pokazuje funkcję umożliwiającą odczytanie nazwy komputera (stacji roboczej), zapisanej w rejestrze. Służy do tego funkcja Win32 GetComputerName(), której parametrami są wskaźnik do bufora przeznaczonego na nazwę oraz wskaźnik do zmiennej, w której zostanie zapisana długość łańcucha zapisanego w buforze. Jak poprzednio, definicję funkcji można znaleźć w pliku win32_util.cpp, zawartym w katalogu Win32Code na dołączonej do książki płycie CD-ROM.
AnsiString ComputerName()
{
AnsiString Name;
DWORD size = MAX_COMPUTERNAME_LENGTH+1;
char name[MAX_COMPUTERNAME_LENGTH+1];
name[0] = '\0'; // zeruj długość nazwy
GetComputerName(name, &size);
Name = AnsiString(name);
return Name;
}
Odczytanie ilości dostępnej pamięci
Poniższy przykład prezentuje użycie funkcji Win32 GlobalMemoryStatus() do pobrania informacji o ilości dostępnej w systemie pamięci. Zwrócona przez funkcję wartość jest przeliczana na kilobajty i zwracana w postaci łańcucha typu AnsiString. Jak poprzednio, poniższa funkcja wchodzi w skład pliku win32_util.cpp, zawartego w katalogu Win32Code na dołączonej do książki płycie CD-ROM.
AnsiString MemFree()
{
AnsiString FreeMem;
MEMORYSTATUS memory ;
memory.dwLength = sizeof (memory) ;
GlobalMemoryStatus (&memory) ;
unsigned int value2 = 0;
unsigned int value1 = memory.dwAvailPhys / 1024;
if (value1 >= 1000)
{
value2 = value1 / 1000;
value1 = (value1 - (value2 * 1000.0));
FreeMem = AnsiString(value2) + "," + AnsiString(value1) + " kB";
}
else
FreeMem = AnsiString(value1) + " kB";
return FreeMem;
}
Ustalenie lokalizacji plików tymczasowych
Możliwość zapisania pliku tymczasowego w przeznaczonym do tego katalogu (np. Windows\Temp) bywa bardzo przydatna. Co prawda położenie katalogu przeznaczonego na pliki tymczasowe jest różne w różnych systemach, jednak na szczęście można je ustalić wywołaniem funkcji Win32 GetTempPath(). Sposób jej użycia przedstawiono poniżej.
void __fastcall TempFileLocation(AnsiString& filelocation,
AnsiString extension)
{
AnsiString TempDir;
UINT BufferSize = GetTempPath(0,NULL);
TempDir.SetLength(BufferSize+1);
GetTempPath(BufferSize+1,TempDir.c_str());
unsigned int id = GetNewID();
AnsiString temp = UINT_To_Ansi(id);
AnsiString TempFile = AnsiString("temp") + UINT_To_Ansi(id) +
"." + extension;
char * tempfile = new char[TempDir.Length() + TempFile.Length()];
sprintf(tempfile, "%s%s", TempDir.c_str(), TempFile.c_str());
filelocation = AnsiString(tempfile);
delete[] tempfile;
}
Przedstawiona powyżej metoda umożliwia tworzenie „sztucznych” nazw plików, jakie często można znaleźć w katalogu Temp (zwłaszcza po przerwanej instalacji jakiegoś programu). Wykorzystujemy tu zdefiniowaną nieco wcześniej funkcję GetNewId() (zobacz punkt „Identyfikatory GUID i ich wykorzystanie”), zwracającą 32-bitowy niepowtarzalny identyfikator, którego wartość wchodzi w skład nazwy tworzonego pliku tymczasowego. Funkcje TempFileLocation() i GetNewId() zdefiniowano w pliku win32_util.cpp, położonym w katalogu Win32Code na dołączonej do książki płycie CD-ROM.
Ustalenie rozmiaru pliku
Teraz coś dla wszystkich, którzy kiedykolwiek potrzebowali sposobu na szybkie i wygodne ustalenie rozmiaru jakiegoś pliku. Pomoże nam w tym funkcja CreateFile(), a całą metodę zademonstrujemy w poniższym przykładzie. Zacznij, jak zwykle, od uruchomienia IDE i utworzenia projektu nowej aplikacji (kompletny kod projektu można znaleźć w katalogu FSize na płycie CD-ROM; plik projektu nosi nazwę FSize.bpr). Wstaw komponenty do głównego formularza, posługując się przepisem podanym w poniższej tabeli i odpowiednio zmieniając ich właściwości. Komponenty FileListBox i DirectoryListBox można znaleźć na karcie Win3.1 palety komponentów.
Tabela 14.21. Komponenty składowe w projekcie FSize
Komponent |
Właściwości |
Form1 |
Width = 433 |
DirectoryListBox1 |
Left = 8 |
FileListBox1 |
Height = 150 |
Label1 |
Left = 8 |
Label2 |
Left = 80 |
Teraz pora na wpisanie kodu źródłowego przedstawionego na wydruku 14.8 (zwróćmy uwagę na włączenie pliku stdio.h, zawierającego deklarację funkcji sprintf()).
Wydruk 14.8. Ustalenie rozmiaru pliku
#include <vcl.h>
#include <stdio.h> // deklaracja funkcji sprintf()
#pragma hdrstop
#include "Unit1.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
}
//---------------------------------------------------------------------------
char* FormatVal(int nValue)
{
int i, j;
char buff1[50];
static char buff2[75] = "";
int fsize = nValue; // zapamiętaj jednostkę
while (nValue > 100000) // pozostaw 5 najbardziej znaczących cyfr
nValue = nValue / 1000;
// zamień liczbę na łańcuch i wstaw separator tysiąca
sprintf(buff1, "%d", nValue);
// wstaw separator tysięcy przed ostatnią trzyznakową grupę cyfr
for(i=0, j=0; i<strlen(buff1); i++, j++)
{
if(((strlen(buff1)-i) % 3 == 0)
&& (strlen(buff1)) > 3) // nie wstawiaj separatora
// dla rozmiarów < 1 kB
buff2[j++] = ','; // (mniej niż 4 cyfry)
buff2[j] = buff1[i];
}
buff2[j] = 0; // zakończ łańcuch
// dodaj oznaczenie jednostki (B, kB lub MB)
if(fsize < 1024) // mniej niż 1 kB -> B
strcat(buff2, " B");
else if(fsize < 1048576) // mniej niż 1 MB -> kB
strcat(buff2, " kB");
else // ponad 1 MB -> MB
strcat(buff2, " MB");
return buff2;
}
Konieczne będzie jeszcze wpisanie kodu funkcji obsługującej zdarzenie OnClick komponentu FileListBox1.
//---------------------------------------------------------------------------
void __fastcall TForm1::FileListBox1Click(TObject *Sender)
{
HANDLE file;
int size;
AnsiString str;
file = CreateFile(FileListBox1->FileName.c_str(), GENERIC_READ,
FILE_SHARE_READ, NULL, OPEN_EXISTING, NULL, NULL);
if(file == INVALID_HANDLE_VALUE)
ShowMessage("Błąd otwarcia pliku (plik może być w użyciu)");
else {
size = GetFileSize(file, NULL);
str.SetLength(size);
str = size;
char *GetFileSizeInfo;
GetFileSizeInfo = FormatVal(size);
lb_Size->Caption = GetFileSizeInfo;
}
}
Zdefiniowana powyżej funkcja FormatVal()przekształca wartość liczbową na łańcuch, wstawiając w odpowiednich miejscach separatory tysięcy (w zapisie anglosaskim przecinki, w polskim spacje lub kropki - przyp. tłum.), ustala odpowiednią jednostkę (bajty, kilobajty lub megabajty) i zwraca tak wypełniony bufor.
Funkcja obsługi zdarzenia OnClick wykorzystuje inną popularną funkcję Win32 - CreateFile(). Możliwości tej ostatniej są znacznie szersze, aniżeli samo tworzenie plików - może ona obsługiwać praktycznie dowolny mechanizm wejścia-wyjścia (np. potoki, porty szeregowe, konsole - przyp. tłum.). W naszym przykładzie wykorzystujemy ją do otwarcia pliku do odczytu, co sygnalizuje stała GENERIC_READ. Nazwę otwieranego pliku pobieramy z komponentu FileListBox1, przekazując ją do funkcji CreateFile() jako pierwszy parametr (konstrukcja FileListBox1->FileName.c_str()). Warto tu zwrócić uwagę na wywołanie metody .c_str(), zamieniające łańcuch typu AnsiString na zwykły łańcuch języka C (typu char*). Drugi parametr wywołania CreateFile() to stała GENERIC_READ, określająca żądany sposób dostępu do pliku (w naszym przypadku - otwarcie pliku do odczytu). Ponieważ tworzony uchwyt nie będzie przekazywany procesom potomnym (i nie korzystamy z deskryptora bezpieczeństwa), czwarty parametr ma wartość NULL. Parametr piąty - stała OPEN_EXISTING - zezwala wyłącznie na otwarcie już istniejącego pliku (w przeciwnym przypadku wywołanie zakończy się błędem). Kolejny parametr, określający atrybuty pliku, ma wartość zero (NULL), gdyż w naszym przykładzie nie korzystamy z atrybutów. Wreszcie ostatni, siódmy parametr również wynosi NULL; określa on uchwyt niewykorzystywanego w tym przypadku pliku szablonu, zawierającego dodatkowe atrybuty otwieranego pliku (parametr ten musi być równy NULL w systemie Windows 95).
Jeśli niczego nie popsuliśmy, wywołanie funkcji CreateFile() powinno zwrócić uchwyt otwartego pliku. W przypadku problemów z dostępem zwracana jest wartość INVALID_HANDLE_VALUE, a błąd otwarcia pliku sygnalizowany jest odpowiednim komunikatem. Wyświetleniem okienka komunikatu zajmuje się użyteczna funkcja ShowMessage(), pochodząca z biblioteki C++Buildera. Dodatkowe informacje o przyczynie błędu można uzyskać za pomocą funkcji Win32 GetLastError(). Funkcję CreateFile() omówiono szczegółowo w punkcie „Podstawy obsługi plikowych operacji wejścia-wyjścia” we wcześniejszej części rozdziału.
Po udanym otwarciu pliku przez CreateFile() wywoływana jest funkcja GetFileSize(), zwracająca rozmiar pliku jako liczbę całkowitą. Deklaracja GetFileSize() jest następująca:
DWORD GetFileSize(
HANDLE hFile, // uchwyt pliku
LPDWORD lpFileSizeHigh // bardziej znaczące 4 bajty rozmiaru pliku
);
Pierwszy parametr to uchwyt badanego pliku, w naszym przypadku zwrócony przez funkcję CreateFile(). Parametr drugi wskazuje zmienną typu DWORD, w której zostanie umieszczone bardziej znaczące podwójne słowo rozmiaru pliku. Parametr ten ma praktyczne zastosowanie tylko w przypadku plików o rozmiarze większym niż 4 GB, toteż w naszym przykładzie został pominięty (zastąpiony wartością NULL).
W następnym kroku deklarujemy zmienną char* GetFileSizeInfo i umieszczamy w niej rozmiar pliku w postaci łańcucha (utworzonego za pomocą omówionej wcześniej funkcji FormatVal()). Przykład działania całego programu pokazano na rysunku 14.5.
Rysunek 14.5. Określenie i wyświetlenie rozmiaru wybranego pliku
Ustalenie ilości wolnej przestrzeni i numeru seryjnego woluminu
Skoro potrafimy już ustalić rozmiar pojedynczego pliku, spróbujmy określić całkowitą ilość wolnej przestrzeni na dysku. Przy okazji pokażemy też sposób odczytania numeru seryjnego woluminu; obie funkcje bywają przydatne.
Ilość wolnego miejsca na dysku określa funkcja Win32 GetDiskFreeSpace(), nie pozwala ona jednak określić numeru seryjnego. Ten z kolei zwraca funkcja GetVolumeInformation(), udostępniająca zresztą znacznie więcej informacji o dysku, jak np. nazwę woluminu, rodzaj systemu plików itd. W naszym przykładzie zależy nam tylko na wartości numeru seryjnego woluminu i ilości dostępnego miejsca, wykorzystamy zatem obie funkcje. Uzupełniają się one logicznie i warto byłoby je połączyć w całość; wykorzystując możliwości C++Buildera można to uczynić bez trudu.
Funkcja GetDiskFreeSpace() zadeklarowana jest następująco:
BOOL GetDiskFreeSpace(
LPCTSTR lpRootPathName, // nazwa katalogu głównego
LPDWORD lpSectorsPerCluster,// liczba sektorów na jednostkę alokacji
LPDWORD lpBytesPerSector, // liczba bajtów w sektorze
LPDWORD lpNumberOfFreeClusters, // liczba wolnych jednostek alokacji LPDWORD lpTotalNumberOfClusters // całkowita liczba jednostek alokacji
);
Parametr lpRootPathName jest wskaźnikiem do łańcucha, zawierającego nazwę katalogu głównego interesującego nas woluminu. Pozostałe parametry wskazują na zmienne, w których znajdą się dane zwracane przez funkcję. Wskazywane przez nie zmienne zawierają odpowiednio:
pSectorsPerCluster - liczbę sektorów przypadających na jednostkę alokacji;
lpBytesPerSector - liczbę bajtów przypadających na sektor;
lpNumberOfFreeClusters - całkowitą liczbę wolnych jednostek alokacji na woluminie;
lpTotalNumberOfClusters - całkowitą liczbę jednostek alokacji na woluminie.
W przykładzie użyjemy także funkcji SepAdd(), której zadaniem będzie wstawienie separatorów do łańcucha reprezentującego liczbę. Wszystkie operacje zamkniemy w funkcji o nazwie GetDriveInfo().
Uruchom IDE, utwórz nowy projekt i wstaw do formularza głównego sześć etykiet i przycisk (nazwy mogą pozostać domyślne). Uzupełnij sekcję publiczną deklaracji klasy TForm1 o zapis zaznaczony na wydruku 14.9 czcionką pogrubioną.
Wydruk 14.9. Plik nagłówkowy modułu definiującego funkcję GetDriveInfo()
//---------------------------------------------------------------------------
#ifndef Unit1H
#define Unit1H
//---------------------------------------------------------------------------
#include <Classes.hpp>
#include <Controls.hpp>
#include <StdCtrls.hpp>
#include <Forms.hpp>
//---------------------------------------------------------------------------
class TForm1 : public TForm
{
__published: // IDE-managed Components
TButton *Button1;
TLabel *Label1;
TLabel *Label2;
TLabel *Label3;
TLabel *Label4;
TLabel *Label5;
TLabel *Label6;
TLabel *Label7;
TLabel *Label8;
TLabel *Label9;
TLabel *Label10;
TLabel *Label11;
TLabel *Label12;
void __fastcall Button1Click(TObject *Sender);
private: // User declarations
public: // User declarations
__fastcall TForm1(TComponent* Owner);
void __fastcall TForm1::GetDriveInfo (String drive);
};
//---------------------------------------------------------------------------
extern PACKAGE TForm1 *Form1;
//---------------------------------------------------------------------------
#endif
W ten sposób zadeklarowaliśmy funkcję GetDriveInfo() w klasie formularza głównego. Teraz należy zdefiniować ją w pliku źródłowym modułu (.cpp). Odpowiedni kod przedstawiamy poniżej.
void __fastcall TForm1::GetDriveInfo (String drive)
{
String YourDrive = drive;
String volumeinfo;
volumeinfo.SetLength(255); // Ustal długość bufora
// dla GetVolumeInformation().
DWORD serialnumber ;
if (GetVolumeInformation(YourDrive.c_str (), volumeinfo.c_str(),
volumeinfo.Length(), &serialnumber,
NULL, NULL, NULL, NULL))
{
Label1->Caption = volumeinfo ;
// Konwertuj liczbę całkowitą (numer seryjny) na łańcuch.
char STRING[35];
ltoa (serialnumber , STRING, 16);
Label2->Caption = STRING;
DWORD spc;
DWORD bps;
DWORD cluster;
DWORD freeclust;
GetDiskFreeSpace (YourDrive.c_str(), &spc, &bps, &freeclust,
&cluster) ;
Label3->Caption = SepAdd(spc);
Label4->Caption = SepAdd(bps);
Label5->Caption = SepAdd(cluster);
unsigned int free_bytes = freeclust * spc * bps;
Label6->Caption = SepAdd(free_bytes);
}
else
{
ShowMessage("Nie można odczytać informacji o dysku!");
}
}
Kluczowymi elementami powyższego kodu są wywołania funkcji Win32 GetDiskFreeSpace() i GetVolumeInformation(). Warto zwrócić uwagę na konwersję zmiennej serialnumber na łańcuch - wartość zwracana przez funkcję jest typu całkowitego (DWORD), jednak dla czytelności należy wyprowadzić ją w formie tekstu (w zapisie szesnastkowym - przyp. tłum.). Oprócz tego wykorzystujemy także zdefiniowaną w programie funkcję SepAdd(), wstawiającą separatory do łańcucha reprezentującego liczbę. Utworzonej w ten sposób funkcji można używać w innych programach, np. do wyświetlania okienek informacyjnych; w naszym przykładzie zwracane dane są prezentowane w postaci etykiet.
Kolejnym krokiem będzie zdefiniowanie funkcji obsługi zdarzenia OnClick przycisku. Wykorzystaj do tego celu okno inspektora obiektów, zaś do samej funkcji wstaw kod przedstawiony poniżej.
void __fastcall TForm1::Button1Click(TObject *Sender)
{
GetDriveInfo("C:\\");
}
Kompletny plik nagłówkowy modułu powinien odpowiadać zawartości wydruku 14.9, zaś plik źródłowy przedstawiono na wydruku 14.10.
Wydruk 14.10. Plik źródłowy modułu demonstrującego użycie funkcji GetDriveInfo()
#include <vcl.h>
#include <stdio.h>
#pragma hdrstop
#include "Unit1.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
}
//---------------------------------------------------------------------------
char* SepAdd(unsigned int nValue)
{
size_t i, j=0;
char buff1[20];
static char buff2[25];
// zamień liczbę na łańcuch
sprintf(buff1, "%lu", nValue);
// wstaw separatory
for(i=0; i <strlen(buff1); i++)
{
if(i && ((strlen(buff1) -i) %3) == 0)
{ buff2[i+j] = ' '; j++; }
buff2[i+j] = buff1[i];
}
buff2[i+j] = buff1[i];
return buff2;
}
//---------------------------------------------------------------------------
void __fastcall TForm1::GetDriveInfo (String drive)
{
String YourDrive = drive;
String volumeinfo;
volumeinfo.SetLength(255); // Ustal długość bufora dla GetVolumeInformation()
DWORD serialnumber ;
if (GetVolumeInformation(YourDrive.c_str (), volumeinfo.c_str(),
volumeinfo.Length(), &serialnumber,
NULL, NULL, NULL, NULL))
{
Label1->Caption = volumeinfo ;
// Konwertuj liczbę całkowitą (numer seryjny) na łańcuch.
char STRING[35];
ltoa (serialnumber , STRING, 16);
Label2->Caption = STRING;
DWORD spc;
DWORD bps;
DWORD cluster;
DWORD freeclust;
GetDiskFreeSpace (YourDrive.c_str(), &spc, &bps, &freeclust,
&cluster) ;
Label3->Caption = SepAdd(spc);
Label4->Caption = SepAdd(bps);
Label5->Caption = SepAdd(cluster);
unsigned int free_bytes = freeclust * spc * bps;
Label6->Caption = SepAdd(free_bytes);
}
else
{
ShowMessage("Nie można odczytać informacji o dysku!");
}
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
GetDriveInfo("C:\\");
}
Skompiluj i uruchom program. Kliknięcie przycisku spowoduje wyświetlenie na formularzu informacji o ilości wolnego miejsca oraz numeru seryjnego woluminu. Aby uzyskać podobne informacje dla dysku innego niż C:, wystarczy zmienić parametr wywołania funkcji GetDriveInfo() w przedostatnim wierszu.
Zasadniczymi elementami przykładu są wywołania funkcji GetDiskFreeSpace() i GetVolumeInformation(). Warto również zapamiętać sposób konwersji numeru seryjnego (danego wartością typu DWORD) na łańcuch czytelny dla użytkownika.
Migające okno
W grupach dyskusyjnych poświęconych programowaniu często pojawia się pytanie o możliwość odszukania wybranego okna lub ustawienia jego tekstu. Obsługę tych zadań umożliwiają dość popularne funkcje: FlashWindow(), FlashWindowEx() i SetWindowText(). Na kilku kolejnych stronach przyjrzymy się ich działaniu i sposobowi praktycznego wykorzystania w programach.
FlashWindowEx()
Funkcja FlashWindowEx() jest nowym członkiem „rodziny Win32”, pojawiła się bowiem w definicji API dopiero w systemach Windows 98 i 2000. Można ją wykorzystać w zastępstwie nieco starszej funkcji FlashWindow(), pozwalającej „mrugnąć” paskiem tytułu. Daje to możliwość poinformowania użytkownika o zajściu określonego zdarzenia w programie lub zgłoszenia gotowości okna do pracy (tj. możliwości odbierania danych wprowadzanych za pomocą klawiatury i myszy). Deklaracje obu tych użytecznych funkcji można znaleźć w pliku nagłówkowym winuser.h.
FlashWindow() umożliwia jednokrotne „mrugnięcie” paska tytułu, nie pozwala natomiast wykonywać tej czynności cyklicznie - w tym celu trzeba wywoływać ją wielokrotnie z wykorzystaniem np. zdarzeń zegara. Funkcja FlashWindowEx() posiada już własny mechanizm odmierzania czasu, a poza tym jest bardziej uniwersalna - pozwala ustalić liczbę „mrugnięć”, częstotliwość migania okna z dokładnością do milisekundy, a także kilka dodatkowych parametrów. Użytkownicy systemu Windows 98 mieli zapewne okazję widzieć omawiane tu funkcje w działaniu - wykorzystują je takie programy, jak np.: ICQ, AOL Instant Messenger czy aplikacje z pakietu Microsoft Office.
Deklaracja funkcji FlashWindowEx() jest następująca:
BOOL FlashWindowEx {
PFLASHWINFO pfwi // wskaźnik do struktury zawierającej ustawienia
);
Jedynym parametrem funkcji jest wskaźnik do struktury typu FLASHWINFO, opisującej sposób działania funkcji. Wartość zwracana określa stan okna przed wywołaniem funkcji i wynosi TRUE (wartość różna od zera), jeśli okno było aktywne, lub FALSE (zero), jeśli nie było aktywne. Funkcja FlashWindow() jest co prawda łatwiejsza w użyciu, jednak w dalszych przykładach użyjemy bardziej uniwersalnej FlashWindowEx().
Typ strukturalny FLASHWINFO zdefiniowany jest następująco:
typedef struct {
UINT cbSize;
HWND hwnd;
DWORD dwFlags;
UINT uCount;
DWORD dwTimeout;
} FLASHWINFO, *PFLASHWINFO;
Zawartość pól struktury steruje sposobem pracy funkcji FlashWindowEx(), czyli zachowaniem okna. Najważniejszym parametrem jest tu pole dwFlags, kontrolujące sposób migania. Może ono zawierać wartości wyszczególnione w tabeli 14.22.
Tabela 14.22. Możliwe wartości pola dwFlags struktury FLASHWINFO
Stała |
Znaczenie |
FLASHW_STOP |
Przerywa miganie i przywraca pierwotny stan okna. |
FLASHW_CAPTION |
Włącza miganie paska tytułu. |
FLASHW_TRAY |
Włącza miganie przycisku w pasku zadań. |
FLASHW_ALL |
Włącza miganie paska tytułu i przycisku w pasku zadań (równoważna sumie logicznej stałych FLASHW_CAPTION i FLASHW_TRAY). |
FLASHW_TIMER |
Włącza cykliczne miganie okna, aż do zatrzymania za pomocą opcji FLASHW_STOP. |
FLASHW_TIMERNOFG |
Włącza cykliczne miganie okna, aż do chwili „wywołania go” na pierwszy plan. |
Użycie powyższych stałych daje programiście szerokie możliwości sterowania zachowaniem wybranego okna.
FindWindow()
W razie konieczności zlokalizowania określonego okna można też wykorzystać funkcję Win32 FindWindow(). Umożliwia ona znalezienie żądanego okna najwyższego poziomu na podstawie zawartości paska tytułu i nazwy klasy, zwraca zaś uchwyt okna. Należy jednak pamiętać, że zakres poszukiwań ograniczony jest do okien najwyższego poziomu i nie obejmuje okien potomnych (typu child). Do lokalizowania tych ostatnich można natomiast wykorzystać funkcję FindWindowEx().
Oto deklaracja funkcji FindWindow():
HWND FindWindow(
LPCTSTR lpClassName, // nazwa klasy okna
LPCTSTR lpWindowName // zawartość paska tytułu
);
Parametry funkcji lpClassName i lpWindowName są wskaźnikami do zakończonych zerami łańcuchów zawierających odpowiednio nazwę klasy okna oraz zawartość jego paska tytułu. Funkcja zwraca wartość uchwytu okna lub NULL w przypadku niepowodzenia.
SetWindowText()
Funkcja SetWindowText() pozwala zmodyfikować tekst okna. W przypadku „zwykłych” okien zmieniana jest zawartość paska tytułu; dla elementów sterujących, takich jak np. przyciski - tekst zawarty w elemencie (np. opis przycisku, tekst etykiety). SetWindowText() nie pozwala na zmianę tekstu elementu sterującego należącego do innego programu. Deklaracja funkcji jest następująca:
BOOL SetWindowText(
HWND hWnd, // uchwyt modyfikowanego okna
LPCTSTR lpString // nowy tekst okna
);
Użycie funkcji SetWindowText() jest bardzo proste - w parametrze hWnd wystarczy umieścić uchwyt modyfikowanego okna, zaś w parametrze lpString - wskaźnik do zakończonego zerem łańcucha zawierającego nowy tekst.
Pora na nieco praktyki
Na wydruku 14.11 przedstawiono kod aplikacji, zawierającej pojedynczy przycisk. Kliknięcie przycisku powinno spowodować odszukanie okna Kalkulatora, zmienić zawartość jego paska tytułu i „mrugnąć” nim. Cały eksperyment wymaga oczywiście wcześniejszego uruchomienia Kalkulatora (calc.exe).
Uruchom IDE i utwórz nowy projekt.
Wstaw do głównego formularza pojedynczy przycisk.
Kliknij przycisk dwukrotnie i w edytorze kodu wpisz zawartość funkcji OnClick, przedstawioną na wydruku 14.11.
Wydruk 14.11. Powiadamianie użytkownika za pomocą funkcji FlashWindow()
void __fastcall TForm1::Button1Click(TObject *Sender)
{
HWND hHandle = FindWindow(NULL, "Kalkulator");
FLASHWINFO pf;
pf.cbSize = sizeof(FLASHWINFO);
pf.hwnd = hHandle;
pf.dwFlags = FLASHW_TIMER|FLASHW_ALL;
pf.uCount = 8;
pf.dwTimeout = 75;
FlashWindowEx(&pf);
if(hHandle)
SetWindowText(hHandle, "Liczydło");
}
Uruchom program kliknięciem przycisku uruchomienia (dla zapominalskich - to ten w kształcie zielonej strzałki).
Po uruchomieniu programu zminimalizuj okna IDE tak, by nie zasłaniały zawartości pulpitu.
Uruchom Kalkulator i zminimalizuj jego okno.
Kliknij przycisk w oknie naszego programu. Zawartość paska tytułu Kalkulatora powinna zmienić się z Kalkulator na Liczydło, a okno powinno „mrugnąć”.
Wyjaśnijmy dokładniej kolejne etapy działania programu.
HWND hHandle = FindWindow(NULL, "Kalkulator");
Powyższa instrukcja, wykonywana jako pierwsza po kliknięciu przycisku, ustala uchwyt okna Kalkulatora na podstawie zawartości paska tytułu. Zwrócona wartość uchwytu okna jest przypisywana do zmiennej hHandle.
Jak zatem skorzystać z naszej funkcji? Jeśli znamy tytuł „poszukiwanego” okna, nie ma problemu - wystarczy użyć kodu w postaci przedstawionej powyżej. Można też wykorzystać opisaną we wcześniejszej części rozdziału metodę identyfikacji okien za pomocą funkcji EnumWindows() i przedstawić użytkownikowi do wyboru listę okien.
Struktura danych sterujących działaniem funkcji FlashWindowEx()wykorzystywana jest następująco:
FLASHWINFO pf;
pf.cbSize = sizeof(FLASHWINFO);
pf.hwnd = hHandle;
pf.dwFlags = FLASHW_TIMER|FLASHW_ALL;
pf.uCount = 8;
pf.dwTimeout = 75;
FlashWindowEx(&pf);
Definiujemy tu zmienną pf typu FLASHWINFO, wypełniamy ją kompletem informacji niezbędnych do wykorzystania funkcji FlashWindowEx() i przekazujemy jej adres jako parametr funkcji. Znaczenie poszczególnych elementów struktury opiszemy poniżej.
Pole cbSize zawiera rozmiar struktury typu FLASHWINFO. W polu hwnd umieszcza się uchwyt interesującego nas okna. W naszym przykładzie uchwyt okna Kalkulatora uzyskaliśmy za pomocą wywołania
HWND hHandle = FindWindow(NULL, "Kalkulator");
Pole dwFlags zawiera opcje sterujące działaniem funkcji FlashWindowEx(). W naszym przypadku włączyliśmy miganie paska tytułu okna oraz przycisku na pasku zadań; użycie opcji FLASHW_TRAY pozwoliłoby ograniczyć sygnalizację wyłącznie do paska zadań. Opcja FLASHW_TIMER nakazuje funkcji działać, aż do ponownego wywołania z użyciem opcji FLASHW_STOP. Możliwe jest też zadanie określonej liczby mignięć okna, do czego służy pole uCount (w naszym przypadku nakazaliśmy funkcji wykonać osiem cykli i zakończyć pracę). Wreszcie pole dwTimeout pozwala określić czas trwania pojedynczego mignięcia w milisekundach (w przykładzie użyliśmy stosunkowo niewielkiej wartości, warto więc sprawdzić kilka innych ustawień).
Przedstawioną tu metodę użycia funkcji FlashWindowEx() można wykorzystać do powiadamiania o błędach i innych zdarzeniach bez konieczności wyświetlania okien komunikatów. Pozwala ona także poinformować użytkownika, że okno aplikacji jest już gotowe do pracy, ale nie zostało wybrane (uaktywnione). Można zatem powiedzieć, że FlashWindowEx() przydaje się w sytuacjach, gdy zależy nam na przyciągnięciu uwagi użytkownika bez zatrzymywania działania programu wyświetleniem okna dialogowego - w tym ostatnim przypadku kontynuacja pracy wymaga wykonania dodatkowego kroku, czyli zamknięcia okna komunikatu. Przykład działania naszej procedury przedstawiono na rysunku 14.6.
Rysunek 14.6. Uruchomiony program lokalizuje okno Kalkulatora i „mruga” jego paskiem tytułu
Zarządzanie stacją roboczą
Kolejną grupę przydatnych funkcji tworzą mechanizmy zarządzania stacją roboczą, czyli np. zablokowania konsoli w systemie Windows NT, zablokowania klawiszy Ctrl+Alt+Del czy też zamknięcia lub ponownego uruchomienia systemu. Również i tu interfejs Win32 udostępnia sporo możliwości. Eksperymenty z systemowymi funkcjami zarządzania stacją roboczą wymagają jednak daleko posuniętej ostrożności. Uruchamiając program, należy pamiętać o częstym zapisywaniu go na dysku, w przeciwnym razie możemy np. niechcący zamknąć system i pozbawić się wyników pracy.
Blokowanie konsoli
Pora na przedstawienie kolejnej funkcji Win32 o nazwie LockWorkStation(), umożliwiającej programowe zablokowanie stacji roboczej (konsoli) w systemie Windows NT. Nie posiada ona parametrów, jest banalnie prosta w użyciu, a jej działanie jest analogiczne do naciśnięcia klawiszy Ctrl+Alt+Del i wybrania opcji Zablokuj komputer. Odblokowanie konsoli po wywołaniu LockWorkStation() wymaga zalogowania użytkownika.
Prototyp funkcji zamieszczono w pliku winuser.h. Jej wykorzystanie, jak już powiedzieliśmy, sprowadza się do jednego wiersza kodu:
LockWorkStation();
Jedynym mankamentem funkcji LockWorkStation() jest jej niedostępność w systemach Windows 9x.
Zamknięcie systemu
Początkujący programiści często zadają pytanie o możliwość automatycznego zakończenia sesji w systemie Windows (w wielu przypadkach jest to równoważne wyłączeniu komputera). Można wykorzystać w tym celu funkcję ExitWindows() lub ExitWindowsEx() (pierwsza z nich pozwala jedynie zakończyć sesję bieżącego użytkownika; druga umożliwia również zamknięcie systemu - przyp. tłum.). Sposób użycia funkcji ExitWindowsEx() przedstawiono poniżej.
ExitWindowsEx(EWX_SHUTDOWN, 0);
W odróżnieniu od LockWorkStation(), funkcja ExitWindowsEx() nie jest nowością, jednak i ona oferuje spore możliwości automatycznego zarządzania systemem. Jeśli komputer wyposażony jest w mechanizm zarządzania energią, wywołanie ExitWindowsEx() pozwala nie tylko zamknąć system, ale również programowo wyłączyć cały komputer.
Możliwości funkcji ExitWindowsEx() nie ograniczają się do zakończenia pracy systemu operacyjnego. Pozwala ona wykorzystać kilka dodatkowych opcji, co widać poniżej:
BOOL ExitWindowsEx(
UINT uFlags, // sposób zamknięcia
DWORD dwReserved // zarezerwowany
);
Opcje sterujące sposobem zamknięcia systemu przekazuje się w polu uFlags. Opisano je w tabeli 14.23.
Tabela 14.23. Opcje zamknięcia systemu
Stała |
Znaczenie |
EWX_FORCE |
Wymusza zakończenie działania procesów. Użycie tej opcji zabrania systemowi operacyjnemu rozsyłania komunikatów WM_QUERYENDSESSION i WM_ENDSESSION. Może to spowodować utratę danych, toteż opcję EWX_FORCE należy traktować jako „ostatnią deskę ratunku”. |
EWX_LOGOFF |
Nakazuje zakończenie pracy procesów uruchomionych w kontekście bezpieczeństwa procesu, który wywołał funkcję ExitWindowsEx(), a następnie zamyka sesję użytkownika (wylogowuje go). |
EWX_POWEROFF |
Wymusza zamknięcie systemu i wyłączenie zasilania (tylko w systemach umożliwiających programowe sterowanie zasilaczem). Użycie tej opcji w systemie Windows NT wymaga od wywołującego procesu uprawnienia SE_SHUTDOWN_NAME; w Windows 9x uprawnienia związane z mechanizmami bezpieczeństwa są niedostępne i nie są wymagane. |
EWX_REBOOT |
Wymusza zamknięcie i ponowne uruchomienie systemu. Użycie tej opcji w systemie Windows NT wymaga uprawnienia SE_SHUTDOWN_NAME. |
EWX_SHUTDOWN |
Wymusza zamknięcie systemu i sprowadza go do stanu, w którym można bezpiecznie wyłączyć zasilanie komputera (oznacza to zapisanie na dyskach zawartości buforów i zakończenie działania wszystkich procesów). Użycie tej opcji w systemie Windows NT wymaga uprawnienia SE_SHUTDOWN_NAME. |
Parametr dwReserved nie jest obecnie używany.
Animacja okien
Rozszerzenia interfejsu użytkownika wprowadzone w systemach Windows 98 i 2000 obejmują między innymi nową funkcję AnimateWindow(), pozwalającą tworzyć atrakcyjne efekty animacyjne. Logicznie należy ona do grupy funkcji obsługujących elementy interfejsu użytkownika. Użytkownicy systemu Windows 98 prawdopodobnie mieli okazję już się z nią zapoznać; należy się też spodziewać dalszego wzrostu jej popularności w nowych aplikacjach. Umiejętne i rozsądne zastosowanie funkcji AnimateWindow() umożliwia stworzenie interesujących efektów wizualnych, a przy odrobinie wysiłku można w podobny sposób uatrakcyjnić także formularze i komponenty używane w systemie C++Builder.
AnimateWindow()
Funkcja AnimateWindow() wygląda dość prosto, jednak jej użycie wymaga wprawy i rozwagi. Oto jej deklaracja:
BOOL AnimateWindow(
HWND hwnd, // uchwyt okna
DWORD dwTime, // czas trwania efektu
DWORD dwFlags // rodzaj efektu
);
Parametr hwnd to uchwyt okna, które ma być poddane animacji. Wartość dwTime określa czas trwania efektu animacji w milisekundach. Parametr dwFlags pozwala określić rodzaj wymaganego efektu; jego wartości opisano w tabeli 14.24.
Tabela 14.24. Opcje sterujące efektami animacji w funkcji AnimateWindow()
Stała |
Znaczenie |
AW_SLIDE |
Nakazuje „przesunięcie” okna, tj. stopniowe wyświetlenie lub usunięcie jego zawartości poprzez „wysuwanie” jej w zadanym kierunku (domyślnie zawartość okna jest „rozwijana”, tj. pozostaje nieruchoma i jest stopniowo odsłaniana lub zasłaniana przez przesuwanie odpowiedniej krawędzi). Ignorowana w przypadku użycia stałej AW_CENTER. |
AW_ACTIVATE |
Aktywuje okno. Nie należy jej używać w połączeniu ze stałą AW_HIDE. |
AW_BLEND |
Nakazuje wyświetlenie lub usunięcie zawartości okna z użyciem efektu zmiennej przezroczystości. Można jej używać wyłącznie dla okien najwyższego poziomu. |
AW_HIDE |
Nakazuje ukrycie okna (domyślnie jest ono wyświetlane). |
AW_CENTER |
Nakazuje użycie efektu „implozji” (zwinięcia) okna w przypadku użycia stałej AW_HIDE lub jego „eksplozji” w przeciwnym razie. |
AW_HOR_POSITIVE |
W przypadku użycia animacji typu przesuwania lub rozwijania ustala kierunek efektu na „od lewej do prawej”. Ignorowana w przypadku użycia stałych AW_CENTER i AW_BLEND. |
AW_HOR_NEGATIVE |
W przypadku użycia animacji typu przesuwania lub rozwijania ustala kierunek efektu na „od prawej do lewej”. Ignorowana w przypadku użycia stałych AW_CENTER i AW_BLEND. |
AW_VER_POSITIVE |
Ustala kierunek efektu na „od góry do dołu”. Używana dla animacji typu rozwijania i przesuwania; ignorowana w przypadku użycia stałych AW_CENTER i AW_BLEND. |
AW_VER_NEGATIVE |
Ustala kierunek efektu na „od dołu do góry”. Używana dla animacji typu rozwijania i przesuwania; ignorowana w przypadku użycia stałych AW_CENTER i AW_BLEND. |
W przypadku powodzenia funkcja zwraca wartość TRUE (niezerową); w przeciwnym razie zwracana jest wartość FALSE (zero). Przyczyną niepowodzenia może być m.in. próba użycia funkcji dla okna wykorzystującego obszar.
Okno poddawane animacji, a także jego okna potomne, powinny zapewniać obsługę komunikatów WM_PRINT i WM_PRINTCLIENT. Pierwszy z nich jest obsługiwany domyślnie przez standardową funkcję obsługi okna; obsługę drugiego zapewniają okna dialogowe i standardowe elementy sterujące Windows.
Pora na nieco efektów.
Uruchom IDE i utwórz projekt nowej aplikacji.
Uzupełnij definicje funkcji FormShow() i FormHide() formularza głównego zgodnie z poniższym wydrukiem:
void __fastcall TForm1::FormShow(TObject *Sender)
{
AnimateWindow(Form1->Handle, 2000, AW_BLEND|AW_HOR_POSITIVE);
}
//------------------------------------------------------------------------
void __fastcall TForm1::FormClose(TObject *Sender,
TCloseAction &Action)
{
AnimateWindow(Form1->Handle, 1000,
AW_HIDE|AW_BLEND|AW_VER_POSITIVE);
}
Skompiluj program, uruchom go i… podziwiaj!
Okno aplikacji powinno powoli pojawić się na ekranie, a w chwili zamknięcia stopniowo zniknąć (czas trwania obu efektów ustaliliśmy odpowiednio na dwie i jedną sekundę). Za płynne „wygaszenie” okna aplikacji odpowiedzialna jest stała AW_HIDE, użyta w funkcji FormClose(). Jak widać, funkcja AnimateWindow() pozwala nieco ubarwić banalne procesy otwierania i zamykania okien.
Uwaga
Użycie funkcji AnimateWindow() może nie dać żadnego efektu, jeżeli w ustawieniach ekranu wyłączono opcję animowania okien, menu i list.
Jeśli komuś jeszcze mało efektów, może spróbować w funkcji FormClose() następującego wywołania:
AnimateWindow(Form1->Handle, 1000,
AW_HIDE|AW_SLIDE|AW_HOR_POSITIVE|AW_VER_NEGATIVE);
Spowoduje ono „rozwinięcie” zawartości okna wzdłuż jego przekątnej. Ustalenie kierunku „rozwijania” osiąga się przez odpowiednie złożenie stałej AW_HOR_POSITIVE lub AW_HOR_NEGATIVE z AW_VER_POSITIVE lub AW_VER_NEGATIVE.
Na koniec warto wypróbować w funkcji FormClose() jeszcze jeden trik:
AnimateWindow(Form1->Handle, 1000, AW_HIDE|AW_SLIDE|AW_VER_POSITIVE);
Spowoduje to „zwinięcie” okna od góry do dołu i jego ukrycie.
Zainteresowani mogą poeksperymentować nieco z czasem trwania animacji, np. zmieniając wartość 1000 milisekund na 2500. Ciekawy efekty „zaniku” okna daje też użycie stałej AW_BLEND.
Jak widać, specjaliści z Microsoftu nie próżnowali, dając programistom do rąk sporo ciekawych rozwiązań, pozwalających ubarwić interfejs użytkownika Windows.
Kółko graniaste, czyli okna dowolnych kształtów
Tak. Korzystanie z zaawansowanych funkcji systemowych to niezła zabawa, i to nie tylko dla programisty - uzyskany w ten sposób niecodzienny wygląd formularza może naprawdę zrobić wrażenie. Obecnie przedstawimy kolejny zestaw trików, wykorzystujących pojęcie obszarów (regionów) i związane z nimi funkcje, jak: CombineRgn(), CreateEllipticRgn() i GetWindowRect(). Mechanizmy te pozwalają kontrolować wygląd okien, dzięki czemu można wykroczyć poza nudny schemat, w którym każde okno jest przewidywalnie prostokątne.
Użycie wspomnianych funkcji w programach tworzonych w C++Builderze nie nastręcza trudności. Odpowiednie deklaracje zawarto w pliku nagłówkowym wingdi.h.
Obszar (obszar ograniczający lub region) jest po prostu... obszarem wyznaczającym granice widoczności okna. Wszystkie obiekty znajdujące się poza nim nie są wyświetlane; innymi słowy, obszar „obcina” zawartość ekranu i zamyka ją w zdefiniowanym przez nas kształcie. Pozwala to tworzyć okna o najróżniejszych kształtach.
CreateRoundRectRgn()
Naszą prezentację rozpoczniemy od funkcji CreateRoundRectRgn(). Definiuje ona obszar o kształcie prostokąta z zaokrąglonymi narożnikami, a jej deklaracja jest następująca:
HRGN CreateRoundRectRgn(
int nLeftRect, // współrzędna x lewego górnego wierzchołka
int nTopRect, // współrzędna y lewego górnego wierzchołka
int nRightRect, // współrzędna x prawego dolnego wierzchołka
int nBottomRect, // współrzędna y prawego dolnego wierzchołka
int nWidthEllipse, // wysokość elipsy wyznaczającej zaokrąglenie
int nHeightEllipse // szerokość elipsy wyznaczającej zaokrąglenie
);
Parametry funkcji określają odpowiednio:
nLeftRect i nTopRect - współrzędne x i y lewego górnego wierzchołka prostokąta wyznaczającego obszar;
nRightRect i nBottomRect - współrzędne x i y prawego dolnego wierzchołka prostokąta wyznaczającego obszar;
nWidthEllipse - szerokość (długość osi x) elipsy wyznaczającej zaokrąglenie narożników obszaru;
nHeightEllipse - wysokość (długość osi y) elipsy wyznaczającej zaokrąglenie narożników obszaru.
Utworzenie okna o zaokrąglonych narożnikach przedstawiono poniżej.
HRGN Region = CreateRoundRectRgn(0, 0, 150, 110, 15, 10);
SetWindowRgn(hWnd, Region, TRUE);
Nic trudnego. Jedynym problemem jest ustalenie współrzędnych obszaru - uzyskanie właściwego wyglądu okna może wymagać kilku eksperymentów.
Spróbujmy obecnie stworzyć program, który pozwoli nam ustalić rozmiary okna za pomocą suwaków, na bieżąco wyświetlając odpowiednie współrzędne i aktualizując kształt okna. Uruchom IDE, utwórz nowy projekt i wstaw do głównego formularza komponenty opisane w tabeli 14.25.
Tabela 14.25. Komponenty formularza o zmiennym kształcie
Komponent |
Właściwości |
Form1 |
Width = 369 |
Label1 |
Left = 14 |
Label2 |
Left = 16 |
Label3 |
Left = 16 |
Label4 |
Left = 16 |
Label5 |
Left = 16 |
Label6 |
Left = 16 |
Label7 |
Left = 16 |
Label8 |
Left = 16 |
Label9 |
Left = 16 |
Label10 |
Left = 240 |
Label11 |
Left = 16 |
Label12 |
Left = 240 |
TrackBar1 |
Name = 'Track1' |
TrackBar2 |
Name = 'Track2' |
TrackBar3 |
Name = 'Track3' |
TrackBar4 |
Name = 'Track4' |
TrackBar5 |
Name = 'Track5' |
TrackBar6 |
Name = Track6 |
Jedyny fragment kodu, jaki należy umieścić w pliku źródłowym formularza, definiuje funkcję obsługi zdarzenia OnChange komponentu Track3. Pozostałe komponenty Trackx powinny wykorzystywać funkcję OnChange(), zdefiniowaną dla komponentu Track3 - w tym celu wystarczy odpowiednio ustawić zawartości pól OnChange w inspektorze obiektów. Sam komponent typu TTrackBar można znaleźć na karcie Win32 palety komponentów.
Kod funkcji OnChange() znajduje się na wydruku 14.12.
Wydruk 14.12. Zmiana kształtu formularza za pomocą funkcji CreateRoundRectRgn()
#include <vcl\vcl.h>
#pragma hdrstop
#include "mainsrc.h"
//---------------------------------------------------------------------------
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
Form1->Height = 416;
Form1->Width = 369;
Track3->Max = Form1->Width+10;
Track3->Min = 70;
Track3->Position = Form1->Width;
// Zaczynamy od pełnowymiarowego formularza
Track4->Max = Form1->Height+10;
Track4->Min = 195;
Track4->Position = Form1->Height;
Track1->Max = 20; // Wyznacza ograniczenie rozmiaru
Track1->Min = 0;
Track1->Position = 0; // Zaczynamy od pełnowymiarowego formularza
Track2->Max = 230; // Wyznacza ograniczenie rozmiaru
Track2->Min = 0;
Track2->Position = 0; // Zaczynamy od pełnowymiarowego formularza
Track5->Max = 500;
Track5->Min = 0;
Track5->Position = 31;
Track6->Max = 500;
Track6->Min = 0;
Track6->Position = 31;
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Track3Change(TObject *Sender)
{
HRGN MyRegion = CreateRoundRectRgn(Track1->Position,
Track2->Position, Track3->Position,
Track4->Position, Track5->Position,
Track6->Position);
SetWindowRgn(Handle, MyRegion, true);
Label1->Caption = Track3->Position;
Label2->Caption = Track4->Position;
Label6->Caption = Track1->Position;
Label8->Caption = Track2->Position;
Label10->Caption = Track5->Position;
Label12->Caption = Track6->Position;
Form1->Update();
}
Po sprawdzeniu, czy funkcje obsługi zdarzenia OnChange wszystkich elementów Trackx ustawiono na wartość Track3Change(), pozostaje nam skompilować i uruchomić program. Manipulowanie suwakami pozwoli na zmianę współrzędnych i zaokrąglenia narożników okna, zaś odpowiednie wartości wyświetlane będą w polach etykiet. Stworzony tu program można wykorzystać do modelowania okien na potrzeby innych aplikacji, dzięki czemu nie trzeba będzie zgadywać lub uciekać się do pomocy papieru milimetrowego.
CreateEllipticRgn()
Tym, którym prostokąt z zaokrąglonymi narożnikami wydaje za mało atrakcyjny, można polecić funkcję CreateEllipticRgn(), tworzącą obszar o kształcie elipsy. Efekt końcowy zależy od wartości parametrów, zaś deklarację funkcji przedstawiono poniżej.
HRGN CreateEllipticRgn(
int nLeftRect, // współrzędna x lewego górnego wierzchołka prostokąta
int nTopRect, // współrzędna y lewego górnego wierzchołka prostokąta
int nRightRect, // współrzędna x prawego dolnego wierzchołka prostokąta
int nBottomRect // współrzędna y prawego dolnego wierzchołka prostokąta
);
Znaczenie poszczególnych parametrów jest oczywiste:
wartości nLeftRect i nTopRect określają współrzędne x i y lewego górnego wierzchołka prostokąta ograniczającego obszar elipsy (opisanego na niej);
wartości nRightRect i nBottomRect określają współrzędne x i y prawego dolnego wierzchołka prostokąta.
Lista parametrów zbliżona jest do znanej nam już z funkcji CreateRoundRectRgn(), jednak tym razem definiujemy tylko współrzędne wierzchołków prostokąta ograniczającego obszar elipsy. Oto przykład użycia funkcji:
HRGN HRegion;
HRegion = CreateEllipticRgn(0, 0, Form1->Width, Form1->Height);
SetWindowRgn(Handle, HRegion, TRUE);
Wykonanie powyższego kodu zmieni kształt widocznej części formularza na eliptyczny. Ponieważ jako prostokąt ograniczający wykorzystaliśmy obszar samego formularza, długości osi elipsy będą równe odpowiednio początkowej szerokości i wysokości formularza.
CombineRgn()
Funkcja CombineRgn(), umożliwiająca złożenie kilku uprzednio zdefiniowanych obszarów, jest narzędziem niezwykle użytecznym. Pozwala ona tworzyć okna o nieregularnym kształcie, coraz częściej spotykane w nowoczesnych programach. Wraz z funkcjami CreateEllipticRgn(), CreateRoundRect() i CreatePolygonRgn() funkcja CombineRgn() pozwala definiować obszary o najbardziej wymyślnych kształtach.
Deklaracja funkcji CombineRgn() ma następującą postać:
int CombineRgn(
HRGN hrgnDest, // uchwyt obszaru wynikowego
HRGN hrgnSrc1, // uchwyt pierwszego obszaru źródłowego
HRGN hrgnSrc2, // uchwyt drugiego obszaru źródłowego
int fnCombineMode // sposób łączenia obszarów
);
Parametr hrgnDest jest uchwytem obszaru powstałego w wyniku złożenia obszarów źródłowych identyfikowanych uchwytami hrgnSrc1 i hrgnSrc2. Warto pamiętać, że hrgnDest musi określać już istniejący (wcześniej utworzony) obszar.
Sposób połączenia obszarów określony jest parametrem fnCombineMode. Może on przyjąć jedną z wartości przedstawionych w tabeli 14.26.
Tabela 14.26. Dopuszczalne wartości parametru fnCombineMode
Stała |
Znaczenie |
RGN_AND |
Utworzenie obszaru równego części wspólnej obszarów źródłowych |
RGN_COPY |
Utworzenie kopii obszaru danego parametrem hrgnSrc1 |
RGN_DIFF |
Utworzenie obszaru równego tej części obszaru hrgnSrc1, która nie należy do hrgnSrc2 |
RGN_OR |
Utworzenie obszaru równego sumie obszarów źródłowych |
RGN_XOR |
Utworzenie obszaru zawierającego wyłącznie punkty należące do obszaru hrgnSrc1 albo hrgnSrc2 (ale nie obu obszarów) |
Wartość zwracana przez funkcję określa rodzaj utworzonego obszaru. Możliwe wartości zwracane przedstawiono w tabeli 14.27.
Tabela 14.27. Wartości zwracane przez funkcję CombineRgn()
Wartość zwracana |
Znaczenie |
NULLREGION |
Utworzono obszar pusty. |
SIMPLEREGION |
Utworzono obszar prosty (pojedynczy prostokąt). |
COMPLEXREGION |
Utworzono obszar złożony (bardziej skomplikowany niż prostokąt). |
ERROR |
Nie utworzono obszaru wynikowego - wystąpił błąd wykonania. |
Obszary przetwarzane przez funkcję CombineRgn() nie muszą być różne. Jeśli jako parametry wywołania przekażemy uchwyty Region1, Region1 i Region2, wynik operacji zostanie umieszczony w pierwszym obszarze źródłowym, co jest jak najbardziej poprawne. Decydując się na użycie oddzielnego obszaru docelowego, należy natomiast pamiętać o jego utworzeniu przed wywołaniem funkcji (samo zadeklarowanie zmiennej typu HRGN nie wystarczy).
Poniżej przedstawiono przykład wykorzystania funkcji CombineRgn(), który powinien dostatecznie wyjaśnić zasady jej użycia i rozwiać wszelkie wątpliwości.
HRGN Region1, Region2;
Region1 = CreateRectRgn(0, 0, 100, 100);
Region2 = CreateRectRgn(50, 50, 150, 150);
CombineRgn(Region1, Region1, Region2, RGN_XOR);
SetWindowRgn(Handle, Region1, TRUE);
Wykorzystanie powyższego kodu dla przykładowego formularza spowoduje wyświetlenie okna w postaci dwóch „przecinających się” prostokątów z pustym obszarem przecięcia. Taka postać obszaru wynika z użycia stałej RGN_XOR, która nakazuje utworzenie obszaru wynikowego, zawierającego wyłącznie punkty należące do jednego z obszarów źródłowych, ale bez ich części wspólnej. Użycie stałej RGN_OR spowodowałoby powstanie obszaru złożonego z obu obszarów źródłowych łącznie z ich częścią wspólną.
Zauważmy, że obszar Region1 został tu użyty zarówno jako pierwszy obszar źródłowy, jak i obszar wynikowy. W efekcie pierwotna postać Region1 zostanie zastąpiona złożeniem Region1 i Region2. Taki sposób użycia parametrów zwalnia nas od konieczności tworzenia dodatkowego obszaru przed wywołaniem funkcji (zakładamy, że pierwszy obszar źródłowy nie będzie nam później potrzebny - przyp. tłum.).
To jeszcze nie wszystko. Kolejną bardzo interesującą funkcją do manipulacji obszarami jest CreatePolygonRgn(), wspomniana już nieco wyżej. Oto jej deklaracja:
HRGN CreatePolygonRgn(
CONST POINT *lppt, // wskaźnik do tablicy wierzchołków
int cPoints, // liczba wierzchołków
int fnPolyFillMode // sposób wypełnienia
);
Parametr lppt wskazuje tablicę struktur typu POINT, określających współrzędne kolejnych wierzchołków wielokąta definiującego obszar. Zakłada się przy tym, że wielokąt jest domknięty (tj. pierwszy i ostatni wierzchołek są automatycznie łączone), a poszczególne wierzchołki nie mogą się pokrywać. Parametr cPoints określa liczbę wierzchołków, zaś wartość fnPolyFillMode decyduje o sposobie wypełnienia wnętrza tworzonego wielokąta i może przyjąć jedną z wartości opisanych w tabeli 14.28.
Tabela 14.28. Wartości parametru fnPolyFillMode
Stała |
Znaczenie |
ALTERNATE |
Wypełnianie w trybie naprzemiennym (wypełniane są obszary pomiędzy parzystymi a nieparzystymi bokami wielokąta) |
WINDING |
Wypełnianie wszystkich obszarów zamkniętych |
Współrzędne poszczególnych wierzchołków definiowane są w postaci struktur typu POINT (można tu użyć zarówno zdefiniowanej w API struktury POINT, jak i dostępnego w C++Builderze typu TPoint). Kreślenie wielokąta sprowadza się do łączenia kolejnych punktów odcinkami.
Struktura typu POINT zawiera dwa pola, określające współrzędne x i y pojedynczego punktu. Aby utworzyć wielokąt, wystarczy zadeklarować tablicę takich struktur i wypełnić je odpowiednimi wartościami.
Pora na przykład. Przedstawiony poniżej kod pokazuje sposób użycia funkcji CreatePolygonRgn() do utworzenia czegoś w rodzaju znaku „Stop” - nieco koślawego, ale na potrzeby szkoleniowe chyba wystarczającego. Przy okazji warto zwrócić uwagę na inną użyteczną funkcję Win32 - GetClientRect(). Określa ona współrzędne prostokąta definiującego wewnętrzny obszar okna (ang. client area) i zwraca je w postaci struktury typu RECT, zawierającej współrzędne lewego górnego i prawego dolnego wierzchołka prostokąta. Zarówno GetClientRect(), jak i typ strukturalny RECT są standardowo udostępniane poprzez bibliotekę VCL.
Uruchom IDE i utwórz nową aplikację.
Zmień kolor tła formularza głównego na czerwony (clRed); wstaw do formularza przycisk i zmień jego tekst na Stop.
Uzupełnij kod funkcji OnClick() przycisku zgodnie z poniższym wydrukiem:
void __fastcall TForm1::BitBtn1Click(TObject *Sender)
{
Close();
}
W pliku źródłowym formularza głównego wpisz następujący kod:
__fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner)
{
RECT R;
R = GetClientRect();
POINT p[8];
p[0].x =87; p[0].y =0;
p[1].x =25; p[1].y =59;
p[2].x =24; p[2].y =123;
p[3].x =79; p[3].y =176;
p[4].x =171; p[4].y =173;
p[5].x =207; p[5].y =123;
p[6].x =208; p[6].y =59;
p[7].x =156; p[7].y =0;
HRGN MyRegion;
MyRegion = CreatePolygonRgn(p, 8, ALTERNATE);
SetWindowRgn(Handle, MyRegion, true);
}
Tytułem demonstracji w powyższej funkcji utworzyliśmy strukturę R typu RECT, a następnie umieściliśmy w niej współrzędne wewnętrznego obszaru okna, wywołując funkcję GetClientRect(). Zadeklarowana poniżej tablica p składa się ze struktur typu POINT, zawierających współrzędne kolejnych wierzchołków naszego „znaku”. Wielokąt ograniczający pole znaku zostanie utworzony przez połączenie poszczególnych wierzchołków.
Po zdefiniowaniu wszystkich punktów deklarujemy uchwyt obszaru o nazwie MyRegion, a następnie wywołujemy funkcję CreatePolygonRgn() celem utworzenia samego obszaru na podstawie definicji wierzchołków zawartych w tablicy p. Pierwszym parametrem CreatePolygonRgn() jest wskaźnik do tablicy wierzchołków, drugim - ich liczba (warto spróbować np. podać tu wartość 4 - wynik jest dość ciekawy). Na koniec ustalamy sposób wypełnienia wielokąta za pomocą stałej ALTERNATE. Utworzony w ten sposób obszar wykorzystujemy w wywołaniu funkcji SetWindowRgn(), ustalającej nowy kształt okna.
Uruchomienie programu powinno dać w efekcie coś w rodzaju znaku „Stop” wyposażonego w przycisk, którego kliknięcie zamknie okno.
Opisane tu zagadnienia to zaledwie podstawy techniki użycia obszarów, powinny jednak umożliwić zrozumienie działania tego mechanizmu i jego skuteczne wykorzystanie w aplikacjach tworzonych w systemie C++Builder. Czytelników zainteresowanych szczegółami zagadnienia odsyłamy do witryny Microsoft Developers Network (http://msdn.microsoft.com), polecając wyszukanie słów kluczowych „window regions” lub „irregular windows”.
Tworzenie apletów Panelu sterowania - metoda tradycyjna
Dostępne w Panelu sterowania aplety są niewielkimi programikami, umożliwiającymi zarządzanie środowiskiem Windows, ustawieniami aplikacji lub elementów sprzętu. Aplety są w istocie bibliotekami dynamicznymi, różniącymi się od tych ostatnich rozszerzeniem nazwy (.cpl w miejsce .dll). Umożliwia to wizualne rozróżnienie obu rodzajów plików.
W ostatnim punkcie tego rozdziału omówimy techniczne zagadnienia związane z tworzeniem i działaniem apletów, a następnie stworzymy prosty program i przekształcimy go w aplet. Co prawda C++Builder oferuje programiście specjalny kreator apletów (Control Panel Applet Wizard), który umożliwia zrobienie tego samego znacznie prościej, jednak warto zapoznać się także z metodą tradycyjną. Nie jest ona o wiele trudniejsza, a daje jeszcze jedną okazję do zapoznania się z mechanizmami współpracy systemu C++Builder z interfejsem Win32.
Zasada działania apletu
Aplety dostępne są w postaci ikon w Panelu sterowania Windows i uruchamiane są tak jak inne aplikacje - przez dwukrotne kliknięcie myszą. Ich podstawowym przeznaczeniem jest udostępnienie użytkownikowi metod konfigurowania funkcji systemu operacyjnego, elementów sprzętu lub aplikacji. Ułatwia to zmianę ustawień większych programów i systemów - zamiast zmuszać użytkownika do uruchamiania całej aplikacji (co bywa czasochłonne) tylko w celu zmiany jakiegoś parametru, można zamknąć funkcje konfiguracyjne w aplecie, który np. zapisze zaktualizowane ustawienia w rejestrze Windows (a przy tym będzie się uruchamiał znacznie szybciej). Każdy aplet dostępny w Panelu sterowania posiada charakterystyczną ikonę i tytuł; może również zawierać dodatkowy opis, wyświetlany w pasku statusu (a także, w widoku szczegółowym, na liście elementów Panelu sterowania - przyp. tłum.).
Tworzenie apletów jest wbrew pozorom bardzo proste. Zanim przejdziemy do praktyki, należy jednak powiedzieć kilka słów o technicznych szczegółach ich działania.
Jak już wspomnieliśmy, aplet jest odmianą biblioteki dynamicznej, eksportującą standardową funkcję CPlApplet(). Funkcja ta stanowi punkt wejścia do apletu i odpowiada za całość jego działania, począwszy od inicjalizacji, poprzez wyświetlanie i zarządzanie oknami dialogowymi, aż do zakończenia działania. Wymiana danych z funkcją CPlApplet() odbywa się poprzez przesyłanie odpowiednich komunikatów.
Zarządzaniem apletami zajmuje się Panel sterowania Windows. Pobiera on odpowiednie dane od poszczególnych apletów i steruje ich wykonywaniem. Nie jest to jedyne rozwiązanie - można także napisać własny program do zarządzania apletami, musi on jednak przestrzegać reguł wymiany danych określonych dla Panelu sterowania.
W chwili uruchomienia (poleceniami Ustawienia|Panel sterowania z menu Start) Panel sterowania identyfikuje wszystkie pliki apletów zawarte w katalogu \Windows\System lub Windows\System32, pobiera ich ikony, określa liczbę udostępnianych przez nie okien dialogowych oraz kilka innych informacji. Proces ten może zająć do kilku sekund, co niektórzy użytkownicy z pewnością zauważyli.
Komunikaty rządzące wymianą danych pomiędzy apletem a Panelem sterowania (lub inną aplikacją zarządzającą) przekazywane są za pośrednictwem funkcji zwrotnej CPlApplet(). Oto jej deklaracja:
LONG APIENTRY CPlApplet(
HWND hwndCPl, // uchwyt okna Panelu sterowania
UINT uMsg, // kod komunikatu
LONG lParam1, // pierwszy parametr komunikatu
LONG lParam2 // drugi parametr komunikatu
);
Parametr hwndCPl zawiera uchwyt okna aplikacji zarządzającej apletami (zwykle jest to Panel sterowania). Komunikat identyfikowany jest kodem przekazywanym w parametrze uMsg, zaś parametry lParam1 i lParam2 pozwalają przekazać dodatkowe wartości (parametry) precyzujące znaczenie komunikatu. Wartość zwracana przez funkcję CPlApplet() zależy od rodzaju komunikatu; zajmiemy się tym w dalszej części rozdziału.
Inicjalizacja apletu przez Panel sterowania obejmuje kilka wywołań funkcji CPlApplet(). Zwracane przez nią wartości zależą od rodzaju komunikatu i jego parametrów; należy także pamiętać, że komunikaty przekazywane są do apletu w określonej kolejności, co wymusza specyficzną organizację jego kodu. Kolejność komunikatów i ich znaczenie przedstawiono w tabeli 14.29 (w nawiasach w pierwszej kolumnie podano wartości liczbowe odpowiadające stałym symbolicznym).
Tabela 14.29. Kolejność i znaczenie komunikatów wysyłanych do funkcji CPlApplet()
Komunikat |
Kolejność |
Znaczenie |
CPL_INIT |
Pierwszy komunikat wysyłany bezpośrednio po załadowaniu pliku .cpl zawierającego kod apletu |
Inicjalizacja apletu. Parametry lParam1 i lParam2 nie są używane. Funkcja CPlApplet() zwraca wartość TRUE w przypadku powodzenia inicjalizacji lub FALSE w razie niepowodzenia. Zwrócenie wartości FALSE nakazuje zarządzającej aplikacji zakończenie wymiany danych i usunięcie kodu apletu z pamięci. |
CPL_GETCOUNT |
Wysyłany bezpośrednio po komunikacie CPL_INIT; aplet zwraca wartość niezerową |
Ustalenie liczby apletów zawartych w pliku .cpl. Liczbę okien określa wartość zwracana przez funkcję CPlApplet(). Parametry lParam1 i lParam2 nie są używane. |
CPL_INQUIRE |
Wysyłany bezpośrednio po komunikacie CPL_GETCOUNT, dokładnie raz dla każdego apletu (okna dialogowego) |
Zwrócenie informacji o apletach logicznych zawartych w pliku .cpl. Parametr lParam1 zawiera numer apletu (liczony od zera), parametr lParam2 jest wskaźnikiem do struktury CPLINFO, zawierającej informacje o ikonie, tytule i innych parametrach apletu. Zwrócenie przez funkcję CPlApplet() wartości 0 oznacza powodzenie operacji. |
CPL_NEWINQUIRE |
Komunikat ten może zostać użyty zamiast CPL_INQUIRE (musi być wysłany jako trzeci w kolejności) |
Zwrócenie informacji o apletach zawartych w pliku .cpl (analogicznie jak dla komunikatu CPL_INQUIRE); parametr lParam2 wskazuje na strukturę NEWCPLINFO, zawierającą m.in. dane zależne od stanu systemu (zmienne). Ze względu na lepszą wydajność w systemach Windows 9x i NT zaleca się użycie komunikatu CPL_INQUIRE. |
CPL_DBLCLK |
Wysyłany po dwukrotnym kliknięciu ikony apletu |
Wywołanie danego apletu logicznego. Parametr lParam1 zawiera numer apletu; lParam2 - wartość pola lData struktury CPLINFO. Efektem wysłania tego komunikatu powinno być wyświetlenie przez aplet określonego okna (formularza). |
CPL_STOP |
Wysyłany do wszystkich apletów bezpośrednio przed zakończeniem działania aplikacji zarządzającej, dokładnie raz dla każdego okna dialogowego |
Polecenie zwolnienia zasobów związanych z danym apletem (identyfikowanym przez parametr lParam1). Parametr lParam2 zawiera wartość pola lData struktury CPLINFO, wypełnianej w odpowiedzi na komunikat CPL_INQUIRE. |
CPL_EXIT |
Wysyłany jednokrotnie po przesłaniu ostatniego komunikatu CPL_STOP i bezpośrednio przed wywołaniem przez zarządzającą aplikację funkcji FreeLibrary(), usuwającej z pamięci kod apletu |
Polecenie zwolnienia zasobów (np. bloków pamięci) przydzielonych „globalnie” przez kod apletu (niezwiązanych z poszczególnymi oknami dialogowymi). Parametry lParam1 i lParam2 nie są używane. |
Przeanalizujmy obecnie poszczególne komunikaty w kolejności ich wysyłania. Jako pierwszy do funkcji CPlApplet() trafia komunikat CPL_INIT, wysyłany przez aplikację zarządzającą apletami (Panel sterowania) w trakcie ich ładowania. Obsługa tego komunikatu obejmuje inicjację pracy apletu, przydzielenie zasobów itd., zaś po jego przetworzeniu funkcja CPlApplet() powinna zwrócić wartość niezerową. Zwrócenie wartości zero (FALSE) sygnalizuje niepowodzenie inicjalizacji apletu i nakazuje Panelowi sterowania zakończenie wymiany danych i usunięcie kodu apletu z pamięci.
Po udanym przetworzeniu komunikatu CPL_INIT aplikacja zarządzająca przesyła komunikat CPL_GETCOUNT, nakazujący zwrócenie liczby apletów logicznych (okien dialogowych) zawartych w pliku .cpl (jeśli np. w pliku zdefiniowaliśmy trzy takie okna, funkcja CPlApplet() powinna zwrócić wartość 3).
W kolejnym kroku aplikacja zarządzająca przesyła do apletu serię komunikatów CPL_INQUIRE i CPL_NEWINQUIRE (po jednej parze dla każdego apletu logicznego). Ich obsługa sprowadza się do wypełnienia przez aplet struktur typu CPLINFO i CPLNEWINFO, zawierających dane, opisujące poszczególne okna. Dane te - identyfikatory ikon i ich podpisów oraz opisy okien - umieszczane są odpowiednio w polach: idIcon, idName i idInfo; pole lData przeznaczone jest na dodatkowe informacje. Zawartość struktury CPLINFO jest buforowana przez aplikację zarządzającą i zachowywana pomiędzy kolejnymi wywołaniami danego apletu. Związana z tym poprawa wydajności jest powodem, dla którego w systemach Windows 9x i NT zaleca się korzystanie właśnie z komunikatu CPL_INQUIRE i ignorowanie CPL_NEWINQUIRE. Informacje zwracane w wyniku obsłużenia tego ostatniego nie są buforowane. Widoczny niekiedy efekt powolnego ładowania Panelu sterowania spowodowany jest właśnie obsługiwaniem przez niektóre aplety komunikatów CPL_NEWINQUIRE (co wynika z dążenia do zachowania wstecznej zgodności). Różnica w obsłudze komunikatów CPL_INQUIRE i CPL_NEWINQUIRE sprowadza się do innej postaci zwracanej struktury danych - w przypadku CPL_NEWINQUIRE parametr lParam2 wskazuje na strukturę typu NEWCPLINFO, zawierającą informacje zależne od bieżącego stanu systemu (pozwala to np. dynamicznie zmieniać postać ikony lub opisu okna dialogowego). Chociaż aplet powinien akceptować zarówno komunikat CPL_INQUIRE, jak i CPL_NEWINQUIRE, ten ostatni można w większości przypadków zignorować. Warto też pamiętać, że opisane tu „zapytania” nie są wysyłane przez aplikację zarządzającą w określonym porządku i nie należy czynić żadnych założeń na temat ich kolejności lub wzajemnej zależności.
Przesyłany wraz z komunikatami CPL_INQUIRE i CPL_NEWINQUIRE parametr lParam1 określa numer apletu logicznego, którego opisu żąda aplikacja zarządzająca (numer może zawierać się w przedziale od zera do wartości zwróconej w odpowiedzi na komunikat CPL_GETCOUNT). Parametr lParam2 wskazuje odpowiednią strukturę CPLINFO lub NEWCPLINFO.
Kolejnym komunikatem przekazywanym do funkcji CPlApplet() jest CPL_DBLCLK, sygnalizujący dwukrotne kliknięcie myszą ikony apletu (jego wywołanie). Parametrami komunikatu są numer uruchamianego apletu logicznego oraz wartość umieszczona w polu lData struktury CPLINFO zwracanej w odpowiedzi na komunikat CPL_INQUIRE (jest ona również przekazywana wraz z komunikatem CPL_STOP).
Żądanie zakończenia działania apletu sygnalizuje seria komunikatów CPL_STOP, po których przesyłany jest pojedynczy komunikat CPL_EXIT. W odpowiedzi na nie funkcja CPlApplet() powinna zwolnić zasoby przydzielone przez kod apletu (pamięć, zarejestrowane klasy okien). Po zwróceniu sterowania aplikacja zarządzająca wywołuje funkcję FreeLibrary(), usuwającą kod apletu z pamięci.
Utworzenie apletu
Pora teraz na praktykę, czyli utworzenie samego apletu. Na początek podamy kilka praktycznych reguł, o których warto pamiętać podczas budowy apletu i po jego skonstruowaniu.
Aby aplet został „zauważony” przez Panel sterowania, należy go skopiować do katalogu Windows\System lub \Winnt\System32 (system przechowuje tam wszystkie pliki .CPL).
Dane o aplecie można wprowadzić do rejestru Windows (standardowym miejscem ich przechowywania jest klucz HKEY_CURRENT_USER\ControlPanel).
Po skompilowaniu kodu apletu należy zmienić rozszerzenie nazwy utworzonego pliku biblioteki DLL na .CPL. C++Builder pozwala też na ustalenie rozszerzenia pliku wynikowego w ramach projektu - w tym celu należy wydać polecenie Options z menu Project, wybrać kartę Application i w polu Target extension wpisać CPL.
Chociaż zawartość okna Panelu sterowania (jak każdego innego okna) można sortować, porządek ładowania apletów nie jest ustalony i nie należy czynić na ten temat żadnych założeń.
Po tych kilku uwagach pozostaje wziąć się do pracy.
Uruchom IDE.
Za pomocą polecenia Close All z menu File zamknij wszystkie otwarte okna, nie zapisując żadnych plików, projektów itd. (w ten sposób utworzymy sobie „czyste pole” do dalszej części eksperymentu).
Obecnie utworzymy dwa formularze, będące oknami dialogowymi naszego apletu.
Wybierz polecenie New Form z menu File.
Do utworzonego w ten sposób formularza wstaw etykietę i przycisk, ustalając ich właściwości zgodnie z tabelą 14.30.
Tabela 14.30. Właściwości komponentów okna dialogowego apletu
Komponent |
Właściwości |
Label1 |
Caption = 'To ja, aplet numer 1' |
Button1 |
Caption = 'Kliknij' |
Uzupełnij funkcję OnClick() przycisku do następującej postaci:
void __fastcall TForm1::Button1Click(TObject *Sender)
{
Close();
}
Zapisz formularz pod nazwą Form1 .
Wybierz ponownie polecenie New Form z menu File i utwórz na formularzu analogiczne komponenty, jak w punkcie 4. (etykieta powinna zawierać numer 2).
Utwórz dla drugiego przycisku funkcję obsługi zdarzenia OnClick identyczną z przedstawioną w punkcie 5.
Za pomocą inspektora obiektów zmień nazwę formularza na Form2 i zapisz go.
Projekt powinien teraz zawierać dwa podobne formularze. Zamknij wszystkie pliki poleceniem Close All z menu File.
Kolejnym etapem będzie utworzenie ikon reprezentujących oba okna. Co prawda definiowanie oddzielnej ikony dla każdego okna nie jest konieczne (w razie ich braku system użyje dla obu okien ikony domyślnej). Skoro jednak nasz aplet ma się wyróżniać, utworzymy dlań niestandardowe ikony.
Zminimalizuj okna IDE i uruchom edytor graficzny (Tools|Image Editor). Utwórz nowy plik zasobów poleceniami New i Resource File z menu File. Kliknij przyciskiem myszy w wyświetlonym oknie dialogowym, z menu kontekstowego wybierz polecenia New i Icon, a w oknie Icon Properties zaznacz pozycje 32x32 i 16 colors. Zmień nazwę utworzonej ikony, klikając ją prawym przyciskiem myszy, wybierając polecenie Rename i wpisując tekst ICON1.
W analogiczny sposób utwórz drugą ikonę - kliknij prawym przyciskiem myszy „korzeń” drzewa zasobów, wybierz polecenia New i Icon z menu kontekstowego, w oknie Icon Properties wybierz pozycje 32x32 i 16 colors i kliknij OK. Utworzoną ikonę nazwij ICON2. Na koniec zapisz cały plik pod nazwą MyCPL.res.
Pozostało jeszcze narysowanie samych ikon, do czego służy okno edytora wywoływane dwukrotnym kliknięciem ikony. Postać ikon pozostawiamy fantazji Czytelników; w przykładzie dostarczonym na płycie CD użyto po prostu symboli „I1” i „I2”. Po zakończeniu dzieła trzeba jeszcze zapisać plik poleceniem Save z menu File (notując przy okazji jego położenie), zamknąć edytor - i gotowe.
Mamy już okna dialogowe i ikony, wciąż natomiast brak właściwego kodu apletu. Aby go utworzyć, wróć do IDE i zamknij wszystkie okna formularzy i plików źródłowych, a następnie wybierz polecenie New z menu File. Na ekranie pojawi się okno dialogowe składnicy obiektów, zatytułowane New Items i umożliwiające utworzenie nowego elementu projektu. Kliknij dwukrotnie ikonę DLL Wizard na karcie New. Domyślne wartości wyświetlane w oknie DLL Wizard są do naszych celów odpowiednie, można je zatem zaakceptować kliknięciem OK. Po kilku sekundach w oknie edytora pojawi się automatycznie wygenerowany szkielet kodu źródłowego biblioteki DLL, podobny do poniższego:
//---------------------------------------------------------------------------
#include <vcl.h>
#include <windows.h>
#pragma hdrstop
//---------------------------------------------------------------------------
#pragma argsused
int WINAPI DllEntryPoint(HINSTANCE hinst, unsigned long reason,
void* lpReserved)
{
return 1;
}
//---------------------------------------------------------------------------
Zanim przystąpimy do tworzenia apletu, warto jeszcze zapisać projekt na dysku poleceniem Save Project As z menu File. Plik modułu Unit1 zapisz w odpowiednim katalogu pod nazwą Main, a sam projekt pod nazwą MyCPL.
Po powrocie do IDE wybierz polecenie Add to Project z menu Project i dodaj do projektu moduły formularzy Form1 i Form2. Poprawność tej operacji można sprawdzić, wyświetlając okno Menedżera projektów (polecenie Project Manager z menu View). „Spis treści” projektu powinien zawierać moduły Form1 i Form2.
Kolejnym krokiem jest dodanie do projektu pliku zasobów zawierających ikony. Ponownie wydaj polecenie Add to Project i w polu typów plików okna Add to Project wybierz skompilowany plik zasobów (*.res). Na liście powinien pojawić się plik MyCPL.res; wybierz go i kliknij OK.
Przed skompilowaniem kodu warto jeszcze zmienić rozszerzenie tworzonego pliku wynikowego. W tym celu wydaj polecenie Options z menu Project, w oknie Project Options kliknij zakładkę Application i w polu Target file extension wpisz rozszerzenie CPL. Dzięki temu wynikowy plik biblioteki dynamicznej zostanie po skompilowaniu zapisany na dysku z rozszerzeniem .cpl, właściwym dla apletów.
Pora na właściwe programowanie. Głównym punktem programu jest definicja funkcji CPlApplet(), którą należy umieścić w pliku źródłowym modułu Main (pamiętając o włączeniu doń pliku Main.h). Powinna ona mieć następującą postać:
extern "C" int __stdcall __declspec(dllexport)
CPlApplet(HWND HwControlPanel, int Msg, int LParam1, int LParam2)
{
switch(Msg)
{
case CPL_INIT:
// Inicjalizacja = zwróć uchwyt apletu.
MyProgInstance = GetModuleHandle("MyCPL.cpl");
return TRUE;
case CPL_GETCOUNT:
// Ile mamy apletów logicznych i ikon?
return 2;
case CPL_NEWINQUIRE:
{
// Zwróć informację o apletach (ikony, tytuły, opisy).
// Wywoływana podczas ładowania Panelu sterowania.
switch (LParam1)
{
case 0:
{
NEWCPLINFO *Info = (NEWCPLINFO *)LParam2;
ZeroMemory(Info, sizeof(NEWCPLINFO));
Info->dwSize = sizeof(NEWCPLINFO);
Info->hIcon = LoadIcon(HInstance, "ICON1");
strcpy(Info->szName, "Aplet nr 1");
strcpy(Info->szInfo, "To jest aplet nr 1");
return 0;
}
case 1:
{
NEWCPLINFO *Info2 = (NEWCPLINFO *)LParam2;
ZeroMemory(Info2, sizeof(NEWCPLINFO));
Info2->dwSize = sizeof(NEWCPLINFO);
Info2->hIcon = LoadIcon(HInstance, "ICON2");
strcpy(Info2->szName, "Aplet nr 2");
strcpy(Info2->szInfo, "To jest aplet nr 2");
return 0;
}
}
// dalsze aplety...
}
case CPL_DBLCLK:
// Dwukrotne kliknięcie oznacza wywołanie apletu.
try
{ // Prosimy na scenę!
if(LParam1==0)
{
Application->Initialize();
Application->CreateForm(__classid(TForm1), &Form1);
Application->Run();
}
else
{
Application->Initialize();
Application->CreateForm(__classid(TForm2), &Form2);
Application->Run();
}
}
catch (Exception &exception)
{
Application->ShowException(&exception);
}
return 0;
case CPL_STOP: break;
case CPL_EXIT: break;
} // Koniec instrukcji switch
return 0;
}
Całość kodu przedstawiono na wydruku 14.13.
Wydruk 14.13. Moduł główny projektu MyCPL
#include <vcl.h>
#include <cpl.h> // niezbędny do utworzenia apletu
#include <windows.h>
#pragma hdrstop
#pragma argsused
// Używamy wszystkich zasobów
USEFORM("Form1.cpp", Form1);
USEFORM("Form2.cpp", Form2);
USERES("MyCpl.res");
// Uchwyt modułu apletu
HINSTANCE MyProgInstance = NULL;
// Funkcja DLLEntryPoint - punkt wejścia do biblioteki DLL
int WINAPI DllEntryPoint(HINSTANCE hinst, unsigned long reason,
void* lpReserved)
{
if (reason==DLL_PROCESS_ATTACH)
MyProgInstance=hinst;
return 1;
}
//---------------------------------------------------------------------------
// Komunikacja z Panelem sterowania odbywa się przez poniższą funkcję.
// [zastępuje ona funkcję WinMain()]
extern "C" int __stdcall __declspec(dllexport)
CPlApplet(HWND HwControlPanel, int Msg, int LParam1, int LParam2)
{
switch(Msg)
{
case CPL_INIT:
// Inicjalizacja = zwróć uchwyt apletu.
MyProgInstance = GetModuleHandle("MyCPL.cpl");
return TRUE;
case CPL_GETCOUNT:
// Ile mamy apletów logicznych i ikon?
return 2;
case CPL_NEWINQUIRE:
{
// Zwróć informację o apletach (ikony, tytuły, opisy).
// Wywoływana podczas ładowania Panelu sterowania.
switch (LParam1)
{
case 0:
{
NEWCPLINFO *Info = (NEWCPLINFO *)LParam2;
ZeroMemory(Info, sizeof(NEWCPLINFO));
Info->dwSize = sizeof(NEWCPLINFO);
Info->hIcon = LoadIcon(HInstance, "ICON1");
strcpy(Info->szName, "Aplet nr 1");
strcpy(Info->szInfo, "To jest aplet nr 1");
return 0;
}
case 1:
{
NEWCPLINFO *Info2 = (NEWCPLINFO *)LParam2;
ZeroMemory(Info2, sizeof(NEWCPLINFO));
Info2->dwSize = sizeof(NEWCPLINFO);
Info2->hIcon = LoadIcon(HInstance, "ICON2");
strcpy(Info2->szName, "Aplet nr 2");
strcpy(Info2->szInfo, "To jest aplet nr 2");
return 0;
}
}
// dalsze aplety...
}
case CPL_DBLCLK:
// Dwukrotne kliknięcie oznacza wywołanie apletu.
try
{ // Prosimy na scenę!
if(LParam1==0)
{
Application->Initialize();
Application->CreateForm(__classid(TForm1), &Form1);
Application->Run();
}
else
{
Application->Initialize();
Application->CreateForm(__classid(TForm2), &Form2);
Application->Run();
}
}
catch (Exception &exception)
{
Application->ShowException(&exception);
}
return 0;
case CPL_STOP: break;
case CPL_EXIT: break;
} // Koniec instrukcji switch
return 0;
}
I to wszystko. Teraz pozostaje tylko zapisać projekt na dysku (jeśli nie zrobiłeś tego wcześniej) i skompilować go. Utworzony w ten sposób plik MyCPL.cpl przenieś do katalogu \Windows\System lub\Winnt\System32. Po uruchomieniu Panelu sterowania powinny pojawić się w nim dwie nowe ikony, reprezentujące nasze aplety. Można też „obejść” Panel sterowania i wywołać aplet bezpośrednio, wpisując w oknie Uruchom (menu Start) polecenie
rundll32 shell32.dll,Control_RunDLL mycpl.cpl @0
Liczba podana po znaku @ jest numerem apletu (okna dialogowego), który ma zostać wyświetlony (w powyższym przykładzie pojawi się pierwszy aplet; użycie parametru @1 spowoduje wyświetlenie drugiego). Samo polecenie jest wywołaniem programu rundll32.exe, który z kolei „sięga” do biblioteki shell32.dll i wywołuje z niej funkcję Control_RunDLL(), pozwalającą uruchomić bibliotekę dynamiczną z odpowiednimi parametrami.
Pozostaje jeszcze wyjaśnić, dlaczego w kodzie apletu obsługujemy komunikat CPL_NEWINQUIRE, a nie CPL_INQUIRE, skoro preferowany jest ten drugi. Wynika to z konstrukcji zasobów użytych w projekcie. Struktury NEWCPLINFO i CPLINFO (używane odpowiednio dla obu powyższych komunikatów) wykorzystują różne sposoby identyfikowania zasobów - gdybyśmy zdecydowali się na obsługę komunikatu CPL_INQUIRE i związanej z nim struktury CPLINFO, informacje o zasobach (ikona, nazwa i opis) należałoby przekazać w postaci identyfikatorów zasobów. Spowodowałoby to konieczność utworzenia odpowiedniego skryptu zasobów (o rozszerzeniu rc) i skompilowania go za pomocą kompilatora BRCC32.EXE. Tak uzyskany plik .res należałoby włączyć do projektu tak samo, jak zrobiliśmy to poprzednio z ikonami. Konieczne byłoby także włączenie do modułu głównego pliku nagłówkowego definiującego identyfikatory zasobów. Dane łańcuchowe (nazwy i opisy okien dialogowych) należałoby zdefiniować w pliku .rc w postaci tablicy łańcuchów, np. tak:
STRINGTABLE
BEGIN
ID_ICONTEXT1 "To jest aplet numer 1";
ID_ICONTEXT2 "To jest aplet numer 2";
END
Decydując się na wykorzystanie komunikatu CPL_INQUIRE, należy pamiętać, że dostęp do zasobów jest w takim przypadku bardziej skomplikowany, wymaga bowiem wykorzystania funkcji LoadResource(). Ponieważ powoduje to komplikację programu, a nie przynosi znaczących zysków czasowych (nasz przykład jest stosunkowo prosty), zdecydowaliśmy się tu na użycie mniej polecanego komunikatu CPL_NEWINQUIRE. Nie zmienia to faktu, że tworząc „prawdziwą”, większą aplikację, najlepiej postąpić zgodnie z regułami, tj. obsłużyć komunikat CPL_INQUIRE.
Biblioteka ta jest 16-bitowa, zatem technicznie nie wchodzi w skład systemu Win32 - przyp. tłum.
W systemie Win16 pierwszy parametr liczył sobie 16 bitów - stąd nazwa typu WPARAM (Word Parameter), która przeniosła się do systemu Win32 jako jedna z licznych zaszłości - przyp. tłum.
W praktyce użycie funkcji RegisterWindowMessage() ma sens jedynie w przypadku definiowania komunikatów do celu wymiany danych między aplikacjami - przyp. tłum.
Nie oznacza to niemożności zmiany położenia wskaźnika plikowego (tj. w dalszym ciągu możliwy jest swobodny dostęp do danych); atrybut ten ma charakter optymalizacyjny - przyp. tłum.
Dla uniknięcia nieporozumień wartości właściwości typu łańcuchowego ujęto w apostrofy (jak ma to miejsce w plikach definicji formularzy). Komponenty, których nazwy należy zmienić, figurują w lewej kolumnie pod swoimi pierwotnymi (domyślnymi) nazwami - przyp. tłum.
Z wyjątkiem EWX_FORCE opcje wymienione w tabeli wykluczają się wzajemnie - przyp. tłum.
W przykładzie użyto przycisku typu BitBtn, jednak można również wykorzystać zwykły przycisk typu TButton. Należy też pamiętać o odpowiednim ustawieniu przycisku, by po „obcięciu” obszaru formularza był on nadal widoczny - przyp. tłum.
Z fizycznego punktu widzenia „apletem” nazywamy plik biblioteki dynamicznej o rozszerzeniu .cpl, natomiast z punktu widzenia użytkownika aplet jest ikoną w Panelu sterowania, której kliknięcie wywołuje okno (lub okna) dialogowe. Pojedynczy aplet „fizyczny” (plik .cpl) może zawierać kilka apletów „logicznych” (okien dialogowych). Aby uniknąć zamieszania, w dalszej części opisu pojęcia „aplet” będziemy używali w odniesieniu do apletu logicznego, zaś plik .cpl będziemy nazywali „plikiem apletu” - przyp. tłum.
Należy pamiętać, że przed nazwą funkcji Control_RunDLL (a po przecinku) nie może być spacji - przyp. tłum.
1
uwaga, w oryginale stosowany jest ukośnik; zmieniam na lewy ukośnik dla zachowania zgodności z konwencjami Windows.
niedopowiedzenie w oryginale; GetWindowText() zwraca w ogólności tekst okna, np. opis przycisku (który też jest oknem).
do składu: proszę o posortowanie tabeli wg pierwszej kolumny; na razie tego nie robię, żeby było wygodniej sprawdzać
do składu: proszę o posrtowanie tabeli wg pierwszej kolumny; na razie tego nie robię, żeby było wygodniej sprawdzać
dla konsekwencji nazwy wszystkich bibliotek DLL zapisuję małymi literami (w Windows nie ma to znaczenia, a poprzednio było małymi)
co w końcu robimy z common controls? Jak wcześniej pisałem, Microsoft nie rozróżnia „standard controls” i „common controls”, dlatego też można bezpiecznie mówić o standardowych elementach interfejsu użytkownika / kontrolkach. W tłumaczeniu TK były „powszechnie stosowane elementy...”, ktoś też sugerował „wspólne”. Ja obstaję za „standardowymi”.
błąd w oryginale; flat scroll bar to wprowadzony w IE4 „płaski” pasek przewijania (po co to Microsoftowi, nie mam pojęcia, nigdy jeszcze tego nie widziałem a działaniu )
to nie jest edit control, a button; faktem jest, że taki przycisk (SpinBox) można podpiąć do pola tekstowego i wymusić automatyczną modyfikację.
w oryginale GetOpenNameFile()
w oryginale commdlg.dll - jest taka biblioteka, ale zawiera definicje dla Win16.
BRAK TYTUŁU TABELI
nie RAS, bo ten jest zarezerwowany dla stałych i struktur.
ostatnie zdanie akapitu usuwam, bo w dalszej części rozdziału nie ma ani słowa o modyfikowaniu WinMain().
w oryginale odwołanie do dalszej części rozdziału, ale nie ma tam mowy o muteksach.
tego słówka w oryginale brakuje
oryginał bez sensu, interrupt handling to coś zupełnie innego.
tu pozwolę sobie tylko na komentarz - zamiast wymieniać wszystkie komunikaty dotyczące lewych, prawych, środkowych itd. przycisków, warto było wspomnieć np. o znacznie ważniejszych komunikatach WM_CREATE czy WM_DESTROY. Ale nie mnie pouczać autorów.
też, nie tylko poruszenie kursora (zob. dokumentacja SDK).
Oryginał: ShellExecuteExe()
w oryginale 255 znaków.
oryginał: odczytywania
ten akapit w oryginale to w zasadzie bełkot, chociaż żywcem (ale z błędami) zerżnięty z dokumentacji SDK. Po pierwsze zaraz na dzień dobry pojawia się słówko „not”, które dokładnie odwraca prawdziwy sens operacji, po drugie nie wiem, dlaczego w opisie funkcji ReadFile() nagle zaczyna się mówić o WriteFile().
oryginał: nie podano
ten fragment przeniosłem tu z końca punktu, tam nie pasował.
oryginał: słowo (high word)
to dodałem, bo inaczej jest za mało miejsca na tekst
w oryginale użyto operatora sizeof, co daje dość dziwny efekt (śmieci na końcu pliku)
tak podaje Microsoft (i tak jest); opis w oryginale jest mętny.
oryginał: Bool (nie ma takiego typu!)
brak cudzysłowu zamykającego
nie bool, bo to różne typy!
w oryginale brak nazwy funkcji, zły typ wartości zwracanej i błąd w zapisie drugiego parametru. Brawo!
no i jak używamy notacji węgierskiej, to konsekwentnie.
w oryginale obie nazwy są takie same, co technicznie jest OK, ale nie ma sensu.
wbrew oryginałowi, na CD go nie ma.
w oryginale niezamknięty komentarz
oryginał jest kompletnie bez sensu. Tłumaczenie zgodne ze specyfikacją Win32 SDK i sprawdzone empirycznie.
Zdanie o dostępności jest nielogiczne i błędne (funkcja jest dostępna we wszystkich systemach Win32, o ile zainstalowano w nich IE4+ (czyli bibliotekę Shell32.dll 4.00+))
w oryginale shqbi - nie ma takiej zmiennej. shqbi to skrótowy zapis identyfikatora struktury SHQUERYBINFO, używany w dokumentacji SDK. Tu i poniżej winno być RCInfo, inaczej mamy parę błędów kompilacji.
było pszRootPAth, co jest o tyle dziwne, że takich rzeczy na ogół się nie przepisuje, a kopiuje z dokumentacji API (tam jest dobrze)
uwaga jw.
oryginał: zwrócenie informacji o... (niedbale skopiowane z poprzedniego punktu)
było „Flag... Value”
brakowało w oryginale, a opcja dość ważna
bądźmy konsekwentni, jak jest Form1, to niech będzie Label1i Label2.
nie FileListBox, bo takowego nie ma.
w oryginale CommaAdd, ale po pierwsze w zapisie polskim separatorem tysięcy jest zwykle spacja lub kropka, a po drugie funkcję trzeba było całkowicie przepisać, bo nie spełniała swojego zadania.
ale nie „tylko do odczytu”, bo to już wynika z braku stałej GENERIC_WRITE.
tak podaje dokumentacja SDK, a jej bardziej wierzę.
to zdanie przeniosłem tutaj z końca akapitu, tam jest wyrwane z kontekstu.
podane w oryginale informacje o „low” i „high file size” to brednie. Usuwam cały fragment; podaję informacje za dokumentacją Win32 SDK (sprawdzone empirycznie!)
oryginał: GetSizeInfo
oryginał: „used earlier”, ale nigdzie wcześniej nie było o niej wzmianki
komentarz w oryginale jest absurdalny
oryginał: „ccp file”; ma być cpp, ale to oczywiście detal.
to wszystko już powiedziano przed chwilą, ale nie miałem serca wycinać tego akapitu, więc tylko odcisnąłem z niego wodę.
tu kolejne zdanie dokładnie powtarzające to, co powiedziano na początku akapitu - usuwam.
oryginał: FLASHINFO
w oryginale jest Notepad, ale są problemy z polskim „ł” w tytule okna („Notatnik - bez tytułu”), dlatego zmieniam na Kalkulator.
sądzę, że po lekturze tysiąca stron o BCB już wszyscy to wiedzą, ale... :->
ten punkt łączę w całość z kolejnym
w oryginale bez sensu - albo znamy nazwę okna i wtedy można wywołać FindWindow(), albo jej nie znamy i wtedy nic nie można zrobić.
w oryginale dosłownie „opcja FLASHW_TIMER oznacza włączenie opcji FLASHW_STOP”
i tyle - reszta opisu w oryginale to bzdura; FlashWindowEx() zwraca sterowanie zaraz po wywołaniu i nie modyfikuje pola dwFlags struktury FLASHWINFO.
oryginał mętny i pełen powtórzeń... nieco go wyprostowałem.
Było „Flag - Value”, czyli „Opcja - wartość”
w oryginale AnimateWindows()
nie „dodatnia lub zero”, bo zwracana wartość jest typu BOOL.
Efekt ten nie ma nic wspólnego z „rolling movie credits”...
a nie „nie są traktowane jako elementy okna” - ich tylko nie widać.
pozwala sobie skondensować opis, bo w oryginale połowa słów się powtarza
deklaracja struktury typu RECT w pierwszym wierszu była niepotrzebna i myląca - usunąłem.
de facto powinno być TRUE, bo parametr jest typu BOOL, a nie bool, chociaż automatyczne rzutowanie załatwia sprawę.
Owal to inna figura. Funkcja tworzy wyłącznie obszary eliptyczne.
i też pozwalam sobie skondensować zapis
było hRgn1 i true, już trochę nie mam siły poprawiać tego ostatniego
skoro w tabeli jest hrgnSrc, to tu też niech będzie.
Jak zwykle było „Flag - Value”
w przeciwieństwie do tego, co mówią autorzy
rzecież ta struktura nikomu tu do niczego nie jest potrzebna.... jakoś trzeba wytłumaczyć jej obecność.
a nie zadeklarowaniu, chociaż autorzy chyba tych pojęć nie rozróżniają
kolejne zdanie usuwam, jest powieleniem opisu pierwszego parametru
wyjaśnienie w oryginale jest nonsensowne; podaję za dokumentacją SDK..
wg dokumentacji SDK; autorzy twierdzą, że jest odwrotnie.
która też jest zasobem, więc pisanie „freeing resources and deallocating memory” to masło maślane
oryginał: CPLApplet(), ale to detal w porównaniu z pozostałymi błędami