Panel użytkownika
Nazwa użytkownika:
Hasło:
Nie masz jeszcze konta?
Autor: 'Złośliwiec'
Biblioteki C++

Okna dialogowe, cz. 5

[lekcja]

Dialog wyszukiwania tekstu

Tym razem zaczniemy trochę filozoficznie ;-). Żyjemy w czasach informacji. Kto ma informację, ten ma i władzę. Pół biedy z samym zdobyciem informacji, zazwyczaj trzeba jeszcze w ich gąszczu odnaleźć właśnie tę, która nas najbardziej interesuje. W przypadku zwykłego peceta zwykłego Kowalskiego polega to bardzo często na prostym wyszukiwaniu jakiegoś słowa w tekście.
 
Z programistycznego punktu widzenia nie jest to zazwyczaj skomplikowana operacja, jednak jeśli użyjemy do niej gotowego dialogu, to będzie jeszcze prostsza ;-). Do dzieła zatem. Naszym zadaniem będzie wydobycie na światło dzienne takiego okienka:

Dialog wyszukiwania i zamiany tekstu (Windows XP)
Dialog wyszukiwania i zamiany tekstu (Windows XP)

Tym razem struktura, której oczywiście już się spodziewasz, zowie się FINDREPLACE. Nazwa ta sugeruje, że przy jej pomocy nie tylko będziemy mogli zrealizować wyszukiwanie, ale także zamianę jakiegoś ciągu znaków na inny. I rzeczywiście tak jest, ale o tym trochę później. Pola struktury prezentują się następująco:
 
C/C++
typedef struct
{
   
DWORD lStructSize;
   
HWND hwndOwner;
   
HINSTANCE hInstance;
   
DWORD Flags;
   
LPTSTR lpstrFindWhat;
   
LPTSTR lpstrReplaceWith;
   
WORD wFindWhatLen;
   
WORD wReplaceWithLen;
   
LPARAM lCustData;
   
LPFRHOOKPROC lpfnHook;
   
LPCTSTR lpTemplateName;
} FINDREPLACE;

Wypełnienie tego to oczywiście bułka z masłem, tym bardziej że większość pól na razie nas nie interesuje. Poza dwoma pierwszymi polami musimy jeszcze podać wskaźnik do bufora (który powinien mieć rozmiar co najmniej 80 znaków):
 
C/C++
FINDREPLACE fr;
ZeroMemory( & fr, sizeof( FINDREPLACE ) );
fr.lStructSize = sizeof( FINDREPLACE );
fr.hwndOwner = hwnd;
fr.lpstrFindWhat = bufor;
fr.wFindWhatLen = 80;

lstrcpy( bufor, "tekst" );

Podobne struktury wypełnialiśmy już tyle razy, że nie ma sensu powyższego tłumaczyć (może oprócz wyjaśnienia, że zmienną bufor powinniśmy zadeklarować globalnie lub zaalokować dynamicznie). Skupmy się lepiej na pewnym haczyku, który tkwi w dialogu wyszukiwania. Otóż uruchom sobie jakiś edytor tekstowy, choćby windowsowy Notatnik i wywołaj w nim ten dialog. Coś zauważyłeś...? Jak to nie?! A kliknij sobie teraz np. na menu. Tak, dialog wyszukiwania jest niemodalny. Oznacza to, że aby poprawnie nam działał, będziemy musieli obsługiwać na bieżąco jego komunikaty. Na szczęście nie musimy pisać dodatkowej procedury dialogowej - panowie z Microsoftu obmyślili nieco prostszy sposób...
 
Wszystkie najważniejsze komunikaty tego dialogu wysyłane są jako jeden specjalny komunikat, który musimy najpierw zarejestrować w systemie. W tym celu deklarujemy odpowiednią zmienną globalną:
 
C/C++
UINT WM_FINDREPLACE;

Oczywiście nazwa jest całkowicie dowolna; użyłem takiej formy (duże litery) tylko po to, żeby kojarzyło się z identyfikatorem komunikatu. Teraz możemy zarejestrować nasz komunikat:
 
C/C++
WM_FINDREPLACE = RegisterWindowMessage( FINDMSGSTRING );

Stała FINDMSGSTRING zawiera stringa komunikatu, który jest wymagany jako argument funkcji RegisterMessageWindow. Po zarejstrowaniu otrzymujemy identyfikator komunikatu i dzięki temu możemy go używać podobnie jak wszystkich innych komunikatów. Jego parametr wParam jest zawsze równy 0, natomiast lParam zawiera wskaźnik do struktury FINDREPLACE (patrz wyżej), która z kolei zawiera wszystko to, co nam będzie potrzebne w przypadku odebrania takiego komunikatu.
 
Teraz możemy już wywołać nasz dialog. Jest to wyjątkowo prosta czynność:
 
C/C++
FindText( & fr );

Niby wszystko pięknie, poza tym, że oczywiście wyszukiwanie nie działa. Pewnie się już domyślasz, o co chodzi - oczywiście musimy obsłużyć komunikat WM_FINDREPLACE, który sobie zarejestrowaliśmy. Otrzymamy ten komunikat, jeśli użytkownik kliknie któryś z przycisków dialogu wyszukiwania bądź zamknie go. Parametr lParam tego komunikatu będzie zawierał wskaźnik na strukturę FINDREPLACE. Z niej dowiemy się wszystkiego, co jest nam teraz potrzebne: który przycisk został kliknięty, jakie checkbox-y z opcjami wyszukiwania są zaznaczone, jaki tekst wpisał użytkownik.
 
