Tutorial 33: Control RichEdit : Lo básico

Hay muchas peticiones de tutoriales sobre controles RichEdit. Finalmente creo que puedo escribir algo sobre ellos. Así que auí está el: primer tutorial sobre el control RichEdit, que describe aproximadamente todo lo que se conoce sobre él al menos lo que yo conozco. La cantidad de información es bastante grande así que lo he dividido en varias partes, este tutorial es la primera parte. En este tutorial, aprenderás que es un control RichEdit, como crearlo y como cargar/salvar datos a/de él.

Baja el ejemplo.

Teoría

Se puede pensar en un control richedit como en un control edit más potete. Nos provee de muchas características de las que nos gustaría disfrutar y que están ausentes en un control edit simple; por ejemplo, la habilidad de usar múltiples tamaños y tipos de fuentes, asi como niveles múltiplesS de Deshacer/rehacer, operaciones de busqueda de texto, objetos OLE incrustados [OLE-embedded], soporte arrastrar-y-soltar [drag-and-drop] de edicion, etc. En definitiva, el control richedit tiene muchas características interesantes, almacenadas en una DLL aparte. Esto también significa que para usarla no basta sólo con llamar a InitCommonControls como otros controles comunes. Tienes que llamar a LoadLibrary para cargar la DLL de richedit.

El problema es que hasta ahora hay tres versiones del control richedit. La versión 1, la 2, y la 3. La tabla de abajo muestra el nombre de la DLL para cada versión.

Nombre de la DLL Versión de RichEdit Nombre de clase del control Richedit
Riched32.dll 1.0 RICHEDIT
RichEd20.dll 2.0 RICHEDIT20A
RichEd20.dll 3.0 RICHEDIT20A

Puedes notar que la versiónes 2 y 3 usan el mismo nombre de DLL. ¿También usan el mismo nombre de clase! Esto puede suponer un problema, si quieres usar características especificas del richedit 3.0. Hasta ahora no he encontrado ningún método oficial para diferenciar entre la versión 2.0 y la 3.0. Sin embargo, hay uno posible que te mostrare después.

.data 
 RichEditDLL db "RichEd20.dll",0 
..... 
.data?
 hRichEditDLL dd ? 

.code 
invoke LoadLibrary,addr RichEditDLL 
mov hRichEditDLL,eax 
...... 
invoke FreeLibrary,hRichEditDLL

Cuando es cargada la DLL del control richedit, registra su clase de ventana RichEdit. Así que es imperativo que cargues la DLL antes de crear el control. Los nombres de clase del control richedit también son diferentes. En este momento puedes hacerte una pregunta: ¿Cómo puedo saber qué versión de richedit debo usar? Usar la última versión no siempre es lo mas apropiado si no requieres las características extras. La tabla de abajo muestra las características provistas con cada versión del control richedit.

CracteristicaVersión 1.0Versión 2.0Versión 3.0
Barra de selección x x x
Edición 'unicode'  x x
Formato caracter/parágrafo x x x
Búsqueda de texto Hacia adelante adelante/atras adelante/atras
OLE embeddingx x x
Edición Arrastrar y Soltar [Drag and drop] x x x
Deshacer/Rehacernivel-simple multi-nivel multi-nivel
Reconocimiento automático de URL  x x
Soporte para teclas aceleradoras  x x
Operación Windowless  x x
Salto de Línea CRLF CR only CR only (can emulate versión 1.0)
Aumento [Zoom]   x
Numeración de Parágrafos   x
Tabla simple  x
Estilos normal y cabeceras  x
Subrayado coloreado  x
Texto oculto   x
Fuente de encuadernacion   x

La tabla de arriba puede no ser del todo comprensible: sólo he incluido una lista de las características importantes.

Creando el control richedit

Tras cargar la DLL del richedit, puedes llmar a CreateWindowEx para crear el control. Puedes usar estilos del control edit y estilos comunes de ventanas en CreateWindowEx excepto ES_LOWERCASE, ES_UPPERCASE y ES_OEMCONVERT.

.const
	RichEditID equ 300
.data
	RichEditDLL db "RichEd20.dll",0
RichEditClass db "RichEdit20A",0 ... .data?
hRichEditDLL dd ? hwndRichEdit dd ? .code
..... invoke LoadLibrary,addr RichEditDLL mov hRichEditDLL,eax
invoke CreateWindowEx,0,addr RichEditClass,WS_VISIBLE or ES_MULTILINE or WS_CHILD or WS_VSCROLL or WS_HSCROLL, \ CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,hWnd,RichEditID,hInstance,0 mov hwndRichEdit,eax

Poniendo texto y color de fondo por defecto

Puedes tener problemas poniendo color al texto y al fondo en un control edit, pero este problema ha sido remediado con el control richedit. Para poner el color de fondo de un richedit, envía EM_SETBKGNDCOLOR al richedit. Este mensaje tiene la sintaxis siguiente.

wParam == opción de color. Un valor de cero en este parámetro especifica que Windows usa el valor del color en lParam como color de fondo. Si este valor es distinto de cero, Windows usa el color de fondo del sistema. Una vez que enviamos este mensaje para cambiar el color de fondo, debemos pasar cero en wParam.
lParam == especifica la estructura COLORREF del color que quieres poner si wParam es cero.

Por ejemplo, si queremos poner el color de fondo azul puro, debo emitir la línea siguiente:

 invoke SendMessage, hwndRichEdit, EM_SETBKGNDCOLOR, 0, 0FF0000h

Para poner el color del texto, el control richedit provee otro nuevo mensaje para esta tarea, EM_SETCHARFORMAT. Este mensaje controla el formato del texto de un rango de caracteres seleccionados o todo el texto del control. Este mensaje tiene la siguiente sintaxis:

wParam == opciones de formateo:

SCF_ALL
La operación afecta a todo el texto en el control.
SCF_SELECTION
La operación afecta sólo al texto seleccionado
SCF_WORD or SCF_SELECTION
Afecta a las palabras seleccionadas. Si la selección está vacía, por ejemplo, el cursor está junto a la palabra, la operación afecta a esa palabra, debe usarse la bandera [flag] SCF_WORD con SCF_SELECTION.

lParam == Puntero a una estructura CHARFORMAT o CHARFORMAT2 que especifica el formato de texto que sera aplicado CHARFORMAT2 esta disponible sólo para el richedit 2.0 y superior. Esto no significa que debas usar CHARFORMAT2 obligatoriamente con RichEdit 2.0 o superior.Todavía puedes usar CHARFORMAT si las características aņadidas de CHARFORMAT2 no te son necesarias.

