Spis tre Wst p... 2 Do czego słu zasoby... 2 Tworzenie zasobów... 4 Pisanie skryptu zasobów... 4 Korzystanie z zasobów w programach...



Podobne dokumenty
Laboratorium z Grafiki InŜynierskiej CAD. Rozpoczęcie pracy z AutoCAD-em. Uruchomienie programu

I Tworzenie prezentacji za pomocą szablonu w programie Power-Point. 1. Wybieramy z górnego menu polecenie Nowy a następnie Utwórz z szablonu

Podstawowe informacje o obsłudze pliku z uprawnieniami licencja.txt

Wskaźniki a tablice Wskaźniki i tablice są ze sobą w języku C++ ściśle związane. Aby się o tym przekonać wykonajmy cwiczenie.

Instrukcja Instalacji

5.4. Tworzymy formularze

Zmienne powłoki. Wywołanie wartości następuje poprzez umieszczenie przed nazwą zmiennej znaku dolara ($ZMIENNA), np. ZMIENNA=wartosc.

KaŜdy z formularzy naleŝy podpiąć do usługi. Nazwa usługi moŝe pokrywać się z nazwą formularza, nie jest to jednak konieczne.

Projekt ZSWS. Instrukcja uŝytkowania narzędzia SAP Business Explorer Analyzer. 1 Uruchamianie programu i raportu. Tytuł: Strona: 1 z 31

Generator Wniosków Płatniczych dla Programu Operacyjnego Kapitał Ludzki. Instrukcja Instalacji

Dodawanie grafiki i obiektów

Programowanie w języku Python. Grażyna Koba

Tworzenie prezentacji w MS PowerPoint

Współpraca Integry z programami zewnętrznymi

Opis obsługi programu KALKULACJA

znajdowały się różne instrukcje) to tak naprawdę definicja funkcji main.

Instalacja i opis podstawowych funkcji programu Dev-C++

WYKONANIE APLIKACJI OKIENKOWEJ OBLICZAJĄCEJ SUMĘ DWÓCH LICZB W ŚRODOWISKU PROGRAMISTYCZNYM. NetBeans. Wykonał: Jacek Ventzke informatyka sem.

XV. Wskaźniki Odczytywanie adresu pamięci istniejących zmiennych Wskaźniki pierwsze spojrzenie.

WSCAD. Wykład 5 Szafy sterownicze

Instrukcja uŝytkownika

WyŜsza Szkoła Zarządzania Ochroną Pracy MS EXCEL CZ.2

Informatyka II. Laboratorium Aplikacja okienkowa

Ustalanie dostępu do plików - Windows XP Home/Professional

etrader Pekao Podręcznik użytkownika Strumieniowanie Excel

Program 6. Program wykorzystujący strukturę osoba o polach: imię, nazwisko, wiek. W programie wykorzystane są dwie funkcje:

Polsko-Niemiecka Współpraca MłodzieŜy Podręcznik uŝytkownika Oprogramowania do opracowywania wniosków PNWM

Temat: Organizacja skoroszytów i arkuszy

1. Przypisy, indeks i spisy.

Tablice (jedno i wielowymiarowe), łańcuchy znaków

Wymagania edukacyjne z informatyki dla klasy szóstej szkoły podstawowej.

KROK 17 i 18. Cel: Tworzymy oddzielne okno - O autorze. 1. Otwórz swój program. 2. Skompiluj i sprawdź, czy działa prawidłowo.

Rozdział 2. Konfiguracja środowiska pracy uŝytkownika

C-geo definicja/edycja obiektów, zapis danych w formacie shape

Po uruchomieniu programu nasza litera zostanie wyświetlona na ekranie

Utworzenie pliku. Dowiesz się:

FAQ: /PL Data: 26/11/2008 Komunikacja w protokole MPI za pomocą Global Data (GD) pomiędzy sterownikami S7-300

Kopiowanie, przenoszenie plików i folderów

PROJEKT CZĘŚCIOWO FINANSOWANY PRZEZ UNIĘ EUROPEJSKĄ. Opis działania raportów w ClearQuest

Przypominacz Instrukcja uŝytkownika

Instrukcja obsługi programu Creative Fotos

MenadŜer haseł Instrukcja uŝytkownika

Praca w programie Power Draft

Obszar pierwszy to pasek narzędzi (rys. 1) zawierający skróty do najczęściej uŝywanych funkcji. Rys. 1 Pasek Narzędzi

Niezwykłe tablice Poznane typy danych pozwalają przechowywać pojedyncze liczby. Dzięki tablicom zgromadzimy wiele wartości w jednym miejscu.

e-podręcznik dla seniora... i nie tylko.

Celem ćwiczenia jest zapoznanie się z podstawowymi funkcjami i pojęciami związanymi ze środowiskiem AutoCAD 2012 w polskiej wersji językowej.

Zamienniki towarów 1/5. Program Handel Premium

Cel: Przypisujemy przyciskom określone funkcje panel górny (Panel1)

Krótki kurs obsługi środowiska programistycznego Turbo Pascal z 12 Opracował Jan T. Biernat. Wstęp

1 Podstawy c++ w pigułce.

Informatyka Arkusz kalkulacyjny Excel 2010 dla WINDOWS cz. 1

Stałe, znaki, łańcuchy znaków, wejście i wyjście sformatowane

Informatyka Arkusz kalkulacyjny Excel 2010 dla WINDOWS cz. 1

Instrukcja automatycznego tworzenia pozycji towarowych SAD na podstawie danych wczytywanych z plików zewnętrznych (XLS).

5. Administracja kontami uŝytkowników

I. Program II. Opis głównych funkcji programu... 19

Wprowadzenie do programowania w języku Visual Basic. Podstawowe instrukcje języka

Politechnika Poznańska Wydział Budowy Maszyn i Zarządzania

Stosowanie, tworzenie i modyfikowanie stylów.

Budowa aplikacji z graficznym interfejsem użytkownika - GUI (Graphic User Interface)

Podstawy programowania. Wykład: 9. Łańcuchy znaków. dr Artur Bartoszewski -Podstawy programowania, sem 1 - WYKŁAD

Instrukcja instalacji i obsługi gotowych szablonów aukcji allegro oraz szablonów na zamówienie

Omówienie procesu zakupowego w sklepie internetowym Papyrus Sp. z o. o. Spis treści

Formularz MS Word. 1. Projektowanie formularza. 2. Formularze do wypełniania w programie Word

Cwiczenie nr 1 Pierwszy program w języku C na mikrokontroler AVR

CRM VISION Instalacja i uŝytkowanie rozszerzenia do programu Mozilla Thunderbird

Instrukcja obsługi Zaplecza epk w zakresie zarządzania tłumaczeniami opisów procedur, publikacji oraz poradników przedsiębiorcy

Włączanie/wyłączanie paska menu

Dodanie nowej formy do projektu polega na:

Podręcznik użytkownika programu. Ceremonia 3.1

Ćwiczenia nr 2. Edycja tekstu (Microsoft Word)

Praca w programie Power Draft

W oknie na środku, moŝna wybrać język, który będzie językiem domyślnym dla TC. Wybierzmy zatem język polski:

Instrukcja zarządzania kontami i prawami

Szkolenie dla nauczycieli SP10 w DG Operacje na plikach i folderach, obsługa edytora tekstu ABC. komputera dla nauczyciela. Materiały pomocnicze

Program dopisujący gwiazdkę na końcu pliku tekstowego o nazwie podanej przez uŝytkownika oraz wypisujący zawartość tego pliku.

Programowanie na poziomie sprzętu. Programowanie w Windows API

6.4. Efekty specjalne

ApSIC Xbench: Szybki start wydanie Mariusz Stępień

Instrukcja uŝytkownika

Expo Composer Garncarska Szczecin tel.: info@doittechnology.pl. Dokumentacja użytkownika

UNIWERSYTET RZESZOWSKI KATEDRA INFORMATYKI

Podstawy programowania skrót z wykładów:

Zawartość. Wstęp. Moduł Rozbiórki. Wstęp Instalacja Konfiguracja Uruchomienie i praca z raportem... 6

Komputery I (2) Panel sterowania:

PREZENTACJE MULTIMEDIALNE cz.2

Konta uŝytkowników. Konta uŝytkowników dzielą się na trzy grupy: lokalne konta uŝytkowników, domenowe konta uŝytkowników, konta wbudowane

1 Podstawy c++ w pigułce.

Rys. 1. Zestawienie rocznych kosztów ogrzewania domów

Jak napisać program obliczający pola powierzchni różnych figur płaskich?

Tworzenie i modyfikowanie wykresów

z :15

Podstawy obsługi aplikacji Generator Wniosków Płatniczych

Informatyka, Ćwiczenie Uruchomienie Microsoft Visual C++ Politechnika Rzeszowska, Wojciech Szydełko. I. ZałoŜenie nowego projektu

Podstawy JavaScript ćwiczenia

Archiwum DG 2016 PL-SOFT

Edycja szablonu artykułu do czasopisma

Transkrypt:

