Aplet w oknie Panel sterowania

Szczawik

Niniejszy tekst został utworzony przez połączenie dwóch, wcześniej istniejących, porad z działu FAQ. Nie zawiera żadnych nowych treści, a jedynie łączy i systematyzuje wcześniej przedstawione opisy.

1 Czym jest aplet w oknie Panel sterowania?
     1.1 Przykład apletu
2 Budowa (*.CPL) z poziomu systemu operacyjnego
     2.2 Tworzenie apletu w Delphi
     2.3 Co jest czym?
     2.4 Dodawanie funkcjonalności
     2.5 Podsumowanie
3 Budowa (*.CPL) z poziomu Delphi 7 Enterprise
     3.6 Tworzenie apletu w Delphi 7 Enterprise
     3.7 Dodatkowe opcje TAppletModule w Delphi 7 Enterprise

Czym jest aplet w oknie Panel sterowania?

Jest to biblioteka, reagująca na odpowiednie komunikaty i w pewien określony sposób siebie identyfikująca. Aby nie robić zamieszania, warto zadbać, by do Panelu sterowania nie dawać apletów, będących zwykłymi programami. Warto przyjąć zasadę, że tam powinny znaleźć się jedynie programy konfiguracji usług, urządzeń, systemu lub rejestru, a nie gry czy inne aplikacje użytkowe.

Biblioteka taka, aby być widoczna dla systemu, musi znajdować się w folderze %SYSTEMROOT%\System32 (w starych Windows 9x w folderze %SYSTEMROOT%\System); czyli na przykład C:\Windows\System32. Powinna również posiadać rozszerzenie pliku (*.CPL) [Control Panel appLet; dawniej Control Panel Library].

Prosty aplet zawiera następujące elementy: opis nazwy widocznej w Panelu sterowania, opis dodatkowy - komentarz widoczny w Panelu sterowania przy włączonym widoku Szczegóły, ikonę widoczną w Panelu sterowania, przypisane działanie na uruchomienie apletu - najczęściej pokazanie okna konfiguracyjnego.

Przykład apletu

Postarajmy się zrobić następujący aplet: ikona - jakaś wybrana przez nas, nazwa - 'Chowanie partycji', opis - 'Pozwala ukrywać wybrane partycje w Eksploratorze', akcja - otwarcie okna posiadającego listę liter dysków z możliwością zaznaczania. Przycisk 'Anuluj' w oknie będzie je po prostu zamykał, 'OK' ukryje partycje (wpis w rejestrze), poinformuje o wymaganym wylogowaniu z systemu (potrzebne do zastosowania tej opcji) i zamknie okno, zapisując ustawienia.

Budowa (*.CPL) z poziomu systemu operacyjnego

Zainteresujmy się tym, jak system Windows widzi pliki (*.CPL)? Po pierwsze jest to zwykła biblioteka DLL. Musi jedynie eksportować funkcję:

function CPlApplet(hwndCPl: THandle; uMsg: DWord; lParam1, lParam2: LongInt): LongInt; stdcall;

Po drugie, funkcja ta, zależnie od parametru uMsg wykonuje inne operacje i zwraca inne rezultaty (po szczegóły, co do tych komunikatów odsyłam do dokumentacji Windows SDK, czy - jak kto woli - do MSDN). Poniżej zaprezentuję minimalną ich obsługę.

Tworzenie apletu w Delphi

Z menu głównego wybieramy File -> New -> Other -> New.. DLL Wizard, naciskamy 'OK'. Powstaje treść pustej biblioteki DLL. Do uses dopisujemy CPL, gdzie mamy definicje stałych komunikatów Panelu sterowania; pozostałe wpisy z uses można usunąć. Następnie używamy opcji File -> Save all, by zapiać projekt do wybranego katalogu - plikowi (*.DPR) dajmy nazwę CplApp.DPR.

Tworzymy dodatkowo plik zasobów (nazwijmy go res.RC) o następującej treści:

1 ICON DISCARDABLE          "ikona.ico"

STRINGTABLE DISCARDABLE
BEGIN
    1             "Chowanie partycji"
    2             "Pozwala ukrywać wybrane partycje w Eksploratorze"
END