CHARFORMATA STRUCT 
	cbSize DWORD ? 
	dwMask DWORD ? 
	dwEffects DWORD    ? 
	yHeight DWORD ? 
	yOffset DWORD ? 
	crTextColor COLORREF ? 
	bCharSet BYTE ? 
	bPitchAndFamily    BYTE ? 
	szFaceName BYTE LF_FACESIZE dup(?) 
	_wPad2 WORD ? 
CHARFORMATA ENDS 
Nombre de campo Descripcion
cbSize
El tamaņo de la estructura. RichEdit usa este campo para determinar la versión de la estructura si es CHARFORMAT o CHARFORMAT2
dwMask Bit indicadores [bit flags] que determinan cuáles miembros son válidos.
CFM_BOLD
El valor CFE_BOLD valor del miembro dwEffects es válido
CFM_CHARSET
El miembro CharSet es válido.
CFM_COLOR
El miembro crTextColor y el valor CFE_AUTOCOLOR del miembro dwEffects son válidos
CFM_FACE
El miembro szFaceName es válido.
CFM_ITALIC
El valor CFE_ITALIC del miembro dwEffects es válido
CFM_OFFSET
El miembro yOffset es válido
CFM_PROTECTED
El valor CFE_PROTECTED del miembro dwEffects es válido
CFM_SIZE
El miembro yHeight es válido
CFM_STRIKEOUT
El valor CFE_STRIKEOUT del miembro dwEffects es válido.
CFM_UNDERLINE
El valor CFE_UNDERLINE del miembro dwEffects es válido
dwEffects Los efectos de caracteres. Pueden ser una combinación de los valores siguientes
CFE_AUTOCOLOR
Usa el color de texto del sistema
CFE_BOLD
Caracteres de tipo negrita
CFE_ITALIC
Caracteres de tipo itálica
CFE_STRIKEOUT
Caracteres de tipo struck.
CFE_UNDERLINE
Caracteres de tipo subrayado
CFE_PROTECTED
Caracteres protegidos; el intento de modificarlos hará que se despliegue un mensaje de notificación EN_PROTECTED
yHeight
Altura de los caracteres, en twips (1/1440 de pulgada o 1/20 de puntos de impresora).
yOffset
Desplazamiento de los caracteres, en twips, desde la linea base. Si el valor de este miembro es positivo, el caracter es un superscript; si es negativo es un subscript.
crTextColor
Color del Texto. Este miembro es ignorado si se ha especificado el efecto de caracter CFE_AUTOCOLOR .
bCharSet
Valor del Caracter 'set'
bPitchAndFamily
Fuente "family and pitch".
szFaceName
Caracter terminado en NULL, array de caracteres especificando el nombre de la fuente
_wPad2
Relleno

Examinando la estructura, verás que podemos cambiar los efectos del texto (negrita, itálica, tachado, subrayado), el color de texto (crTextColor), el tamaño de la fuente tamaņo, etc.. Una bandera [flag] notable es CFE_RPOTECTED. El texto con esta bandera está marcado como protegido, lo cual significa que cuando el usuario intente modificarlo, se enviará el mensaje de notificación EN_PROTECTED a la ventana padre. Y aquí, puedes permitirlo o no.

CHARFORMAT2 aņade mas estilos de texto tales como fuente, altura, espaciado, color de texto y de fondo, etc. Si no necesitas estas caracterrísticas extras simplemente usa CHARFORMAT.

Para poner texto formateado, has de pensar en el rango del texto que quieres aplicarle. El Richedit introduce la noción de Rango de caracter del texto. El Richedit da a cada caracter un número comenzando desde cero: El primer caracter en el control tiene el ID número cero, el segundo el uno y así sucesivamente. Para especificar el rango de un texto, debes dar al richedit dos números, el ID del primero caracter del rango y el del último. Para aplicar el formato de texto con EM_SETCHARFORMAT, tienes al menos tres opciones:

  1. Aplicarlo a todo el texto en el control (SCF_ALL)
  2. Aplicarlo al texto actualmente seleccionado (SCF_SELECTION)
  3. Aplicarlo a la palabra completa actualmente seleccionada (SCF_WORD or SCF_SELECTION)

La primera y segunda opción son "straightforward" (Directas). La última opción requiere una aclaratoria. Si la selección actual sólo cubre uno o mas de los caracteres en la palabra pero no la palabra completa, especificando la bandera SCF_WORD+SCF_SELECTION se aplicará el formato de texto a la palabra completa, incluso si no hay una selección en ese momento, por ej. Sólo el cursor está colocado en la palabra. La tercera opción también aplica el formato de texto a la palabra completa.

Para usar EM_SETCHARFORMAT, necesitas rellenar varios miembros de la estructura CHARFORMAT (o CHARFORMAT2) . Por ejemplo si queremos poner el color del texto, llenaremos la estructura CHARFORMAT como sigue:

.data?
cf CHARFORMAT <>
....
.code
mov cf.cbSize,sizeof cf
mov cf.dwMask,CFM_COLOR
mov cf.crTextColor,0FF0000h
invoke SendMessage,hwndRichEdit,EM_SETCHARFORMAT,SCF_ALL,addr cf

El recorte de código de arriba pone el color de texto del control richedit en azul puro. Nota que si no hay texto en el control richedit cuando EM_SETCHARFORMAT sea emitido, el texto introducido en el control richedit seguirá al mensaje que usará el formato de texto especificado por el mensaje EM_SETCHARFORMAT.

Poniendo texto/salvando texto

Aquellos que han usado el control edit seguramente estarán familiarizados con los mensajes WM_GETTEXT/WM_SETTEXT, que significan obtener texto/poner texto del control. Este método aún funciona con el control richedit pero puede no ser eficiente si el archivo es muy grande. El control Edit limita el texto que puede ser introducido a 64KB, pero el control richedit puede aceptar una cantidad de texto mucho mayor. Sería muy engorroso localizar un bloque de memoria muy grande (tal como 10 MB) para recibir el texto usando el mensaje WM_GETTEXT. El control richedit ofrece una nueva aproximación a este método, por ej. tratamiento del texto como un stream (flujo continuo) de datos [text streamming]. (NOTA: la palabra 'stream', que se puede traducir como "flujo", designa aquí una cadena continua de datos, con un tamaño determinado).

Para ponerlo simplemente, provees la dirección de una función callback para el control richedit, y éste la llamará pasándole la dirección del buffer cuando esté listo. La función callback llenará el buffer con los datos deseados para enviar al control o leer los datos desde el buffer y entonces espera la siguiente llamada hasta que la operación termina. Este paradigma es usado por ambos streams: introducir el stream [streaming in] y sacando el stream [streaming out]. Verás que estos métodos son mas eficientes: el buffer es provisto por el control richedit así que los datos son divididos en pedazos. Las operaciones involucran dos mensajes: EM_STREAMIN y EM_STREAMOUT

Ambos EM_STREAMIN y EM_STREAMOUT usan la misma sintaxis:

wParam == opciones de formateo.

SF_RTF
Los datos en el texto enriquecido están en formato RTF
SF_TEXT
Los datos están en formato de texto plano
SFF_PLAINRTF
Sólo las palabras claves comunes a todos los lenguajes son introducidas [streamed in].
SFF_SELECTION
Si es especificada, el objetivo de la operación es el texto actualmente seleccionado. Si tu vuelcas el texto en el control richeedit (stream in), el texto reemplazará la selección actual. Si sacas ( stream out ) el texto, sólo el texto seleccionado actualmente es sacado (streamed out). Si esta bandera no está especificado la operación afectará a todo el texto del control.
SF_UNICODE
( Disponible en el RichEdit 2.0 o posterior ) Especifica el texto unicode.

lParam == Puntero a una estructura EDITSTREAM la cual tiene la definición siguiente:

 
EDITSTREAM STRUCT 
        dwCookie DWORD ? 
        dwError DWORD ? 
        pfnCallback DWORD ? 
EDITSTREAM ENDS
dwCookie
Valor definido por la aplicación que será pasado a la función callback y que está especificado en el miembro pfnCallback de abajo. Normalmente pasamos algún valor importante a la función callback tal como el manejador [handle] del archivo para que sea usado en el procedimiento stream-in/out (lectura /escritura de stream).
dwError
Indica el resultado de las operaciones de stream-in (lectura) o stream-out (escritura). Un valor de cero indica que no hay error. Un valor que no sea cero puede ser el valor de retorno de la función EditStreamCallback o un código indicando que el control ha encontrado un error.
pfnCallback
Puntero a una función EditStreamCallback la cual es una función definida por la aplicacion que controla las llamadas para transferir datos . El control llama a la función callback repetidamente transfiriendo una porción de los datos en cada llamada

La función callback del editor de streams tiene la siguiente definición:

	EditStreamCallback proto dwCookie:DWORD,
pBuffer:DWORD, NumBytes:DWORD, pBytesTransferred:DWORD

Puedes crear una función en tu programa con el prototipo de arriba y pasarle la dirección de EM_STREAMIN o EM_STREAMOUT a través de la estructura EDITSTREAM.

Para operaciones stream-in (Poner el texto en el control richedit):

dwCookie: El valor definido por la aplicación que pasas a EM_STREAMIN a través de la estructura EDITSTREAM.
Casi siempre pasamos aquí el handle del archivo que queremos poner su contenido al control. 

pBuffer: puntero al buffer provisto por el control richedit que recibirá el texto desde tu función callback. 

NumBytes: El número maximo de bytes que puedes escribir al buffer (pBuffer) 
en esta llamada. Siempre DEBES obedecer este límite, por ej. se pueden enviar menos datos que el valor en 
NumBytes pero nunca mas. Puedes pensar en este valor como el tamaņo del buffer en pBuffer. 

pBytesTransferred: Puntero al dword en el que debes poner el valor indicando el número  de bytes realmente 
transferidos al buffer. Este valor es usualmente idéntico al valor en NumBytes. La excepción es cuando los datos 
enviados son menores que el tamaņo del buffer provisto, como cuando se halla el final del archivo .

Para operaciones stream-out (obteniendo el texto del control richedit):

dwCookie: Igual que en la operación stream-in. Usualmente pasamos el handle 
del archivo al que queremos escribir los datos en este parámetro. 
pBuffer: puntero al buffer provisto por el control richedit que es llenado 
con los datos del control richedit. Para obtener este tamaņo debes examinar el 
valor de NumBytes.  
NumBytes: 
El tamaņo de los datos en el buffer apuntado por pBuffer. 
pBytesTransferred: Puntero a un dword en el que debes poner el valor indicando el 
número de bytes actualmente leídos del buffer.

La función callback devuelve 0 para indicar éxito y el control richedit continuara llamando a la función callback si todavía hay datos para leer/escribir. Si ocurre algún error durante el proceso y quieres parar la operación, devuelve un valor distinto de cero al control richedit y descartará el dato apuntado por pBuffer. El valor de error/éxito será puesto en el campo dwError de EDITSTREAM, así puedes examinar el estado de error/éxito de la operación con el stream, después que regresa la llamada a SendMessage .

Ejemplo:

Este ejemplo es un simple editor, con él puedes abrir un archivo de de código fuente en asm, editarlo y guardarlo. He usado control richedit versión 2.0 o superior.

.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\comdlg32.inc
include \masm32\include\gdi32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\gdi32.lib
includelib \masm32\lib\comdlg32.lib
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib

WinMain proto :DWORD,:DWORD,:DWORD,:DWORD

.const
IDR_MAINMENU                   equ 101
IDM_OPEN                      equ  40001
IDM_SAVE                       equ 40002
IDM_CLOSE                      equ 40003
IDM_SAVEAS                     equ 40004
IDM_EXIT                       equ 40005
IDM_COPY                      equ  40006
IDM_CUT                       equ  40007
IDM_PASTE                      equ 40008
IDM_DELETE                     equ 40009
IDM_SELECTALL                  equ 40010
IDM_OPTION 			equ 40011
IDM_UNDO			equ 40012
IDM_REDO			equ 40013
IDD_OPTIONDLG                  equ 101
IDC_BACKCOLORBOX               equ 1000
IDC_TEXTCOLORBOX               equ 1001

RichEditID 			equ 300

.data
ClassName db "IczEditClass",0
AppName  db "IczEdit version 1.0",0
RichEditDLL db "riched20.dll",0
RichEditClass db "RichEdit20A",0
NoRichEdit db "Cannot find riched20.dll",0
ASMFilterString 		db "ASM Source code (*.asm)",0,"*.asm",0
				db "All Files (*.*)",0,"*.*",0,0
OpenFileFail db "Cannot open the file",0
WannaSave db "The data in the control is modified. Want to save it?",0
FileOpened dd FALSE
BackgroundColor dd 0FFFFFFh		; blanco por defecto
TextColor dd 0		; negro por defecto

.data?
hInstance dd ?
hRichEdit dd ?
hwndRichEdit dd ?
FileName db 256 dup(?)
AlternateFileName db 256 dup(?)
CustomColors dd 16 dup(?)

.code
start:
	invoke GetModuleHandle, NULL
	mov    hInstance,eax
	invoke LoadLibrary,addr RichEditDLL
	.if eax!=0
		mov hRichEdit,eax
		invoke WinMain, hInstance,0,0, SW_SHOWDEFAULT
		invoke FreeLibrary,hRichEdit
	.else
		invoke MessageBox,0,addr NoRichEdit,addr AppName,MB_OK or MB_ICONERROR
	.endif
	invoke ExitProcess,eax
	