Spis treści Wstęp... 2 Do czego słuŝą zasoby... 2 Tworzenie zasobów... 4 Pisanie skryptu zasobów... 4 Zasoby wczytywane z plików... 4 Menu... 5 Tablice łańcuchów... 9 Zasoby własne... 9 Preprocesor i skrypty zasobów... 12 Korzystanie z zasobów w programach... 12 Ikony... 13 Kursory... 14 Bitmapy... 16 Dźwięki... 19 Menu... 20 Przypisywanie menu do okna programu... 20 Obsługa zdarzeń menu... 21 Tablice łańcuchów... 22 Zasoby własne... 22 Podsumowanie... 23 Dodatek obsługa programu Resource Hacker... 24 Edycja zasobów... 25 Zapisywanie zmian... 29 Przykład I zmiana ikony programu... 29 Przykład II sztuczki z menu... 31

Aplikacje przeznaczone dla systemu Windows mogą posiadać róŝnorodne elementy nie wchodzące w skład samego kodu programu. MoŜna w nie wbudować komponenty takie jak: ikony, kursory czy dźwięki. Te i podobne składniki naleŝą do tzw. zasobów programu. Z ich pomocą moŝliwe jest m. in. dodanie do aplikacji własnego menu, czy teŝ zaopatrzenie jej w dane, których nie chcemy umieszczać bezpośrednio w kodzie źródłowym (np. całe pliki). Wstęp Zasobami są najprościej rzeczy, z których program korzysta w czasie swojego działania. Jak juŝ wcześniej napisałem mogą to być np. ikony. Niniejszy artykuł opisuje rolę jaką pełnią zasoby oraz sposób ich wykorzystania we własnych programach. Zajmiemy się m. in. tworzeniem menu dla programu, nadawaniem mu własnej ikony czy kursora. Dodatkowo w ostatnim akapicie znajduje się omówienie narzędzia Resource Hacker pozwalającego na zamianę zasobów w gotowych aplikacjach. Do pełnego zrozumienia zawartych tutaj informacji potrzebna będzie znajomość składni języka C/C++ oraz podstaw API systemu Windows (WinAPI). Czytelnikom nie wprawionym w tych dziedzinach, nie mniej polecam przeczytanie dodatku z przewodnikiem programu Resource Hacker. Bez potrzeby posiadania wiedzy o programowaniu, uŝywając tego narzędzia, będą oni w stanie modyfikować struktury aplikacji znajdujących się na ich komputerach. Co za tym idzie, przykładowo zmieniać ikony programów. Do czego słuŝą zasoby Mówiąc bardziej technicznie zasoby stanowią ciąg bajtów umieszczony na końcu programu (zaraz po jego faktycznym kodzie źródłowym). W tym ciągu zakodowane są oraz odpowiednio rozmieszczone konkretne elementy, do których aplikacja ma dostęp: Zasoby zostały zaprojektowane głównie po to by nie było konieczne wczytywanie do programu osobnych plików (np. z rysunkami). Kiedy program korzysta z zewnętrznych plików to pewność, Ŝe zostanie wykonany poprawnie maleje, poniewaŝ pliki te mogą zostać uszkodzone bądź usunięte. Ponadto program nie uŝywający dodatkowych danych poza wbudowanymi jest wygodniejszy przy przenoszeniu nie ma konieczności kopiowania równieŝ pozostałych, przynaleŝących do niego zbiorów. Zasoby pozwalają teŝ na nadanie 2

programowi ikony pod jaką jest on widziany w systemie. KaŜdy bowiem program jeśli posiada własną ikonę to znajduje się ona właśnie w jego zasobach. W poniŝszej tabeli zebrane zostały najpopularniejsze typy zasobów wraz z ich krótkimi opisami. Druga kolumna tego zestawienia zawiera oznaczenia odpowiadające poszczególnym zasobom, uŝywane przy ich tworzeniu w programach. Typ zasobu Nazwa typu Opis Ikona ICON SłuŜy do przechowywania ikon - przeznaczonych najczęściej do graficznego reprezentowania programu w środowisku systemu (np. na pulpicie) jak równieŝ w oknie samego programu oraz na pasku zadań. Oprócz wymienionych ikony mogą dodatkowo być wyświetlane jak standardowe obrazy. Kursor CURSOR Pozwala umieścić w aplikacji dodatkowe kursory - odpowiadające za graficzny wygląd wskaźnika myszy. Kursory mogą zmieniać się dynamicznie (np. po najechaniu kursorem myszy na nowe okno). Menu MENU Reprezentuje tzw. menu - zawierające dostępne w programie polecenia i mogące występować w dwóch postaciach: w formie paska umieszczonego na górze okna (menu programu / główne) bądź podporządkowane kursorowi myszy (menu kontekstowe). Bitmapa BITMAP Przechowuje pliki formatu *.bmp. Bitmapy mogą być wykorzystywane w róŝnych celach, przykładowo do rysowania przycisków czy teŝ elementów gry. Dźwięk WAVE Pozwalają na przechowywanie plików w formacie *.wav, które później mogą być odtwarzane w programie. Tablica łańcuchów STRINGTABLE Mieści w sobie zbiór łańcuchów znakowych, z których kaŝdy posiada osobny identyfikator. MoŜe być uŝywana do przechowywania tekstu wyświetlanych komunikatów. Zasoby własne * SłuŜą do przechowywania danych wszelkiego rodzaju. Zasoby te nie posiadają jednoznacznego identyfikatora, poniewaŝ podobnie jak ich dane, identyfikatory takŝe mogą być dowolne. Skoro zostały juŝ przybliŝone informacje na temat ogólnego przeznaczenia zasobów oraz ich najczęściej stosowane rodzaje to moŝemy przejść do omówienia metod wykorzystania tych elementów w praktyce. 3

Tworzenie zasobów Zasoby buduje się podobnie jak same programy - najpierw naleŝy napisać odpowiedni kod, a następnie przekazać go kompilatorowi. Kod słuŝący do sporządzenia zasobu nazywa się skryptem zasobu. Zawiera on instrukcje mówiące o tym co ma się w danym zasobie znaleźć i występuje pod postacią pliku z rozszerzeniem *.rc. Tak samo jak w przypadku języków programowania, pisząc skrypt zasobów równieŝ naleŝy posługiwać się odpowiednią składnią. Jednymi z elementów tej składni są nazwy typów, z których kilka zostało przedstawione we wcześniejszej tabeli. Do przetwarzania skryptów zasobów został stworzony specjalny kompilator nazywany po prostu kompilatorem zasobów. Na podstawie przekazanego mu pliku wejściowego (*.rc) sporządza on gotowy, binarny plik zasobów posiadający rozszerzenie *.res. Skompilowany plik zasobów naleŝy połączyć z właściwym programem za pomocą standardowego konsolidatora (linkera). Najpopularniejsze z dostępnych obecnie kompilatorów zasobów dla systemu Windows to: RC.EXE (Microsoft), BRC32.EXE (Borland), oraz WINDRES.EXE (GNU). Jeden z wymienionych tutaj programów powinien znajdować się w katalogu uŝywanego środowiska programistycznego obok innych narzędzi. Poza wymienionymi, istnieją teŝ inne aplikacje tego typu jednak większość z nich nie jest juŝ tak powszechna. Kompilatory zasobów nie są skomplikowanymi programami i ich obsługa nawet z wiersza poleceń nie powinna sprawiać trudności. Oprócz tego kiedy korzystamy ze zintegrowanego środowiska takiego jak Visual C++ czy Dev-C++ to wykonuje ono za nas całą pracę związaną z kompilacją. Wystarczy jedynie dołączyć do projektu odpowiedni plik z rozszerzeniem *.rc. Nie przedłuŝając zatem przejdźmy do wyjaśnienia co powinno się w takim pliku znaleźć. Pisanie skryptu zasobów Definiowanie pliku opisującego zasoby jest bardzo proste. Nie trzeba nawet tworzyć dla niego podstawowego szkieletu, poniewaŝ takowy nie istnieje. Wystarczy tylko wypisywać jeden po drugim wszystkie komponenty mające się w zasobie znaleźć. Dodawane elementy moŝna zasadniczo podzielić na dwie grupy wczytywane z dysku oraz formułowane bezpośrednio w skrypcie. Spośród zasobów, którymi zajmujemy się w tym artykule do pierwszej grupy naleŝą typy: ICON, CURSOR, BITMAP oraz WAVE. Z kolei typy MENU i STRINGTABLE zalicza się do grupy drugiej. Z podziału zostały wykluczone zasoby własne, gdyŝ mogą one być tworzone na oba sposoby. Zasoby wczytywane z plików Ogólna definicja elementu budowanego na podstawie pliku wygląda następująco: Identyfikator TYP ścieŝka Pierwszy składnik identyfikator zasobu występuję w definicji kaŝdego typu zasobu. SłuŜy on do nadania komponentowi unikalnej nazwy dzięki, której będzie się moŝna do niego 4