Zawiera on opis naszej ikony oraz dwa ciągi znaków - nazwę i opis apletu. Należy go skompilować (w katalogu Delphi\Bin znajduje się program BRCC32.EXE; można też użyć np.: Resource Workshop). Plik ikony przed kompilacją musi znajdować się w tym samym katalogu, co plik zasobów. Kompilacja polega na wykonaniu polecenia:

BRCC32 res.rc

W wyniku otrzymamy plik (res.RES). Należy go dodać do CplApp. Początek tego pliku Delphi może wyglądać wtedy tak:

library CplApp;

{$R 'res.res'}

uses
//...

Pozostaje nam obsługa samych działań komunikowanych przez Panel sterowania. Z menu wybierzmy File -> New -> Form. Będzie to nasze okno apletu (zapewne zostanie nazwane Form1). Zapiszmy pliki w tym samym katalogu, co cały projekt. Nowe okno będzie kreowane dopiero po uruchomieniu apletu. Przechodzimy do kodu głównego biblioteki (View -> Units.. albo [CTRL]+[F12]) i doprowadzamy go do następującej postaci:

library CplApp;

{$R 'res.res'}

uses
  CPL, Controls,
  Unit1 in 'Unit1.pas' {Form1};

var
  CPLInfo : PCPLINFO;

function CPlApplet(hwndCPl: THandle; uMsg: LongWord; lParam1, lParam2: Longint): Longint; stdcall;
begin
case uMsg of
  CPL_INIT :    begin
                result := 1;
                end;
  CPL_INQUIRE : begin
                CPLInfo := PCPLINFO(lParam2);
                CPLInfo.idIcon := 1;
                CPLInfo.idName := 1;
                CPLInfo.idInfo := 2;
                CPLInfo.lData  := 0;
                result := 0;
                end;
  CPL_DBLCLK :  begin
                Form1:=TForm1.Create(nil);
                try
		  // Inicjowanie okna apletu
		  if Form1.ShowModal()=mrOK then
		    begin
		    // Zapisywanie zmian w aplecie
		    end;
                finally
                  Form1.Free;
                  end;
                result := 0;
                end;
  CPL_GETCOUNT :result := 1;
  CPL_STOP :    result := 0;
  CPL_EXIT :    result := 0;
  else          result := 0;
  end;
end;

exports CPlApplet;

begin
end.

W sekcji interface, w dziale uses dopisaliśmy jeszcze Controls, gdyż w tej bibliotece znajdują się definicje okien ModalResults (zatrzymujących wykonanie reszty aplikacji do czasu ich zamknięcia i zwracających kod zamknięcia okna). My właśnie tak wykorzystamy Form1, aby przycisk 'OK' zamykał okno z kodem mrOK, a przycisk 'Anuluj' - czy zamknięcie przez menu systemowe okna - zwracały coś innego. Jeśli zamknięto okno przyciskiem 'OK' (go dopiero skonfigurujemy) wykonamy kod z miejsca '//Zapisywanie zmian w aplecie' i usuniemy okno, w przeciwnym przypadku tylko usuniemy okno, bez zapisania zmian.

Naciskamy Run ([F9]) i uzyskujemy komunikat: 'Cannot debug project unless a host application is defined. Use the 'Run|Parameters... dialog box.'. Nasz projekt się skompilował. W folderze, gdzie początkowo zapisaliśmy projekt powinien istnieć plik CplApp.DLL . Dla testu skopiujmy ten plik wynikowy ze zmienionym rozszerzeniem - CplApp.CPL - do folderu %SYSTEMROOT%\System32 (Możemy też poustawiać opcję Delphi Run -> Parameters, ale nie o tym mowa). Uruchomimy Panel sterowania, a tam już czeka nasza ikonka; co prawda po naciśnięciu jeszcze nic się nie dzieje - z wyjątkiem otwarcia pustego okna, ale do tego zaraz dojdziemy.. Ma natomiast swój opis i wygląd.

Co jest czym?

