Programación General > Visual Basic para principiantes
Tutorial: Crear un control de usuario en VB (Controles ActiveX)
Nebire:
En este tutorial, se enseña como crear controles de usuario en VB6, desde 0. Crearemos un botón totalmente personalizado, partiendo de las características siguientes:
1 - Que admita una imagen ajustada siempre al tamaño del control.
2 - Que pueda llevar opcionalmente un icono.
3 - Que pueda llevar opcionalmente un texto y que este pueda ser alineado.
Otras características se irán definiendo a lo largo del tutorial, en la docena (aprox.) de mensajes que acabará teniendo el tutorial.
El tutorial se va presentando en partes de modo que cada parte es un mensaje y el único motivo de ser de cada parte es el tiempo libre que disponga mientras lo desarrollo. Aun así, a modo de índice, se expone lo que se discute en cada una de las partes.
:smartass:
Parte 1: Preparación del proyecto y creación de los archivos.
Parte 2: Se diserta acerca de la intencionalidad del control y se informa bastante acerca de las propiedades, creamos nuestra primera propiedad y se explica la persistencia de las propiedades más allá de la sesión.
Parte 3: Proporcionamos más propiedades (ColorTexto, Texto, Alineaciontexto, Fuente e Icono) y ahondamos en más detalles sobre las mismas. Inicialización de las propiedades. También señalamos cuando existe la importancia o no de provocar errores, o de 'salir adelante' cuando el error es perfectamente derivable.
Parte 4: Se añaden 4 propiedades más, necesarias para poder empezar a dibujar, Activo, Imagen , Icono y IconoTamaño además se detalla como inicializar el control con una imagen cargada que luego es pasada a una variable interna.
Parte 5: Se realiza la primera parte del dibujado, se explica los 2 métodos para dibujar y mantener actualizado los gráficos, se recalca la importancia de ordenar en capas, se explica el evento resize y se diserta acerca de cómo superar los escollos de errores que a veces resultan casi imperceptibles exponiendo un ejemplo, finalmente se acomete una optimización y se explica los 'porteros' como control de repetición de código por llamadas que a su vez llaman a quien los ha invocado (recursividad involuntaria).
Parte 6: Se ahonda en la importancia del evento resize para mostrar como se controla el tamaño del control y se termina de pintar el control, haciendo el Relieve y dando una solución gráfica al estado deshabilitado del control.
Parte 7: Aprovechando un pequeño bug, se explica el orden e importancia de la cascada de eventos de inicialización, se señalan optimizaciones para el repintado y también se aborda un par de optimizaciones funcionales para la propiedad imagen. Se indica como aportar y guardar la info (documentación) de las propiedades, métodos y eventos disponibles para el cliente .
Parte 8: Se empieza a explicar lo fundamental de los eventos, partes de que se compone. Dónde se debe ubicar un evento y porqué. Control sobre bucles infinitos a causa de los eventos. Control sobre desbordamiento de pila a causa de los eventos. Cómo se logra una propiedad de X escrituras-lecturas. Eventos síncronos y asíncronos, como generarlos. Prioridad en los eventos.
Parte 9: Se realizan los eventos de teclado y ratón, que al no tener apenas código adicional, no precisa muchos comentarios.
Parte 10: Se explican cuestiones referentes al foco, y se aborda una solución gráfica para reflejarlo, la ganancia y pérdida de foco, se sugieren 2 rutinas parecidas entre sí y se invita a realizar otras de curso similar...
Nebire:
En esta primera parte vamos a describir todo lo necesario para poder desarollar los proyectos y crear los archivos necesarios para crear el control y probarlo. Incluyendo la primera prueba del control, sin código alguno, listo para la siguiente parte en que iré exponiendo código y las razones de qué, cómo, cuándo y porqué de cada cosa.
______________________________
Para empezar es conveniente saber que un control de usaurio se crea cuando necesitamos exponer una interfaz de usuario, es decir algo gráfico. Desde este punto de vista estos controles deberían mejor llamarse 'control gráfico de usuario' frente a las clases que podrían llamarse 'control no gráfico de usuario', como el orden de suceso de las cosas fueron de una manera cada cosa se llamó como en su momento se consideró oportuno. También es importante señalar que quien ya ha trabajado con clases encontrará bastante asequible trabajar con controles de usuario.
Ahora crearemos todos los archivos y proyectos que necesitamos para empezar, se creará un proyecto para elcontrol y un proyecto para probarlo y ambos proyectos serán controlados por lo que en el entorno VB se llama 'grupo de proyectos' . Así abramos el entorno de desarrollo y elegimos eltipo de proyecto 'control Activex'. Ahora cambiamos el nombre del proyecto (para no perdernos en explicaciones conviene que le des el mismo nombre que sugiero, luego en tu desarrollo dale el nombre que prefieras, éste nombre elegido es por comodidad en la didáctica, nada más) desde 'proyecto1' a 'PryBoton'. Ahora igualmente cambiamos el nombre del control de usuario desde 'UserControl1' a 'CtlBoton'. finalmente le damos al botón guardar para guardar el proyecto, la ruta elígela como prefieras yo sugiero algo como: 'C:\Mis proyectos\controles\Botón simple'.
Una vez creado el proyecto del control, podremos cerrar el entorno y volverlo a abrir o bien desde el menú 'archivo' elegir 'crear nuevo proyecto', ahora elegimos un proyecto de tipo 'exe estándar'. Igualmente que con el anterior a este proyecto le renombramos desde 'proyecto1' a 'Probar_ctlBoton' . el nombre del formulario no importa. Ahora guardamos el proyecto, la ruta será la misma que elegimos para el control pero creamos una carpeta llamada 'pruebas' y lo guardamos dentro de esa carpeta ('C:\Mis proyectos\controles\Botón simple\Pruebas'). Este proyecto está destinado a probar el control mientras lo estamos diseñando e incluso para probarlo una vez hayamos compilado el control.
Para que ambos proyecto trabajen juntos ambos deben estar dentro de un grupo de proyectos, así ahora vamos al menú 'archivo' y elegimos 'añadir proyecto' y desde la ficha 'recientes' elegimos el proyecto 'pryBoton' (o bien lo localizamos desde la ficha existente, también podríamos haber creado en este momento el proyecto del botón) y lo abrimos. Si nos fijamos bien en el cuadro de herramientas se nos ha añadido el icono para el control que hemos añadido (más adelante se indicará como proporcionar un icono personalizado para el control). Seleccionamos dicho icono del control (veremos que el tooltip del icono señala el tipo de control que es (ctlBoton) . Bien pués ahora pongamos una instancia del control en el formulario del proyecto de prueba.
Ahora pulsamos el botón de guardar proyecto, el archivo del grupo de proyectos 'grupo1.vbg' (vb-Group ) lo guardamos también donde el proyecto de prueba, no es necesario cambiarle el nombre. Si ahora cerramos el entorno cuando volvamos a abrir el entorno ahora buscaremos abrir el archivo de proyectos, 'grupo1.vbg', ya que si sólo abrimos uno de los proyectos se abrirán sólo los archivos concerniente a ese proyecto, este archivo de 'grupo' es el que vincula 2 o más proyectos entre sí. Mas adelante se darán las indicaciones oportunas a la hora de compilar el control.
Bien ahora ya tenemos todo listo para empezar. Vayamos al control de usaurio y abramos el control, sobre las propiedades del control busquemos la propiedad backcolor cambiémosla por cualquier color que no sea gris... rojo, negro va bien. Si apartamos la ventana del control lo suficiente para ver el formulario veremos que el control aparece 'rayado', esto es indicativo de que el control está en 'modo diseño' . Cerremos la ventana del control (la ventana no el proyecto) veremos que inmediatamente nuestro control en el formulario cambia, no sólo desaparece el rayado sino que ahora aparece del color que le elegimos para el fondo, se dice que ahora el control está en 'modo de ejecución'. Esto es importante tenerlo en cuenta, ya que un poyecto de vb estaba en ejecución cuando pulsábamos f5 y aquí aún no lo hemos pulsado.
Podemos proceder, pero primero damos al botón paa guardar todos los cambios operados (yo en mis opciones de vb tengo por defecto que cuando ejecute un proyecto me pida si se guardan o no los cambios así, yo no necesito pulsar el botón, pero hay que acostumbrarse a guardar los cambios antes de ejecutar (el proyecto deprueba). Pulsemos la tecla F5 y veremos nuestro control en ejecución, aunque de momento no hace nada, sin embargo se ve y (en mi caso aparece rojo).
Ya hemos creado los archivos necesarios del proyecto, por tanto desde ahora abriremos el entorno desde el archivo Group1.vbg que como señalé es el que mantiene unido ambos proyectos el del control y el de prueba.
Hay que señalar que cuando hay un grupo de proyectos, sólo uno es el 'proyecto inicial', el que se va a ejecutar cuando se arranque el proyecto para su ejecución, en este caso el proyecto inicial es el de prueba ya que el del control actúa como secundario dentro del proyecto de prueba... justo cuando se inicializa el formulario (cuando se carga los controles ya deben haberse cargado, de hecho se carga justo cuando el último control constituyende del formulario se ha cargado) se invoca la carga de todos y cada uno de los controles que lo integran. Para hacer la prueba vayamos al código del formulario y escojamos el procedimiento de evento del formulario initialize, este sería su código :
--- Código: Visual Basic --- Private Sub Form_Initialize() MsgBox "Formulario inicializando"End Sub Acto seguido vamos a la ventana de código del control, también en su procedimiento de evento initialize.... este sería su código:
--- Código: Visual Basic --- Private Sub UserControl_Initialize() MsgBox "Control inicializando"End Sub
Para verificar lo que se ha dicho finalmente exponemos código en el load del formulario:
--- Código: Visual Basic --- Private Sub Form_Load() MsgBox "formulario inicializado y cargando..."End Sub
Ahora ejecutamos paso a paso el grupo de proyectos (tecla F8).. veremos que vamos al procedimiento initialize del formulario, luego al initialize del control y finalmente al load del formulario.
Para el diseño del control esto no tiene mucha importancia, pero es precisamente lo que nos ayudará a entender la cascada de sucesos de arranque del control. Si se piensa bien el evento initialize del formulario ocurre una sola vez, en cambio el evento load del formulario puede ocurrir varias veces durante la vida del mismo, esto mismo sucede con el control, aunque más adelante se detallará esta cuestión. Ahora lo que nos importa es saber si el proyecto de inicio fue exitosamente el de prueba. si fue el del control debemos ir al 'explorador de proyectos' (CTRL + R) y fijarnos cualo de los dos proyectos aparece en negrita, eso marca el proyecto inicial, si es necesario, por tanto pulsamos click sobre el proyecto de prueba y sobre el menú emergente pulsamos en 'establecer como inicial'.
Debe quedar constancia que ambos proyectos pueden ser creados en el mismo momento, si elegimos crear un proyecto 'exe', después podemos indicar ene l menú añadir nuevo proyecto y elegir un proyecto de tipo 'control Activex', o también al revés. conviene en todo caso, guardar cada proyecto en su carpeta y lo mejor es que dentro de la carpeta del control haya unca carpeta "probar" o "pruebas", donde constará el proyecto de prueba y el ejecutable del grupo de proyectos. El proyecto de prueba no tiene otro interés que ir probando que la funcionalidad que se dota al control es la que queremos darle y que funciona correctamente. Para ello, se usará un formulario donde al menos se deberá poner una instancia del control y utilizarlo para probarlo.
También es posible crear un proyecto de tipo 'exe' y adjuntar un 'nuevo control Activex', pero al carecer éste de su propio proyecto, el control será privado y se compilaría sólo con el proyecto, 'exe', no aparte. Esa es la opción ideal cuando deba construirse un control que sólo se va a usar en un único proyecto y del que no pretendamos usar en ninguna otra parte. al compilarlo de esta manera, se compila como parte del ejecutable (proyecto1.exe) y por tanto no se genera un UserControl1.ocx. Es importante tener esto en cuenta...
Ya borramos todo el código introducido anteriormente y guardamos el proyecto.
Ya estamos listos para empezar a diseñar nuestro botón. Esta es la primera parte, los preparativos iniciales, realmente lleva más tiempo explicarlo que hacerlo... y bastante más si además se tiene que teclear las indicaciones así que ruego paciencia.
Mañana empezaremos a diseñar el botón y explicaremos el ciclo de vida de un control de usuario.
Nebire:
Empezamos la siguiente parte. Hoy se describe acerca de la intencionalidad del control y parte acerca de las propiedades, así como algunas características de las propiedades que son arropadas por el usercontrol.
Cuando vamos a crear un control, previamente es conveniente hacer una descripción sobre un papel (papel, fichero de texto, eso al gusto de cada uno) de que funcionalidad es la que esperamos que tenga, evidentemente siempre en medio del proyecto podremos hacer añadidos y de hecho los haremos a propósito para ver como afecta los añadidos a última hora. Sin embargo cuanto mejor tengamos explicitado lo que queremos que haga en mejores condiciones estaremos para proveer lo que precisa...
Es habitual que la razón por la que queramos construir un control es que determinado existente no tenga tal o cual característica/s por lo que nuestro modelo podría ser una copia en cierto modo similar a otro ya existente pero con algunos cambios.... por ejemplo, a mi nunca me gustó que los botones de microsoft no tuvieran una propiedad 'forecolor', por narices siempre es negro (se puede cambiar empleando APIs, pero no hay una propiedad), si queremos cambiar el color del fondo debemos cambiar la propiedad estilo a 'graphical', sólo entonces toma el color que hayamos designado a la propiedad backcolor, ese comportamiento también nos parece 'idiota', otra cuestión es que por ejemplo admite una imagen, pero se pega al tamaño real, también debe estar establecido estilo a graphical (Command1.style=1: Command1.picture= loadpicture(ruta...)) . Para colmo incluso creando una imagen justo a la medida del botón, si es oscurapor abajo nos 'tapa' el texto del botón... en fin como puede sentirse, hay razones que ya justifican nuestro botón y ya de entrada tenemos unas descripciones de lo que tiene que ser...
Vamos pués a proceder a dar una somera descripción de lo que pretendemos crear... El control debe poder cambiar el color del fondo, el color del texto, alinear el texto horizontalmente, admitir una imagen ajustada en todo momento al fondo, tener capacidad para un icono alineado siempre a la izquierda y poseer un borde perimetral que de momento no definimos como se comportará (podría ser fijo o que se hunda al pulsar y se eleve al soltar), debe tener algunas propiedades típicas, activo (enabled), font, etc...
Es una descripción pequeña, pero para empezar a contruirlo nos vale, luego a medida que vayamos diseñando y probando terminaremos de definir tal vez nuevas propiedades o cambios de funcionalidad... esto debe entenderse como algo lógico y razonable, tampoco es estrictamente necesario tener una descripción exhaustiva del mismo, siendo como es para nosotros... A medida que uno se familiariza creando controles bastantes ideas de la descripción están ya en la cabeza y no necesitamos tener que plasmarlas por escrito, en cambio si conviene dejar escrito esas funcionalidades específicas que pretendíamos para el control ya que si no, cabe la posibilidad de que tras algún tiempo sin retomar el proyecto se nos haya olvidado por completo la misma, la funcionalidad que lo haría diferente de otros modelos previos que ya hayamos realizado, por ejemplo puede que este nuevo botón se deba a que queramos unir varios botones en línea y columna formando una tabla y por ello queramos redefinir la forma en que se dibuja el relieve de los mismos... o podríamos querer en un nuevo botón que tenga una pequeña animación cuando se pasa el ratón por encima, o simplemente añadir un sonido cuando se oprime y suelta elbotón... estas 2 últimas funcionalidades las utilñizaremos como un añadido de última hora cuando se dé al control por terminado...
El mejor sitio para dejar escrito la funcionalidad (las ideas de como debe operar) es usar un archivo de texto en la misma carpeta que el control, así siempre estará disponible.
Por tanto empezaremos definiendo propiedades bastante conocidas: Backcolor, forecolor, enabled, Text... pero a mi particularmente me gusta usar el español que es mi idioma, por tanto todas las propiedades y demás se reflejarán en lo posible en español... hay una razón más o menos poderosa para dejarlo en inglés, que ya para el final se indicará si no me olvido, y si me olvido me lo recordais (me refiero al final de la elaboración del control).
Las propiedades pueden usarse tanto en clases como en formularios y por supuesto en los controles que es donde más lo vemos. Las propiedades son un trío de funciones realmente pero que el entorno de VB lo enmascara como si fueran algo distinto de funciones, de hecho podríamos olvidar las propiedades y tratarlas como funciones pero nos perderíamos ciertas funcionalidades del entorno, como mostrar los valores de las propiedades del control en la 'Ventana de propiedades'.
Las propiedades como digo son un trío, una devuelve un valor y las otras 2 entregan un valor. de estas 2 una se utiliza cuando se usa un tipo de datos genérico y la otra cuando se usa como tipo de datos un objeto.
A continuación iremos poniendo código y explicando...
--- Código: Visual Basic --- Public Property Get ColorTapiz() As OLE_COLOR ColorTapiz = UserControl.BackColorEnd Property Public Property Let ColorTapiz(ByVal ct As OLE_COLOR) If ct <> UserControl.BackColor Then UserControl.BackColor = ct UserControl.PropertyChanged "ColorTapiz" ' añadidos posteriores End If End Property Acabamos de definir la propiedad ColorTapiz (colorFondo, BackColor) para el control, analicemos cada parte...
GET: es como una función, devuelve un valor, se usaría así en el formulario: dim X as long: x= CtlBoton1.Colortapiz : msgbox x
Public: porque queremos que esta propiedad sea legible, si quisiéramos que no se pudiera leer pondríamos private y sería una propiedad sin derecho a lectura.
LET: es la asignación de la propiedad, puede verse que es como una función tiene un parámetro que es por donde se recibe el valor, a dicho parámetro le hemos lamado, ct (abreviatura de Colortapiz, yo suelo usar esta convención), este parámetro no es visible desde fuera, luego no importa el nombre que se le dé. Let también se declaró public, si lo hubiéramos declarado private la propiedad estaría privada de escritura, es decir sólo podría invocarse para ser leída pero nunca podría escribirse.
Más adelante se mostrará que es posible dar más de un parámetro a las propiedades, sin embargo debe quedar claro que 1º no es obligatorio el par de procedimientos de la propiedad, puede usarse sólo el get y no aparecer el let, o al revés. 2º si aparecen ambos deben ser congruentes en el tipo de datos y en la cantidad de parámetros, veremos que siempre LET tiene un parámetro más que GET, 3º en las propiedades del control (en el formulario) sólo aparece una propiedad si aparecen ambos métodos y también ambos son públicos, si uno de los 2 es privado no aparece en las propiedades del control. (conviene que luego se pruebe a cambiarlos a private uno de ellos y se verá como deja de aparecer).
La propiedad almacena un color que a fin de cuentas es un tipo de datos long, porqué lo hemos declarado del tipo OLE_COLOR ?, porque cuando se declara de esa manera el entorno de VB nos despliega una 'página de propiedades' para elegir visualmente el color, si utilizamos un tipo de datos long, desde el entorno sólo podríamos introducir valores numéricos desde el teclado. Los controles pueden poseer páginas de propiedades que es casi un pequeño mundo lo mismo que los controles pero que una vez conocidos los controles son más sencillos de conocer, el uso de páginas de propiedades para los controles facilita al programador poder cambiar cómodamente algunas propiedades incluso a varios controles a la vez, la contrapartida es que diseñar una página de propiedades nos lleva más tiempo y hace al control más pesado (el control una vez compilado pesa más)... si al final hay ganas podríamos crear una sencilla página de propiedades.
Véase que usando una propiedad en vez de 2 funciones, ambos métodos se llaman iguales, usando funciones por necesidad cada uno debería tener un nombre distinto)... Ahora ponemos un ejemplo de como se asigna la propiedad del control en el formulario.... Dim x as long : x= 1234567: CtlBoton1.ColorTapiz = x .Aquí escritura y antes dimos un ejemplo de lectura de la propiedad. Antes de seguir, podemos probar ya esta nueva propiedad, para ello debemos ir al formulario y pulsar en el control, en las propiedades del control veremos que aparece la propiedad ColorTapiz y podemos ver como tiene el color que le asignamos (el tipo Ole_color hace que el entorno nos muestre el color visualmente en vez de un número y podemos cambiar el color y ver como el cambio se refleja en el control...por ejemplo cambíalo a azul... IMPORTANTE: si el control en el formulario aparece 'rayado diagonalmente', tal como se dijo ayer es porque está en modo diseño, para poder probarlo en el formulario es preciso ponerlo en modo ejecución, y para ponerlo en modo ejecución tenemos que cerrar la ventana del control (no la de código si no la de la interfaz del control)... esto no lo voy a repetir más veces, debe quedar claro YA.
Sólo nos queda describir el código introducido en los métodos de la propiedad ColorTapiz... para el get le decimos que colortapiz es igual a un valor que el propio control ya mantiene una variable... (Cada contenedor provee determinada funcionalidad, disponible para los objetos insertados en él y los contenedores del entorno de VB proveen propiedades como Backcolor,y algunas otras que se irán describiendo, al objeto Usercontrol, nuestra instancia. Por tanto la línea 'ColorTapiz = UserControl.BackColor' le está diciendo que entregue la propiedad almacenada en la variable Backcolor que provee ya el control.
En cuanto al código de la asignación LET, lo primero que hacemos es verificar si el color recibido es el mismo, y sólo si es distinto se lo asignamos a la propiedad backcolor del usercontrol. La razón de esto es doble por un lado más adelante tenemos que añadir funcionalidad al final del código de este método, dicho código queremos que sólo se ejecute si realmente el color asignado cambió si no estaremos ejecutando cosas para que quede todo como estaba antes. La otra razón es que ahora el valor del color se guarda en la propiedad backcolor del control (en el que hemos delegado), pero si (cambiamos el color del tapiz a azul como se sugirió más arriba al probar y cerramos el entorno y volvemos a abrirlo, volverá a ser rojo, es decir el cambio no se ha mantenido, justo para eso está la siguiente línea: PropertyChanged "ColorTapiz", esta línea le dice al entorno que esta propiedad ha cambiado, sin embargo esta línea no trabaja sola sino que trabaja en conjunto con 2 métodos del control, uno se encarga de guardar a fichero el cambio registrado (cuando le demos al botón de guardar cambios en el proyecto), y la otra funcionalidad se encargará de volver a leer el valor cambiado cuando arranquemos el proyecto, o se ponga en modo de ejecución el proyecto.
La última línea de código del método LET es una línea comentada, más adelante tenemos que introducir código ya que el cambio del color del fondo, implica que nos borrará el resto del contenido gráfico del control, por lo que se añadirá una línea que salte a una función que vuelva a dibujar todo lo que teníamos y que se ha borrado. Como ese método aún no lo tenemos, dejamos una línea comentándolo.
Justo ahora describiremos estos métodos que ayudan a almacenar de forma permanente las propiedades y que permiten luego al ejecutarlo volver a leer ese valor guardado y lo dejamos por hoy, mañana iremos más rápido con las siguientes propiedades una vez que hemos comprendido de forma general acerca de las propiedades, faltan detallesque se irán presentando según surjan los casos.
Para probar que el color no permanece, vayamos al modo ejecución y cambiemos el color del fondo... y acto segudio pulsemos la tecla F5, veremos que aparecerá el formulario pero con el color que le asignamos en la propiedad backcolor de la ventana de propiedades de la interfaz del control.
Ahora añadamos este código al control (abajo del todo):
--- Código: Visual Basic --- Private Sub UserControl_WriteProperties(PropBag As PropertyBag) With PropBag .WriteProperty "ColorTapiz", UserControl.BackColor, Ambient.BackColor End WithEnd Sub Private Sub UserControl_ReadProperties(PropBag As PropertyBag) With PropBag UserControl.BackColor = .ReadProperty "ColorTapiz", Ambient.BackColor End WithEnd Sub Estos métodos pueden localizarse en la ventana de código del control sobre la lista de procedimientos del control (usarcontrol), por tanto elparámetro éste de propbag, lo provee el entorno de VB, este es un objeto interno de VB cuya funcionalidad procede de una interfaz cuyo objetivo es precisamente la de almacenar y recuperar valores de propiedades que se han asignado durante el diseño (impronta), es algo así como si compras un vehículo, cuando se fabrica es metal y debe ser pintado para evitar que se oxide, aunque a los vehículos se los elige el color y se piden se nosenvía cuando la fábrica dispone de una demanda suficiente de ese color para cambiar los tambores de pinturas en el proceso en cadena, por ejemplo para pintar 200 vehículos seguidios de ese color. sin embargo aquí en VB es como si todos salieran con una capa antióxido gris (que en el entorno se llama Ambient.Backcolor y que es el contenedor quien decide ese color, de hecho es el color del contenedor, es como si la fábrica fuera azul por defecto todos los vehículos salieran azules), luego para asignar el color que actualmente hay guardado el objeto Propbag (Bolsa de propiedades) le dice asigna a la variable (Usercontrol.Backcolor) leyendo en las propiedades guardadas ( .Readproperties) el valor de la propiedad ColorTapiz que se guardó bajo ese mismo nombre en el fichero, y si no se encuentra en el fichero un valor guardado tomamos el valor almacenado en usercontrol.backcolor actualmente.
Es decir el método readproperties se compone de 3 partes la asignación (usercontrol.Backcolor =), la invocación del método del objeto pararecuperar el valor (propbag.Readproperties), pero que como usamos with, nos ahorramos escribir en cada línea el nombre del objeto), y los parámetros que necesita esta invocación, que al caso son 2, el nombre de la propiedad guardada y un valor que se asigna por defecto si tal propiedad no se encuentra: UserControl.BackColor = .ReadProperty ("ColorTapiz", Ambient.BackColor) igualmente en vez de poner por defecto el color del contenedor podríamos poner un color específico, por ejemplo: .ReadProperty( "ColorTapiz", vbBlue) o incluso un valor numérico: .ReadProperty ("ColorTapiz", 234567)
El método readproperties ocurre 1 única vez (realmente es un evento que se dispara para el usercontrol) y es cuando el control entra en modo de ejecución, una vez leídas las propiedades ya no se vuelve a invocar dicho método.
Writeproperties, es el método que usa el control para que se guarden las propiedades y ocurre también una sola vez, justo antes de que el control sea descargado de memoria. Esto sólo sucede mientras la 'aplicación cliente' está en diseño, es decir el control está en modo de ejecución, ya que en modo diseño lo que se guarda es el valor de la propiedad que nosostros le dimos en la ventana de propiedades del control, pero ese no es accesible a nosotros. Es decir nosotros hemos diseñado que cada vez que un cliente utilice el control éste aparecerá cuando se cargue por primera vez la instancia el color rojo, sin embargo el cliente querrá y podrá cambiar que cuando su aplicación se ejecute el control aparecerá con el color que él eligió, es ese valor el que nosotros guardamos en writeproperties, los cambios que el cliente introduce cuando está diseñando la aplicación.
Este método consta de un parámetro más que el método readproperties del objeto propertybag: guarda a fichero (writeporperties) una propiedad con el nombre "ColorTapiz", cuyo valor actual está en la variable: Usercontrol.Backcolor (la variable que actalmente contiene su valor) y si el valor no se encuentra o no es válido entonces asigna éste por defecto (Ambient.Backcolor, el del contenedor donde se halleel control). Igualmente que para readproperties podría asignar un valor directo, como vbBlue ó 56789. Conviene en cualquier caso que en ambos sitios el color asignado por defecto sea el mismo en ambos casos
Bien ahora que ya hemos descrito como se logra que las propiedades permanezcan entre sesiones, podemos volver al formulario cambiar el color a uno que no hayamos usado (por ejemplo un violeta) y guardar el proyecto para que ocurra un evento writeproperties y efectivamente se guarden, luego pulsamos la tecla F5, con lo que ahora se crea el control (recordemos el evento initialize que probamos al principio de esta parte) y luego se invoca el evento readproperties, que asigna las propiedades que hemos guardado. en el evento initialize el color será rojo, pero ahora tras el readproperties será el que le dimos la última vez (violeta)...
Y finalmente por hoy ya que hemos guardado el color violeta para esta instancia veamos como al añadir una nueva instancia del control al formulario el color del fondo de éste es el rojo. Ahora debería quedar claro que valores son los que se guardan en writeproperties y se leen con readproperties Y también debería quedar claro el significado del método propertychanged.
También podemos probar a cambiar la propiedad del control por código, no sólo desde la ventana de propiedades de los objetos del formulario. Por ejemplo añade un botón microsoft y escribe código, para que cuando se pulse cambie nuestro control de color o nos diga el color actual.
Después hacer todas las pruebas que queramos, conviene dejar sólo una instancia del control (del que estamos diseñando) en el formulario para no liarnos cuando mañana continuemos...
Mañana ahondaremos en las propiedades y crearemos bastantes más ahora que se ha explicado como crearlas. También mañana explicaremos como se soportan propiedades que ni siquiera existen, backcolor lo posee el usercontrol y enabled, por lo que en estos caso podemos delegar en elas aunque como hemos visto podemos cambiarle el nombre por el camino...
Nebire:
Si alguien no se ha acabado de enterar muy bien de las propiedades puede abrir el grupo de proyectos ir al código que escribimos seleccionar la palabra property 1 pulsar la tecla F1 que nos lleva a la ayuda, naturalmente se explicará bien incluso mejor que yo el significado y declaración de la propiedad aunque todo el intríngulis de las propiedades no aparece... pero entre la ayuda y mis indicaciones creo que todo el mundo podría saciar sus dudas.
En el apartado de ayer se explicó gran parte de las propiedades y también se explicó la persistencia (preservación) de los valores de las propiedades. Hoy avanzaremos con más propiedades de nuestro control y se indicarán más detalles acerca de las propiedades a medida que surja cada caso.
Quedamos en que íbamos a requerir determinadas propiedades: ColorTapiz (BackColor), ColorTexto (ForeColor), el propio Texto del botón (Caption o Text), alinear el texto, un Icono, una Imagen autoajustable al botón... pués hoy procederemos con el código para estas propiedades y de fondo otras característica sobre el Usercontrol...
El código para la propiedad ColorTexto (ForeColor)
--- Código: Visual Basic --- Public Property Get ColorTexto() As OLE_COLOR ColorTexto = UserControl.ForeColorEnd Property Public Property Let ColorTexto(ByVal ct As OLE_COLOR) If ct <> UserControl.ForeColor Then UserControl.ForeColor = ct UserControl.PropertyChanged "ColorTexto" ' añadidos posteriores End If End Property Vemos que prácticamente podemos copiar el código del Backcolor y hacer manualmente los cambios (de hecho yo lo he hecho así). ColorTexto es lo que nos va a permitir cambiar el color al texto que se escribe en el botón, de momento no hemos realizado la propiedad texto, por lo que aparece también la línea comentada 'añadidos posteriores. Lo dicho ayer sobre Usercontrol.BackColor es aplicable también sobre Usercontrol.ForeColor, es una propiedad también sobre la que delegamos ya que la 'plantilla' que se nos presenta del contrl la trae, estas propiedades sobre las que podemos delegar pueden verse sobre la ventana depropiedades de la ventana de la interfaz del control. Igualmente si por defecto deseamos que la aplicación cliente tenga nada más cargar una instancia en un formulario un color determinado es en esa ventana donde debemos designarlo. Yo lo dejo en negro.
Debe quedar claro desde ya que todas las propiedades que vayamos a añadir y que el control posee delegaremos sobre ellas, salvo que la funcionalidad sea totalmente distinta a la que realiza la plantilla... si surge un caso así ya se explicará.
Es conveniente (no obligatorio) que cada vez que añadimos una propiedad hagamos los propio para su persistencia, añadiendo las entradas correspondientes en los métodos Readproperties y Writeproperties... Yo copio una entrada anterior, pego y luego la modifico... entonces ya tendremos esto:
--- Código: Visual Basic --- Private Sub UserControl_ReadProperties(PropBag As PropertyBag) With PropBag UserControl.BackColor = .ReadProperty("ColorTapiz", Ambient.BackColor) UserControl.ForeColor = .ReadProperty("ColorTexto", Ambient.ForeColor) End WithEnd Sub Private Sub UserControl_WriteProperties(PropBag As PropertyBag) With PropBag .WriteProperty "ColorTapiz", UserControl.BackColor, Ambient.BackColor .WriteProperty "ColorTexto", UserControl.ForeColor, Ambient.ForeColor End WithEnd Sub Es decir ayer pusimos la del color tapiz ahora metemos la del color texto.... en lo sucesivo sólo pondré la línea concreta como recordatorio, ya vosotros sabeis donde debe añadirse (a continuación de la anterior y dejamos una nueva línea para la siguiente entrada... bueno esto al gusto).
Ahora vamos con la siguiente propiedad; Texto. Vemos que el control no tienen una propiedad Text ni Caption, esto significa que no podemos delegar en ella sino que la tenemos que contruir completamente. Al hacerlo se entenderá fácilmente su lógica y se explicará como se le cede su primer valor.
--- Código: Visual Basic --- ' esta línea de código debe ir arriba del todo, por encima de cualquier declaración de propiedades y funciones...Private p_Texto As String ' el texto del botón se almacena en esta variable internamente. ' esta línea podemos ponerla a continuación (debajo de) la propiedad ColorTexto.Public Property Get Texto() As String Texto = p_TextoEnd Property Public Property Let Texto(ByVal t As String) If t <> p_Texto Then p_Texto = t PropertyChanged "Texto" ' añadidos posteriores End If End Property Como se puede ver, el valor de la propiedad realmente la almacenamos en una variable (p_Texto) declarada privada, por tanto no es accesible desde fuera y debe verse como lógicamente la hemos declarado de tipo string. Surge una pregunta lógica de parte del aprendiz, porqué no guardamos directamente la propiedad sobre la variable p_Texto ?. De hecho podríamos hacerlo, pero entonces si tenemos algo como esto:
--- Código: Visual Basic --- public TextoBoton as string ...Es realmente una propiedad con todas las de la ley, pero presenta unos inconvenientes para determinadas situaciones: 1º Si utilizamos una propiedad así, no hay forma de saber cuando el cliente ha cambiado su valor, por tanto tampoco sabemos cuando debe actualizarse lo que deba actualizarse.
2º Si necesitamos garantizar que el valor que introduce el cliente (el cliente en este caso es el programador que utiliza el control compilado) cumpla ciertos requisitos no tenemos forma de hacerlo porque como se indicó antes no sabemos cuando se introduce un nuevo valor.
Al expresarse las propiedades en forma de 2 funciones, una que entrega el valor y otra que lo recibe, la que lo recibe cuenta con 2 notificaciones, a) que se recibe un valor, B) si lo deseamos podemos comprobar si ese valor es distinto del actual, es decir si cambia de valor. Hasta ahora si os fijais en el código de las 3 propiedades que llevamos hasta el momento, en los 3 casos hemos verificado si el valor que se introduce es distinto del actual, ya se explicó ayer porque hacemos esa comprobación, no siempre la haremos como severá más adelante, básicamente cada vez que una comprobación se amás engorrosa o conlleve más tiempo que hacer las operaciones que un cambio supone.
Hemos creado la propiedad texto, pero sabemos que cuando se declara una variable antes de usarse su valor es el que VB asigna por defecto, en el caso de los números (byte, long,etc...) siempre valen 0 y en el caso de los tipo string valen "" es decir una cadena vacía. Naturalmente nosotros podemos querer que ya de entrada cuando se cree una instancia sobre el formulario del cliente éste texto (y en general casi todaslas propiedades), tenga ya un valor concreto y determinado por nuestro interés, etc... bien esto se llama inicialización de variables y nos interesa que sólo ocurra una vez, justo cuando se ha creado la instancia, luego ya sabemos que writepoprties guardará el valor y que readproperties lo recuperará, además si el cliente cambia el valor entonces el que se guardará y luego se leerá será ese valor y no el que nosotros inicialmente le dimos, para que este funcionamiento sea así, por tanto este valor debe ejecutarse una sola vez, justodurante la creación por primera vez de la instancia. es preciso saber que el evento initialice ciertamente crea la instancia, sin embargo cuando se pasa al modo ejecución se destruye y es vuelta a crear es decir se vuelve a ejecutar initialize, parece pués un buen sitio para colocar ese código, pero claro si se carga y descarga 10 veces el formulario, también 10 veces se ejecuta initialize y por tanto 10 veces le estamos asignando nuestro valor 'de capricho' siendo que luego 10 veces también se ejecutará el readproperties... no sería más útil que sólo se ejecutara 1 única vez ?.... pués esto es precisamente lo que hace un procedimiento del usercontrol.... ponemos el código (puede encontrarse el método en la lista de procediemientos de eventos cuando en la lista de la izquierda tenemos seleccionado el usercontrol, si tenemos seleccionado general, aparecerán las propiedades y funciones que vayamos creando ):
Por tanto en la lista izquierda seleccionamos usercontrol y en la lista derecha desplegamos hasta encontrar 'InitProperties'
--- Código: Visual Basic --- Private Sub UserControl_InitProperties() p_Texto = UserControl.Extender.NameEnd Sub En este código hemos asignado el valor que tomará justo cuando el cliente inserta una instancia del control, sólo en esa ocasión ocurre el evento initproperties, por tanto en efecto se ejecutauna sola vez, aunque siempre sucede (cuando sucede) justo después del evento initialize (que de momento no lo tenemos presente, porque de momento no le hemos asignado código).
Hay nuevamente que recordar que hemos añadido una línea comentada, que está destinada a poner algó de código más adelante, de momento podemos cambiar el valor del texto, pero en nuestro botón no aparece el texto, esa línea comentada es justo donde le indicaremos cuando dónde y cómo dibujarse, pero esto loharemos cuando tengamos más elementos de juicio para dibujarlo, concretamente cuando decidamos como funcionará el alineamiento, ya que pensamos también incluir un icono, es lógico que el texto no quede cubierto por dicho icono... justo cuando describamos esas 2 propiedades pasaremos a dibujar el texto, porque ya tendremos definido todos los elementos que afectan al texto.
Ahora comentamos la línea de código: p_Texto = UserControl.Extender.Name
Podríamos haber puesto p_Texto = "Viva Yo y mi botón", y eso sería lo que siempre saldría cuando un cliente pusiera una nueva instancia del control sobre un formulario. Sin embargo lo lógico (y que estamos acostumbrado) es que cuando creamos un boton de microsoft , éste se llama Command1 y si ponemos otro este se llama Command2 y el nombre es precisamente el texto que contiene el botón. Esto es coherente y en esa coherencia nos movemos. Entonces que significa la parte derecha de la igualdad de p_Texto ?. Bien digamos que mientras se crea el control, VB crea y mantiene unas propiedades siempre vinculadas a cada control, esas propiedades están almacenadas en un objeto llamado VbControlExtender y de la que el usercontrol mantiene una instancia a través de la propiedad extender. Para curiosos decir que estas propiedades son aquellas que son propias e intrínsecas del control PERO DE las que el contenedor, realiza un seguimiento.
Ayer hablamos de otro objeto que es una propiedad del usercontrol Ambient. Conviene no confundir uno con otro. Extender, Por ejemplo además del nombre del control, mantiene la propiedad de posición Left, Top y de tamaño Width y Height... si quereis saber con exactitud cuantas y cuales son las propiedades alojadas en el objeto extender, ir al formulario, seleccionad el control en el formulario y mirad las propiedades que tiene el control en la ventana de propiedades, si está en modo ejecución, pués son todas excepto las que nostros hemos creado hasta el momento, y si el control está en modo diseño (rayado) todas esas propiedades nosotros no las hemos creado, es el propio VB quien se encarga de crearlas para cada control y son aquellas que los contenedores deben tener acceso... al contenedor no le importa ni necesita saber si yo tengo una propiedad llamada Icono ó Realte ó AlineaciónTexto... pero si necesita saber la posición del control, para saber si está parcialmente tapado o no, igual que la propiedad visible, etc... Hay también que decir que algunas de estas propiedades son de sólo lectura...
Podemos por tanto decir que un control se compone de 4 tipos de propiedades:
1 - Las que trae la propia plantilla del usercontrol (BackColor, Forecolor, Font, Picture, etc...) estas podemos usarlas o no, son opcionales no estamos obligados a usarlas si no las necesitamos.
2 - Las que se proveen a través de la propiedad Extender (del objeto VBControlExtender del que el usercontrol tiene una instancia accesible a través de la propiedad Extender) estas son obligadas y por tanto las provee el propio VB, como ya dije, Left, Top TooltipText, Name, Tag... ).
3 - Las que provee el contenedor y que tenemos acceso a ellas a través de la propiedad Ambient (del objeto AmbientProperties, como ), estas son todas de sólo lectura para el control.
4 - Y finalmente las que nosotros implementamos, ColorTapiz, ColorTexto, Texto, AlineacionTexto, Icono...
Tanto extender como Ambient no sólo proveen propiedades sino también funciones y eventos.
Finalmente, como hemos hecho con las otras propiedades, nos aseguramos de su persistencia en el tiempo:
--- Código: Visual Basic --- ' en el evento readpropertiesp_Texto = .ReadProperty("Texto", UserControl.Extender.Name) ' en el evento writeproperties.WriteProperty "Texto",p_Texto, UserControl.Extender.Name En lo sucesivo ya no se comentará donde irá cada línea de persistencia de la propiedad... nos debe quedar claro donde corresponde cada una. Si pondré cada línea para que no se nos olvide...
Ahora deberíamos avanzar con una propiedad relacionada directamente con el texto AlineacionTexto:
--- Código: Visual Basic --- ' declaramos la variable que contendrá el valor.Private p_Alineacion As TiposDeAlineacion 'AlignmentConstants ' Guarda el valor de alineación del texto. ' Creamos una enumeración para los valores posibles. Este código debe ir arriba del todo, incluso por encima de la declaración de variables.Public Enum TiposDeAlineacion ALINEACION_IZQUIERDA = 0 ALINEACION_CENTRADA = 1 ALINEACION_DERECHA = 2End Enum ' la declaración de la propiedad, la pondremos debajo de la última declaración de propiedad que pusimosPublic Property Get AlineacionTexto() As TiposDeAlineacion AlineacionTexto = p_AlineacionEnd Property Public Property Let AlineacionTexto(ByVal at As TiposDeAlineacion) If at <> p_Alineacion Then p_Alineacion = at PropertyChanged "AlineacionTexto" ' añadidos posteriores End If End Property ' el establcecimiento inicial de la propiedad dentro del evento initproperties, que ahora va quedando así...Private Sub UserControl_InitProperties() p_Texto = UserControl.Extender.Name p_Alineacion = ALINEACION_IZQUIERDA End Sub ' y finalmente el código de persistencia, cada línea donde corresponde ya debeis saber cual en cual... p_Alineacion = .ReadProperty("AlineacionTexto", TiposDeAlineacion.ALINEACION_IZQUIERDA) .WriteProperty "AlineacionTexto", p_Alineacion, TiposDeAlineacion.ALINEACION_IZQUIERDA Ahora comentamos el código... Vemos que esta propiedad tampoc delega el en objeto usercontrol, por lo que hemos declarado una variable para contener su valor (yo a las variables de apoyo a la propiedades siempre les doy un nombre como p_nombrepropiedad , aunque no suelo poner el nombre de la propiedad completa, aldecidir un nombre de propiedad debe pensarse que debe resultar intuitivo al cliente su significado, por lo que no demos escatimar letras, si pusieramos alineación, el cliente podría tener dudas si se refiere al icono, o al control, pero con alineacionTexto, no da lugar a confusión, como internamente el cleinte no tiene acceso y sólo tengo una propiedad que tenga alineación, en la variable privada no me complico, un sencillo comentario al final de cada declaración, nos puede ayudar a recordar para qué se usa.
Se puede ver que hemos creado una enumeración para los 3 valores de la alineación, realmente vb, tiene una enumeración equivalente llamada AlignmentConstants, pero henmos desistido de usarla por 3 razones:
1º Nos sirve de excusa para aprender a usar enumeraciones como tipo de datos para cuando necesitemos variables.
2º La enumeración de windows no es correlativa de derecha a izquierda, nosotros tenemos izquierda, centro, derecha, con valores 0,1 y 2. Vb usa izquierda, derecha y centro que al menos a mi me parece menos coherente.
3º Mantenemos en mente que estamos creando nuestro control en español (que se jodan los estadounidenses :devil: :devil: :devil: así les damos algo de su propia medicina ... es broma).
En vez de una enumeración realmente para la declaración de la variable podríamos haber usado un tipo de datos byte, pero si usamos la enumeración mientras escribamos código el intellisense, nos ayudará sugiriéndono cual delos valores usar, con ello adelantamos al escribir y tampoco nos obliga a memorizar, esto limita en algo un posible error por ese problema de no haber recordado bien el valor... De modo consecuente, también se ha optado por usar el tipo de datos enumeración para la propiedad, porque así en la ventana de propiedades cuando el cliente decida cambiar el valor se despliega una lista precisamente exponiendo los nombres que les hemos dado a nuestras constantes de alineación.
Respecto de la alineación, nosotros sólo realizaremos una alineación horizontal, queda como ejercicio para el interesado cambiarlo por una alineación también vertical. A este respecto VB tiene otra enumeración AlignConstant, que usa valores semejantes a los anteriores pero que además añade top y bottom.
El código de la propiedad no está realmente bien terminado, si el lector es avispado hace rato que debería estar rondándole en la cabeza lo que vamos a describir a continuación...
Puesto que la alineación sólo tiene 3 posibilidades, que le impide al cliente introducir un valor de por ejemplo 25 ?. Realmente si así lo hiciera ahoraese valor sería introducido y si nuestro cálculo de reposicionamiento del texto estuviera ya listo, nos arrojaría un error, nos falta por tanto un 'hervor' en el código de la asignación de la propiedad.
Ese hervor es controlar que valor se introduce, y al efecto sólo debemos permitir que se introduzca uno de los 3 valoresposibles. Hay 2 formas (coherentes) de resolver esto, si el control es para nosotros, (como lo es) lo solucionaremos de una manera 'llana y sencilla' porque conocemos los detalles y sabemos que entenderemos laforma de funcionar que le vamos a dar, ahora bien si el control fuera un encargo de una empresa, lo que deberíamos hacer realmente es marcar un error cuando se introdujera un valor incorrecto, con ello el cleinte sabría que ha introducido un valor noadmitido y se supone que acabaría por 'conocer' bien el control. Pondremos ambos ejemplos de código.
' el código para un encargo de empresa:
--- Código: Visual Basic --- Public Property Let AlineacionTexto(ByVal at As TiposDeAlineacion) If at <> p_Alineacion Then If (at < 0) or (at > 2) Then Raise Error(380) ' el error 380 tiene asignado el significado de: Se ha asignado a una propiedad un valor incorrecto else p_Alineacion = at PropertyChanged "AlineacionTexto" ' añadidos posteriores End If End If End Property
El código, más inteligente, para gente más inteligente:
--- Código: Visual Basic --- Public Property Let AlineacionTexto(ByVal at As TiposDeAlineacion) If at <> p_Alineacion Then If at < 0 Then at = 0 'ALINEACION_IZQUIERDA ElseIf at > 2 Then at = 2 ' ALINEACION_DERECHA end if p_Alineacion = at PropertyChanged "AlineacionTexto" ' añadidos posteriores End If End Property
En este caso hemos optado por no señalar un error, sino que cualquier valor por encima de 2 lo hacemos igual a 2 y cualquier valor menor que 0 lo hacemos = a 0. Cyuando el cliente está en modo de diseño y escribe una propiedad, se pasa el valor al procedimiento de propiedad, lapropiedad actúa, como no marcamos error, el valor queda correctamente almacenado en la variable p_Alineacion,pero partimos de que el cliente introdujo por ejemplo un valor de 37, entonces qué ocurre ?. a continuación (estando en diseño como hemos dicho), el entorno de vb una vez termina el procedimiento let realiza una llamada (de forma transparente) al procedimiento get, para actualizar el valor en la ventana de propiedades, con lo que en efecto al leer el valor de p_Alineacion, le entregará el valor 2 que sustituirá al 37 que introdujo el cliente.
Todavía podría decir que a la propiedad alineacionTexto le falta un hervor más, pero en este caso no se aplica del todo, así que lo dejamos para más adelante cuando una propiedad lo justifique y si vamos terminando el control y no ha sucedido pués aprovechando esta propiedad locomentaríamos y dejaríamos puerta libre para que qien qiera lo aplicara o no.
Sobre la propiedad alineación sólo nos resta decir que al igual que las propiedades anteriores hemosdejado un comentario como recordatorio de que ahí va algo de código, que mañana cuando por fin pongamos el código para los gráficos se añada el código faltante en estas propiedades.
Ahora vamos a añadir una propiedad Font, para que nuestro cliente pueda decidir que tipo de letra se pone al texto del botón, este es el sitio adecuado para introducirlo (la posición).
--- Código: Visual Basic --- ' el código de la propiedad fuente (Font)Public Property Get Fuente() As IFontDisp Set Fuente = UserControl.FontEnd Property Public Property Set Fuente(ByRef f As IFontDisp) Set UserControl.Font = f PropertyChanged "Fuente" ' añadidos posteriores End Property Comentamos el código para esta propiedad. Esta propiedad es una propiedad de ambiente, por lo que también el usercontrol la contiene, por tanto delegamos en ella para contenerla. Ayer hablamos de que había 3 procedmientos de propiedad y que uno de ellos era para cuando el tipo de datos era un objeto. Este es el caso presente, Font es un objeto, por tanto no puede usarse 'property let' sino que deb usarse 'property Set', véase igualmente como la asignación de objeto utiliza Set para almacenarlo en la variable, el procedmiento Get, igualmente debe usarse un set y el cliente necesariamente deberá alamcenar este objeto en una variable que él designe como objeto sino obtendrá un error.
Luego hemos usado la interfaz Ifontdisp, del mismo modo y razón por la que usamos OLE_COLOR para las propiedades de color, ya que así VB presenta el cuadro de diálogo para que el cliente elija la fuente deseada así como sus propiedades (size, Bold, etc...)
Podríamos dividir la asignación de fuente en cada una delas propiedades del objeto, por ejemplo FontSize por un lado y fontName por otro, FontBold,etc... esto sólo interesa si por ejemplo queremos limitar al usuario de una de dichas características, aunque también en lapropiedad Fuente que hemos declarado, podríamos incluir código para hacer lo mismo, es cuestión de gustos, yo prefiero poner una sola propiedad fuente en vez de varias, que llenen la ventana de propiedades... Finalmente hemos elegido pasar el dato por referencia y no por valor, aunque realmente VB hace una copia de la fuente, ya que si no un cambio originaría un cambio de donde procede, es decir el contenedor.
Ahora como ya habeis aprendido, a asignar un valor por defecto para la propiedad y también darle persistencia:
--- Código: Visual Basic --- Private Sub UserControl_InitProperties() p_Texto = UserControl.Extender.Name p_Alineacion = ALINEACION_IZQUIERDA Set UserControl.Font = Ambient.Font ' no es estrictamente necesario, ya que esto sucede automáticamente End Sub ' persistencia: Set UserControl.Font = .ReadProperty("Fuente", UserControl.Ambient.Font) .WriteProperty "Fuente", UserControl.Font, UserControl.Ambient.Font
A continuación, crearemos una propiedad icono y damos por terminado esta parte de hoy.
--- Código: Visual Basic --- ' en la sección de declaración de variables....Private p_Icono As IPictureDisp ' icono que se presenta alineado enla izquierda ' debajo de todas las propiedades hasta el momento....Public Property Get Icono() As IPictureDisp Set Icono = p_IconoEnd Property Public Property Set Icono(ByRef i As IPictureDisp) Set p_Icono = i PropertyChanged "Icono" ' añadidos posteriores End Property ' persistencia. Set p_Icono = .ReadProperty("Icono", Nothing) .WriteProperty "Icono", p_Icono, Nothing Esta propiedad es fácil y rápido de comentar... al como pasó para el color y la fuente usamos un tipo de datos que nos mostrará una ventana de propiedades como cuadro de diálogo para localizar y elegir la imagen deseada que se usará como icono. Hay que señalar que por defecto el icono es... ninguno, es decir si el cliente no elige un icono, no dibujamos nada. Por ello en el evento initproperties no le asignamos nada, ya es nothing...
Ahora guardar todo y podeis probar los añadidos realizados, claro que al ejecutar de momento sólo nos resulta tangible el colorTapiz ya que de momento no hacemos nada gráfico...
Mañana nos centramos en graficar todo lo que tenemos hasta ahora y que se pueda plasmar en el control. Veremos que alinear el texto tiene consecuencias según exista o no un icono, así mismo decidiremos un margen entre el control y el icono, ya que todavía nos falta una propiedad que realice un relieve sobre el contorno del control...
Nebire:
Comenzamos una nueva parte, en esta parte nos centraremos en completar todas las propiedades y detalles que nos faltan para poder procesar el grafismo que requiere el control.
Nos ponernos manos a la obra, crearemos una propiedad, la que permite que nuestro control esté enabled o disabled, como hablamos en castellano esta propiedad se llamará Activo. Queremos que cuando el control esté deshabilitado cambie de aspecto, para que el usuario final perciba con claridad que el control está deshabilitado y no le queden dudas sobre si 'no funciona'. Sabemos que cuando un control está deshabilitado, lo que en realidad sucede es que el control no envía eventos al cliente, sin embargo es responsabilidad nuestra posibilitar esa impresión de deshabilitado, dado que esto se enmarca también dentro del proceso gráfico es necesario que dispongamos también de esta propiedad y de la forma que queramos señalar que el control está deshabilitado. Más adelante comentamos algunas cosas que podemos hacer, para reflejar esta situación, ahora vamos al código de esta propiedad y luego lo comentamos.
--- Código: Visual Basic --- Public Property Get Activo() As Boolean Activo = UserControl.EnabledEnd Property Public Property Let Activo(ByVal a As Boolean) If a <> UserControl.Enabled Then UserControl.Enabled = a PropertyChanged "Activo" ' añadidos posteriores End If End Property Como se ve, esta propiedad hemos delegado en el usercontrol, nosotros no tenemos que preocuparnos de activar o desactivar eventos, el asunto se lo pasamos al usercontrol y él se encarga de hacerlo, no nos importan los detalles. el código es fácil de entender, si el valor introducido es distinto del actual entonces guardamos el nuevo valor y graficaremos del modo conveniente, para eso al igual como otras propiedades que afectan al grafismo, hemos añadido la dichosa línea comentada a la espera de que sea remplazada por el código conveniente.
Personalmente me gusta poner siempre esta propiedad la primera dentro de las propiedades y la última en los métodos de persistencia... los ponemos ahora.
--- Código: Visual Basic --- ' persistencia... cada línea en su método... UserControl.Enabled = .ReadProperty("Activo", True) .WriteProperty "Activo", UserControl.Enabled, True Aquí podemos ver que el valor por defecto no lo hemos delegado en ambient, sino que preferimos que sea activo por defecto.
La inicialización de esta propiedad no es preciso definirla, por defecto cuando una instancia se crea su estado siempre es activo.
Recordemos que siempre que un contenedor este desactivado, per sé todos los controles contenidos también lo están, podríamos invocar un evento desde ambient de modo que cuando la propiedad enabled del contenedor cambie de estado nos informe, sin embargo no lo haremos. El estado del control no debe ir a la par que el estado del contenedor, es el propio contenedor quien debe concienciar el usuario final que está deshabilitado y que por tanto todos sus controles son inaccesibles... de hecho el contenedor cuando está deshabilitado deja de pasar eventos al control tal como si nosotros mismos lo hubieramos deshabilitado, por lo que alcliente no le llega ningún evento.
Ayer, que se me hizo tarde también dejé sin implementar la propiedad picture que como convinimos, nuestro control tendría la posibilidad de tener 1 imagen como fondo y siempre ajustada al tamaño del control, aún en este caso nos falta decidir el comportamiento de la imagen... proponemos 2 casos (podrían ser más)...
A - La imagen está como fondo, pero cuando pulsamos (down) cambiamos a otra imagen y cuando soltamos el botón vuelve a cambiar a la inicial.
B - La imagen permanece como fondo inalterable, la percepción de pulsación (hundir y soltar) lo reflejaremos de otra manera.
Nos vamos a decantar por la opción B, Con la opción A necesitaríamos duplicar la propiedad y darle un nombre que diferenciara claramente ambas imágenes.
Puesto que hemos elegido la opción A, pondremos el código para dicha propiedad y de paso la usaremos de mod que aprendamos algo más acerca de las propiedades... después comentamos el código de la propiedad.
--- Código: Visual Basic --- ' esta línea con las demás en la sección de declaracion de variables..Private p_Imagen As IPictureDisp ' imagen autoajustable al fondo del control. Public Property Get Imagen() As IPictureDisp Set Imagen = p_ImagenEnd Property Public Property Set Imagen(ByVal i As IPictureDisp) Set p_Imagen = i ' añadidos posteriores End Property ' persistencia ... Set p_Imagen = .ReadProperty("Imagen", nothing) .WriteProperty "Imagen", p_Imagen, nothing ' inicialización (ya sabeis que con esto quiero señalar que va dentro del evento initproperties) Set p_Imagen = UserControl.Picture Set UserControl.Picture = Nothing Primero hemos declarado una propiedad para contener la imagen. No delegamos en la propiedad picture del control, porque al hacerlo implicaría que el control la vuelca al control, ya se dijo en otro apartado más arriba que a veces no nos interesa como sucede una funcionalidad y que en dicho caso nosotros la debemos socorrer como nos interese. Picture no funciona bien, porque automáticamente pega la imagen sobre el control pero en unas determinadas condiciones, para empezar a tamaño real, y para terminar centrada posicionada en la cordenada 0,0 del control, ni una cosa ni la otra nos interesa, la funcionalidad que deseamos es que la imagen ocupe siempre la totalidad del control, tenga éste el tamaño que tenga. Por esto la imagen la guardamos en nuestra propiedad.
Como se ve en le código de la propiedad, al serr un objeto necesitamos el 'formato' SET, también puede verse que no comprobamos si se mete una imagen o está vacío, a efectos prácticos podría no bastar con verificar si el valor recibido tiene o no una imagen sino que además deberíamos verificar si en estos momentos tenemos o no una imagen ya en el control y teniendo 2 variables tendríamos 4 posibilidades de decisión... por ejemplo una posible situación sería que ahora mismo no hubiera imagen y que tampoco se recibiera imagen en cuyo casono sería necesario hacer nada porque nada cambió, en los otros 3 casos deberíamos actualizar, facilitamos la tarea si nos evitamos esas comprobaciones, después de todo a la hora de dibujar suele hacerse esta pregunta de si existe la imagen, luego sería tonto preguntarlo 2 veces... cada uno debe valorar en su situación si el código a ejecutar es más demorado que la verificación teniendo en cuenta las posibilidades que puedan darse de una verificación falida o exitosa.
Finalmente tras lapersistencia que a estas alturas no debería requerir comentarios, vemos que para la inicialización tenemos 2 líneas y un código de entrada 'algo extraño'. Bien, hemos decidido que por defecto el control siempre provea ya de 'fábrica' una imagen, si o si, luego el cliente puede optar por sustituirla o eliminarla. Ahora bien donde alojamos esa imagen ?... por un lado podríamos dejarla en los recursos y en el evento rescatarla, desde luego es una opción, no onstante yo he optado por cargarla ahora mismo durante el diseño en la propiedad picture (esto es teneís que ir a la ventana de la interfaz del contro, localizar la propiedad picture y cargar ahí una imagen que será la que se utilice) , os muestro una imagen de como aparece en mi caso:
Es una imagen tipo gif, conviene que sea algo que no tenga dibujos que al ajustarla al tamaño del botón de la apariencia de estar desproporcionado, es decir evitad que tenga cosas reales, más que una imagen podríamos considerar que es una 'textura'. También se debe tener en cuenta el tamaño de la imagen... considerando que nuestro control una vez compilado tendrá entre 30 y 60 kb, haceros una idea... en mi caso la imagen pesa 1' 73Kb. y hasta 5 kb, tampoco sería pesado...
Bien finalmente el código de esas líneas de inicialización pueden quedar claramente despejadas... en diseño hemos subido una imagen que ahora mismo está guardada en la propiedad picture del usercontrol, cuando el cliente cree una instancia de nuestro botón, se ejecutará esa y solo esa vez el evento initproperties, copia sobre la propiedad p_imagen la imagen contenida en el usercontrol.picture y luego elimina la copia de usercontrol.picture, por lo que en el control ya no aparece, desde ahora se conserva en p_imagen hasta que la persistencia se encargue de guardarla a fichero.
Hay algún detalle más acerca de la imagen, que se dirá un poco más adelante o quizás mañana y también diremos a estas alturas que debemos deciidir en que método visual va a consistir la deshabilitación del control, podríamos poner por ejemplo una imagen gris, podríamos trabajar con el relieve o hacer algún efecto gráfico. en este momento nos hacemos el loco, como si no se nos hubiera pasado aún por la cabeza (durante el diseño de un control siempre hay algún detalle que hasta que no estás sobre él no lo has decidido o bien se te había olvidado, o esperabas a ver que tal combinaba con otras opciones en conjunto, etc... por lo que está cuestión la decidiremos resolveremos en el mismo instante que se nos presente o incluso la demoraremos aún para más adelante.... (eso sí con su línea de recordatorio pertinente).
Ahora, antes de empezar a dibujar nuestro control (tampoco hemos empezado esta parte dando el código de pintar como seguramente se esperaba), para empezar a dibujar ya tenemos bastante detalles nos falta ordenarlos, yo en mi cabeza los tengo ordenados, pero evidentemente cuando uno se enfrenta por primera vez a una situación suele ser necesario anotar los detalles y ordenarlos... esto haremos en este instante que a modo de resumen fija la funcionalidad gráfica.
El control lleva un color de fondo que puede ser cambiado por el cliente en cualquier momento, lleva un texto que puede cambiar de color, además de la fuente y el propio texto, tenemos además que alinearlo sobre el control y para esto nos influye la existenciao no de un icono y finalmente (por ahora) tenemos una imagen... una vez que hemos enumerado los detalles ordenémoslos en capa, lo que tiene que ir más al fondo y lo que irá encima...
Al fondo del todo siempre va el ColorTapiz, luego irá la imagen, encima de esta irá el icono alineado a la izquierda con un margen que describiremos en el próximo párrafo y finalmente encima del todo en primer plano irá el texto. Pués es en este orden que debemos empezar a pintar....
Si colocamos un icono debe ser para que se vea, si no no tiene sentido entonces el icono debería tener unas medidas mínimas, pongamos 16x16 píxeles, luego cuando hagamos un relieve perimetral nos comeremos 2 píxeles por cada lado, y sería agradable a la vista que el icono no fuera completamente pegado al relieve luego estimemos un margen y digamos que sean 2 px. mínimo por arriba, izquierda y abajo... entonces resolvemos que para poner el icono necesitamos una medida como mínimo de 16+4+4= 24 píxeles de alto y lo mismo de ancho, si esa medida es menor aunque tengamos icono no lo pondremos. Y finalmente que haremos cuando el botón sea gigantesco... horizontalmente dijimos desde un principio que lo queríamos alineado a la izquierda, luego el icono estará ubicado en: 2px relieve + 2 px margen = 4px. y verticalmente pocríamos decidir una de 3 opciones:
A - Alineado arriba
B - Centrado verticalmente:
C - Alineado arriba y abajo, es decir se expandería al hacerlo el control.
Examinado la opción C, Hacerlo así haría que laimagen se mostrada se viera distosionada si no creciera también, proporcionalmente en dirección horizontal, pero hacer esto nos limita el acnho para el texto, luego esta opción la descartamos. Examinado la opción A, quedaría bien pero parecería más el botón de un título de algo que la descripción de un botón de ación, luego nos decantamos por la opción B.
Aún respecto del icono queda otra noción, si tenemos un tamaño mínimo de icono, podríamos tener un tamaño máximo de icono, o podría quedar fijo?.... nos decantamos porque sea elcliente quien en este caso decida el tamaño del icono, esto requerirá una nueva propiedad que exponga al cliente la capacidad de modificarlo, de momento creamos sólo la enumeración y la variable privada, luego la propiedad es como otras que ya hemos realizado... En cualquier caso nos parece que si se usa un icono tampoco deberá tener un tamaño infinito, luego parece razonable limitarlo a un tamaño máximo. en estas circunstancias parece que el icono deba ser cuadrado, pero definitivamente muchas veces un icono alargado a lo ancho o alto podría ser más vistoso, y por tanto adoptamos esta idea... Total decidiremos un total de 24-25 tamaños de icono... que ya se presentarán en el código en una enumeración... y un último detalle para este caso, es qué ha de suceder cuando el usuario tiene un alto de botón y fija un tamaño e icono mayor que él... bien en este caso lo que haremos será fijar un alto del botón tal que sea el alto del icono + los márgenes y el relieve es decir altoicono + 8 px, lo mismo para el ancho.
Debe uno darse cuenta como este icono nos está complicando la vida casi más que el resto de propiedades que hemos definido hasta el momento. Nos parece razonable, así que aceptamos seguir adelante, confiamos en que el resultado merece la pena. No hay que olvidar que el objetivo de este control es mostrar como se diseña un control y como se resuelven ciertos detalles, podríamos haber tirado por lo sencillo y fijar que el icono tuviera un tamaño siempre fijo centrado a lo alto del botón. Todos estos detalles sin embargo nos mostrarán como nos apoyamos en otras variables e incluso constantes para lograr nuestros propósitos.
--- Código: Visual Basic --- Public Enum TamañosDeIcono ICONO_16x16 = -3 ICONO_24x16 = -2 ICONO_16x24 = -1 ICONO_24x24 = 0 ICONO_32x24 = 1 ICONO_24x32 = 2 ICONO_32x16 = 3 ICONO_32x32 = 4 ICONO_40x32 = 5 ICONO_32x40 = 6 ICONO_40x40 = 7 ICONO_48x40 = 8 ICONO_40x48 = 9 ICONO_48x48 = 10 ICONO_48x24 = 11 ICONO_64x32 = 12 ICONO_32x64 = 13 ICONO_64x48 = 14 ICONO_48x64 = 15 ICONO_64x64 = 16 ICONO_80x64 = 17 ICONO_64x80 = 18 ICONO_80x80 = 19 ICONO_128x128 = 20End Enum La enumeración de tamaños.... 2 detalles a tener en cuenta, primero debe verse como nuestra enumeración lejos de ser compleja, ayuda al cliente a decidir que tamaño tiene el icono, pués inserto en el nombre de la constante está. el otro detalle es fijarse en como, la enumeración la hemos emepzado e -3 y no en 0, la razón es porque queremos dar la impresión general de que el tamaño de 24x24 es eltamaño normal y por tanto valores menores sería considerarlos pequeños, no hay más razonamiento que ese, y seguro estamos que el cliente tendrá esa misma impresión.
--- Código: Visual Basic --- ' en la sección de declaraciones... todas estas..Private p_TamañoIcono As TamañosDeIcono ' Determina el tamaño de icono... private Const c_MinIcono = -3 ' ancho y alto mínimo del iconoprivate Const c_MaxIcono = 20Private s_AnchoIcono As BytePrivate s_AltoIcono As Byte Primero vemos una variable que guardará la propiedad del tamaño del icono. Luego usamos 2 constantes, que realmente no necesitamos, se pone simplemente para ilustrar su uso (nótese como yo suelo nombrar a las constantes empezando por 'c_', esto cada cual a su gusto. Los valores de las constantes son los límites de la enumeración de tamaños de los iconos, y los usamos exclusivamente para determinan en la propiedad si el cliente introduce un dato válido...
al final tenemos dos variables que son también privadas, no guardan valor de propiedad pero indirectamente guardarán la medida que representa el tamaño de icono elegido.
--- Código: Visual Basic --- Private Sub UserControl_InitProperties() p_Texto = UserControl.Extender.Name p_Alineacion = ALINEACION_IZQUIERDA Set UserControl.Font = Ambient.Font Set p_Imagen = UserControl.Picture Set UserControl.Picture = Nothing p_TamañoIcono = 0 s_AnchoIcono = 24 s_AltoIcono = 24 End Sub Éste es el aspecto que viene presentando a estas alturas el código del evento initproperties, las líneas que ahora nos interesan son las 3 últimas que he separado del resto para mayor claridad... vemos que tal como deseábamos a la variable que da soporte a la propiedad del tamaño deicono le damos el valor 'normal' que hemos hecho coincidir con 0, de hecho por eso mismo, podríamos omitir esa línea las variables numéricas siempre valen 0 al inicializarse... conjuntamente con dicha variable y acorde a su valor las variables que han de mantener el valor de las medidas, deben ser siempre como corresponde, la correspondencia puede mirarse en la enumeración...
Ahora nos fijamos que algo parecido necesitamos hacer en la persistencia de la propiedad.. a la propiedad al final la hemos llamado Iconotamaño.
--- Código: Visual Basic --- ' persistencia, cada línea donde corresponda.. p_TamañoIcono = .ReadProperty("IconoTamaño", 0) Call MedirIcono .WriteProperty "IconoTamaño", p_TamañoIcono, 0
La propiedad ya sabemos guardarla, vemos que en coherencia con el valor por defecto dado en initproperties le damos el mismo valor (vale igual si ponemos el nombre de la constante ICONO_24x24). Debajo hay una línea que llama a un método, en dicho método (cuyo código se expone a continuación) lo que se hace es obtener el valor real que tendrá el icono dado un valor asignado a la propiedad, en initproperties como sabíamos que el valor era 0, las medidas eran 24 de ancho y 24 de alto.
--- Código: Visual Basic --- Private Sub MedirIcono() Select Case p_TamañoIcono Case -3, 1 s_AnchoIcono = 16 Case 1, 3, 4, 6, 13 s_AnchoIcono = 32 Case 5, 7, 9 s_AnchoIcono = 40 Case 8, 10, 11, 15 s_AnchoIcono = 48 Case 12, 14, 16, 18 s_AnchoIcono = 64 Case 17, 19 s_AnchoIcono = 80 Case 20 s_AnchoIcono = 128 Case Else s_AnchoIcono = 24 End Select Select Case p_TamañoIcono Case -3, -2, 3 s_AltoIcono = 16 Case 2, 4, 5, 12 s_AltoIcono = 32 Case 6, 7, 8 s_AltoIcono = 40 Case 9, 10, 14 s_AltoIcono = 48 Case 13, 15, 16, 17 s_AltoIcono = 64 Case 18, 19 s_AltoIcono = 80 Case 20 s_AltoIcono = 128 Case Else s_AltoIcono = 24 End SelectEnd Sub El método para calcular cada medida no tiene más complejidad que la correspondencia siendo que únicamente hemos unificado casos comunes.
También es de resaltar que podríamos haber hecho persistencia sobre las variables así, al leer los valores de las propiedades no tendríamos que saltar a esta rutina y buscar (más que calcular) el valor correspondiente. La razón es que el evento readproperties sólo se ejecuta una única vez justo cuando la aplicación se inicia luego, no hay apenas sacrificio de CPU, después de todo acceder a disco para leer valores seguramente no es más rápido que esto. Finalmente si sólo se usara esta rutina en dicho lugar quizás no merecería la pena las líneas de código empleada, pero dado que también la precisa la propiedad justo está bien...
Ahora rematando la cuestión del icono, su propiedad:
--- Código: Visual Basic --- Public Property Get IconoTamaño() As TamañosDeIcono IconoTamaño = p_TamañoIconoEnd Property Public Property Let IconoTamaño(ByVal ti As TamañosDeIcono) If ti <> p_TamañoIcono Then If (ti >= c_MinIcono) And (ti <= c_MaxIcono) Then p_TamañoIcono = ti Call MedirIcono PropertyChanged "IconoTamaño" ' añadidos posteriores End If End If End Property
Ya creo que la propiedad no merece ser comentada... ved como usamos las constantes, realmente si no las vamos a usar en ninguna parte más, se podrían omitir, no obstante una razón para usar constantes es que te recuerdan para qué sirven por e hecho de tener un nombre, una vez pasado el tiempo al ver el código y ver un -3 y un 20 uno podría preguntarse, porque 20 y no 30... su nombre refleja su significado... ya sabeis que al compilar, las constantes son remplazadas in situ por su valor, y por tanto no hay traducción de valores ya con la aplicación compilada en ejecución, como puede suceder cuando el código es interpretado.
Y como al final nos hemos enrollado con propiedades y el texto se ha hecho largo, cortamos aquí esta parte y finalmente no va aser hoy cuando pintemos aunque ya tenemos todos los detalles... (excepto el relieve, del que de momento hemos decidido que es perimetral, pegado al margen y de 2 px de ancho) para hacerlo.
Navegación
[#] Página Siguiente
Ir a la versión completa