odwołać w kodzie programu. MoŜna tutaj uŝyć zarówno wartości liczbowej jak i ciągu znaków (bez cudzysłowów). Po identyfikatorze określić naleŝy typ zasobu, czyli np. ICON, a na końcu ścieŝkę prowadzącą do pliku, z którego ma on być wczytany. ŚcieŜka ta moŝe być podana w cudzysłowach bądź z ich pominięciem. Wszystkie zasoby oparte o zewnętrze pliki tworzy się w identyczny sposób. PoniŜszy kod prezentuje sposób zdefiniowania czterech z poznanych typów zasobów: 100 ICON main.ico 200 CURSOR cross.cur TloProgramu BITMAP "tlo.bmp" Sygnal WAVE "sygnal.wav" Te instrukcje wystarczą by umieścić w programie ikonę, kursor, rysunek oraz dźwięk formatu wave. Jak juŝ powiedziałem wcześniej wystarczy tylko umieścić je w pliku z rozszerzeniem *.rc, następnie dołączyć ten plik do projektu programu i włączyć kompilację. Przejdźmy do omówienia zasobów z drugiej grupy, czyli tworzonych bez uŝycia plików. Na początek zajmiemy się tworzeniem menu. Menu Podstawowa struktura typu MENU ma poniŝszą postać: Identyfikator MENU Wymaganym argumentem jest podobnie jak przy poprzednich typach zasobów identyfikator w formie napisu bądź liczby. Taki kod menu następnie rozbudowuje się umieszczając wewnątrz nawiasów klamrowych definicje poszczególnych pozycji. Wygląda to następująco: Identyfikator MENU MENUITEM tekst, identyfikator, styl WyraŜenie MENUITEM dodaje do menu nową pozycję. Pierwszy z przekazywanych mu argumentów tekst określa jaką etykietę nowy element będzie posiadał. Wartość ta musi być zawarta w cudzysłowach. Drugi parametr to identyfikator liczbowy, który trafi do programu gdy element zostanie wybrany (kliknięty) przez uŝytkownika. Ostatni natomiast słuŝy do nadawania pozycji menu dodatkowych atrybutów. Przykładowo moŝe ona być zaznaczona, niedostępna bądź teŝ pełnić rolę separatora. Dostępne wartości dla tego argumentu oraz ich znaczenie ilustruje poniŝszy rysunek. 5

Określanie stylu elementu jest opcjonalne. Znaczy to, Ŝe argument precyzujący tę cechę moŝe zostać pominięty. Element będzie wtedy posiadał standardowy wygląd. NaleŜy jeszcze dodać, Ŝe jeśli uŝywa się stylu SEPARATOR to z definicji elementu wyklucza się wartość jego etykiety oraz identyfikatora: MENUITEM SEPARATOR W przypadku tego stylu pozycja jest bowiem tylko separatorem dla innych elementów. Nie reaguje na zdarzenia myszy więc identyfikator nie jest jej potrzebny. Etykieta z kolei w ogóle nie jest wyświetlana. Proste menu zawierające kilka elementów moŝna stworzyć w następujący sposób: 1000 MENU MENUITEM "Plik", 1001 MENUITEM "Edycja", 1002 MENUITEM "Pomoc", 1003 Będzie ono wyglądało jak poniŝej. Nie wspomniałem jeszcze jak w menu tworzyć rozgałęzienia. Realizuje to instrukcja POPUP: 6

POPUP tekst, styl Po słowie kluczowym POPUP umieszczana jest tu etykieta pełniąca identyczną funkcję co w przypadku standardowych elementów. RównieŜ argument styl działa analogicznie. Jeśli chodzi o przeciwieństwa to z racji tego, Ŝe rozgałęzienia słuŝą jedynie do zawierania w sobie innych elementów, nie są im potrzebne identyfikatory. Pomiędzy nawiasami klamrowymi instrukcji POPUP naleŝy zamieścić definicje wszystkich pozycji wchodzących w skład danego rozgałęzienia. WyraŜenie POPUP moŝna umieszczać wewnątrz innych wyraŝeń POPUP tworząc wielokrotne rozgałęzienia. Przykładowo kod: MenuGlowne MENU MENUITEM "Element1", 1001 POPUP "Element2" MENUITEM "Element3", 1002 MENUITEM "Element4", 1003 POPUP "Element5" MENUITEM "Element6", 1004 MENUITEM "Element7", 1005 spowoduje powstanie następującego menu: Ostatnią rzeczą dotyczącą tworzenia menu o jakiej pragnę wspomnieć są znaki specjalne wykorzystywane w etykietach elementów. Znakami specjalnymi nazywa się takie znaki, które nie są przeznaczone do wyświetlenia. Ich celem jest natomiast wykonanie pewnej czynności. W naszym przypadku słuŝą do modyfikowania wyglądu etykiet menu. Znaki te przewaŝnie występują w postaci pojedynczych liter poprzedzanych symbolem '\' (backslash). Dla elementów menu zostały zarezerwowane trzy tego typu oznaczenia: & (ampersand) słuŝy do tworzenia skrótów klawiaturowych, ustawienie tego symbolu przed jednym ze znaków etykiety powoduje, Ŝe znak ten jest podkreślany podczas wciśnięcia klawisza ALT na klawiaturze, 7