WinMain proc hInst:DWORD,hPrevInst:DWORD,CmdLine:DWORD,CmdShow:DWORD
	LOCAL wc:WNDCLASSEX
	LOCAL msg:MSG
	LOCAL hwnd:DWORD
	mov   wc.cbSize,SIZEOF WNDCLASSEX
	mov   wc.style, CS_HREDRAW or CS_VREDRAW
	mov   wc.lpfnWndProc, OFFSET WndProc
	mov   wc.cbClsExtra,NULL
	mov   wc.cbWndExtra,NULL
	push  hInst
	pop   wc.hInstance
	mov   wc.hbrBackground,COLOR_WINDOW+1
	mov   wc.lpszMenuName,IDR_MAINMENU
	mov   wc.lpszClassName,OFFSET ClassName
	invoke LoadIcon,NULL,IDI_APPLICATION
	mov   wc.hIcon,eax
	mov   wc.hIconSm,eax
	invoke LoadCursor,NULL,IDC_ARROW
	mov   wc.hCursor,eax
	invoke RegisterClassEx, addr wc
	INVOKE CreateWindowEx,NULL,ADDR ClassName,ADDR AppName,\
           WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\
           CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,\
           hInst,NULL
	mov   hwnd,eax
	invoke ShowWindow, hwnd,SW_SHOWNORMAL
	invoke UpdateWindow, hwnd
	.while TRUE
		invoke GetMessage, ADDR msg,0,0,0
		.break .if (!eax)
		invoke TranslateMessage, ADDR msg
		invoke DispatchMessage, ADDR msg
	.endw
	mov   eax,msg.wParam
	ret
WinMain endp

StreamInProc proc hFile:DWORD,pBuffer:DWORD, NumBytes:DWORD, pBytesRead:DWORD
	invoke ReadFile,hFile,pBuffer,NumBytes,pBytesRead,0
	xor eax,1
	ret
StreamInProc endp

StreamOutProc proc hFile:DWORD,pBuffer:DWORD, NumBytes:DWORD, pBytesWritten:DWORD
	invoke WriteFile,hFile,pBuffer,NumBytes,pBytesWritten,0
	xor eax,1
	ret
StreamOutProc endp

CheckModifyState proc hWnd:DWORD
	invoke SendMessage,hwndRichEdit,EM_GETMODIFY,0,0
	.if eax!=0
		invoke MessageBox,hWnd,addr WannaSave,addr AppName,MB_YESNOCANCEL
		.if eax==IDYES
			invoke SendMessage,hWnd,WM_COMMAND,IDM_SAVE,0
		.elseif eax==IDCANCEL
			mov eax,FALSE
			ret
		.endif
	.endif
	mov eax,TRUE
	ret
CheckModifyState endp

SetColor proc
	LOCAL cfm:CHARFORMAT
	invoke SendMessage,hwndRichEdit,EM_SETBKGNDCOLOR,0,BackgroundColor
	invoke RtlZeroMemory,addr cfm,sizeof cfm
	mov cfm.cbSize,sizeof cfm
	mov cfm.dwMask,CFM_COLOR
	push TextColor
	pop cfm.crTextColor
	invoke SendMessage,hwndRichEdit,EM_SETCHARFORMAT,SCF_ALL,addr cfm
	ret
SetColor endp

OptionProc proc hWnd:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
	LOCAL clr:CHOOSECOLOR
	.if uMsg==WM_INITDIALOG
	.elseif uMsg==WM_COMMAND
		mov eax,wParam
		shr eax,16
		.if ax==BN_CLICKED
			mov eax,wParam
			.if ax==IDCANCEL
				invoke SendMessage,hWnd,WM_CLOSE,0,0
			.elseif ax==IDC_BACKCOLORBOX
				invoke RtlZeroMemory,addr clr,sizeof clr
				mov clr.lStructSize,sizeof clr
				push hWnd
				pop clr.hwndOwner
				push hInstance
				pop clr.hInstance
				push BackgroundColor
				pop clr.rgbResult
				mov clr.lpCustColors,offset CustomColors
				mov clr.Flags,CC_ANYCOLOR or CC_RGBINIT
				invoke ChooseColor,addr clr
				.if eax!=0
					push clr.rgbResult
					pop BackgroundColor
					invoke GetDlgItem,hWnd,IDC_BACKCOLORBOX
					invoke InvalidateRect,eax,0,TRUE
				.endif
			.elseif ax==IDC_TEXTCOLORBOX
				invoke RtlZeroMemory,addr clr,sizeof clr
				mov clr.lStructSize,sizeof clr
				push hWnd
				pop clr.hwndOwner
				push hInstance
				pop clr.hInstance
				push TextColor
				pop clr.rgbResult
				mov clr.lpCustColors,offset CustomColors
				mov clr.Flags,CC_ANYCOLOR or CC_RGBINIT
				invoke ChooseColor,addr clr
				.if eax!=0
					push clr.rgbResult
					pop TextColor
					invoke GetDlgItem,hWnd,IDC_TEXTCOLORBOX
					invoke InvalidateRect,eax,0,TRUE
				.endif
			.elseif ax==IDOK
				;==================================================================================
				; Guardar el estado del control richedit porque al cambiar el  color del texto se cambia el
				; estado de modificación del control richedit.
 				;==================================================================================
				invoke SendMessage,hwndRichEdit,EM_GETMODIFY,0,0
				push eax
				invoke SetColor
				pop eax
				invoke SendMessage,hwndRichEdit,EM_SETMODIFY,eax,0
				invoke EndDialog,hWnd,0
			.endif
		.endif
	.elseif uMsg==WM_CTLCOLORSTATIC
		invoke GetDlgItem,hWnd,IDC_BACKCOLORBOX
		.if eax==lParam
			invoke CreateSolidBrush,BackgroundColor			
			ret
		.else
			invoke GetDlgItem,hWnd,IDC_TEXTCOLORBOX
			.if eax==lParam
				invoke CreateSolidBrush,TextColor
				ret
			.endif
		.endif
		mov eax,FALSE
		ret
	.elseif uMsg==WM_CLOSE
		invoke EndDialog,hWnd,0
	.else
		mov eax,FALSE
		ret
	.endif
	mov eax,TRUE
	ret
OptionProc endp