Podsumujmy, co dotychczas zrobiliśmy. Analizując od początku kodu: mamy bibliotekę DLL z dodanymi bibliotekami CPL, Controls oraz Unit1 (zawierającą okno naszego apletu). Dyrektywą {$R} załączamy wcześniej skompilowany plik zasobów. PCPLINFO to wskaźnik struktury, którą będzie trzeba uzupełnić, kiedy Panel sterowania zarząda opisu apletu. Pozostaje sama funkcja obsługi komunikatów apletu, którą musimy wyeksportować na zewnątrz naszego DLL. Na komunikat CPL_INIT powinniśmy zareagować zwracając wartość 1. CPL_INQUIRE prosi o podanie opisu apletu, CPL_DBLCLICK to uruchomienie apletu, CPL_STOP to zatrzymanie apletu, CPL_EXIT to zakończenie apletu. Waszej intuicji pozostawię CPL_GETCOUNT, bo może to okazać się interesujące..

Dodawanie funkcjonalności

Zajmiemy się ukrywaniem dysków w Eksploratorze. Wybrałem tą opcję nieprzypadkowo. Po pierwsze operuje na ustawieniach systemu - rejestrze, a - jak wcześniej wspomniałem - do konfiguracji wspólnych ustawień systemu służy właśnie Panel sterowania. Po drugie, jak sądzę, dla wielu z was ta opcja sama w sobie może być ciekawa. Więc zaczynamy..

Przełączamy się na Form1 i dodajemy tam następujące komponenty: Przycisk z napisem 'OK', przycisk z napisem 'Anuluj', CheckListBox oraz ewentualne napisy Label, do podpisania, co jest czym. W przycisku 'OK' zmieniamy wartość pola ModalResult na wartość mrOK, a przycisku 'Anuluj' na mrCancel. Naciskanie przycisku z włączonym ModalResult innym niż mrNone automatycznie zamyka okno pokazane przez metodę ShowModal(), zwracając ustawioną wartość powrotną. Zamykaniem okna nie musimy się więc już martwić. W CheckListBox będziemy wypisywali wszystkie litery dysków (dla uproszczenia nawet te, których dysków nie mamy; po prostu od A do Z). Obok każdej litery będzie pole do zaznaczenia, czy partycja ma być ukryta (zaznaczone) czy nie (odznaczone).

Zapiszmy na wszelki wypadek naszą pracę. Przejdźmy do akcji OnCreate w naszej Form1. Doprowadźmy to do takiej postaci:

procedure TForm1.FormCreate(Sender: TObject);
var i:char;
begin
for i:='a' to 'z' do
  CheckListBox1.Items.Add(i+':');
end;

Dzięki temu w liście będą, już przy tworzeniu okna, wpisane litery partycji. Pozostają nam dwie rzeczy. Przy uruchomieniu apletu zaznaczyć niewidoczne partycje na liście (można to zrobić też w procedurze powyżej, ale zostawimy to dla obsługi apletu), a przy zamykaniu okna zapisać ustawienia (można zapisać w akcji na naciśnięcie 'OK', ale znów zostawmy to do obsługi przy zamknięciu okna z ModalResult równym mrOK).

Ukrywanie partycji zapisane jest w rejestrze. Przejdźmy do CplApp. W sekcji interface, dziale uses dopiszmy biblioteki Registry, Windows. Ta druga zawiera opisy stałych kluczy rejestru, pierwsza zajmuje się całą obsługą jego operacji. W obsłudze komunikatu CPL_DBLCLK w pliku głównym biblioteki, zastąpmy '//Inicjowanie okna apletu' kodem:

Reg:=TRegistry.Create;
Reg.RootKey:=HKEY_CURRENT_USER;
bit:=1;
if Reg.OpenKey('SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\Explorer',false) and Reg.ValueExists('NoDrives') then
  begin
  Reg.ReadBinaryData('NoDrives', bits, 4);
  for i:=0 to 25 do
    begin
    Form1.CheckListBox1.Checked[i]:=( (bits and bit)=bit );
    bit:=bit shl 1;
    end;
  Reg.CloseKey;
  end;
Reg.Free;