\a (align) powoduje, Ŝe część etykiety znajdująca się za tym znakiem jest wyrównywana do prawej strony kolumny, w której występuje, symbol ten nie powinien być pierwszym znakiem w etykiecie, poniewaŝ moŝe to powodować błędy w wyświetlaniu, \t (tabulator) w miejscu gdzie jest umieszczony wstawia tabulator słuŝący do wyrównywania tekstu w kolumnach, symbol ten nie powinien być uŝywany w etykietach elementów naleŝących do głównego paska menu. Aby uŝyć jednego ze znaków specjalnych wystarczy wpisać go do łańcucha określającego etykietę elementu: MENUITEM "&Plik", 2001 Jak widać znak & jest uŝywany bez symbolu backslash. Tak, więc aby wstawić go do łańcucha jako standardowy, a nie specjalny znak, nie wystarczy go tam po prostu umieścić. Zostanie on bowiem zinterpretowany jako znak specjalny. Celem wyświetlenia tego symbolu w zwykły sposób naleŝy wymienić go dwukrotnie: MENUITEM "AT&&T", 2002 PowyŜsza sytuacja odnosi się takŝe do znaku backslash ('\'). UŜyty dwa razy zostanie wypisany standardowo, a w przeciwnym razie z góry rozpatrywany będzie jako część symbolu specjalnego. Działanie wymienionych tutaj znaków specjalnych prezentuje poniŝszy skrypt. 1000 MENU POPUP "&Plik" MENUITEM "&Zapisz\tCtrl+S", 1001 MENUITEM "&Zapisz jako...\tctrl+f12", 1002 MENUITEM "A\aB", 1003 Jego wynikiem będzie poniŝsze menu: 8

Tablice łańcuchów Tablice łańcuchów są zasobami, których przeznaczeniem jest przechowywanie ciągów znakowych. KaŜda tablica moŝe zawierać jeden lub więcej takich ciągów. Ogólna definicja typu STRINGTABLE wygląda następująco: STRINGTABLE identyfikator, tekst... Argument identyfikator jest indeksem (numerem) przypisywanym kaŝdemu łańcuchowi tablicy. Zaraz po nim naleŝy po przecinku wpisać treść danego łańcucha. Kolejne ciągi znaków dodaje się do tablicy wypisując je linijka po linijce. Tablice same w sobie nie posiadają identyfikatorów. Mają je jedynie zawarte w nich napisy. Oto przykładowa definicja przedstawionego typu: STRINGTABLE 1, "Błąd: Plik nie istnieje." 2, "Błąd: Plik jest plikiem tylko do odczytu." 3, "Plik został pomyślnie skopiowany." PoniewaŜ tablice łańcuchów nie posiadają własnych nazw (identyfikatorów) toteŝ nie istnieje Ŝaden czynnik wyraŝający ich odrębność. W wyniku tego, wszystkie tablice w gotowym zasobie stanowią jedną całość. Nie trudno jest się zatem domyśleć, Ŝe kaŝdy łańcuch niezaleŝnie od tego do jakiej tablicy naleŝy musi posiadać inny identyfikator. Napisy o takich samych identyfikatorach (znajdujące się nawet w róŝnych tablicach) będą ze sobą kolidowały. Jeśli dojdzie do takiej sytuacji to stara wartość spod danego indeksu zostanie nadpisana nowszą. Przykładowo w skrypcie: STRINGTABLE 1, "A" 2, "B" STRINGTABLE 2, "C" 3, "D" występują dwa łańcuchy z identyfikatorami równymi 2. Pierwszy z nich zostanie zastąpiony drugim, a więc pod wspomnianym indeksem ostatecznie będzie ukrywać się wartość "C", a nie "B". Zasoby własne Zasoby własne mogą przechowywać informacje kaŝdego rodzaju. Jesteśmy w stanie uŝyć tutaj danych dowolnego formatu i rozmiaru. Ich źródło takŝe ustalamy sami, gdyŝ jak zostało 9

powiedziane wcześniej zasoby własne moŝna tworzyć zarówno na podstawie zewnętrznych plików jak i definiować je w samym skrypcie. Aby stworzyć zasób własnego typu na podstawie pliku naleŝy postępować podobnie jak w przypadku ikon i kursorów. Struktura definicji wygląda tutaj identycznie: Identyfikator TYP ścieŝka Pola identyfikatora i ścieŝki pełnią tę samą rolę co poprzednio. Jedynie drugi argument (TYP) zachowuje się odmiennie. Tym razem nie przyjmuje on nazwy jednego z dostępnych typów. W zasobach własnych typ ustala się samodzielnie. MoŜna mu przypisać dowolne (własne) oznaczenie: w formie łańcucha (bez cudzysłowów), lub w formie liczby w tym przypadku wartość ta musi być większa od 255, poniewaŝ liczby od 1 do 255 są zarezerwowane dla typów juŝ istniejących oraz dla tych, które mogą powstać w przyszłości. Zdefiniowanie kilku zasobów własnego typu moŝe wyglądać następująco: 10 MojTyp1 tresc.txt 11 MojTyp2 tlo.jpg dane 256 data.dat 12 256 data2.dat tlo Bitmapa tlo.bmp PowyŜej widać, Ŝe pliki określane trzecim parametrem mogą mieć dowolny format. Nie jest waŝne czy wczytujemy tekst, obraz, dokument czy nawet inny program. Ostatecznie jest to po prostu ciąg bajtów. Przejdźmy do wyjaśnienia drugiej metody tworzenia zasobów własnych opisywania ich w skrypcie. Definicję taką równieŝ rozpoczynają identyfikator zasobu oraz nazwa naszego typu: Identyfikator TYP dane,... Później w bloku ograniczonym nawiasami klamrowymi naleŝy wypisać wszystkie dane oddzielając je przecinkami. Pojedyncza porcja danych moŝe być łańcuchem znakowym lub liczbą: 14 DANEWLASNE "Tekst", "Tekst", 100, 200 Wszystkie elementy (porcje danych) tak stworzonego zasobu po kompilacji będą stanowiły jeden ciąg bajtów jak gdyby był to plik. Wymieniane pomiędzy nawiasami klamrowymi dane zostaną więc połączone. Wynika z tego, Ŝe poniŝszy skrypt: 10

1 256 "abc", "def" spowoduje powstanie identycznego zasobu jak przy skrypcie: 1 256 "ab", "cd", "ef" Obie definicje po kompilacji dadzą w wyniku ciąg o wartości "abcdef". Liczby wymieniane w bloku zasobów własnych są standardowo traktowane jako dane typu WORD (słowo). MoŜna jawnie zaŝądać od kompilatora by uczynił daną liczbę wartością DWORD (podwójne słowo) poprzez dodanie przyrostka L na jej końcu: 2 256 10L, 23L, 32L Tak, więc powyŝszy zasobów będzie posiadał rozmiar sześciu słów. Liczby moŝna dodatkowo podawać na trzy sposoby jako wartości dziesiętne, szesnastkowe oraz ósemkowe. UŜywana notacja jest taka sama jak w językach C/C++: 3 256 10, // zapis dziesietny, pisany standardowo 0xA, // zapis szesnastkowy, poprzedzony symbolem 0x 012 // zapis osemkowy, poprzedzony symbolem 0 RównieŜ w przypadku zapisu wartości łańcuchowych naleŝy wspomnieć o jednym waŝnym szczególe. Mianowicie na końcu ciągu znakowego nie jest automatycznie dodawany znak zerowy ('\0') tak jak to jest w C/C++. By móc w programie wykorzystywać te łańcuchy w róŝnych funkcjach biblioteki standardowej naleŝy na końcu ręcznie dodać symbol zerowy: 4 256 "To jest pełnowartościowy łancuch\0", "To jest łańcuch bez końca", Znak końca łańcucha ('\0') jest oczywiście jednym ze znaków specjalnych. Zasoby własne z pewnością naleŝą do jednego z ciekawszych typów, gdyŝ mają wszechstronne zastosowania. To w jakim celu uŝyjemy tego rodzaju zasobów zaleŝy wyłącznie od naszej woli i potrzeb. Kompilator zasobów w Ŝaden sposób nie ingeruje w zawartość przechowywanych w ten sposób danych tak więc moŝemy być pewni, Ŝe ich 11

format zawsze będzie prawidłowy. Sami musimy tylko zadbać by właściwie te dane wykorzystać. Preprocesor i skrypty zasobów RozwaŜania na temat tworzenia zasobów zakończę informacją o tym, Ŝe podczas ich budowania moŝna stosować dyrektywy preprocesora oraz komentarze. Kompilator zasobów posiada swój preprocesor, którego składnia jest taka sama jak preprocesora języków C i C++. MoŜna więc korzystać z definiowanych stałych i makr, kompilacji warunkowej oraz dołączania plików nagłówkowych. Potencjał preprocesora najczęściej słuŝy do definiowania stałych symbolicznych dla liczbowych identyfikatorów zasobów. W takich przypadkach zaleca się utworzenie osobnego pliku nagłówkowego zawierającego wszystkie stałe, a następnie dołączenie go do skryptu zasobów i głównego pliku źródłowego programu. Próbka takiej strategii moŝe wyglądać następująco: Plik zasoby.h: #define IDI_MAINICON 100 #define IDB_DRAWING 300 #define IDM_MAIN 1000 #define IDM_MAIN_PLIK 1100 #define IDM_MAIN_EDYCJA 1200 Plik zasoby.rc: #include "zasoby.h" IDI_MAINICON ICON "mainicon.ico" IDB_DRAWING BITMAP "drawing.bmp" IDM_MAIN MENU MENUITEM "&Plik", IDM_MAIN_PLIK MENUITEM "&Edycja", IDM_MAIN_EDYCJA Plik main.cpp: #include <windows.h> #include <windowsx.h> #include "zasoby.h"... Pliki odpowiadające za zasoby (zarówno *.rc jak i *.h) powinny dla uproszczenia posiadać takie same nazwy. By nie komplikować powyŝszego przykładu w pliku nagłówkowym zrezygnowałem z dyrektyw kompilacji warunkowej chroniących przed wielokrotnym załączaniem tych samych definicji. Korzystanie z zasobów w programach W tym momencie, kiedy zilustrowany został juŝ sposób tworzenia zasobów, spokojnie moŝemy zająć się najciekawszą czynnością, czyli ich uŝyciem ich w konkretnych 12

programach. Przedstawię po kolei metody wczytywania i wykorzystywania wszystkich opisanych uprzednio zasobów. Ikony NajwaŜniejszą rolą ikony jest graficzne reprezentowanie programu w środowisku systemu. Cel ten jest akurat bardzo łatwy do uzyskania. Nie potrzeba tutaj nawet ingerencji w kod źródłowy aplikacji, gdyŝ system Windows jako ikonę programu przyjmuje pierwszą ikonę widniejącą w jego zasobach. Funkcjonuje to w ten sposób jako, Ŝe nie istnieje Ŝaden inny sposób pozwalający na ustalenie ikony bez wykonania kodu programu. System jak wiadomo na takie działanie nie mógłby sobie pozwolić. Wczytuje on tylko zasoby danej aplikacji, znajduje tam pierwszą z dostępnych ikon i później wyświetla ją obok nazwy programu. By mieć pewność, Ŝe dana ikona będzie pierwszą w zasobach naleŝy nadać jej najniŝszy (jeśli uŝywa się liczby) lub najwcześniejszy zgodnie z porządkiem alfabetycznym (jeśli uŝywa się łańcucha) identyfikator. Ponadto naleŝy wiedzieć, Ŝe identyfikatory łańcuchowe mają pierwszeństwo przed identyfikatorami liczbowymi. Tak więc niezaleŝnie od tego jak niskie będą wartości liczbowe, ikony o nazwach złoŝonych ze znaków i tak znajdą się bliŝej początku zasobu. Dla przykładu w następującym skrypcie: XYZ ICON "ikona1.ico" MainIcon ICON "ikona2.ico" 0 ICON "ikona3.ico" pierwszą będzie ikona o etykiecie "MainIcon". Tutaj z kolei: 6 ICON "ikona1.ico" 5 ICON "ikona2.ico" na początku zasobu znajdzie się ikona o identyfikatorze równym 5. Po skompilowaniu skryptu z definicją ikony zostaje ona natychmiast nadana programowi. Ikony mają jednak takŝe inne zastosowania. Przykładowo po wykonaniu wyŝej opisanej operacji ikona zmieni się tylko i wyłącznie z punktu widzenia interfejsu systemu. Nie zobaczymy jej natomiast po uruchomieniu aplikacji w jej belce tytułowej, ani teŝ na pasku zadań. Aby ikona znalazła się w tych miejscach trzeba juŝ odpowiednio zmodyfikować kod programu. Zmiany naleŝy poczynić w wypełnianiu pól struktury opisującej klasę okna (WNDCLASSEX). Znajdują się tam dwa elementy odpowiedzialne wygląd ikon: hicon oraz hiconsm. Pierwszy z nich przechowuje uchwyt duŝej (standardowej) ikony, a drugi ikony małej. DuŜa ikona wyświetlana jest podczas przełączania się między programami kombinacją klawiszy ALT+TAB. Z kolei jej mniejsza wersja pokazuje się w rogu okna oraz na pasku zadań. Poza tym system w razie konieczności decyduje o wyświetleniu jednej z tych ikon zaleŝnie od swoich potrzeb. Kiedy nie uŝywa się własnych ikon pola hicon i hiconsm są uzupełniane w następujący sposób: wndclass.hicon = LoadIcon(NULL, IDI_APPLICATION); wndclass.hiconsm = LoadIcon(NULL, IDI_APPLICATION); Korzysta się tutaj z funkcji LoadIcon co powinno być wszystkim dobrze znane. W wyniku przekazania w jej drugim argumencie wartości NULL zwracana jest ikona ze standardowej 13

kolekcji Windows. Podczas wczytywania do programu swojej ikony równieŝ stosuje się tą funkcję. NaleŜy jedynie zmienić podawane argumenty. Pierwszy z nich określa instancję aplikacji, z której pochodzi ikona. Skoro korzystamy z zasobów wbudowanych w nasz program toteŝ powinno się tutaj uŝyć uchwytu pochodzącego z definicji funkcji WinMain (najczęściej o nazwie hinstance). Do uzupełnienia pozostaje drugi argument. Odpowiada on za identyfikator wczytywanej ikony. JeŜeli identyfikator ten jest łańcuchem to wystarczy wpisać go ujmując oczywiście w cudzysłowy: LoadIcon(hInstance, "MainIcon"); JeŜeli jednak identyfikator ma formę liczby to sprawa nieco się komplikuje. Dodatkowo trzeba skorzystać z odpowiedniego makra, które przekształci identyfikator na format akceptowany przez funkcję LoadIcon. Argument jest bowiem typu znakowego więc nie moŝna standardowo przekazać w tym miejscu wartości liczbowej. Wspomniane makro nosi nazwę MAKEINTRESOURCE i przyjmuje jeden parametr identyfikator naszego zasobu: LoadIcon(hInstance, MAKEINTRESOURCE(5)); W takiej postaci wywołanie funkcji moŝna podstawić do właściwego kodu: wndclass.hicon = LoadIcon(hInstance, MAKEINTRESOURCE(5)); wndclass.hiconsm = LoadIcon(hInstance, "MainIcon"); Przedstawiony przykład prezentuje przypisanie dwóch róŝnych zasobów w miejsce ikon duŝej i małej. JeŜeli jednak nie posiadamy dwóch osobnych ikon to moŝemy obu polom nadać taką samą wartość: wndclass.hicon = LoadIcon(hInstance, MAKEINTRESOURCE(5)); wndclass.hiconsm = LoadIcon(hInstance, MAKEINTRESOURCE(5)); // lub wndclass.hicon = LoadIcon(hInstance, MAKEINTRESOURCE(5)); wndclass.hiconsm = wndclass.hicon; Gdy w kodzie znajdą się takie instrukcje aplikacja będzie w pełnym zakresie reprezentowana własną ikoną. Najczęściej wszystkie opisane w tym punkcie rodzaje ikon, czyli ta uŝywana przez Eksplorator Windows oraz dwie właśnie wprowadzone są takie same (odwołują się do jednego zasobu). Kursory WinAPI pozwala na uŝywanie jednolitych oraz animowanych wskaźników myszy. Kursor taki moŝna przypisać całej klasie bądź tylko jednemu, konkretnemu oknu. Pierwsze z zagadnień jest znacznie łatwiejsze tak więc od niego zaczniemy. Zabieg ten wykonuje się, tak jak w przypadku ikon, podczas definiowania pól struktury WNDCLASSEX, a konkretnie pola hcursor. Domyślnie przypisywany jest mu uchwyt standardowego kursora strzałki: wndclass.hcursor = LoadCursor(NULL, IDC_ARROW); Funkcja LoadCursor jak i jej argumenty zachowują się identycznie jak w uprzednio opisanej procedurze LoadIcon, z tą tylko róŝnicą, Ŝe w rezultacie otrzymujemy uchwyt nie ikony, a 14

kursora. Na podstawie wiedzy, którą juŝ posiadamy moŝemy wyprowadzić następującą konstrukcję słuŝącą do nadania klasie własnego kursora: wndclass.hcursor = LoadCursor(hInstance, "Kursor1"); Oczywiście moŝliwe jest teŝ korzystanie z liczbowych identyfikatorów zasobu: wndclass.hcursor = LoadCursor(hInstance, MAKEINTRESOURCE(20)); PowyŜsze wyraŝenia spowodują, Ŝe określony w nich kursor będzie uŝywany we wszystkich oknach zbudowanych w oparciu o definiowaną tutaj klasę. Aby nadać nowy kursor samemu tylko oknu, a nie ogólnej klasie trzeba skorzystać z funkcji SetCursor. Jej postać jest następująca: HCURSOR SetCursor(HCURSOR hcursor); Funkcja ta wymaga jednego argumentu określającego uchwyt nowego kursora. Jako wynik natomiast zwraca uchwyt kursora poprzednio uŝywanego przez okno. PoniŜej przedstawiam przykładowe wywołanie tej funkcji: SetCursor(LoadCursor(hInstance, MAKEINTRESOURCE(20))); NaleŜy tutaj wspomnieć o jednym bardzo waŝnym szczególe. Funkcja ta nie ustawia nowego kursora na stałe ale jedynie tymczasowo. Konkretnie do momentu gdy poruszymy myszą. Dlatego za kaŝdym razem gdy takie zdarzenie będzie miało miejsce trzeba wywoływać funkcję SetCursor. Odpowiednią przestrzenią do umieszczenia tej instrukcji jest kod obsługi komunikatu WM_MOUSEMOVE okna: switch(msg) case WM_MOUSEMOVE: SetCursor(LoadCursor(hInstance, "Kursor1")); break; // obsluga pozostalych komunikatow //... W taki sposób moŝna zmieniać kursory w czasie działania programu. MoŜe to posłuŝyć do zmiany wskaźnika w programach graficznych podczas wyboru nowego narzędzia. Uchwyt kursora mógłby być wtedy przechowywany w zmiennej globalnej i za kaŝdym razem przypisywany oknu wywołaniem funkcji SetCursor (za pomocą kodu przedstawionego wyŝej). Następnie gdy zaszłaby potrzeba, wartość uchwytu byłaby modyfikowana przypisaniem mu wyniku funkcji LoadCursor: HCURSOR hcursor; // globalna zmienna przechowująca aktualny kursor //... // fragment instrukcji modyfikujących uchwyt kursora hcursor = LoadCursor(hInstance, "Kredka"); //... // fragment procedury obsługi komunikatow switch(msg) 15

case WM_MOUSEMOVE: SetCursor(hCursor); break; // obsluga pozostalych komunikatow //... Bitmapy Kolejnym waŝnym typem zasobów są bitmapy. Aby wyświetlić ten element graficzny najpierw naleŝy pobrać go z zasobów funkcją LoadBitmap: HBITMAP LoadBitmap(HINSTANCE hinstance, LPCTSTR lpbitmapname); Funkcja ta jest w swoim działaniu identyczna jak dwie poprzednio opisane (LoadIcon i LoadCursor). W wyniku jej wywołania otrzymujemy uchwyt danej bitmapy przystosowany do uŝywania go z funkcjami GDI: HBITMAP hbitmap = LoadBitmap(hInstance, MAKEINTRESOURCE(300)); Gdy bitmapa zostanie juŝ w taki sposób załadowana do programu naleŝy skorzystać z kolejnych procedur, które nie wyprowadzą jej póki co na ekran ale przygotują odpowiednio do tego celu. OtóŜ by wyświetlić rysunek w takiej formie musi on posiadać własny kontekst urządzenia (taki sam jaki uŝywany jest przez WinAPI do malowania po obszarze okna). Nowy kontekst tworzy się funkcją CreateCompatibleDC: HDC CreateCompatibleDC(HDC hdc); Tworzy ona na podstawie innego kontekstu (przekazywanego w argumencie) nowy, o takich samych parametrach. Dzięki temu moŝliwe będzie wykonywanie operacji transferu zawartości jednego kontekstu w drugi. Kiedy element ten nie będzie juŝ potrzebny naleŝy zwolnić go procedurą DeleteDC: BOOL DeleteDC(HDC hdc); W przypadku powodzenia funkcja ta zwraca wartość TRUE. Inaczej, np. gdy przekazany w argumencie kontekst został juŝ wcześniej usunięty, zwracana jest wartość FALSE. Kontekst urządzenia dla bitmapy trzeba utworzyć wspomnianą funkcją w oparciu o główny kontekst wykorzystywany do wyświetlania grafiki w oknie. Tak więc standardowy kod, będący fragmentem procedury obsługi komunikatów, o następującej postaci: PAINTSTRUCT ps; HDC hdc; switch(msg) case WM_PAINT: hdc = BeginPain(hWnd, &ps); // instrukcje rysujące EndPaint(hWnd, &ps); break; 16

// obsluga pozostalych komunikatow //... naleŝy uzupełnić wywołaniami wszystkich opisanych funkcji w poniŝszy sposób: PAINTSTRUCT ps; HDC hdc; HDC hdcbitmapa; HBITMAP hbitmap = LoadBitmap(hInstance, MAKEINTRESOURCE(20)); switch(msg) case WM_PAINT: hdc = BeginPain(hWnd, &ps); hdcbitmapa = CreateCompatibleDC(hDC); // instrukcje rysujące DeleteDC(hDCBitmapa); EndPaint(hWnd, &ps); break; // obsluga pozostalych komunikatow //... Po stworzeniu kontekstu powinno się wypełnić go zawartością bitmapy wywołaniem dobrze znanej funkcji SelectObject z parametrem będącym uchwytem bitmapy: SelectObject(hDCBitmapa, hbitmap); Oczywiście jak wiadomo po zakończeniu pracy z wybranym w ten sposób obiektem naleŝy go usunąć: DeleteObject(hBitmap); Nasz kod obsługi komunikatu WM_PAINT rozszerza się więc do poniŝszej postaci: PAINTSTRUCT ps; HDC hdc; HDC hdcbitmapa; HBITMAP hbitmap = LoadBitmap(hInstance, MAKEINTRESOURCE(20)); switch(msg) case WM_PAINT: hdc = BeginPain(hWnd, &ps); hdcbitmapa = CreateCompatibleDC(hDC); SelectObject(hDCBitmapa, hbitmap); // instrukcje rysujące DeleteObject(hBitmap); DeleteDC(hDCBitmapa); EndPaint(hWnd, &ps); break; 17

// obsluga pozostalych komunikatow //... Brakuje tutaj juŝ tylko procedur, a właściwie jednej procedury, która odpowiada za wyświetlenie rysunku na ekran. Poszukiwana funkcja to BitBlt wykonująca kopiowanie zawartości jednego kontekstu do pamięci innego kontekstu. BOOL BitBlt( HDC hdcdest, // kontekst urzadzenia docelowego int nxdest, // wspolrzedna osi X wyswietlenia rysunku int nydest, // wspolrzedna osi Y wyswietlenia rysunku int nwidth, // szerokosc wyswietlanego rysunku int nheight, // wysokosc wyswietlanego rysunku HDC hdcsrc, // kontekst urzadzenia zrodlowego int nxsrc, // wspolrzedna osi X pobrania rysunku int nysrc, // wspolrzedna osi Y pobrania rysunku DWORD dwrop // opcje wykonywanej operacji ); Wystarczy więc podać oba zainteresowane konteksty, odpowiednie współrzędne i wymiary, i wreszcie opcje wykonywanego wyświetlenia. Wszystkie dostępne wartości dla ostatniego argumentu moŝna znaleźć w dokumentacji API Windows. Ta jednak, która nas interesuje i jest jednocześnie najczęściej stosowana to SRCCOPY. Dyktuje ona funkcji wykonanie dokładnej kopii fragmentu bądź całości kontekstu źródłowego i zapisanie jej do kontekstu docelowego. Wywołanie tej funkcji naleŝy zamieścić po wybraniu bitmapy jako obiektu jej kontekstu: PAINTSTRUCT ps; HDC hdc; HDC hdcbitmapa; HBITMAP hbitmap = LoadBitmap(hInstance, MAKEINTRESOURCE(20)); switch(msg) case WM_PAINT: hdc = BeginPain(hWnd, &ps); hdcbitmapa = CreateCompatibleDC(hDC); SelectObject(hDCBitmapa, hbitmap); BitBlt(hDC, 0, 0, 64, 64, hdcbitmapa, 0, 0, SRCCOPY); DeleteObject(hBitmap); DeleteDC(hDCBitmapa); EndPaint(hWnd, &ps); break; // obsluga pozostalych komunikatow //... W takiej formie kod jest juŝ pełnowartościowy i spowoduje wyświetlenie rysunku na ekranie. 18

Dźwięki Do odtwarzania plików muzycznych zawartych w zasobach słuŝy funkcja PlaySound. Ponadto moŝe ona równieŝ uruchamiać dźwięki znajdujące się na dysku. By móc z niej skorzystać trzeba do projektu dołączyć bibliotekę winmm. Ciało procedury PlaySound wygląda następująco: BOOL PlaySound(LPCTSTR pszsound, HMODULE hmod, DWORD fdwsound); Pierwszy argument określa dźwięk jaki ma zostać odegrany. W tym miejscu wolno umieścić identyfikator zasobu lub ścieŝkę do pliku na dysku. Wartość hmod stanowi uchwyt aplikacji, z której pobierany jest dźwięk. JeŜeli utwór jest odgrywany z dysku to naleŝy przypisać mu wartość NULL. Ostatni argument opisuje jak funkcja ma się zachować. NajwaŜniejsze dostępne dla tej właściwości opcje zostały zebrane w poniŝszej tabeli. Opcja SND_RESOURCE SND_FILENAME SND_ASYNC SND_SYNC SND_NODEFAULT SND_NOSTOP SND_PURGE Opis Oznacza, Ŝe dany dźwięk pochodzi z zasobów aplikacji i tym samym argument pszsound zawiera jego identyfikator, a argument hmod jest równy uchwytowi aplikacji, która go zawiera. Określa, Ŝe dźwięk znajduje się na dysku i argument pszsound przechowuje jego ścieŝkę. Argument hmod powinien wtedy mieć wartość NULL. Ustala, Ŝe dźwięk będzie odtwarzany asynchronicznie, tzn., program nie będzie zablokowany w czasie odtwarzania. Zaznacza, Ŝe dźwięk będzie odtwarzany synchronicznie, tzn., do czasu aŝ odtwarzanie się nie zakończy program będzie zablokowany. Oznacza, Ŝe gdy funkcja nie znajdzie zasobu lub pliku określonego parametrem pszsound to nie zostanie odtworzony Ŝaden dźwięk. Kiedy ta opcja nie jest uŝywana to w przypadku nie odnalezienia utworu odtwarzany jest krótki sygnał systemowy oznaczający błąd. Ustala, Ŝe jeśli w danej chwili aplikacja będzie odtwarzała juŝ pewien dźwięk to aktualne wywołanie funkcji nie spowoduje zatrzymania pracy poprzednio wywołanej. Tym samym nie zostanie odtworzony nowy dźwięk. Oznacza, Ŝe funkcja jest wywoływana wyłącznie po to aby wyłączyć wszystkie odtwarzane aktualnie przez aplikację dźwięki. Gdy ta opcja jest uŝywana to dwa pozostałe argumenty powinny posiadać wartość NULL. Przedstawione stałe mogą być stosowane razem połączone operatorem sumy logicznej. PoniŜej znajduje się przykład kilku zastosowań funkcji PlaySound. 19

// asynchroniczne odtworzenie dzwieku o identyfikatorze 400 PlaySound(MAKEINTRESOURCE(400), hinstance, SND_RESOURCE SND_ASYNC); // synchroniczne odtworzenie dzwieku o identyfikatorze "Dzwiek1" PlaySound("Dzwiek1", hinstance, SND_RESOURCE SND_SYNC); // odtworzenie pliku z dysku pod warunkiem ze inny nie jest odgrywany PlaySound("C:\\muzyka.wav", NULL, SND_FILENAME SND_NOSTOP SND_ASYNC); // zatrzymanie wszystkich dzwiekow PlaySound(NULL, NULL, SND_PURGE); Menu Właśnie zakończyliśmy przedstawianie metod korzystania z zasobów tworzonych na podstawie plików. Teraz zajmiemy się trzema typami z grupy drugiej. UŜywanie menu jest nieco bardziej skomplikowane w stosunku do poprzednich zasobów. Trzeba bowiem najpierw przypisać dane menu programowi, a potem jeszcze we właściwy sposób obsłuŝyć jego zdarzenia. Przypisywanie menu do okna programu Menu moŝna podporządkowywać całej klasie lub jednemu oknu. Przypisanie menu do klasy okna polega na podaniu jego identyfikatora jako wartości pola lpszmenuname struktury WNDCLASSEX: wndclass.lpszmenuname = "MainMenu"; Jak widać nie ma tutaj konieczności wykorzystywania dodatkowych funkcji do pobierania menu z zasobów aplikacji. Wystarczy tylko podać określającą je nazwę bądź liczbę (przy uŝyciu makra MAKEINTRESOURCE). Po skompilowaniu programu menu będzie juŝ częścią głównego okna. Celem przypisania menu do samego okna naleŝy zmodyfikować trzeci od końca argument funkcji tworzącej to okno (CreateWindowEx): HWND CreateWindowEx( DWORD dwexstyle, LPCTSTR lpclassname, LPCTSTR lpwindowname, DWORD dwstyle, int x, int y, int nwidth, int nheight, HWND hwndparent, HMENU hmenu, // uchwyt menu HINSTANCE hinstance, LPVOID lpparam ); Argument hmenu określa uchwyt menu mającego zostać dodane do okna. PoniewaŜ jest to uchwyt, a nie identyfikator toteŝ trzeba tutaj skorzystać z funkcji, która taki uchwyt stworzy. Mam na myśli procedurę LoadMenu: HMENU LoadMenu(HINSTANCE hinstance, LPCTSTR lpmenuname); 20

Jest to kolejna funkcja naleŝąca do serii wczytujących zasoby i działa dokładnie tak samo jak poprzednio opisane. Tak więc pobiera uchwyt aplikacji oraz identyfikator zasobu, a zwraca uchwyt właściwego elementu w tym przypadku uchwyt menu. Wynik wywołania tej funkcji naleŝy przekazać funkcji CreateWindowEx we wspomnianym argumencie hmenu: hwnd = CreateWindowEx(NULL, "KlasaOkna", "TytulOkna", WS_OVERLAPPEDWINDOW, 0, 0, 400, 300, NULL, LoadMenu(hInstance, MAKEINTRESOURCE(1000)), hinstance, NULL)); Obsługa zdarzeń menu Gdy uŝytkownik wybierze (kliknie) jedną z pozycji menu to do okna zostanie wysłany komunikat WM_COMMAND. W młodszym słowie parametru wparam tego komunikatu znajdować się będzie identyfikator wybranego elementu (nadany mu podczas sporządzania skryptu zasobu). By więc obsłuŝyć zdarzenia występujące w menu naleŝy w procedurze obsługi komunikatów dołączyć kod sprawdzający jaki identyfikator został przesłany przez zmienną wparam i na tej podstawie odpowiednio zareagować. Dostęp do młodszego słowa uzyskuje się za pomocą makra LOWORD. Przykład zaprezentowanego postępowania moŝe wyglądać tak: switch(msg) case WM_COMMMAND: switch(loword(wparam)) case IDM_PLIK_KOMUNIKAT: MessageBox(hWnd, "Kliknąłeś element", "Komunikat", MB_OK); break; case IDM_PLIK_ZAMKNIJ: SendMessage(hWnd, WM_CLOSE, 0, 0); break; break; // obsluga pozostalych komunikatow //... W powyŝszym kodzie następuje więc wyświetlenie komunikatu w razie wybrania elementu menu o identyfikatorze IDM_PLIK_KOMUNIKAT oraz zamknięcie aplikacji w przypadku wyboru pozycji IDM_PLIK_ZAMKNIJ. Są to stałe symboliczne ukrywające konkretne identyfikatory. MoŜna załoŝyć, Ŝe fragment skryptu zasobów definiujący obsłuŝone wyŝej menu wygląda następująco: 1000 MENU 21