WndProc proc hWnd:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
	LOCAL chrg:CHARRANGE
	LOCAL ofn:OPENFILENAME
	LOCAL buffer[256]:BYTE
	LOCAL editstream:EDITSTREAM
	LOCAL hFile:DWORD
	.if uMsg==WM_CREATE
		invoke CreateWindowEx,WS_EX_CLIENTEDGE,addr RichEditClass,0,WS_CHILD or WS_VISIBLE or ES_MULTILINE or WS_VSCROLL or WS_HSCROLL or ES_NOHIDESEL,\
				CW_USEDEFAULT,CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,hWnd,RichEditID,hInstance,0
		mov hwndRichEdit,eax
		;=============================================================
		; Se establece el límite del texto. El valor por defecto es  64K
		;=============================================================
		invoke SendMessage,hwndRichEdit,EM_LIMITTEXT,-1,0
		;=============================================================
		; Se establece el color del fondo del texto por defecto
		;=============================================================
		invoke SetColor
		invoke SendMessage,hwndRichEdit,EM_SETMODIFY,FALSE,0
		invoke SendMessage,hwndRichEdit,EM_EMPTYUNDOBUFFER,0,0
	.elseif uMsg==WM_INITMENUPOPUP
		mov eax,lParam
		.if ax==0		; file menu			
			.if FileOpened==TRUE	; a file is already opened
				invoke EnableMenuItem,wParam,IDM_OPEN,MF_GRAYED
				invoke EnableMenuItem,wParam,IDM_CLOSE,MF_ENABLED
				invoke EnableMenuItem,wParam,IDM_SAVE,MF_ENABLED
				invoke EnableMenuItem,wParam,IDM_SAVEAS,MF_ENABLED
			.else
				invoke EnableMenuItem,wParam,IDM_OPEN,MF_ENABLED
				invoke EnableMenuItem,wParam,IDM_CLOSE,MF_GRAYED
				invoke EnableMenuItem,wParam,IDM_SAVE,MF_GRAYED
				invoke EnableMenuItem,wParam,IDM_SAVEAS,MF_GRAYED
			.endif
		.elseif ax==1	; edit menu
			;=============================================================================
			; Chequear si hay algún texto en el clipboard. Si es así, habilitamos pegar [paste] en el ítem del  menú
			;=============================================================================
			invoke SendMessage,hwndRichEdit,EM_CANPASTE,CF_TEXT,0
			.if eax==0		; no text in the clipboard
				invoke EnableMenuItem,wParam,IDM_PASTE,MF_GRAYED
			.else
				invoke EnableMenuItem,wParam,IDM_PASTE,MF_ENABLED
			.endif
			;==========================================================
			; Chequear si la solicitud de deshacer [undo] está vacía
			;==========================================================
			invoke SendMessage,hwndRichEdit,EM_CANUNDO,0,0
			.if eax==0
				invoke EnableMenuItem,wParam,IDM_UNDO,MF_GRAYED
			.else
				invoke EnableMenuItem,wParam,IDM_UNDO,MF_ENABLED
			.endif
			;=========================================================
			; check whether the redo queue is empty
			;=========================================================
			invoke SendMessage,hwndRichEdit,EM_CANREDO,0,0
			.if eax==0
				invoke EnableMenuItem,wParam,IDM_REDO,MF_GRAYED
			.else
				invoke EnableMenuItem,wParam,IDM_REDO,MF_ENABLED
			.endif
			;=========================================================
			; Chequear si hay una selección actual en el control richedit.
			; Si lo hay, habilitamos los ítems de menú cut/copy/delete [cortar/copiar/suprimir]
			;=========================================================
			invoke SendMessage,hwndRichEdit,EM_EXGETSEL,0,addr chrg
			mov eax,chrg.cpMin
			.if eax==chrg.cpMax		; no current selection
				invoke EnableMenuItem,wParam,IDM_COPY,MF_GRAYED
				invoke EnableMenuItem,wParam,IDM_CUT,MF_GRAYED
				invoke EnableMenuItem,wParam,IDM_DELETE,MF_GRAYED
			.else
				invoke EnableMenuItem,wParam,IDM_COPY,MF_ENABLED
				invoke EnableMenuItem,wParam,IDM_CUT,MF_ENABLED
				invoke EnableMenuItem,wParam,IDM_DELETE,MF_ENABLED
			.endif
		.endif
	.elseif uMsg==WM_COMMAND
		.if lParam==0		; órdenes de menu
			mov eax,wParam
			.if ax==IDM_OPEN
				invoke RtlZeroMemory,addr ofn,sizeof ofn
				mov ofn.lStructSize,sizeof ofn
				push hWnd
				pop ofn.hwndOwner
				push hInstance
				pop ofn.hInstance
				mov ofn.lpstrFilter,offset ASMFilterString
				mov ofn.lpstrFile,offset FileName
				mov byte ptr [FileName],0
				mov ofn.nMaxFile,sizeof FileName
				mov ofn.Flags,OFN_FILEMUSTEXIST or OFN_HIDEREADONLY or OFN_PATHMUSTEXIST
				invoke GetOpenFileName,addr ofn
				.if eax!=0
					invoke CreateFile,addr FileName,GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,0
					.if eax!=INVALID_HANDLE_VALUE
						mov hFile,eax
						;================================================================
						; introducir el stream de texto en el control richedit
						;================================================================						
						mov editstream.dwCookie,eax
						mov editstream.pfnCallback,offset StreamInProc
						invoke SendMessage,hwndRichEdit,EM_STREAMIN,SF_TEXT,addr editstream
						;==========================================================
						; Inicializar el estado de modificación como falso
						;==========================================================
						invoke SendMessage,hwndRichEdit,EM_SETMODIFY,FALSE,0
						invoke CloseHandle,hFile
						mov FileOpened,TRUE
					.else
						invoke MessageBox,hWnd,addr OpenFileFail,addr AppName,MB_OK or MB_ICONERROR
					.endif
				.endif
			.elseif ax==IDM_CLOSE
				invoke CheckModifyState,hWnd
				.if eax==TRUE
					invoke SetWindowText,hwndRichEdit,0
					mov FileOpened,FALSE
				.endif
			.elseif ax==IDM_SAVE
				invoke CreateFile,addr FileName,GENERIC_WRITE,FILE_SHARE_READ,NULL,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,0
				.if eax!=INVALID_HANDLE_VALUE
