PDA

View Full Version : [C++] Intercettare movimento cursore in un editor


ArtificialBoy
05-02-2007, 23:50
Ho un editor di testo. In realtà è quello che si ottiene nell'ambiente Visual C++ 6 quando con il wizard si crea una nuova applicazione MFC single document, scegliendo la CEditView come classe base per la vista.
A questo editor ho aggiunto delle funzioni accessibili da menù per fare delle elaborazioni sui file di testo aperti. Ma questo non è importante ora.

Ciò che vorrei sapere è come poter visualizzare il numero di linea (e magari di colonna) corrispondenti alla posizione del cursore, nella status bar in basso (come nel notepad, tanto per capirci).
Diciamo che a ricavare il numero di riga dalla posizione del cursore, ci sono più o meno riuscito. Però questo calcolo dovrebbe essere fatto ad ogni spostamento del cursore, quindi mi servirebbe intercettare l'evento di spostamento del cursore. Infine dovrei mandare l'informazione alla status bar (ma penso che a questo ci penserò dopo).

Spero di essermi spiegato bene, non saprei come meglio rendere il concetto, avendo poca esperienza di programmazione.

Grazie per eventuali suggerimenti.

cionci
06-02-2007, 10:21
Provo a ti faccio sapere...comunque con MFC non ti aspettare di poter fare grandi cose ;)

franklar
06-02-2007, 12:46
mi iscrivo, dato che la cosa interessa anche me :)

cionci
06-02-2007, 14:36
Mi sa che ho trovato un altro metodo...un attimo...

cionci
06-02-2007, 15:02
Ecco qua:

CEdit &edit = this->GetEditCtrl();

int startchr, endchr;
edit.GetSel(startchr, endchr);


edit.LineFromChar(endchr) //per la riga
endchr-edit.LineIndex(edit.LineFromChar(endchr)) //per la colonna

ArtificialBoy
06-02-2007, 17:11
Uhm, cionci grazie. Purtroppo non è quello che mi serve. ^^

Ti spiego. Il numero di linea sono riuscito a ricavarmelo, il problema è che quando muovi il cursore con i tasti freccia, con il mouse o in altro modo, ogni volta va ricalcolato il numero di linea. Quello che mi serve appunto, come dicevo nel primo post, è un evento (appartenente al ceditview, al cedit, al cframe, al cwnd o quello che è) che risponda agli spostamenti del cursore, in modo da richiamare ogni volta le operazioni che hai detto anche tu.

Ho visto che nel cwnd ci sono metodi, tipo OnKeyUp, che forse potrebbero essere associati alla pressione dei tasti freccia, ma sinceramente non capisco bene come funzionano (per l'esatezza nn capisco gli argomenti) e non so se fanno al caso mio.

Comunque grazie ancora per esserti sbattuto. Se dovessi avere qualche altra idea sono tutto orecchie. :D

vizzz
06-02-2007, 18:25
void CTuaView::OnMouseMove(UINT nFlags, CPoint point)
{
// TODO: Add your message handler code here and/or call default
POINT pt;
GetCursorPos( &pt );
TRACE2("pos: x=%d - y=%d\n", pt.x, pt.y);
CView::OnMouseMove(nFlags, point);
}
in class wizard cerca WM_MOUSEMOVE, doppio click e ti crea l'evento.
ciao

ArtificialBoy
06-02-2007, 18:56
Grazie anche a te per l'intervento. Purtroppo muovere il mouse sulla finestra non equivale a muovere il cursore. Dovrei clickare col sinistro per spostare il cursore altrove. Credo ci sia anche l'evento per il click sinistro ma non ne capisco gli arogmenti come per OnKeyUp.
Inoltre dovrei comunque implementare altri eventi legati alla frecce direzionali o alla pressione del tasto Invio che manda a capo e sposta il cursore o anche dei tasti Home e Fine.
Magari però esiste un modo più semplcie di tutto questo e nella mia ignoranza non so vederlo. °°

vizzz
06-02-2007, 19:03
sono un po fuso ho letto cursore e ho pensato al mouse :D
cerco ancora...

vizzz
06-02-2007, 19:12
ok, ho la mente lucida ora:

void CTuaView::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
{
// TODO: Add your message handler code here and/or call default

if (nChar == VK_PRIOR)
TRACE0("pgup\n");
else if (nChar == VK_NEXT)
TRACE0("pgdown\n");
else if (nChar == VK_UP)
TRACE0("arrowup\n");
else if (nChar == VK_DOWN)
TRACE0("arrowdown\n");
else if (nChar == VK_RETURN)
TRACE0("return\n");

CView::OnKeyDown(nChar, nRepCnt, nFlags);
}

cionci
06-02-2007, 19:17
Tanto vale aggiornarlo per ogni tasto rilasciato e per ogni click.

ArtificialBoy
06-02-2007, 19:41
Uh, ecco, allora funziona così quell'evento. °° A guardarlo così codificato mi pare debba essere quello che mi serve, dunque domani proverò a implementarlo.

Grazie. :)


