Edytor tekstu cz. III - podstawowe operacje na tekście
Nadesłał Codeguy
Tuesday, 01 March 2005
Na początku chciałbym przeprosić, że tak długo nic nie pisałem...sorki ;)
Po przeczytaniu tego arta będziecie mogli wyposażyć swoje edytory o nowe, bardzo przydatne funkcje. Postarałem się przystępnie opisać zamienianie, pobieranie, zaznaczanie, cofanie, kopiowanie, wklejanie, wycinanie, wstawianie tekstu, zwiekszanie ilości przechowywanych danych w naszej pięknej kontrolce i...przechodzenie do wybranej linii.
1. Przechodzenie do wybranej linii.
Zaczęliśmy od końca, ale to tak na rozgrzewkę. Od razu mówię, że nie jest to trudne do zrealizowania ;) Jedyne co nam potrzebne to nowe okno dialogowe, które wypadałoby wykonać (na stronie jest art, który zresztą sporo osób już czytało). Później wystarczy obsłużyć klika komunikatów. Cały proces obrazuje poniższy kawałek kodu:
int numer_wiersza = GetDlgItemInt(hDlg, IDC_ENRWIERSZA, NULL, FALSE) ; int ilosc_wszystkich = (int) SendMessage(hEdit, EM_GETLINECOUNT, 0, 0) ;
Najpierw pobieramy sobie do zmiennej liczbowej numer wiersza, który poda użytkownik. Można to zrealizować za pomocą GetDlgItemInt - działa tak samo jak GetDlgItemText z małą różnicą :P. Następnie należy pobrać ilość wszystkich wierszy jakie mamy w naszym richedicie. Służy do tego komunikat EM_GETLINECOUNT, który nie przyjmuje żadnych wartości w polach wParam i lParam. Potem aby uniknąć błędów sprawdź czy user nie podał czasem zbyt wielkiej liczby...a jeśli wszystko sie zgadza to wysyłamy EM_LINEINDEX i otrzymujemy odpowiednie współrzędne za pomocą, których EM_SETSEL przesuwa kursor w nowe miejsce..ufff
2. Regulacja pojemności.
Jak już kiedyś napisałem richedit pozwala przechowywać duuużo więcej tekstu niż zwykły edit. Zamiast EM_SETLIMITTEXT używamy EM_EXLIMITTEXT. Oto przykładzik:
Jak widzisz w lParam podajemy ilość tekstu. Domyślną wartością jest 32kb. Jeśli pole lParam przyjmie wartość zerową ustawia nam właśnie 32kb (czyli domyślna wielkość danych). Dlaczego nie działa EM_SETLIMITTEXT? Odpowiedzią jest pojemność, ten komunikat może ustawić maks 32,766 bajtów dla jednowierszowej kontrolki edit oraz 65,535 bajtów jeśli będzie ze stylem ES_MULITLINE...
3. Zaznaczanie.
Jest na to kilka sposobów, a właściwie kilka rodzajów komunikatów, które nieznacznie się różnią. Pierwszy to:
EM_SETSEL wParam = (WPARAM) (INT) nStart; // pierwszy znak lParam = (LPARAM) (INT) nEnd; // ostatni znak
Po prostu wysyłamy ten komunikat do kontrolki (richedit, rzecz jasna), a w polach wParam i lParam podajemy zakres. Przykładowo jeśli w nStart wpiszesz 12, a w nEnd 33 to zostaną zaznaczone znaki od pozycji 12 do 33, logiczne prawda..??
Czasami zachodzi potrzeba pobrania pozycji zaznaczenia. W tym celu skorzystaj z komunikatu EM_GETSEL bez żadnych wartośći w wParam i lParam. Funkcja zwróci wartość 32 bitową w której młodsze słowo (low) obrazuje pozycje startową, zaś starsze (high) końcową. Do pobrania tych wartośći użyj makra LOWORD i HIWORD, albo czytaj dalej...
Teraz opisze małą strukturkę, która przydaje się często podczas programowania richedita, mianowicie CHARRANGE. Oto wyciąg z helpa:
typedef struct _charrange { LONG cpMin; LONG cpMax; } CHARRANGE;
cpMin zawiera (jak się pewnie domyślasz ;) pozycje pierwszego znaku (chodzi o zaznaczany tekst, jeśli jesteś nie w temacie..hehe), a cpMax ostatniego. Powyższe komunikaty są używane głównie w edicie. Oczywiście nic nie stoi na przeszkodzie stosować je w naszym richedicie. Dla niego jednak (tak samo jak w przypadku EM_SETLIMITTEXT) przypisano takie specjalne ;) Po co? Głównie dotyczy to miejsca jakie przechowuje nasza kontrolka. Przykładowo komunikat EM_EXGETSEL zaleca się stosować w przypadku gdy user zaznaczył ok. 64kb danych. Jak zapewne zauważyłeś często nazwy komunikatów dla richedita mają przedrostek EX, tak też jest w tym przypadku: EM_EXGETSEL i EM_EXSETSEL używamy tak samo z tą różnicą, że pole wParam zostawiamy puste, a w lParam podajemy wskaźnik do strukturki CHARRANGE, która wcześniej sobie wypełnimy ;)
4. Pobieranie tekstu.
Do pobierania tekstu można się posługiwać funkcjami typu GetWindowText, ale nie trzeba ;) Winapi, a dokładniej biblioteka richedita, oferuje nam komunikat EM_GETTEXTRANGE, który wypełni strukture CHARRANGE oraz zwróci wskaźnik do bufora zawierającego tekst. W celu połączenia tych dwóch zwranych wartośći storzono strukturę TEXTRANGE:
Do wstawiania tekstu służy komunikat o nazwie EM_REPLACESEL. Wstawia on tekst zarówno w przypadku jeśli tekst jest zaznaczony (wtedy go nadpisuje) oraz gdy w polu tekstowym miga sobie pojedynczy kursor ;) Oto kolejna mądrość helpa:
fCanUndo przyjmuje wartość typu bool, czyli TRUE (jeśli pozwalamy na cofnięcie tej operacji wstawiania) oraz FALSE (w przeciwnym wypadku). lpszReplace to wskaźnik do bufora zawierającego tekst, który ma zostać wstawiony.
Wykorzystując tę wiadomość możesz z powodzeniem wyposażyć swój edytor w funkcje, która wstawia bieżącą datę i godzinę (tak jak w notatnik, czy wordzie). Jeśli chcesz dowiedzieć się wiecej o tych systemowych informacjach, przeczytaj art w dziale inne o..CZASIE.
Prawie każdy edytor tekstowy umożliwia właśnie takie operacje. Nie jest to skomplikowanie i pewnie nie będziesz zaskoczony, jeśli po raz kolejny użyjemy komunikatów. Oto małe zestawienie:
Komunikat
Operacja z tekstem
EM_UNDO
cofa
EM_REDO
powtarza
WM_PASTE
wkleja
WM_CUT
wycina
WM_COPY
kopiuje
WM_CLEAR
usuwa
Wszystkie z powyższych komunikatów nie przyjmują żadnych wartości w polach wParam i lParam. Teraz wystarczy stworzyć odpowiednie pozycje menu i do każdego przypisać zdarzenie, które wyśle na przykład komunikat EM_UNDO do naszego richedita.
Nie wiem czy zróciłeś uwagę, ale edytory tekstu w przypadku, gdy nie da sie cofnąć, potwórzyć, wkleić, skopiować...tekstu dezaktywuje te części menu. Z pomocą przyjdą nam kolejne...taak...komunikaty ;)
Komunikat
Odpowiada na pytanie:
EM_CANUNDO
czy można cofnąć
EM_CANREDO
czy można powtórzyć
EM_CANPASTE
czy jest coś do wklejenia
Po wysłaniu takich wiadomości (nie przyjmują wartości w polach w- i lParam) funkcja SendMessage zwróci nam odpowiednią wartość jeśli da się wykonać daną akcje (TRUE lub FALSE). Nasuwa się pytanie: w jakim momecie mam wysłać te komunikaty i dezaktywować pozycje menu? Odpowiedzią jest:
Na początku sprawdzamy pole lParam (młodsze słowo ma zapisany numer aktualnie wybieranego podmenu), jeśli jest ono równe 1 (w moim przypadku podmenu edycja) to wysyłam komunikat sprawdzający (EM_CANUNDO) i w zależności od zwróconej wartości wykonuje odpowiednie akcje. Funkcja EnableMenuItem w pierwszym parametrze przyjmuje uchwyt do menu (jest ono zapisane w polu wParam), następnie podajesz indentyfikator tej pozycji menu, no i falgi (MF_ENABLED - aktywny, MF_GRAYED - niekatywny). Z pewnością poradzisz sobie z wysyłaniem innych wiadomości. Pozostaje jeszcze sprawdzanie czy można skopiować, wyciąć, wyczyścić tekst. Na początku należy ustalić, kiedy te pozycje mają być aktywne? Kiedy już dojdziesz do wniosku, że kiedy tekst jest zaznaczony te pozycje mają być dostępne ;)...możesz spojrzeć na przykładzik:
Najpierw pobieramy bieżące zanaczenie (EM_EXGETSEL, opisywałem gdzieś na górze..;) i sprawdzamy czy cpMin jest równe cpMax. Jeśli tak jest to znaczy, że nie ma określonego przedziału tekst, a więc nie został zaznaczony =) Dalej mamy EnableMenuItem, ale to już wałkowaliśmy. Problem, który się nasuwa - po włączeniu programu menu cofnij cały czas jest aktywne, dlaczego? Ponieważ najpierw trzeba wyczyścić flagi modyfikacji oraz bufor cofania, słyżą do tego EM_SETMODIFY oraz EM_EMPTYUNDOBUFFER:
Nie omówiliśmy jeszcze sprawy menu do końca..hehe. Zapewne widziałeś programy, w których po kliknięciu w pole tekstowe wyświetla się podmenu, które ma różne opcje (przykładem może być Dev-Cpp). Aby udostępnić taką opcje należy wysłać komuniakt EM_SETEVENTMASK:
Zapewne cieakawi cię pole dwMask (to nie wszystkie możliwości):
Pole dwMask
Wysyła powiadomienie
ENM_CHANGE
EN_CHANGE (czyli coś się zmieniło)
ENM_DROPFILES
EN_DROPFILES (upuszczono plik)
ENM_KEYEVENTS
EN_MSGFILTER dla zdarzeń klawiatury
ENM_MOUSEEVENTS
EN_MSGFILTER dla zdarzeń myszki
ENM_OBJECTPOSITIONS
EN_OBJECTPOSITIONS
ENM_SCROLL
EN_HSCROLL i EN_VSCROLL
ENM_SCROLLEVENTS
EN_MSGFILTER dla zdarzeń kółka myszki
ENM_SELCHANGE
EN_SELCHANGE
Interesujące prawda?? Po co to wszystko?? Jest potrzebne, aby richedit wysyłał nam informacje o zdarzeniach określonych w dwMask, tak zwanych nofify messages. Nas interesuje kliknięcie myszy, a dokładniej jej prawego przycisku, to własnie w tym momencie wyświetlimy menu:
Jak widać komunikat WM_NOTIFY korzysta głównie z pola lParam, gdzie upchane zostają ciekawe informacje (w wParam jest identyfikator okna, który wysłał tę wiadomość). Na początku rzutujemy wartość lParam na wskaźnik dla struktury typu NMHRD, to właśnie w niej (a konkretnie w polu code) są zawarte informacje na temat rodzaju komunikatu, który aktualnie został wysłany. Za pomocą instrukcji switch wyszczególniamy sobie wszystkie wiadomości, które chcemy obsługiwać...w naszym wypadku tylko jedną - EN_MGSFILTER. Następnie przypisujemy zmiennej MSGFILTER (musisz ją sobie wcześniej zadeklarować np. przed główną instrukcją switch) to co mamy w lParam rzutując oczywiście na odpowiedni typ. Teraz otrzymaliśmy informacje o zdarzeniu, które zaszło. Sprawdzamy czy jest nim WM_RBUTTONDOWN i wyświetlamy menu. Pozostają jeszcze te nieudolne współrzędne. Pewnie zastanawiasz się dlaczego znowu korzystamy z lParam...zdarzenie WM_RBUTTONDOWN przypisuje współrzędne kursora właśnie do pola lParam, a dokładnie do jego młodszego i starszego słowa - stąd wykorzystanie makr LOWORD i HIWORD. Pozostaje mi się jeszcze wytłumaczyć z wysyłanego komunikaty WM_INITMENUPOPUP. Wcześniej w obsłudze tego zdarzenia określiliśmy, które z pozycji mają być aktywne, teraz, żeby nie powtarzać kodu, który już przecież mamy, odnosimy się do niego poprzez wysłanie wiadomości.
Tak, wiem, że to wszystko trochę zawiłe...ale cóż, kto powiedział, że programowanie jest proste...hehe.
Jeśli masz jakieś problemy z tworzeniem menu odsyłam do arta w dziale podstawy, właśnie na ten temat...
5. Konec.
Możecie mieć pretensje, że nie opisałem tutaj wyszukiwania, ale niektórym naprawdę namieszałbym w głowie (o ile przed chwilą tego nie zrobiłem). Wyszukiwanie i zamienianie tekstu będzie w następnym arcie, który ukaże się naprawdę za niedługo ;) Z chęcią przyjmę każde uwagi i propozycje, pytania...na ftp jak zwykle mały przykład.