POPUP "&Plik" MENUITEM "&Komunikat", IDM_PLIK_KOMUNIKAT MENUITEM "&Zamknij", IDM_PLIK_ZAMKNIJ To jaki identyfikator ma dane menu nie ma wpływu kod obsługi. Pod uwagę bierze się tylko identyfikatory poszczególnych jego pozycji. Tablice łańcuchów Tablice łańcuchów przechowują ciągi znakowe. By wczytać jeden z nich do programu naleŝy stworzyć tablicę znakową, w której zostanie on przechowany. Później wystarczy zamieścić w kodzie wywołanie funkcji LoadString: int LoadString(HINSTANCE hinstance, UINT uid, LPTSTR lpbuffer, int nbuffermax); Przyjmuje ona cztery argumenty z czego dwa pierwsze to dobrze znany uchwyt aplikacji oraz identyfikator zasobu. Przypomnę, Ŝe identyfikator w przypadku tablic łańcuchów zawsze jest liczbą i w związku z tym nie ma konieczności korzystania z makra MAKEINTRESOURCE. Pojawiają się tutaj jeszcze dwa parametry opisujące bufor (miejsce zapisania) dla wczytywanego ciągu. Argument lpbuffer jest tablicą, do której wczytany zostanie łańcuch, nbuffermax definiuje natomiast maksymalną liczbę znaków jaka moŝe być do tej tablicy zapisana. Funkcja w przypadku powodzenia zwraca wartością będącą liczbą wczytanych do bufora znaków, a w razie błędu wartość zero. Sposób uŝycia poznanej funkcji moŝe wyglądać następująco: char bufor[80]; LoadString(hInstance, 5, bufor, strlen(bufor)); Wczytaną wartość moŝna następnie wykorzystać jak standardowy łańcuch znakowy, przykładowo do wyświetlenia komunikatu: MessageBox(hWnd, bufor, "Komunikat", MB_OK); Zasoby własne Przyszedł czas na wyjaśnienie metody uŝycia ostatniego typu zasobów jakim są zasoby własne. Idea jest tutaj ogólnie bardzo prosta, gdyŝ polega jedynie na wczytaniu zasobu do programu. Jego konkretne wykorzystanie pozostaje w rękach kaŝdego programisty z osobna. Jakkolwiek występuje tutaj pewne skomplikowanie jako, Ŝe operacja pobrania zasobu własnego pociąga za sobą wywołania aŝ trzech funkcji. Są to kolejno: FindResource, LoadResource oraz LockResource. KaŜda następna uŝywa wyniku poprzedniej jako swojego argumentu. Ich deklaracje posiadają poniŝsze postaci: HRSRC FindResource(HMODULE hmodule, LPCTSTR lpname, LPCTSTR lptype); HBLOBAL LoadResource(HMODULE hmodule, HRSRC hresinfo); LPVOID LockResource(HGLOBAL hresdata); 22