Se tutto va bene, poi magari vi chiedo come mandare le info alla status bar. Ovviamente prima ci proverò da solo.

cionci
06-02-2007, 19:54
Mi sa che ti serve OnKeyUp anche perchè altrimenti rilevi il valore prima dell'inserimento del carattere

vizzz
06-02-2007, 19:59
Mi sa che ti serve OnKeyUp anche perchè altrimenti rilevi il valore prima dell'inserimento del carattere
giusto
per la status bar guardati in giro, esistono molti esempi in rete
i miei siti di riferimento sono: codeproject e codeguru

edit:
http://msdn2.microsoft.com/en-us/library/yddt1ty6(VS.80).aspx
per i parametri della funzione onkeyup

ArtificialBoy
06-02-2007, 20:09
Sì, avevo pensato anche io di usare il KeyUp.

Terrò presente quei due siti. Grazie. E comunque aggiornerò sui risultati.

cionci
06-02-2007, 20:51
Comunque non basta quell'evento e nemmeno quelli per il mouse a meno di gestire una combinazione di eventi...in ogni caso ci deve essere qualcos'altro...un messaggio magari che viene lanciato per qualsiasi modifica fatta nell'edit box...compreso il cambiamento di posizione del cursore...

cionci
06-02-2007, 20:56
Allora...il migliore è:

ON_CONTROL_REFLECT_EX(EN_CHANGE, OnEditChange)

In combinazione con WM_KEYUP per prendere i tasti freccia...

Il problema ora è il mouse...

ArtificialBoy
06-02-2007, 21:03
Effettivamente se io faccio un paste da menù o da tasto destro dle mouse, non intercetterebbe lo spostamento. Ora guardo quest'altro evento che mi dici.
Comunque, prima di chiedere qui, non riuscivo a capire come funzionasse l'OnKeyUp perché non ci pensavo che venisse chiamato dal framework e quindi i parametri non dovessi passarli io. :D Poi vedendo il codice scritto, ho capito quanto ero fesso ed infatti i parametri non mi serve affatto usarli.


EDIT: cmq a me sembra strano che nn esista un metodo un po' più rapido per fare il tutto °°

cionci
07-02-2007, 15:28
L'evento che ti ho detto serve anche per catturare le ripetizioni dei tasti...se tenevi premuto un tasto WM_KEYUP non aggiornava il conteggio.

Per aggiornare la status bar ho aggiunto un indicator e poi con un messaggio utente (con costante WM_USER + 1) ho usato la SendMessage per notificarlo dal CEditView al CFrameWnd.

ArtificialBoy
07-02-2007, 16:14
Allora...il migliore è:

ON_CONTROL_REFLECT_EX(EN_CHANGE, OnEditChange)

In combinazione con WM_KEYUP per prendere i tasti freccia...

Il problema ora è il mouse...


Sinceramente non ho capito bene come funziona sta cosa dei messaggi riflessi. °°
Effettivamente in questo modo mi intercetta eventuali paste da menù e ripetizioni di tasti, solo che non mi chiama più il SaveModified() quando chiudo l'editor o anche solo i ldocumento. °°

cionci
07-02-2007, 18:23
Ovviamente devi inoltrare nuovamente la chiamata alla classe base in fondo al tuo metodo:

return CEditView::OnEditChange();

cionci
07-02-2007, 18:25
Io l'ho fatta così:
BOOL CMioEditView::OnEditChange()
{
CEdit &edit = GetEditCtrl();

int startchr, endchr;
edit.GetSel(startchr, endchr);

AfxGetMainWnd()->SendMessage(WM_USER + 1, edit.LineFromChar(endchr), endchr-edit.LineIndex(edit.LineFromChar(endchr)));

return CEditView::OnEditChange();
}

cionci
07-02-2007, 18:28
Comunque questa non va bene:

edit.GetSel(startchr, endchr);

In quanto ritorna in endchr sempre la posizione maggiore, anche se si fa una selezione all'indietro...