@@:				
					mov hFile,eax
					;================================================================
					; llevar el stream de texto al archivo
					;================================================================						
					mov editstream.dwCookie,eax
					mov editstream.pfnCallback,offset StreamOutProc
					invoke SendMessage,hwndRichEdit,EM_STREAMOUT,SF_TEXT,addr editstream
					;==========================================================
					; Inicializar el estado de modificación como falso
					;==========================================================
					invoke SendMessage,hwndRichEdit,EM_SETMODIFY,FALSE,0
					invoke CloseHandle,hFile
				.else
					invoke MessageBox,hWnd,addr OpenFileFail,addr AppName,MB_OK or MB_ICONERROR
				.endif
			.elseif ax==IDM_COPY
				invoke SendMessage,hwndRichEdit,WM_COPY,0,0
			.elseif ax==IDM_CUT
				invoke SendMessage,hwndRichEdit,WM_CUT,0,0
			.elseif ax==IDM_PASTE
				invoke SendMessage,hwndRichEdit,WM_PASTE,0,0
			.elseif ax==IDM_DELETE
				invoke SendMessage,hwndRichEdit,EM_REPLACESEL,TRUE,0
			.elseif ax==IDM_SELECTALL
				mov chrg.cpMin,0
				mov chrg.cpMax,-1
				invoke SendMessage,hwndRichEdit,EM_EXSETSEL,0,addr chrg
			.elseif ax==IDM_UNDO
				invoke SendMessage,hwndRichEdit,EM_UNDO,0,0
			.elseif ax==IDM_REDO
				invoke SendMessage,hwndRichEdit,EM_REDO,0,0
			.elseif ax==IDM_OPTION
				invoke DialogBoxParam,hInstance,IDD_OPTIONDLG,hWnd,addr OptionProc,0
			.elseif ax==IDM_SAVEAS
				invoke RtlZeroMemory,addr ofn,sizeof ofn
				mov ofn.lStructSize,sizeof ofn
				push hWnd
				pop ofn.hwndOwner
				push hInstance
				pop ofn.hInstance
				mov ofn.lpstrFilter,offset ASMFilterString
				mov ofn.lpstrFile,offset AlternateFileName
				mov byte ptr [AlternateFileName],0
				mov ofn.nMaxFile,sizeof AlternateFileName
				mov ofn.Flags,OFN_FILEMUSTEXIST or OFN_HIDEREADONLY or OFN_PATHMUSTEXIST
				invoke GetSaveFileName,addr ofn
				.if eax!=0
					invoke CreateFile,addr AlternateFileName,GENERIC_WRITE,FILE_SHARE_READ,NULL,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,0
					.if eax!=INVALID_HANDLE_VALUE
						jmp @B
					.endif
				.endif
			.elseif ax==IDM_EXIT
				invoke SendMessage,hWnd,WM_CLOSE,0,0
			.endif
		.endif
	.elseif uMsg==WM_CLOSE
		invoke CheckModifyState,hWnd
		.if eax==TRUE
			invoke DestroyWindow,hWnd
		.endif
	.elseif uMsg==WM_SIZE
		mov eax,lParam
		mov edx,eax
		and eax,0FFFFh
		shr edx,16
		invoke MoveWindow,hwndRichEdit,0,0,eax,edx,TRUE		
	.elseif uMsg==WM_DESTROY
		invoke PostQuitMessage,NULL
	.else
		invoke DefWindowProc,hWnd,uMsg,wParam,lParam		
		ret
	.endif
	xor eax,eax
	ret
WndProc endp
end start

;===================================================================
; El archivo de recursos
;===================================================================
#include "resource.h"
#define IDR_MAINMENU                    101
#define IDD_OPTIONDLG                   101
#define IDC_BACKCOLORBOX                1000
#define IDC_TEXTCOLORBOX                1001
#define IDM_OPEN                        40001
#define IDM_SAVE                        40002
#define IDM_CLOSE                       40003
#define IDM_SAVEAS                      40004
#define IDM_EXIT                        40005
#define IDM_COPY                        40006
#define IDM_CUT                         40007
#define IDM_PASTE                       40008
#define IDM_DELETE                      40009
#define IDM_SELECTALL                   40010
#define IDM_OPTION                      40011
#define IDM_UNDO                        40012
#define IDM_REDO                        40013

IDR_MAINMENU MENU DISCARDABLE 
BEGIN
    POPUP "&File"
    BEGIN
        MENUITEM "&Open",                       IDM_OPEN
        MENUITEM "&Close",                      IDM_CLOSE
        MENUITEM "&Save",                       IDM_SAVE
        MENUITEM "Save &As",                    IDM_SAVEAS
        MENUITEM SEPARATOR
        MENUITEM "E&xit",                       IDM_EXIT
    END
    POPUP "&Edit"
    BEGIN
        MENUITEM "&Undo",                       IDM_UNDO
        MENUITEM "&Redo",                       IDM_REDO
        MENUITEM "&Copy",                       IDM_COPY
        MENUITEM "C&ut",                        IDM_CUT
        MENUITEM "&Paste",                      IDM_PASTE
        MENUITEM SEPARATOR
        MENUITEM "&Delete",                     IDM_DELETE
        MENUITEM SEPARATOR
        MENUITEM "Select &All",                 IDM_SELECTALL
    END
    MENUITEM "Options",                     IDM_OPTION
END


IDD_OPTIONDLG DIALOG DISCARDABLE  0, 0, 183, 54
STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU | DS_CENTER
CAPTION "Options"
FONT 8, "MS Sans Serif"
BEGIN
    DEFPUSHBUTTON   "OK",IDOK,137,7,39,14
    PUSHBUTTON      "Cancel",IDCANCEL,137,25,39,14
    GROUPBOX        "",IDC_STATIC,5,0,124,49
    LTEXT           "Background Color:",IDC_STATIC,20,14,60,8
    LTEXT           "",IDC_BACKCOLORBOX,85,11,28,14,SS_NOTIFY | WS_BORDER
    LTEXT           "Text Color:",IDC_STATIC,20,33,35,8
    LTEXT           "",IDC_TEXTCOLORBOX,85,29,28,14,SS_NOTIFY | WS_BORDER
END

Análisis:

El programa primero carga la dll correspondiente al control richedit, que en este caso es riched20.dll. Si no puede ser cargada la dll saldrá a Windows.

invoke LoadLibrary,addr RichEditDLL 
.if eax!=0 
	mov hRichEdit,eax 
	invoke WinMain,hInstance,0,0, SW_SHOWDEFAULT 
	invoke FreeLibrary,hRichEdit 
.else 
	invoke MessageBox,0,addr NoRichEdit,addr AppName,MB_OK or MB_ICONERROR 
.endif 
invoke ExitProcess,eax

Después que la dll es cargada con éxito procedemos a crear una ventana normal que será la ventana padre del control richedit. Dentro del manejador del mensaje WM_CREATE, creamos el control richedit:

		invoke CreateWindowEx,WS_EX_CLIENTEDGE,addr RichEditClass,0,WS_CHILD or WS_VISIBLE or ES_MULTILINE or WS_VSCROLL or WS_HSCROLL or ES_NOHIDESEL,\
				CW_USEDEFAULT,CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,hWnd,RichEditID,hInstance,0
		mov hwndRichEdit,eax

Nota que especificamos el estilo ES_MULTILINE sino sería un control de sólo una linea [single-lined].

		invoke SendMessage,hwndRichEdit,EM_LIMITTEXT,-1,0

Después que el control richedit es creado, debemos ponerle el nuevo límite de texto. por defecto, el control richedit tiene 64KB de límite de texto lo mismo que un simple control Edit multilínea. Debemos extender este límite para permitir operaciones con archivos grandes. En la línea de arriba he puesto -1 el cual recoge la cantidad de 0FFFFFFFFh, un valor bien grande.

 invoke SetColor

