Artículo original: Creating Undo functionality in a Visual FoxPro TextBox
http://west-wind.com/weblog/posts/3296.aspx
Autor: Rick Strahl
Traducido por: Ana María Bisbé York
El cuadro de texto de Visual FoxPro no es precisamente un gran control como el que yo tengo en el Help Builder (http://www.west-wind.com/wwHelp/). Tuve que trabajarlo para hacer que funcione como si estuviera basado en un editor de textos que incluye formato. Pero al mismo tiempo no era capaz de encontrar una forma decente de sustitución. La mayoría de los controles ActiveX basados en textos son poco menos que un infierno (al menos en VFP) o son muy muy lentos si trata de enganchar algún evento COM a la clave al procesar, como necesito que haga el Help Builder.
En general el TextBox de VFP funciona bien, salvo en dos cosas:
- Existe un bug en el control que provoca que el control envuelva al original si existen avance de línea en un área específica del margen derecho. Puede causar avances de línea que sean "comidos" por el cuadro de texto con el texto que puede ser un salto súbito cuando el cuadro de texto es redimensionado u otros cuadros de texto entren fuera del margen. Esto se puede ver como un bug muy oscuro; pero si trabaja modificando grandes cantidades de textos se lo encontrará muy pronto. Según Calvin Hsia se corrige en el VFP 9.0 SP1...
- Comportamiento para Deshacer. El cuadro de texto de Visual FoxPro no tiene un comportamiento para Deshacer - el buffer para Deshacer se pierde cada vez que hay cualquier tipo de actualización del dato. Esto incluye el enlace al origen de datos (ControlSource), establecer el valor explícitamente, cambiar SelText o incluso pasar texto al control. Se limpia también si se oprime la tecla Tab y sale del control e inmediatamente regresa. Todo esto es realmente limitado y no es un comportamiento estándar.
Hasta la salida del SP1 no puedo hacer nada con el punto 1; pero he pensado que puedo controlar mi propio Deshacer con buffer en mi control TextBox personalizado. Mientras hablaba con Calvin en SoutWest Fox comenzamos a colocar un mejor comportamiento en el TextBox; pero sobre el comportamiento Deshacer está profundamente dentro del runtime de VFP y cambiar eso es algo como romper mucho del código que ya está. Por tanto no hay ayuda en este sentido. Entonces Calvin me sugirió... escribe el tuyo propio....
Lo primero que pensé - sí, bien. Controlar Deshacer buffer con código Fox es muy lento y ocuparía mucha memoria, porque hay que guardar el buffer entero del valor del control ya que los eventos InteractiveChange y ProgrammaticChange no brindan información de qué es lo que ha cambiado, entonces, no hay una forma fácil de capturar cuál de los cambios es el que hay que deshacer.
Después de pensarlo un poco, intenté de todas formas para observar cómo van a trabajar las cosas y entonces trabajé con estas variantes:
- Comportamiento opcional para Deshacer personalizado
- Deshacer que sobreviva a cambios por programa
- Deshacer que sobreviva al foco de otro control
- Deshacer que se limpie sólo con Refresh o un Clear explícito del buffer de Deshacer
- Rehacer que permita Deshacer lo deshecho
He aquí un código que se encarga de un control que controla un comportamiento Deshacer en mi clase TextBox.
DEFINE CLASS wwhtmleditbox AS editbox OLEDropMode = 1 FontName = "Tahoma" FontSize = 8 Alignment = 0 AllowTabs = .T. Height = 188 ScrollBars = 2 Width = 443 oundobuffer = .NULL. *-- La última vez, UndoBuffer fue actualizado en segundos. *-- Internamente el valor utilizado mantiene cada carácter *-- por tenerlo añadido al buffer de deshacer. nlastundoupdate = 0 *-- El indicador utilizado inhabilita los cambios de programación *-- en el bufer de deshacer. lundonoprogrammaticchange = .F. lundotracking = .F. oredobuffer = .NULL. Name = "wwhtmleditbox" PROCEDURE Init this.oUndoBuffer = CREATEOBJECT("wwNameValueCollection") this.oRedoBuffer = CREATEOBJECT("wwNameValueCollection") ENDPROC PROCEDURE undo IF THIS.lundotracking AND THIS.oUndoBuffer.Count > 0 THIS.lUndoNoProgrammaticChange = .T. this.oRedoBuffer.FastAdd(TRANSFORM(this.SelStart),this.Value) lcValue = this.oUndoBuffer.aItems[this.oUndoBuffer.Count,2] IF lcValue = this.Value AND this.oUndoBuffer.Count > 1 THIS.Value = this.oUndoBuffer.aItems[this.oUndoBuffer.Count-1,2] this.oUndoBuffer.Remove(this.oUndoBuffer.Count) ELSE this.Value = lcValue ENDIF this.SelStart = VAL(this.oUndoBuffer.aItems[this.oUndoBuffer.Count,1]) THIS.lUndoNoProgrammaticChange = .F. this.oUndoBuffer.Remove(this.oUndoBuffer.Count) ENDIF ENDPROC PROCEDURE redo IF THIS.lundotracking AND THIS.oRedoBuffer.Count > 0 THIS.lUndoNoProgrammaticChange = .T. this.Value = this.oRedoBuffer.aItems[this.oReDoBuffer.Count,2] this.SelStart = VAL(this.oRedoBuffer.aItems[this.oRedoBuffer.Count,1]) THIS.lUndoNoProgrammaticChange = .F. this.oRedoBuffer.Remove(this.oRedoBuffer.Count) ENDIF ENDPROC PROCEDURE KeyPress LPARAMETERS nKeyCode, nShiftAltCtrl *** No desea oprimir ESC para eliminar el contenido del campo. IF nKeyCode = 27 *** Se come la tecla, la ignora NODEFAULT ENDIF *** Ctrl-Z IF THIS.lUndoTracking IF nKeyCode = 26 *** Debe verificar la tecla Ctrl-<- la que tiene número 26 DECLARE INTEGER GetKeyState IN WIN32API INTEGER IF GetKeyState(0x25) > -1 THIS.Undo() NODEFAULT ENDIF ENDIF *** Rehacer Ctrl-R IF nKeyCode = 18 THIS.Redo() NODEFAULT ENDIF ENDIF ENDPROC PROCEDURE ProgrammaticChange IF THIS.lUndoTracking AND !THIS.lUndoNoProgrammaticChange this.oUndoBuffer.FastAdd(TRANSFORM(this.SelStart),this.Value) this.oRedoBuffer.Clear() ENDIF ENDPROC PROCEDURE InteractiveChange IF THIS.lUndoTracking *** Actualizar sólo en la segunda mitad del intervalo, *** entonces, si escribe varias letras va en lote IF SECONDS() - THIS.nLastUndoUpdate < 1 this.nLastUndoUpdate = SECONDS() RETURN ENDIF *** Solo deshace la escritura de la última palabra IF LASTKEY() = 32 OR LASTKEY() = 13 OR LASTKEY() = 44 OR ; LASTKEY() = 46 OR LASTKEY() = 9 this.oUndoBuffer.FastAdd(TRANSFORM(this.SelStart),this.Value) this.oRedoBuffer.Clear() this.nLastUndoUpdate = SECONDS() ENDIF ENDIF ENDPROC PROCEDURE Refresh IF THIS.lUndoTracking THIS.oUndobuffer.Clear() ENDIF ENDPROC ENDDEFINE
Vea que este código tiene una dependencia que no incluyo aquí. Estoy utilizando una clase de usuario llamada NameValueCollection la que guarda el nombre y el valor en una matriz. Puede cambiar este código para utilizar una Collection y un objeto que guarde el valor del buffer en la posición SelStart.
La idea es esencialmente que cada InteractiveChange y ProgrammaticChange son monitoreados y potencialmente escritos fuera del valor de la colección UndoBuffer. El método InteractiveChange se alterna de tal forma que solo escribe fuera el dato si el usuario en realidad no lo está escribiendo activamente y si el cursor está en el límite de una palabra. Esto reduce tremendamente la cantidad de valores que se guardan. Parece que otras aplicaciones como Word utilizan un proceder similar aunque el comportamiento de Word es algo diferente.
Vea además este código
IF nKeyCode = 26 *** Debe verificar Ctrl-<- el cual es 26 DECLARE INTEGER GetKeyState IN WIN32API INTEGER IF GetKeyState(0x25) > -1 THIS.Undo() NODEFAULT ENDIF ENDIF
En su infinita sabiduría alguien decidió que el mapa de código de teclas (KeyCode) fuera 26 para Ctrl-Z y para Ctrl-Flecha izquierda, por lo que no hay una forma sencilla de decir el carácter. En su lugar, tiene que hacer otra verificación sobre el KeyCode para ver si tiene una Flecha izquierda (0X25). Si devuelve -127 ó -128. Diga que esto es HACK; pero funciona. Fue simpático por unos minutos tener Ctrl+Flecha izquierda atado a la tecla Deshacer (Retroceso). Menos mal que decidí generar un comportamiento de Rehacer desde el inicio...
Ahora he colocado esto en el Help Builder y como yo trabajo con los documentos del Web Connection 5.0 (http://www.west-wind.com/wconnect/) utilizo mucho características con tópicos muy largos. Entonces, este comportamiento se ve muy bien. No he visto problemas de rendimiento ni por la memoria ni nada apreciable mientras escribo.
Puedo imaginar que puedo marcar este en mi lista de deseos para VFP que nunca iba a tener.
No hay comentarios. :
Publicar un comentario
Los comentarios son moderados, por lo que pueden demorar varias horas para su publicación.