Forse ti converrebbe provare con un altro controllo, ad esempio il RichEdit...

cionci
07-02-2007, 19:05
Ho trovato un altro modo, ma ha un bug...

void CMioEditView::aggiornaPosizione(void)
{
CEdit &edit = this->GetEditCtrl();

int chr = edit.CharFromPos(edit.GetCaretPos());

int row = chr >> 16;
int column = (chr & 0x0FFFF) - edit.LineIndex(edit.LineFromChar(chr & 0x0FFFF));

AfxGetMainWnd()->SendMessage(WM_USER + 1, row, column);
}

Spesso succede che la posizione chr (proprio il valore su cui mi baso per avere la posizione) non venga aggiornata dopo il primo carattere di una nuova linea, ma al secondo viene aggiornata di 2...questo è un mistero, sembra quasi un bug di MFC.

ArtificialBoy
07-02-2007, 21:07
Non preoccuparti per il numero di linea, quello riesco a ricavarmelo. Lo faccio con il metodo CharFromPos che mette nella word bassa il char index e in quelal alta il line index. Ho già testato e funziona.

Quello che mi preme capire ora è questo:

Hai detto che in fondo al mio metodo OnEditChange devo richiamare quello della classe base. Quando io aggiungo la gestione del segnale tramite il class wizard lui mi aggiunge il metodo OnChange associandolo al segnale EN_CHANGE e facendo quella cosa del REFLECT.
Ora mi chiedo se il metodo nella classe base si chiami esattamente OnChange o è solo un nome che dà lui al momento associandolo al segnale suddetto. Per esempio non capisco perché da te risulta OnEditChange. A me dice che questi metodi non esistono. °°

Comunque ti ringrazio ancora per l'interessamento.


EDIT: ops, non mi ero accorto che nell'ultimo post hai parlato proprio del metodo che stavo usando e ho descritto ^^

cionci
07-02-2007, 21:39
ON_CONTROL_REFLECT_EX(EN_CHANGE, OnEditChange)

Guarda il message map, deve essere questo sopra e non ON_EN_CHANGE...io l'ho aggiunto a mano.

Edit: comunque io andrei su un RichEdit al posto del CEditView

ArtificialBoy
07-02-2007, 22:00
C'è scritto proprio quello che hai scritto tu. Ma quando con il class wizard aggiungo la gestione del segnale lui mi chiede di scegliere il nome per la funzione e mi propone OnChange. Ne deduco che il nome sia impostabile a piacere.
Ma tu lo hai fatto col CRichEditView? Provando col richedit non mi risulta nemmeno il segnale en_change nel class wizard. °°

cionci
07-02-2007, 22:06
Nono...con il CEditView...
Il nome del metodo è assolutamente a piacere, l'importante è richiamare il metodo giusto della classe base.

ArtificialBoy
07-02-2007, 22:23
Boh, continuerò a cercare in questa direzione, ma al momento non riesco a capire quale sia il metodo della classe base da richiamare. :\

cionci
07-02-2007, 22:27
L'avevo scritto sopra ;)

return CEditView::OnEditChange();

ArtificialBoy
07-02-2007, 22:49
Sì, ho letto tutto quello che hai scritto ma il mio problema è che mi dice che non esiste quel metodo per il CEditView. Guarda non so che dirti.

ArtificialBoy
08-02-2007, 00:59
Siccome questo OnEditChange non riesco ad applicarlo, che ne dici se vado direttamente io a settare la modified flag del documento tramite:

this->GetDocument()->SetModifiedFlag()

Dovrebbe essere ciò che fa la parent windows del controllo quando gli arriva il messaggio di en_change. Mi pare funzionare, ma non so se ci sono altre implicazioni. Tu che dici?

cionci
08-02-2007, 08:55
Ci sta anche che sia una versione diversa di MFC, io sto usando VS 2003.
In alternativa prova ON_EN_CHANGE(EN_CHANGE, OnChange), ma non mi sembra che faccia lo stesso effetto. O al limite gestisci l'altro messaggio e richiama OnChange della classe base, se c'è.

ArtificialBoy
08-02-2007, 21:38
Dev'essere così perché anche nel msdn relativo a visual 6 non ne parla. Dice invece che quando si riflette un segnale sul controllo, si potrebbe poi usare il send message per avvisare la parent window, ma non ci riesco uguale.
L'unica cosa che mi è riuscita è quella che ho detto prima, cioè settare direttamente io la modified flag del documento.