arrowHome arrow Podstawy arrow Edytor tekstu cz. III - podstawowe operacje na tekście Saturday, 11 September 2010  



 
Google
Web winapi.org
Main Menu
Home
News
FAQ
Links
Download
Kontakt
FORUM

Artykuły
Podstawy
GDI i Multimedia
Kontrolki
inne
Winapi + asm
WinSock
Soft
Login Form
Login

Hasło

Zapamiętaj mnie
Nie pamiętasz hasła?
Nie masz konta? Załóż je sobie
Edytor tekstu cz. III - podstawowe operacje na tekście Drukuj E-mail
Oceny: / 20
KiepskiBardzo dobry 
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) ;
                   
if(ilosc_wszystkich > numer_wiersza)
{
  int pos = SendMessage(hEdit, EM_LINEINDEX, numer_wiersza, 0) ;
  SendMessage(hEdit, EM_SETSEL, pos, pos) ;
  SetFocus(hEdit) ;
  EndDialog(hDlg, LOWORD(wParam)) ;
}  
else
{
  MessageBox(hDlg, "Podany wiersz nie istnieje!!!nPodaj mniejszy...", "WARNING", MB_OK | MB_ICONWARNING) ;
}   

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:

SendMessage(hRichEdit, EM_EXLIMITTEXT, (WPARAM) 0, (LPARAM) 500000) ;

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:

typedef struct _textrange { 
    CHARRANGE chrg;
    LPSTR lpstrText;
} TEXTRANGE;

Przykładowe wysłanie komunikatu może wyglądać tak:

TEXTRANGE txtr ;

SendMessage(hRichEdit, EM_GETTEXTRANGE, (WPARAM) 0, (LPARAM) (TEXTRANGE *) txtr ;

txtr.chrg.cpMin... ; //przedział
txtr.chrg.cpMax... ;
txtr.lpstrText... ;  //tekst

Myślę, że wszystko powinno być jasne. Czasami jednak będziesz chciał pobrać tekst, który user sobie zaznaczył, żaden problem:

char zaznaczone[255] ;
SendMessage(hRichEdit, EM_GETSELTEXT, (WPARAM) 0, (LPARAM) (LPSTR) zaznaczone) ;

Używamy EM_GETSELTEXT i po kłopocie ;)

5. Wstawianie tekstu.

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:

EM_REPLACESEL 
fCanUndo = (BOOL) wParam ;
lpszReplace = (LPCTSTR) lParam ;

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.

6. Cofanie, powtarzanie, wklejanie...dostosowanie menu.

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:

WM_INITMENUPOPUP 
hmenuPopup = (HMENU) wParam;         // uchwyt menu
uPos = (UINT) LOWORD(lParam);        // numer klikanego podmenu
fSystemMenu = (BOOL) HIWORD(lParam); // flagi (nieistotne ;)

Jak się domyślasz musisz dodać obsługę tego zdarzenia do procedury obsługi okna. Teraz popatrz na poniższy przykład:

//----
case WM_INITMENUPOPUP:
 if(LOWORD(lParam) == 1)
        {
    if(SendMessage(hEdit, EM_CANUNDO, (WPARAM) 0, (LPARAM) 0))
           {
             EnableMenuItem((HMENU) wParam, ID_MCOFNIJ, MF_BYCOMMAND | MF_ENABLED) ;
           }   
           else
           {
             EnableMenuItem((HMENU) wParam, ID_MCOFNIJ, MF_BYCOMMAND | MF_GRAYED) ;
           }  
           
 }
 break ;
//obsługa innych zdarzeń
//----

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:

//----gdzieś na górze
CHARRANGE chrg ;
//----
SendMessage(hEdit, EM_EXGETSEL, (WPARAM) 0, (LPARAM) (CHARRANGE *) &chrg) ;

if(chrg.cpMin == chrg.cpMax)
{
  EnableMenuItem((HMENU)wParam, ID_MKOPIUJ, MF_BYCOMMAND | MF_GRAYED) ;
}
//----

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:

SendMessage(hEdit, EM_SETMODIFY, (WPARAM) 0, (LPARAM) 0) ;
SendMessage(hEdit, EM_EMPTYUNDOBUFFER, (WPARAM) 0, (LPARAM) 0) ;

I to cała filozofia ;)

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:

EM_SETEVENTMASK 
wParam = 0;
lParam = (LPARAM) (DWORD) dwMask;

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:

SendMessage(hEdit, EM_SETEVENTMASK, (WPARAM) 0, (LPARAM) ENM_MOUSEEVENTS) ;

Najelepiej wysyłać tę wiadomość bezpośrednio po utowrzeniu kontrolki (richedita, rzecz jasna ;)

Teraz pokażę jak obsługiwać zadarzenie WM_NOTIFY - i znowy trochę kodu:

case WM_NOTIFY:
  switch (((NMHDR *)lParam)->code)
  {
     case EN_MSGFILTER:
        m = (MSGFILTER *) lParam ;
                       
        if(m->msg == WM_RBUTTONDOWN)
        {
           x = LOWORD(m->lParam);
           y = HIWORD(m->lParam);
           hMenu = LoadMenu(hInstance, "EDYTOR_MENU") ;
           hSub = GetSubMenu(hMenu, 1) ;
           pt.x = (LONG)x ;
           pt.y = (LONG)y ;
           ClientToScreen(hEdit, &pt) ;
           SendMessage(hwnd, WM_INITMENUPOPUP, (WPARAM) hMenu, (LPARAM) 1) ;
           TrackPopupMenu(hSub, TPM_LEFTALIGN, pt.x, pt.y, 0, hwnd, NULL) ;
                    DestroyMenu(hMenu) ;
        }   
        break ;  
  }   
break ;

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.

Przeto powiadam wam trzymajcie się i...powodzenia

 

Ostatnia aktualizacja ( Tuesday, 08 March 2005 )
< Poprzedni
Dodaj do ulubionych
Ustaw stronę startową
Ostatnio dodane
Popularne
 
top

www.winapi.org © 2003 - 2007