Tworzymy obiekt do działania na rejestrze, odczytujemy w kluczu 'HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\Explorer' wartość danej binarnej 'NoDrives'. Jest ona typu LongWord (32bit) [jak ktoś woli DWord, to jest to samo]. Każdy z pierwszych 26 bitów odpowiada widoczności kolejnej partycji (pierwszy bit to partycja A, drugi to B, trzeci to C, etc.). Jeśli klucz nie istnieje lub wartość jest samymi zerami, wszystkie dyski są widoczne i nie trzeba zaznaczać na liście. W pętli [0..25] przechodzimy przez wszystkie interesujące nas bity; jeśli bit jest ustawiony, na liście zaznaczamy opcję, jeśli nie - to nie. Następnie przesuwamy maskę bitową (zmienna bit) i sprawdzamy kolejny bit. Po tym wszystkim zamykamy klucz w rejestrze i niszczymy obiekt obsługi rejestru (można by go zostawić do ewentualnego zapisu ustawień, ale dla porządku wtedy stworzymy go od nowa). Należy jeszcze w ramach procedury dopisać definicje zmiennych:

var Reg:TRegistry;
    bit:LongWord;
    bits:LongWord;
    i:integer;

Wczytywanie jest zrobione. Zapis będzie wyglądał analogicznie, tylko w druga stronę. Od razu może podam kod. Na podstawie zaznaczonych opcji tworzy on daną binarną 32bit, wypełniając jedynkami te bity, które odpowiadają partycjom ukrytym. Potem zapisuje to do rejestru. W obsłudze CPL_DBLCLK zastąpmy '//Zapisywanie zmian w aplecie' kodem:

Reg:=TRegistry.Create;
Reg.RootKey:=HKEY_CURRENT_USER;
bit:=1;
bits:=0;
if Reg.OpenKey('SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\Explorer',true) then
  begin
  for i:=0 to 25 do
    begin
    if Form1.CheckListBox1.checked[i] then bits:=bits or bit;
    bit:=bit shl 1;
    end;
  Reg.WriteBinaryData('NoDrives', bits, 4);
  Reg.CloseKey;
  end;
Reg.Free;

Warto jeszcze, zgodnie z założeniami, dodać do tego informację, że użytkownik powinien się przelogować, by zobaczyć efekty. Dodajemy więc polecenie:

MessageBox(Form1.Handle, 'Aby zobaczyć efekty, zaloguj się ponownie!','Panel sterowania', MB_OK or MB_ICONASTERISK);

Podsumowanie

Tak oto powstał Wasz pierwszy działający aplet Panelu sterowania. Dla tych co się pogubili, poniżej przedstawiam pełen kod pliku głównego biblioteki DLL. Jeśli jednak jesteś szczęśliwym posiadaczem Delphi 7 Enterprise, w dalszej części tekstu - po kodzie źródłowym - znajdziesz wskazówki, jak skorzystać z dobrodziejstw tego środowiska przy tworzeniu apletów Panelu sterowania.

library CplApp;

{$R 'res.res'}

uses
  CPL, Controls, Registry, Windows,
  Unit1 in 'Unit1.pas' {Form1};

var
  CPLInfo : PCPLINFO;

function CPlApplet(hwndCPl: THandle; uMsg: LongWord; lParam1, lParam2: Longint): Longint; stdcall;
var Reg:TRegistry;
    bit:LongWord;
    bits:LongWord;
    i:integer;
begin
case uMsg of
  CPL_INIT :    begin
                result := 1;
                end;
  CPL_INQUIRE : begin
                CPLInfo := PCPLINFO(lParam2);
                CPLInfo.idIcon := 1;
                CPLInfo.idName := 1;
                CPLInfo.idInfo := 2;
                CPLInfo.lData  := 0;
                result := 0;
                end;
  CPL_DBLCLK :  begin
                Form1:=TForm1.Create(nil);
                try
                  Reg:=TRegistry.Create;
                  Reg.RootKey:=HKEY_CURRENT_USER;
                  bit:=1;
                  if Reg.OpenKey('SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\Explorer',false) and Reg.ValueExists('NoDrives') then
                    begin
                    Reg.ReadBinaryData('NoDrives', bits, 4);
                    for i:=0 to 25 do
                      begin
                      Form1.CheckListBox1.Checked[i]:=( (bits and bit)=bit );
                      bit:=bit shl 1;
                      end;
                    Reg.CloseKey;
                    end;
                  Reg.Free;
          		    if Form1.ShowModal()=mrOK then
            		    begin
                    Reg:=TRegistry.Create;
                    Reg.RootKey:=HKEY_CURRENT_USER;
                    bit:=1;
                    bits:=0;
                    if Reg.OpenKey('SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\Explorer',true) then
                      begin
                      for i:=0 to 25 do
                        begin
                        if Form1.CheckListBox1.checked[i] then bits:=bits or bit;
                        bit:=bit shl 1;
                        end;
                      Reg.WriteBinaryData('NoDrives', bits, 4);
                      Reg.CloseKey;
                      end;
                    Reg.Free;
                    MessageBox(Form1.Handle, 'Aby zobaczyć efekty, zaloguj się ponownie!','Panel sterowania', MB_OK or MB_ICONASTERISK);
                    end;
                finally
                  Form1.Free;
                  end;
                result := 0;
                end;
  CPL_GETCOUNT :result := 1;
  CPL_STOP :    result := 0;
  CPL_EXIT :    result := 0;
  else          result := 0;
  end;