Funkcja FindResource odszukuje dany element w zasobach programu. Wymaga podania uchwytu aplikacji oraz identyfikatora i typu zasobu. Po wykonaniu zwraca uchwyt z opisem zasobu pozwalający drugiej procedurze LoadResource na faktyczne wydobycie zasobu i umieszczenie go w bloku tzw. pamięci globalnej. Funkcja LoadResource ponownie wymaga podania uchwytu aplikacji. Jej rezultatem jest uchwyt do wspomnianego bloku pamięci, który jednocześnie stanowi argument ostatniej funkcji (LockResource) blokującej dany zasób w zwykłej pamięci tak by moŝna było z niego skorzystać. W wyniku zwraca ona wskaźnik do danych tego zasobu. JeŜeli przykładowo stworzyliśmy skrypt zasobów zawierający definicję zasobu własnego o typie "PLIK" i identyfikatorze "Tekst", któremu został przypisany plik tekstowy z dysku to moŝemy wyświetlić jego zawartość w formie komunikatu w następujący sposób: char *bufor; bufor = LockResource(LoadResource(hInstance, FindResource(hInstance, "Tekst", "PLIK"))); MessageBox(hWnd, bufor, "Zawartość pliku", MB_OK); Po wykonaniu takiego kodu na ekranie powinniśmy ujrzeć widok analogiczny do zilustrowanego poniŝszej. Podsumowanie Zagadnienia dotyczące tworzenia zasobów w programach Windows wybiegają w pewnym stopniu poza zakres tego artykułu. Przykładowo nie zawarłem tutaj informacji na temat kilku, innych, rzadziej lub częściej uŝywanych typów zasobów oraz sposobu ich modyfikacji w gotowych programach z poziomu kodu. Nie mniej jednak, przekazane zostały najbardziej znaczące wiadomości o temacie, które pozwolą swobodnie tworzyć zasoby i z nich korzystać, a w przyszłości będą takŝe stanowić punkt zaczepienia podczas dalszego zgłębiania poznanej dziedziny. Po przeczytaniu artykułu zapraszam do zapoznania się z dodatkiem, czyli krótkim omówieniem programu narzędziowego Resource Hacker. 23