Zacznijmy od kliknięcia na przycisku "Znajdź następny". I tutaj niemiła niespodzianka: nie możemy po prostu dodać kolejnego case'a do naszej "głównej" konstrukcji switch, by obsłużyć nasz komunikat, ponieważ musimy przechowywać go w zmiennej, a etykiety case muszą być stałymi. Nie ma rady, musimy dopisać if-a pod etykietą default. Teraz będzie ona wyglądała mniej więcej tak:
 
C/C++
default:
{
   
if( message == WM_FINDREPLACE )
   
{
       
    }
   
else
   
{
       
return DefWindowProc( hWnd, message, wParam, lParam );
   
}
}

Poza tym drobiazgiem nie różni się to od obsługi "zwykłego" komunikatu. Teraz możemy sobie dopisać kod, który w reakcji na kliknięcie "Znajdź następny" pokaże wiadomość, że nic nie znaleziono (i nawet nie będzie to kłamstwo, bo nic nie próbowaliśmy szukać ;-)).
 
C/C++
default:
{
   
if( message == WM_FINDREPLACE )
   
{
       
FINDREPLACE * fr =( FINDREPLACE * ) lParam;
       
       
if( fr->Flags & FR_FINDNEXT )
       
{
           
MessageBox( hWnd, "Nic nie znaleziono.", NULL, MB_ICONINFORMATION );
       
}
    }
   
else
   
{
       
return DefWindowProc( hWnd, message, wParam, lParam );
   
}
}

W taki sam sposób, jeśli chcemy, możemy obsłużyć pozostałe przyciski, tj. "Zamień" (FR_REPLACE), "Zamień wszystko" (FR_REPLACEALL) oraz zamknięcie dialogu (FR_DIALOGTERM). Oczywiście, aby był jakikolwiek pożytek z dialogu, musimy jeszcze napisać właściwy kod, który coś wyszukuje, zamiast z góry mówić, że nie udało się nic znaleźć ;-). Ale to już oczywiście praca domowa, a tymczasem zajmiemy się zamianą dialogu na dialog zamieniania oraz wprowadzimy małą poprawkę do głównej pętli programu.
 
Ponieważ nasz dialog jest niemodalny, system nie zapewnia mu domyślej obsługi klawiatury - kombinacji Alt+litera, skakania po kontrolkach za pomocą Tab itp. Możemy to naprawić, wywołując IsDialogMessage w pętli głównej - jeśli nie pamiętasz, jak to zrobić, cofnij się do odcinka poświęconego dialogom niemodalnym.
 
Teraz zamiana tekstu. Będziemy oczywiście wywoływać inną funkcję - ReplaceText() zamiast FindText(), ale to nie wszystko. Musimy jeszcze podać drugi bufor na tekst do zamiany. Podobnie jak bufor do wyszukiwania, powinien być tablicą globalną lub dynamicznie zaalokowaną pamięcią:
 
C/C++
CHAR bufor2[ 80 ];

Wpisujemy adres tego bufora i jego rozmiar do struktury FINDREPLACE i próbujemy wywołać funkcję:
 
C/C++
FINDREPLACE fr;
ZeroMemory( & fr, sizeof( FINDREPLACE ) );
fr.lStructSize = sizeof( FINDREPLACE );
fr.hwndOwner = hwnd;
fr.lpstrFindWhat = bufor;
fr.wFindWhatLen = 80;
fr.lpstrReplaceWith = bufor2; ///
fr.wReplaceWithLen = 80; ///

lstrcpy( bufor, "tekst" );
lstrcpy( bufor2, "inny tekst" ); ///

ReplaceText( & fr );

Wszystko w porządku, dialog nam się rozrósł, mamy dodatkowe przyciski i pole, w którym można wpisać tekst, którym zastępujemy to, co wyszukaliśmy.
 
Warto jeszcze wiedzieć, że mamy wpływ na to, jakie opcje wyszukiwania/zamiany będą w dialogu. Możemy schować lub wyłączyć tymczasowo lub na dobre te opcje, których nie chcemy. Jeśli na przykład nie chce nam się pisać obsługi wyszukiwania z uwzględnianiem wielkości liter i wyszukiwania całych słów, możemy wywalić obydwa te pola z okienka. Jeśli jesteśmy jeszcze bardziej leniwi, możemy też schować przyciski radiowe do określania kierunku wyszukiwania (góra/dół). Dla wprawy schowajmy niektóre, a niektóre wyłączmy:
 
C/C++
fr.Flags = FR_HIDEUPDOWN | FR_NOMATCHCASE | FR_HIDEWHOLEWORD;

Aby sprawdzić, które z checkbox-ów zaznaczył użytkownik, sprawdzamy w obsłudze naszego komunikatu WM_FINDREPLACE, czy odpowiednie flagi w strukturze FINDREPLACE są ustawione: FR_UPDOWN, FR_MATCHCASE, FR_WHOLEWORD.
 
Jest jeszcze parę innych flag, ale to już dalszy ciąg pracy domowej :-). Umiemy już pokazać dialog, wybrać dostępne przyciski i opcje i obsłużyć wszelkie akcje użytkownika w dialogu wyszukiwania. Jak już powiedzieliśmy, realizacja samego wyszukiwania należy już do nas, ale znalezienie przykładowego kodu w internecie (albo po prostu napisanie go) nie powinno być już trudne.
Poprzedni dokument Następny dokument
Okna dialogowe, cz. 4 Okna dialogowe, cz. 6