end;

exports CPlApplet;

begin
end.

Budowa (*.CPL) z poziomu Delphi 7 Enterprise

Większość szczegółów technicznych budowy takiej biblioteki załatwi za nas Delphi. Zajmiemy się więc samym apletem. Nie oznacza to, że tak stworzony plik wynikowy różni się w budowie. Po prostu Delphi ukrywa przed nami całą, być może niepotrzebną, logikę apletu.

Tworzenie apletu w Delphi 7 Enterprise

Z menu głównego wybieramy File -> New -> Other -> New.. Control Panel Application, naciskamy 'OK'. Powstaje AppletModule1. Ustawmy w nim: AppletIcon (ikona do Panelu sterowania), Caption (nazwa: 'Chowanie partycji'), Help (komentarz: 'Pozwala ukrywać wybrane partycje w Eksploratorze'), ewentualnie Name (nazwa komponentu). Resztą pól zajmiemy się później. Opcja File -> Save all, by zapisać projekt do wybranego katalogu - plikowi (*.DPR) dajmy nazwę Applet1.DPR.

Wróćmy do Delphi. Z menu wybierzmy File -> New -> Form. Będzie to nasze okno apletu (zapewne nazwie je Form2). Aby oszczędzić pamięci, usuniemy je z toku automatycznego tworzenia wraz z tworzeniem apletu. Okno będzie kreowane dopiero po uruchomieniu apletu. Naciskamy [CTRL]+[F12] (albo View -> Units..) i wybieramy Applet1. Tu znajduje się kod uruchomieniowy apletu. Kasujemy linię:

Application.CreateForm(TForm2, Form2);

To ona odpowiada za automatyczne tworzenie formy. No dobrze.. ale co teraz? Jak stworzyć tą formę? Odpowiedź jest prosta. Wybieramy nasz pierwotny AppletModule1, w oknie ObjectInspector zakładka Events jak zwykle pozwala przypisywać obsługę zdarzeniom. Teraz interesuje nas akcja OnActivate wywoływana, kiedy ktoś uruchomi wybrany aplet. Klikamy na nią i wpisujemy kod:

Form2:=TForm2.Create(nil);
try
  // Inicjowanie okna apletu
  if Form2.ShowModal()=mrOK then
    begin
    // Zapisywanie zmian w aplecie
    end;
finally
  Form2.Free;
  end;

Kod ten jest analogiczny do operacji wykonywanych przy komunikacie CPL_DBLCLK, podczas wykorzystania metody pokazanej na początku porady. W rzeczywistości CPL_DBLCLK odpowiada OnActivate z użyciem komponentów Delphi. Naciśnięcie klawisza [F9] wywołuje pokazanie zapytania o dopisanie referencji do Unit2, gdyż tam zdefiniowano klasę okna TForm2, a także zmienną Form2 typu TForm2. Dajemy odpowiedź 'Yes' i kontunuujemy edycję kodu. W sekcji interface, w dziale uses dopisujemy Controls, gdyż w tej bibliotece znajdują się definicje okien ModalResults (zatrzymujących wykonanie reszty aplikacji do czasu ich zamknięcia i zwracających kod zamknięcia okna). My właśnie tak wykorzystamy Form2, aby przycisk OK zamykał okno z kodem mrOK, a przycisk 'Anuluj' czy zamknięcie przez menu systemowe okna zwracały coś innego. Jeśli zamknięto okno przyciskiem 'OK' (go dopiero skonfigurujemy) wykonamy kod z miejsca '//Zapisywanie zmian w aplecie' i usuniemy okno, w przeciwnym przypadku tylko usuniemy okno, bez zapisania zmian.