Dodatek obsługa programu Resource Hacker Akapit ten zawiera streszczenie moŝliwości udostępnianych przez program Resource Hacker narzędzie słuŝące prostemu opracowywaniu zasobów gotowych programów. Jest to aplikacja na licencji freeware, tak więc kaŝdy zainteresowany moŝe z niej swobodnie korzystać. Resource Hacker moŝna pobrać z następującego adresu: http://www.angusj.com/resourcehacker/reshack.zip (rozmiar ok. 0,5 MB) Omawiane narzędzie nie wymaga instalacji, po ściągnięciu go otrzymujemy archiwum z plikami uruchomieniowymi. Wystarczy odnaleźć wśród nich zbiór ResHacker.exe i następnie go włączyć. Na ekranie pokaŝe się okno o prostym interfejsie, zilustrowane poniŝej. Po lewej stronie obszaru aplikacji znajduje się pole, w którym, w czasie edytowania zasobów przeglądać będziemy wszystkie dostępne elementy. Po prawej stronie natomiast ukaŝe się panel wyświetlający zawartość wybranego przez nas elementu. Prócz podanych program posiada jeszcze oczywiście pasek menu. Wyjaśnienia jego poszczególnych poleceń wprowadzę w dalszym ciągu opracowania. Zacznijmy od wczytania do programu pliku, na którym mamy zamiar przeprowadzać operacje. NaleŜy w tym celu przejść do menu File/Open... Otwarte zostanie okno dialogowe wyboru pliku. Dostępnymi formatami plików, akceptowanymi przez program, są poza standardowymi aplikacjami (*.exe) równieŝ biblioteki dynamiczne (*.dll), skompilowane pliki zasobów (*.res) i kilka innych. Dla celów opisu narzędzia Resource Hacker stworzyłem program o nazwie "Przykładowy program.exe", który w tym miejscu wczytuję. Stworzyłem własną aplikację, poniewaŝ jak wiadomo modyfikowanie zawartości cudzych programów jest zabronione. 24

Gdy juŝ dany plik zostanie załadowany do programu, jego wygląd powinien być podobny do następującego: W lewym panelu pojawił się jak widać, zbiór wszystkich zasobów w postaci drzewa. Pierwszy poziom tego drzewa to lista typów zasobów. Po rozwinięciu jednej z tych pozycji uzyskujemy dostęp do wszystkich elementów danego typu. Przykładowo po otwarciu składnika Bitmap wymienione zostaną przechowywane przez program bitmapy. Drugi poziom drzewiastej struktury zawiera identyfikatory poszczególnych elementów. Ostatni natomiast ich wersje językowe. KaŜdy zasób bowiem moŝe występować w róŝnych wersjach językowych. Ta właściwość stosowana jest naturalnie do tworzenia dodatkowych wersji językowych programu. Opisana cecha zasobu występuje pod postacią liczby (np. 1033). Edycja zasobów Gdy konkretny zasób (wraz z wersją językową) zostanie zaznaczony, w prawym obszarze programu uaktywnia się panel z jego treścią. Na poprzednim rysunku widać panel z wyświetloną zawartością zasobu tekstowego, którą moŝna na bieŝąco zmieniać. Wspomniany panel moŝe mieć róŝną formę w zaleŝności od rodzaju wybranego zasobu. Przykładowo dla ikon będzie wyglądał następująco: 25

Zawiera więc w tym przypadku nie tyle pole edycji zasobu co informacje na jego temat. Panel ten moŝe równieŝ reprezentować element podając skrypt z jego definicją (np. menu), ponadto takŝe w postaci szesnastkowej (gdy format zasobu nie został rozpoznany) lub po prostu go wyświetlając (np. ikony i bitmapy). Wszystko zaleŝy od typu zasobu. W przypadku kiedy w programie edytujemy skrypt danego zasobu to po zakończeniu tej czynności naleŝy taki skrypt skompilować. Realizuje się to przyciskiem Compile Script widocznym nad polem tekstowym z treścią skryptu: 26

