11 Control de scroll

Un control de desplazamiento, o scroll permite al usuario desplazar el contenido de una ventana, o parte de ella. Se encapsula en la clase wxScrollBar.
Aunque tienen la misma apariencia y la misma forma de generar eventos que las barras de desplazamiento que se insertan de forma automática en algunas ventanas, se trata de componentes diferentes, de hecho, estas barras de desplazamiento envían eventos de la clase wxScrollWinEvent, que derivan directamente de wxEvent, y los controles wxScrollBar envían eventos wxScrollEvent, que derivan de wxCommandEvent.
En un capítulo posterior hablaremos de la clase ventana y de cómo se procesan los eventos de desplazamiento en esa clase.
Aunque el funcionamiento de estos controles, desde el punto de vista del usuario, es simple e intuitivo, a la hora de procesar su funcionamiento suelen ser engorrosos, ya que proporcionan muchas formas diferentes de modificar su valor: los botones con flechas en los extremos modifican el valor en unidades, mover el pulgar (thumb) permite establecer un valor arbitrario, y pulsar en el área no ocupada por el pulgar modifica el valor en páginas.
Cada una de estas acciones envía eventos diferentes, además de eventos especiales cuando pulgar alcanza uno de los extremos, etc.
Tal cantidad de diferentes eventos requiere más código para el procesado que otros controles.
Estilos propios
Estos controles sólo admiten dos estilos: wxSB_HORIZONTAL y wxSB_VERTICAL, para crear barras de desplazamiento horizontales y verticales, respectivamente.
Eventos
Aunque la documentación de wxScrollEvent menciona veinte macros diferentes para procesar eventos de estos controles, en realidad no hay tantos tipos de eventos diferentes.
Podemos dividir estas macros en dos grupos:
- Las que empiezan con EVT_SCROLL. Estas macros no diferencian el control que emite el evento. Todos los controles wxScrollBar, si hay más de uno, se procesarán usando las mismas funciones para cada evento.
- Las que empiezan con EVT_COMMAND_SCROLL. Estas macros asignan funciones diferentes para cada wxScrollBar, de modo que se usarán funciones separadas para cada control y evento.
No existen eventos separados para los controles verticales y horizontales. Por ejemplo, la dirección hacia arriba corresponde a la dirección izquierda si el control tiene orientación horizontal. El tope superior corresponde al tope izquierdo en controles horizontales, etc.
A su vez, cada uno de esos grupos se puede dividir en otros:
- Eventos que se emiten cuando el pulgar llega a las posiciones en el extremo del control: EVT_SCROLL_TOP y EVT_SCROLL_BOTTOM, y sus equivalentes EVT_COMMAND_SCROLL_TOP y EVT_COMMAND_SCROLL_BOTTOM.
- Eventos por desplazamientos de una línea, cuando se activa uno de los botones en los extremos del control: EVT_SCROLL_LINEUP y EVT_SCROLL_LINEDOWN, y sus equivalentes EVT_COMMAND_SCROLL_LINEUP y EVT_COMMAND_SCROLL_LINEDOWN.
- Eventos por desplazamiento de una página, cuando se activa una de las áreas junto al pulgar: EVT_SCROLL_PAGEUP y EVT_SCROLL_PAGEDOWN y sus equivalentes EVT_COMMAND_SCROLL_PAGEUP y EVT_COMMAND_SCROLL_PAGEDOWN.
- Eventos cuando el usuario arrastra el pulgar. Estos eventos se envían con más frecuencia mientras se produce ese arrastre: EVT_SCROLL_THUMBTRACK y EVT_COMMAND_SCROLL_THUMBTRACK.
- Eventos cuando el usuario libera el pulgar: EVT_SCROLL_THUMBRELEASE y EVT_COMMAND_SCROLL_THUMBRELEASE.
- Sólo en Windows existe otro evento, EVT_SCROLL_CHANGED, que se emite cuando finaliza el desplazamiento del pulgar, y su equivalente EVT_COMMAND_SCROLL_CHANGED.
- Podemos optar por usar sólo la macro EVT_SCROLL, que agrupará todos los eventos anteriores en una única función, y su equivalente EVT_COMMAND_SCROLL.
Cuando optemos por usar EVT_SCROLL, necesitaremos diferenciar qué tipo de evento hemos recibido. Para ello podemos usar el método GetEventType, heredado de wxEvent.
De forma análoga, si usamos cualquier variante de EVT_COMMAND, si hay más de un control wxScrollBar, necesitaremos determinar qué control ha enviado el evento. Para ello podemos usar el método GetId, también heredado de wxEvent.
Otra alternativa, si sólo tenemos que distinguir entre un control vertical y uno horizontal es usar el método GetOrientation de wxScrollEvent.
Para terminar, la clase wxScrollEvent también dispone de un método para obtener el valor del control, GetPosition.
Insertar controles de desplazamiento
El constructor tiene siete parámetros. Los habituales, ventana padre, identificador, posición, tamaño, estilos, validador y nombre.
m_horizontal = new wxScrollBar(this, idHorizontal, wxPoint(20,0), wxSize(200, wxDefaultCoord), wxSB_HORIZONTAL, sbValidator(&datos.horizontal)); m_horizontal->SetRange(data.hrango); m_horizontal->SetPageSize(10);
Posteriormente tendremos que establecer los valores de rango y el tamaño de página. Es posible que cualquiera de estos valores o ambos tengan que ser modificados durante la ejecución, dependiendo del contenido de la ventana o de los eventos que procesemos.
No hay nada especial que resaltar en este punto.
Validadores
Si queremos intercambiar valores con la ventana que usa estos controles en un diálogo podemos usar validadores para establecer valores iniciales y recuperar los valores de retorno. El dato que almacenan estos controles es un entero, de modo que el validador es bastante simple. La declaración puede tener esta forma:
class sbValidator : public wxValidator { public: sbValidator(int *v = nullptr) : m_valor(v) {} virtual wxObject* Clone() const { return new sbValidator(*this); } virtual bool TransferFromWindow(); virtual bool TransferToWindow(); virtual bool Validate(wxWindow * parent) { return true; } private: int* m_valor; DECLARE_DYNAMIC_CLASS( sbValidator ) DECLARE_EVENT_TABLE() };
Y la definición:
IMPLEMENT_DYNAMIC_CLASS( sbValidator, wxValidator ) BEGIN_EVENT_TABLE( sbValidator, wxValidator ) END_EVENT_TABLE() bool sbValidator::TransferFromWindow() { try { wxScrollBar *sb = dynamic_cast<wxScrollBar*>(m_validatorWindow); *m_valor = sb->GetThumbPosition(); } catch (...) { wxFAIL_MSG( _T("sbValidator sólo funciona con wxScrollBar")); return false; } return true; } bool sbValidator::TransferToWindow() { try { wxScrollBar *sb = dynamic_cast<wxScrollBar*>(m_validatorWindow); sb->SetThumbPosition(*m_valor); } catch (...) { wxFAIL_MSG( _T("sbValidator sólo funciona con wxScrollBar")); return false; } return true; }
Procesar eventos
Lo más habitual es que dejemos que el control se comporte de su forma automática, es decir, los avances y retrocesos de línea añadirán o restarán una unidad a la posición del pulgar y los avances y retrocesos de página añadirán o restarán el valor de una página. Esto, claro, mantiene siempre que el valor del pulgar entre cero y el valor del rango-1.
Por lo tanto, no es frecuente que nuestros programas deban preocuparse por estos eventos.
Si necesitamos que el valor del control afecte directamente al contenido del diálogo de cualquier manera, como en el programa de ejemplo, mostrando el valor en un control estático, o desplazando el contenido de la ventana o de cualquier otro modo, podemos procesar el evento genérico EVT_SCROLL, o si es necesario un tratamiento diferente para cada control de este tipo, procesando el evento EVT_COMMAND_SCROLL.
Tenemos varias formas diferentes de detectar qué control ha provocado el evento:
- Usando la orientación. En el caso de tener un control con orientación vertical y otro con orientación horizontal, podemos diferenciarlos por ese estilo, usando el método GetOrientation de la clase wxScrollEvent.
wxBEGIN_EVENT_TABLE(scroll, wxDialog) EVT_CLOSE(scroll::OnClose) EVT_SCROLL(scroll::OnScroll) wxEND_EVENT_TABLE() ... void scroll::OnScroll(wxScrollEvent& event) { wxString cad; cad << event.GetPosition(); if(event.GetOrientation() == wxHORIZONTAL) { m_textHorizontal->SetLabel(cad); } else { m_textVertical->SetLabel(cad); } }
- Usando el identificador. Siempre es posible obtener el identificador el control que ha provocado un evento usando el método GetId, de la clase wxEvent.
... if(event.GetId() == idHorizontal) { ...
- Usando el puntero al objeto. El método GetEventObject de wxEvent nos devuelve el puntero al objeto que ha provocado el evento. Se puede usar para distinguir el control, o para acceder a sus propiedades para leerlas o modificarlas.
... if(event.GetEventObject () == m_horizontal) { ...
- Usar la macro EVT_COMMAND_SCROLL, y creando un método para manejar los eventos de cada control.
wxBEGIN_EVENT_TABLE(scroll, wxDialog) EVT_CLOSE(scroll::OnClose) EVT_COMMAND_SCROLL(idHorizontal, scroll::OnScrollH) EVT_COMMAND_SCROLL(idVertical, scroll::OnScrollV) wxEND_EVENT_TABLE() ... void scroll::OnScrollH(wxScrollEvent& event) { wxString cad; cad << event.GetPosition(); m_textHorizontal->SetLabel(cad); } void scroll::OnScrollV(wxScrollEvent& event) { wxString cad; cad << event.GetPosition(); m_textVertical->SetLabel(cad); }
Por otra parte, si queremos dar algún tratamiento específico a algunos tipos de eventos, podemos usar cualquiera de los métodos anteriores, y obtener el tipo de evento mediante el método GetEventType.
También podríamos usar cualquiera de las macros para asignar un método a ciertos tipos de eventos, pero teniendo en cuenta que en ese caso no podríamos usar una de las macros genéricas EVT_SCROLL o EVT_COMMAND_SCROLL, ya que estas tienen prioridad, y cada evento sólo se procesa una vez.
wxBEGIN_EVENT_TABLE(scroll, wxDialog) EVT_CLOSE(scroll::OnClose) //EVT_COMMAND_SCROLL(idHorizontal, scroll::OnScrollH) <- no usar EVT_COMMAND_SCROLL(idVertical, scroll::OnScrollV) EVT_COMMAND_SCROLL_PAGEDOWN(idHorizontal, scroll::OnPageH) EVT_COMMAND_SCROLL_PAGEUP(idHorizontal, scroll::OnPageH) wxEND_EVENT_TABLE() ... void scroll::OnPageH(wxScrollEvent& event) { if(event.GetEventType() == wxEVT_SCROLL_PAGEUP) m_horizontal->SetThumbPosition(m_horizontal->GetThumbPosition()+1); else m_horizontal->SetThumbPosition(m_horizontal->GetThumbPosition()-1); }
Ejemplo 11
Nombre | Fichero | Fecha | Tamaño | Contador | Descarga |
---|---|---|---|---|---|
Ejemplo 11 | wx011.zip | 2024-12-02 | 5334 bytes | 8 |