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.
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.
Cracteristica | Versión 1.0 | Versión 2.0 | Versió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 embedding | x | x | x |
Edición Arrastrar y Soltar [Drag and drop] | x | x | x |
Deshacer/Rehacer | nivel-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.
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
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.
|
||||||||||||||||||||
dwEffects | Los efectos de caracteres. Pueden ser una combinación
de los valores siguientes
|
||||||||||||||||||||
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:
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.
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 .
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
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.
[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