W razie gdy podczas edycji został popełniony jakiś błąd to program wypisze poniŝej odpowiedni komunikat (podobnie jak to robi kompilator zasobów). JeŜeli pewnego elementu nie da się edytować poprzez panel w prawym obszarze okna lub teŝ zaleŝy nam na przeprowadzeniu innych, niŝ udostępniane przez niego, operacji to naleŝy to zrobić wywołując odpowiednią opcję menu Action, uprzednio zaznaczając dany element kliknięciem myszy. Opcje dostępne w tym menu pozwalają na zapis, podmianę, modyfikację identyfikatorów oraz dodawanie całkiem nowych zasobów. Ich znaczenia zamieszczone są w poniŝszej tabeli. Występujące tam (w niektórych opcjach) nazwy: Typ, Element oraz Wersja, są ogólnymi oznaczeniami, które w praktyce zastępowane są w programie kolejno typem, identyfikatorem oraz wersją językową zaznaczonego przez nas zasobu. Opcja Save resource as a Binary file Save Resource as a *.res file Save [Typ : Element] as a *.rc file Save [Typ] resources Opis Zapisuje dany zasób na dysku w postaci binarnej. Zapisuje dany zasób na dysku w formie skompilowanego pliku zasobów (który moŝe zostać wykorzystany w innych programach). Zapisuje na dysku skrypt z definicją danego zasobu zgodny ze składnią jakiej uŝywają kompilatory zasobów. Zapisuje na dysku wszystkie zasoby mające taki typ jak aktualnie wybrany. Tworzone są: plik ze skryptem oraz wszystkie pliki, do których skrypt ten się odwołuje. 27

Save all resources Replace Icon Replace Cursor Replace Bitmap Replace other Resource Save [Typ : Element : Wersja] Update all Resources Add new Resource Replace Resource Rename Resource [Typ : Element : Wersja] Delete Resource [Typ : Element : Wersja] Change Language [Typ : Element : Wersja] Zapisuje na dysku wszystkie zasoby znajdujące się w aplikacji. Podobnie jak przy poprzedniej opcji tworzone są przy tym: skrypt zasobu oraz pliki, do których on się odwołuje. Otwiera okno dialogowe pozwalające na przeglądanie i podmienianie znajdujących się w zasobach ikon. Otwiera okno dialogowe pozwalające na przeglądanie i podmienianie znajdujących się w zasobach kursorów. Otwiera okno dialogowe pozwalające na przeglądanie i podmienianie znajdujących się w zasobach bitmap. Otwiera okno dialogowe pozwalające na podmienianie innych zasobów (po podaniu ich dokładnego opisu). Dyktuje programowi zapisanie na dysku wybranego aktualnie zasobu. Pozwala na przypisanie programowi całkiem nowych zasobów na podstawie skompilowanego pliku zasobów (*.res). Wywołuje okno dialogowe dodawania nowego elementu do zasobów programu. Nowy zasób moŝe mieć dowolny format oraz moŝna nadać mu dowolny identyfikator, typ i wersję językową. Podmienia wybrany zasób innym wczytanym z dysku. UmoŜliwia zmianę nazwy, tj. identyfikatora danego zasobu. Powoduje usunięcie wybranego aktualnie zasobu. Pozwala na zmianę wersji językowej elementu. Niektóre z wymienionych tutaj pozycji mogą pojawić się w menu dopiero po zaznaczeniu na liście konkretnego zasobu. Dostęp do poleceń edycji zasobu moŝna równieŝ uzyskać uaktywniając menu kontekstowe prawym przyciskiem myszy nad danym elementem: 28

Zapisywanie zmian Po dokonaniu modyfikacji w danym zasobie, by poczynione przez nas zmiany dały rezultat we właściwym programie, trzeba je zapisać poleceniem menu File/Save. Po tej operacji aplikacja, którą wczytaliśmy na początku do programu zostanie zaktualizowana zasobami w nowej formie. Następnie po jej uruchomieniu będziemy mogli dostrzec efekty poczynionej pracy. Za pomocą elementów opisanego menu Action oraz panelu edycji jesteśmy w stanie zmieniać zawartość zasobów kaŝdą z moŝliwych metod. Ogółem nie jest to więc specjalnie trudne. Proponuję zatem prezentację kilku przykładów, ukazujących krok po kroku zastosowanie poznanych funkcjonalności. Przykład I zmiana ikony programu Najczęściej wykonywaną operacją dokonywaną za pomocą narzędzi pokroju Resource Hacker'a jest zmienianie ikon. Jest to teŝ jednocześnie jedna z najprostszych czynności. Zacząć naleŝy oczywiście od wczytania danej aplikacji z poziomu menu File/Open... Drugi krok to wyświetlenie okna dialogowego, którego zadaniem jest modyfikacja ikon. Najwygodniej będzie to zrobić przechodząc do menu Action, a następnie do polecenia Replace Icon. Na ekranie zobaczymy poniŝsze okno: 29

Po prawej stronie jest umieszczona lista wszystkich dostępnych w zasobie ikon. Po lewej zaś widnieje pole przechowujące ikony jakie moŝemy przypisać programowi zamiast istniejących. Jak na razie pole to pozostaje puste, gdyŝ najpierw naleŝy wczytać w tym miejscu plik, z którego pobrana zostanie nowa ikona. Klikamy zatem przycisk Open file with new icon... po czym wybieramy odpowiednie źródło i powracamy ponownie do okna zmiany ikony, które teraz zostało uzupełnione nowymi ikonami: 30

W tym momencie z listy po lewej stronie naleŝy wybrać ikonę jaka ma zastąpić jedną z ikon istniejących. Następnie wystarczy juŝ tylko zatwierdzić operację uŝywając przycisku Replace i w głównym oknie programu zapisać zmiany. JeŜeli spotkamy się z sytuacją gdzie dana aplikacja, której ikonę chcemy podmienić będzie zawierała w swoich zasobach więcej niŝ jeden element tego typu to będziemy zmuszeni do zlokalizowania wśród nich właściwego naszym zamierzeniom. W wielu przypadkach główna ikona programu podpisana jest w specyficzny sposób, np. MAINICON, tak więc mimo wszystko proces zamiany nie powinien stwarzać problemów. W prawie taki sam sposób jak ikonami, manipulować moŝna teŝ zasobami bitmap oraz kursorów. Trzeba tylko odwołać się do innej pozycji menu Action. Przykład II sztuczki z menu Przyjrzyjmy się teraz sposobowi w jaki moŝna częściowo zmienić funkcjonalność menu programu. Naszym zadaniem będzie taka modyfikacja identyfikatorów poszczególnych poleceń menu, aby pierwotne ich przeznaczenie zostało zastąpione przez przeznaczenie zupełnie innych opcji. Bardziej obrazowo jeśli na początku polecenie menu Plik/Zamknij słuŝyło do kończenia pracy programu to po naszych zabiegach moŝe powodować np. czyszczenie obrazu lub cofnięcie ostatnio wykonanej operacji. W danej aplikacji moŝe znajdować się wiele zasobów typu MENU. Po pierwsze naleŝy więc dokonać wyboru, który z nich zamierzamy edytować. W tym celu po lewej stronie okna, na liście wszystkich typów znajdujemy typ Menu. Następnie rozwijamy tę gałąź i odszukujemy składnik nas interesujący: 31

Podczas zaznaczania kolejnych zasobów, ich opisy będą pojawiały się (w formie skryptu zasobu) w panelu w prawym obszarze aplikacji. Ponadto jeŝeli, tak jak w tym przypadku, mamy do czynienia z menu, Resource Hacker wyświetli dodatkowe okno ilustrujące rzeczywisty wygląd zaznaczonego elementu: Zawartość poznanego okna jest odzwierciedleniem tego co znajduje się w skrypcie. JeŜeli więc dopiszemy coś do skryptu, a później go skompilujemy, wyświetlane menu natychmiast zostanie zaktualizowane. Aby zmienić pełnione przez poszczególne elementy menu zadania przyjdzie nam przeprowadzić edycję ich identyfikatorów. Jak wiadomo to na ich podstawie program wykonuje określone czynności. Przypomnę, Ŝe identyfikatory te w skrypcie są ostatnią składową definicji danego elementu. PoniŜej został zaznaczony identyfikator pozycji Koniec. 32

Widzimy tutaj równieŝ identyfikatory pozostałych elementów (Zapisz 1001, Zapisz jako... 1002). Tak więc jeśli wybierzemy polecenie menu Plik/Zapisz to program otrzyma komunikat z wartością 1001,na którą zareaguje operacją zapisu. Gdybyśmy teraz zamienili między sobą identyfikatory dwóch pozycji menu to program po wybraniu pierwszej z nich w wyniku wykona czynność jaką normalnie wykonałby po wybraniu tej drugiej. Przykład tak zmodyfikowanego fragmentu skryptu moŝe wyglądać następująco: Tym razem pozycja Zapisz posiada identyfikator 1003, a pozycja Koniec 1001. W tym momencie wystarczy juŝ tylko skompilować skrypt przyciskiem Compile Script i zapisać zmiany poprzez menu File/Save. Ingerencja w skrypt menu pozwala nam równie dobrze na zmianę etykiet poszczególnych elementów. Etykiety są pierwszymi argumentami występującymi po słowie kluczowym MENUITEM, a więc przykładowo moŝnaby zamienić poniŝszą linijkę skryptu: na następującą: Efekt będzie widoczny zaraz po skompilowaniu zasobu, w oknie z podglądem menu: 33