Skompilujmy nasz aplet. Tym razem dodana automatycznie w pliku głównym apletu dyrektywa {$E} pozwala od razu określić rozszerzenie pliku wyjściowego (według poprzedniej porady musieliśmy to robić ręcznie). Dla testu skopiujmy Applet1.CPL ponownie na wskazane miejsce i sprawdźmy w Panelu sterowania. Po naciśnięciu ikony otwiera się nasze okno. Jeszcze puste, ale zaraz je wypełnimy dokładnie takimi samymi komponentami, jak w pierwszej pokazanej metodzie. Zarówno '* Inicjowanie okna apletu', '*Zapisywanie zmian w aplecie' oraz akcja TForm2.FormCreate będą wyglądały w identyczny sposób.

Tak właśnie możemy wykończyć nasz aplet stworzony w Delphi 7 Enterprise.

Dodatkowe opcje TAppletModule w Delphi 7 Enterprise

ResidIcon, ResidName, ResidInfo - jeśli chcielibyśmy informacje o aplecie (ikonę, nazwę, opis) podać w zewnętrznym pliku zasobów, to tu możemy wpisać ich identyfikatory z tego pliku, na przykład ResidIcon = 0 (0 czyli ładowanie z komponentu, a nie z pliku zasobów), ResidInfo = 2, ResidName = 1. Dla takiej sytuacji plik zasobów (nazwijmy go res.RC) powinien mieć treść:

STRINGTABLE DISCARDABLE
BEGIN
    1             "Chowanie partycji"
    2             "Pozwala ukrywać wybrane partycje w Eksploratorze"
END

Należy go skompilować tak, jak poprzednio:

BRCC32 res.rc

W wyniku otrzymamy plik (res.RES). Należy go dodać do Aplet1 (View -> Units.. albo [CTRL]+[F12]). Początek tego pliku Delphi może wyglądać wtedy tak:

library Applet1;

{$R 'res.res'}

uses
//...

Plik zasobów możemy stworzyć jeszcze inaczej - korzystając z Delphi: File -> New -> Other -> New.. Text. Wypełniamy tak stworzony plik stosownym kodem. Zapisujemy plik jako res.RC . Project -> Add to Project.. -> rec.RC. W pliku głównym kodu źródłowego dodana została linia:

{$R 'res.res' 'res.RC'}

Teraz przy każdym odbudowaniu projektu (Build), plik będzie automatycznie przekompilowany na res.RES, a potem wkompilowywany w plik wyjściowy. Nie trzeba zewnętrznie używać BRCC32.

Wracając do komponentów wspomagających tworzenie apletów Panelu sterowania: dostępne też są zdarzenia (Events) związane z TAppletModule, ale po ich znaczenie odsyłam do pomocy Delphi, gdyż to jest tam bardzo dobrze opisane, a nie będziemy tu przepisywać plików pomocy. Wspomnę tylko, że pozwalają na przykład na dynamiczne podawanie opisu i komentarza, generowane przy żądaniu przez Panel sterowania i obsługę akcji na zatrzymanie apletu czy wywołanie go poprzez program RunDLL z podaniem parametrów (co przy uruchamianiu z Panelu sterowania nie jest możliwe).

Miłej zabawy.

6 komentarzy

To przeczytaj cały artykuł - opisuje jak stworzyć w dowolnym Delphi, a tylko końcówka pokazuje, jak wykorzystać do tego Delphi 7 Enterprise i jego dodatki.

fajnie że ja nie mam Delphi 7 Enterprise tylko Personal

nie zeby cos ... ale czy to nie jest <ort>kozystanie</ort> z cudzej pracy? :P

Tak - mojej własnej, bo oba artykuły były moje :] Obecnie są usunięte już, by nie śmiecić.

No i gut. Akcja "sprzątanie świata" :)

ojej! nie zauwazylem ... :P nie chcialo mi sie szukac starych artow ;)