A continuación ponemos el color del texto/fondo. Luego puede ser performateado en otra perte del programa. He puesto el código en una función llamada SetColor.

SetColor proc
	LOCAL cfm:CHARFORMAT
	invoke SendMessage,hwndRichEdit,EM_SETBKGNDCOLOR,0,BackgroundColor

Poner el color de fondo del control richedit es una operación directa: solo envia el mensaje EM_SETBKGNDCOLOR al control richedit. (si usas un control Edit multilínea, debes procesar WM_CTLCOLOREDIT). El color por defecto es el blanco.

 invoke RtlZeroMemory,addr cfm,sizeof cfm mov cfm.cbSize,sizeof 
cfm mov cfm.dwMask,CFM_COLOR push TextColor pop cfm.crTextColor

Tras haber puesto el color de fondo, llenamos los miembros de CHARFORMAT con el fin de poner el color del texto. Nota que llenamos cbSize con el tamaņo de la estructura, así el control richedit sabe que estamos enviándole CHARFORMAT, no CHARFORMAT2. dwMask sólo tiene una bandera, CFM_COLOR, y que indica sólo queremos poner el color del texto y crTextColor es llenado con el valor del color deseado para el texto.

 invoke SendMessage,hwndRichEdit,EM_SETCHARFORMAT,SCF_ALL,addr cfm 
ret 
SetColor endp

Después de esto tienes vacío el buffer 'undo', simplemente porque el acto de cambiar el color del texto/fondo está habilitado para deshacer [undo]. Enviamos el mensaje EM_EMPTYUNDOBUFFER para lograr esto.

invoke SendMessage,hwndRichEdit,EM_EMPTYUNDOBUFFER,0,0 

Tras rellenar la estructura CHARFORMAT enviamos EM_SETCHARFORMAT al control richedit, especificando la bandera [flag] SCF_ALL en wParam para indicar que queremos que el texto formateado sea aplicado a todo el texto en el control.

Nota que cuando creamos elprimero control richedit, no especificamos su tamaņo/posición en ese momento. esto es porque queremos cubrir todo el área cliente de la ventana padre. La redimensionamos cuando cambia el tamaņo de la ventana padre.

.elseif uMsg==WM_SIZE 
      mov eax,lParam 
      mov edx,eax 
      and eax,0FFFFh 
      shr edx,16
      invoke MoveWindow,hwndRichEdit,0,0,eax,edx,TRUE

En el código de arriba, usamos la nueva dimensión del area cliente pasada en lParam para redimensionar el control richedit con MoveWindow.

Cuando el usuario hace click sobre el menú File/Edit, procesamos WM_INITPOPUPMENU así que podemos preparar los estados de los items del menú en el submenú antes de mostrarlos al usuario. Por ejemplo, si un archivo ya está abierto en el control richedit, queremos desactivar el item del menú open y activar el resto de items del menú.

En el caso del menú File, usamos la variable FileOpened como la bandera [flag] para determinar si ya hay un archivo abierto. Si el valor de esta variable es TRUE, sabremos que ya hay un archivo abierto.

.elseif uMsg==WM_INITMENUPOPUP 
     mov eax,lParam 
     .if ax==0 ; file menu 
          .if FileOpened==TRUE ; a file is already opened 
               invoke EnableMenuItem,wParam,IDM_OPEN,MF_GRAYED 
               invoke EnableMenuItem,wParam,IDM_CLOSE,MF_ENABLED 
               invoke EnableMenuItem,wParam,IDM_SAVE,MF_ENABLED 
               invoke EnableMenuItem,wParam,IDM_SAVEAS,MF_ENABLED 
          .else 
               invoke EnableMenuItem,wParam,IDM_OPEN,MF_ENABLED 
               invoke EnableMenuItem,wParam,IDM_CLOSE,MF_GRAYED 
               invoke EnableMenuItem,wParam,IDM_SAVE,MF_GRAYED 
               invoke EnableMenuItem,wParam,IDM_SAVEAS,MF_GRAYED 
          .endif

Como puedes ver, si un archivo ya está abierto, difuminamos [grayed] el menú open y activamos el resto de ítems del menú. Ocurre lo contrario si FileOpened es false.

En el caso del menú edit, necesitamos mirar primero el estado del control richedit/clipboard.

	
invoke SendMessage,hwndRichEdit,EM_CANPASTE,CF_TEXT,0 
     .if eax==0 ; no text in the clipboard 
          invoke EnableMenuItem,wParam,IDM_PASTE,MF_GRAYED 
     .else 
          invoke EnableMenuItem,wParam,IDM_PASTE,MF_ENABLED 
     .endif

Primero revisamos si hay algun texto en el clipboard enviando el mensaje EM_CANPASTE. Si lo hay, SendMessage devuelve TRUE y activamos el menú paste, sino, lo difuminamos [grayed].

 invoke SendMessage,hwndRichEdit,EM_CANUNDO,0,0 
.if eax==0 invoke EnableMenuItem,wParam,IDM_UNDO,MF_GRAYED .else invoke EnableMenuItem,wParam,IDM_UNDO,MF_ENABLED 
.endif

A continuacion miramos si el buffer 'undo' está vacío enviando el mensaje EM_CANUNDO, si no está vacío, SendMessage devuelve TRUE y activamos el menú undo.

	
invoke SendMessage,hwndRichEdit,EM_CANREDO,0,0 
     .if eax==0 
          invoke EnableMenuItem,wParam,IDM_REDO,MF_GRAYED 
.else invoke EnableMenuItem,wParam,IDM_REDO,MF_ENABLED .endif

Revisiramos el buffer 'redo' enviando el mensaje EM_CANREDO al control richedit. si no está vacío, SendMessage devuelve TRUE y activamos el menú 'redo'.

	
invoke SendMessage,hwndRichEdit,EM_EXGETSEL,0,addr chrg mov eax,chrg.cpMin 
     .if eax==chrg.cpMax ; no current selection 
          invoke EnableMenuItem,wParam,IDM_COPY,MF_GRAYED 
          invoke EnableMenuItem,wParam,IDM_CUT,MF_GRAYED 
          invoke EnableMenuItem,wParam,IDM_DELETE,MF_GRAYED 
     .else 
          invoke EnableMenuItem,wParam,IDM_COPY,MF_ENABLED 
          invoke EnableMenuItem,wParam,IDM_CUT,MF_ENABLED 
          invoke EnableMenuItem,wParam,IDM_DELETE,MF_ENABLED 
     .endif

por último miramos si existe una selección enviando el mensaje EM_EXGETSEL. Este mensaje usa una estructura CHARRANGE la cual se define como sigue:

CHARRANGE STRUCT 
     cpMin DWORD ? 
     cpMax DWORD ? 
CHARRANGE ENDS

cpMin contiene el índice de posición del caracter immediatamente precedente al primer caracter en el rango.
cpMax contiene la posición del caracter inmediatamente posterior al último caracter en el rango.

Después que EM_EXGETSEL retorna, la estructura CHARRANGE es llenada con con el índice de la posición (starting-ending) principio/final del rango de selección. Si no hay selección, cpMin y cpMax son idénticos y ponemos en gris los menús cut/copy/delete.

Cuando el usuario hace clcik en el menú Open, mostramos el cuadro de dialogo "open file" y si el usuario selecciona un archivo abrimos el archivo y volcamos el contenido al control richedit.

 invoke CreateFile,addr FileName,GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,0 
     .if eax!=INVALID_HANDLE_VALUE 
          mov hFile,eax 
          mov editstream.dwCookie,eax 
          mov editstream.pfnCallback,offset StreamInProc 
          invoke SendMessage,hwndRichEdit,EM_STREAMIN,SF_TEXT,addr editstream

Una vez que el archivo ha sido abierto exitosamente con CreateFile, rellenamos la estructura EDITSTREAM en preparación para el mensaje EM_STREAMIN. Escogemos enviar el manejador [handle] del archivo abierto a través del miembro dwCookie y pasamos la dirección de la función callback del stream en pfnCallback.

El procedimiento callback del stream es la esencia de la simplicidad.

StreamInProc proc hFile:DWORD,pBuffer:DWORD, NumBytes:DWORD, pBytesRead:DWORD 
     invoke ReadFile,hFile,pBuffer,NumBytes,pBytesRead,0 
     xor eax,1 
     ret 
StreamInProc endp

Puedes ver todos los parámetros del procedimiento callback del stream perfectamente adaptado a ReadFile. Y el valor de retorno de ReadFile es "xoredado" con 1 asi que si devuelve 1 (éxito), el valor actual devuelto en eax es 0 y viceversa.

	
invoke SendMessage,hwndRichEdit,EM_SETMODIFY,FALSE,0 
invoke CloseHandle,hFile 
mov FileOpened,TRUE

Después que EM_STREAMIN retorna, significa que la operación stream se ha completado. En realidad, debemos mirar el valor del miembro dwError de la estructura EDITSTREAM.

El Richedit (y edit) control soportan una bandera para indicar que el contenido ha cambiado. Podemos obtener el valor de esta bandera enviando el mensaje EM_GETMODIFY al control. SendMessage devuelve TRUE si este ha cambiado. Al volcar el texto al control, es una clase de modificación. Debemos poner la bandera de modificación a FALSE enviando EM_SETMODIFY con wParam==FALSE para comenzar de nuevo después que termine. la operación 'stream-in'. Inmediatamente cerramos el archivo y ponemos FileOpened a TRUE para indicar que el archivo fue abierto.

Cuando el usuario hace click sobre los menús Guardar/Guardar, usamos el mensaje EM_STREAMOUT para sacar el contenido del control richedit a un archivo. Como con la función stream-in callback, la función stream-out callback es simple. Se adapta perfectamente a WriteFile.

Las operaciones tales como cut/copy/paste/redo/undo son faciles de implementar enviando un simple mensaje al control richedit, WM_CUT/WM_COPY/WM_PASTE/WM_REDO/WM_UNDO respectivamente.

Las operaciones 'delete/select all', son como sigue:

	
.elseif ax==IDM_DELETE 
     invoke SendMessage,hwndRichEdit,EM_REPLACESEL,TRUE,0 
.elseif ax==IDM_SELECTALL 
     mov chrg.cpMin,0 
     mov chrg.cpMax,-1 
     invoke SendMessage,hwndRichEdit,EM_EXSETSEL,0,addr chrg

La operación 'delete' afecta a la actual selección. Envio el mensaje EM_REPLACESEL con la dirección de la cadena igual a cero o NULL, así el control richedit reemplazará el texto actualmente seleccionado con una cadena nula.

La operación 'select-all', se hace enviando el mensaje EM_EXSETSEL, especificando cpMin==0 y cpMax==-1 lo cual recoge todo el texto seleccionándolo.

Cuando el usuario selecciona la barra de menú 'Option', mostramos un cuadro de diálogo presentando el actual color de fondo/texto.

Cuando el usuario hace click sobre una de las cajas de color, se muestra el diálogo de 'elegir color' ['choose-color']. La caja de colores es de hecho un control estático con la bandera SS_NOTIFY y WS_BORDER. Un control estático con la bandera SS_NOTIFY notificará a su ventana padre con las acciones del ratón, tales como, BN_CLICKED (STN_CLICKED). Ese es el truco.

	
.elseif ax==IDC_BACKCOLORBOX 
     invoke RtlZeroMemory,addr clr,sizeof clr 
     mov clr.lStructSize,sizeof clr 
     push hWnd pop clr.hwndOwner 
     push hInstance 
     pop clr.hInstance 
     push BackgroundColor 
     pop clr.rgbResult 
     mov clr.lpCustColors,offset CustomColors
     mov clr.Flags,CC_ANYCOLOR or CC_RGBINIT 
     invoke ChooseColor,addr clr 
     .if eax!=0 
          push clr.rgbResult 
          pop BackgroundColor 
          invoke GetDlgItem,hWnd,IDC_BACKCOLORBOX 
          invoke InvalidateRect,eax,0,TRUE .endif

Cuando el usuario hace click sobre un color, rellenamos el miembro de la estructura CHOOSECOLOR y llamamos a ChooseColor para mostrar el diálogo "choose-color". Si el usuario selecciona un color, el valor 'colorref' es devuelto en el miembro rgbResult y guardamos el valor en la variable BackgroundColor. Después de esto, forzamos que se vuelva a pintar sobre la caja de color llamando a InvalidateRect sobre el manejador [handle] de la caja de color. La caja de color envía el mensaje WM_CTLCOLORSTATIC a su ventana padre.

 invoke GetDlgItem,hWnd,IDC_BACKCOLORBOX 
 .if eax==lParam 
          invoke CreateSolidBrush,BackgroundColor	
          ret

Dentro del manejador [handle] WM_CTLCOLORSTATIC comparamos el manejador [handle] del control estático pasado en lParam para ambas cajas de color. Si el valor coincide creamos una nueva brocha usando el color de la variable e inmediatamente regresamos. Usaremos la nueva brocha para pintar el fondo.


Index

Next

[Iczelion's Win32 Assembly Homepage]

n u M I T_o r's   Programming Page

Este tutorial, original de Iczelion, ha sido traducido por mnemox, y revisado por   n u M I T_o r

Página personal de mnemox

www.000webhost.com