Custom Control


Windows Controls

A Windows Control is an object that can be displayed in the screen. Each control is used for a specific user interaction. For instance, the text box provides a two way text communication between the user and a program. Some of the Windows controls are: the text box, the drop down list, the button, the list view, etc.
Un control de Windows es un objeto que puede ser mostrado en la pantalla. Cada control es usado para la interacción específica con el usuario. Por ejemplo, la caja de texto proporciona una comunicación de texto bidireccional entre el usuario y un programa. Algunos de los controles de Windows son: la caja de texto, la lista desplegable, el botón, la vista de lista, etc.

Custom Control

In some very specific cases, the Windows Controls may not be useful for some applications. In these cases, it is possible to create a Custom Control. Typically a custom control is derived from a standard class modifying some of the class functions. In most cases, the Window_Paint function must be modified so that the user can see how the custom control looks like. All languages, such as Java or C#, allow the programmer implement custom controls; remember that any language you must create a new class and provide a function that paints (or draw) the control.
En algunos casos específicos, los controles de Windows pueden no ser útiles para algunas aplicaciones. En estos casos, es posible crear un control personalizado. Típicamente un control personalizado se deriva de una clase estándar modificando algunas de las funciones de la clase. En la mayoría de los casos, la función Window_Paint debe ser modificada de tal forma que el usuario puede ver el control. Todos los lenguajes, tales como Java o C#, permiten al programador implementar controles personalizados; recuerde que en cualquier lenguaje usted debe crear una clase y proporcionar una función que pinte (o dibuje) el control.

Custom Control States

Most objects in the screen behave similarly. Thus, a custom control must follow some simple rules so that the user can easily interact with the control. A control can, at least, be: enabled or disabled, selected or unselected, with focus or without the focus. Remember that a control with the focus can receive direct input from the keyboard; a control looks different when it has the focus. A control must have, at least, a width and a height. Additionally, a control may have: a background color, a text color, and a font family. A control may also have events such as: click, double click, etc.
La mayoría de los objetos en la pantalla se comportan en forma similar. Así, un control personalizado debe seguir algunas reglas simples para que el usuario pueda fácilmente interactuar con el control. Un control puede, al menos, estar: habilitado o deshabilitado, seleccionado o des-seleccionado, con el focus o sin el focus. Recuerde que un control con el focus puede recibir entrada directa del teclado, un control se ve diferente cuando tiene el focus. Un control debe tener, al menos, un ancho y un alto. Adicionalmente, un control puede tener: un color de fondo, un color para el texto, y una fuente para el texto. Un control puede tener también eventos tales como: clic, doble clic, etc.

Problem 1
Create a Dialog Application using Wintempla called Board to show domino pieces.
Crear una Aplicación de Diálogo usando Wintempla llamada Board para mostrar las piezas del domino.

BoardRun

Problem 2
Create a custom control called Domino: Tools > Add Wintempla Item... > Custom Control . The Wizard will create two files: Domino.h and Domino.cpp. In this specific case, the Domino class is derived from the Win::Window class; in Java you may derive from the JPanel class.

Note for previous versions of Wintempla: From the Microsoft Visual Studio menu: Project > Add New Item..., select Wintempla: Wintempla Custom Control and set the name to Domino.
Cree un control personalizado llamado Domino: Tools > Add Wintempla Item... > Custom Control El asistente creará dos archivos: Domino.h y Domino.cpp. En este caso específico, la clase Domino está derivada de la clase Win::Window; en Java usted puede derivar de la clase JPanel.

Nota para versiones previas de Wintempla: Desde el menú de Microsoft Visual Studio: Project > Add New Item..., seleccione Wintempla: Wintempla Custom Control y fije el nombre a Domino.

DominoAdd

Tip
To remove a custom control from a project, you may remove or delete the respective class files.
Para remover un control personalizado de un proyecto, usted puede remover o borrar los archivos respectivos de la clase.

Problem 3
Edit the Board.h file to include the Domino.h file.
Edite el archivo Board.h para incluir el archivo Domino.h

Board.h
#pragma once //______________________________________ Board.h
#include "resource.h"

#include "Domino.h"
class Board: public Win::Dialog
{
public:
     Board()
     {
     }
     ~Board()
     {
     }
     ...
};

Tip
In order to use a class (or a custom control), you must include the respective class file,
A fin de usar una clase (o un control personalizado), usted debe incluir el archivo respectivo de la clase.

Problem 4
Open the Board.cpp file, and then open Wintempla. From the toolbar selectShow All Controls In ToolboxShow All Controls In Toolbox select the Custom Control tool and draw one Custom Control. Set the name to One. In the class tab, set the class to Domino. Insert another Custom Control. Set the name to Two and the class to Domino. Run the program to test it.
Abra el archivo Board.cpp, entonces abra Wintempla. Desde la barra de herramientas seleccione Show All Controls In ToolboxShow All Controls In Toolbox seleccione la herramienta de Custom Control y dibuje un Custom Control. Fije el nombre en One. En la pestaña de class, fije la class a Domino. Inserte otro Custom Control. Fije el nombre a Two y la clase a Domino. Corre el programa para probarlo.

InsertDominoOne

CustomControlTwo

BoardRunTwo

Spy++

Spy++ is a program that comes together with Microsoft Visual Studio. This tool can be used to spy other programs. With this tool, it is possible to know the class of any object in the screen, or monitor the events of this object.
Spy++ es un programa que viene junto con Microsoft Visual Studio. Esta herramienta puede ser usada para espiar a otros programas. Con esta herramienta, es posible conocer la clase de cualquier objeto en la pantalla o monitorear los eventos de este objeto.

Problem 5
Run the Spy++ program, from Microsoft Visual Studio menu Tools > Spy++. From the Spi++ toolbox use the Find button to check that our control is from the Domino class. Check the Hide Spy++ option. Then Drag the Finder tool to the Domino control.
Corre el programa Spy++, desde el menú de Microsoft Visual Studio Herramientas > Spy++. Desde la barra de herramientas de Spy++ use el botón de Find para verificar que nuestro control es de la clase Domino. Marque la opción Hide Spy++. Entonces arrastre la herramienta Finder hacia el control Domino.

SpyPlusPlus

HideSpy

DominoSpy

Windows Class Background

Each window (or custom control) must have a color background. When a Windows class is registered, the programmer must provide a brush or a color to paint the background of the window (or custom control). The NULL brush can be used to create a transparent window.
Cada ventana (o control personalizado) debe tener un color de fondo. Cuando una clase de Windows se registra, el programador debe proporcionar una brocha o un color para pintar el fondo de la ventana (o control personalizado). La brocha nula (NULL) puede ser usada para crear una ventana transparente.

Problem 6
Change the background color of the Domino class. Modify the Domino.h and Domino.cpp files as shown.
Cambie el color de fondo de la clase Domino. Modifique los archivos Domino.h y Domino.cpp como se muestra.

Domino.h
// Domino.h
#pragma once
#include "resource.h"

class Domino: public Win::Window
{
public:
     Domino();
     ~Domino();
     static CG::Brush brushBackground;
     bool IsEvent(Win::Event& e, int notification);
private:
     const wchar_t * GetClassName(void){return L"DOMINO";}
     static bool isRegistered;
protected:
     //______ Wintempla GUI manager section begin: DO NOT EDIT AFTER THIS LINE
     void Window_Open(Win::Event& e);
     void Window_Paint(Win::Event& e);
     void Window_Size(Win::Event& e);
};

Domino.cpp
// Domino.cpp
#include "stdafx.h"
#include "Domino.h"

bool Domino::isRegistered= false;
CG::Brush Domino::brushBackground;

Domino::Domino()
{     
     if (!this->isRegistered)
     {

          brushBackground.CreateSolid(RGB(250, 255, 220));
          this->RegisterClassEx(
               LoadCursor(NULL, IDC_ARROW),
               brushBackground.GetHBRUSH()); //Background
          this->isRegistered = true;
     }
}
...

BackgroundRun

The Focus

When a window (or control) gains the focus, the function (event) Window_SetFocus is called. When a window (or control) loses the focus, the function (event) Window_KillFocus is called. It is very important to display some changes in a window (or control) when it has or not the focus. In the next problem, the _hasFocus member variable will be used to know when the control has the focus. Observe how the Repaint (InvalidadeRect) function is called to force the window to repaint.
Cuando una ventana (o control) gana el focus, la función (evento) Window_SetFocus se llama. Cuando una ventana (o control) pierde el focus, la función (evento) Window_KillFocus es llamada. Es muy importante mostrar algunos cambios en la ventana (o control) cuando este tiene o no el focus. En el próximo problema, la variable miembro _hasFocus será usada para saber cuando el control tiene el focus. Observe como la función Repaint (InvalidateRect) se llama para forzar a la ventana a repintarse.

Problem 7
Add the Focus to the Domino control. Remember that the Focus indicates that the control may receive input from the keyboard. Remember also that the user press the Tab key to move the Focus to one control to the next one. Modify the Domino.h and Domino.cpp files as shown.
Agregue el Focus al control Domino. Recuerde que el Focus indica que el control puede recibir entrada del teclado. Recuerde también que el usuario presiona la tecla de Tab para mover el Focus de un control al siguiente. Modifique los archivos Domino.h y Domino.cpp como se muestra.

Domino.h
// Domino.h
#pragma once
#include "resource.h"

class Domino: public Win::Window
{
public:
     Domino();
     ~Domino();
     static CG::Brush brushBackground;
     bool IsEvent(Win::Event& e, int notification);
private:
     const wchar_t * GetClassName(void){return L"DOMINO";}
     static bool isRegistered;
     bool _hasFocus;
protected:
     //______ Wintempla GUI manager section begin: DO NOT EDIT AFTER THIS LINE
     void Window_Open(Win::Event& e);
     void Window_Paint(Win::Event& e);
     void Window_Size(Win::Event& e);
     void Window_SetFocus(Win::Event& e);
     void Window_KillFocus(Win::Event& e);
};

Domino.cpp
// Domino.cpp
#include "stdafx.h"
#include "Domino.h"

bool Domino::isRegistered= false;
CG::Brush Domino::brushBackground;

Domino::Domino()
{     
     if (!this->isRegistered)
     {

          brushBackground.CreateSolid(RGB(250, 255, 220));
          this->RegisterClassEx(
               LoadCursor(NULL, IDC_ARROW),
               brushBackground.GetHBRUSH()); //Background
          this->isRegistered = true;
     }
     _hasFocus = false;
}

Domino::~Domino()
{
}

void Domino::Window_Open(Win::Event& e)
{
}

void Domino::Window_Paint(Win::Event& e)
{
     CG::Gdi gdi(e.hWnd, true, false);
     RECT rect;

     //_____________________________________ Paint The Focus
     if (_hasFocus == true)
     {
          CG::Pen pen;
          pen.Create(PS_DOT, 1, RGB(50, 50, 50));
          gdi.Select(pen);
          gdi.SelectNullBrush(); // No filling

          rect.left = 0;
          rect.top = 0;
          rect.right = width;
          rect.bottom = height;
          gdi.Rectangle(rect);
     }

}

void Domino::Window_Size(Win::Event& e)
{
}

void Domino::Window_SetFocus(Win::Event& e)
{
     this->_hasFocus = true;
     this->Repaint(NULL, true);
}

void Domino::Window_KillFocus(Win::Event& e)
{
     this->_hasFocus = false;
     this->Repaint(NULL, true);
}
...

FocusOne

FocusTwo

Properties

To learn more about properties see: Wintempla > OOP > Properties. The problem below illustrates how to use properties. A property can be of any data type (or class): int, double, bool, COLORREF, valarray<double>, etc.
Para aprender más acerca de las propiedades vea: Wintempla > OOP > Properties. El problema de abajo ilustra cómo usar las propiedades. Una propiedad puede ser de cualquier tipo de datos (o clase): int, double, bool, COLORREF, valarray<double>, etc.

Problem 8
The domino piece has two integer values, one that is displayed in the top part of the piece, and another one that is displayed at the bottom of the piece. Add two integer properties: TopValue and BottomValue. Modify the Domino.h and Domino.cpp files as shown. Add two more Domino pieces called Three and Four, and edit the Board.cpp file.
La pieza de domino tiene dos valores enteros, uno que se muestra en la parte superior de la pieza, y otro que se muestra en la parte de abajo de la pieza. Agregue las propiedades enteras: TopValue y BottomValue. Modifique los archivos Domino.h y Domino.cpp como se muestra. Agregue dos más piezas de domino llamadas Three y Four, y edite el archivo Board.cpp.

Domino.h
// Domino.h
#pragma once
#include "resource.h"

class Domino: public Win::Window
{
public:
     Domino();
     ~Domino();
     static CG::Brush brushBackground;
     //______________________________________________ TopValue
     int GetTopValue();
     void SetTopValue(int value);
     __declspec( property( get=GetTopValue, put=SetTopValue ) ) int TopValue;
     //______________________________________________ BottomValue
     int GetBottomValue();
     void SetBottomValue(int value);
     __declspec( property( get=GetBottomValue, put=SetBottomValue ) ) int BottomValue;
     bool IsEvent(Win::Event& e, int notification);
private:
     const wchar_t * GetClassName(void){return L"DOMINO";}
     static bool isRegistered;
     bool _hasFocus;
     int _topValue;
     int _bottomValue;
     void DrawCircle(CG::Gdi& gdi, int x, int y, int radius);
     void DrawValue(CG::Gdi& gdi, int value, int offsetY);
protected:
     //______ Wintempla GUI manager section begin: DO NOT EDIT AFTER THIS LINE
     void Window_Open(Win::Event& e);
     void Window_Paint(Win::Event& e);
     void Window_Size(Win::Event& e);
     void Window_SetFocus(Win::Event& e);
     void Window_KillFocus(Win::Event& e);
};

Domino.cpp
// Domino.cpp
#include "stdafx.h"
#include "Domino.h"

bool Domino::isRegistered= false;
CG::Brush Domino::brushBackground;

Domino::Domino()
{     
     if (!this->isRegistered)
     {

          brushBackground.CreateSolid(RGB(250, 255, 220));
          this->RegisterClassEx(
               LoadCursor(NULL, IDC_ARROW),
               brushBackground.GetHBRUSH()); //Background
          this->isRegistered = true;
     }
     _hasFocus = false;
     _topValue = 1;
     _bottomValue = 2;
}

Domino::~Domino()
{
}

int Domino::GetTopValue()
{
     return _topValue;
}

void Domino::SetTopValue(int value)
{
     if (value < 0 || value > 6) return;
     if (_topValue == value) return;
     _topValue = value;
     Repaint(NULL, true);
}

int Domino::GetBottomValue()
{
     return _bottomValue;
}

void Domino::SetBottomValue(int value)
{
     if (value < 0 || value > 6) return;
     if (_bottomValue == value) return;
     _bottomValue = value;
     Repaint(NULL, true);
}

void Domino::Window_Open(Win::Event& e)
{
}

void Domino::DrawCircle(CG::Gdi& gdi, int x, int y, int radius)
{
     //____________________ Compute surrounding box
     RECT box;
     box.left = x-radius-1;
     box.right = x+ radius+1;
     box.top = y-radius-1;
     box.bottom = y+radius+1;

     // if the circle does not overlap with the RC paint, we do not need to paint
     if (gdi.DoRcPaintOverlap(box) == false) return;
     //
     gdi.Circle(x, y, radius);
}

void Domino::DrawValue(CG::Gdi& gdi, int value, int offsetY)
{
     int x, y;
     const int half = height/2;
     const int radius = (int)(MINIMUM(width, height)/20.0 +0.5);
     if (value == 0) return;

     if (value == 1 || value == 3 || value == 5) // Middle circle
     {
          x = width/2;
          y = offsetY + half/2;
          DrawCircle(gdi, x, y, radius);
     }
     if (value == 2 || value == 3 || value == 4 || value == 5 || value == 6)
     {
          x = width/4;
          y = offsetY + half/4;
          DrawCircle(gdi, x, y, radius);
          //
          x = 3*width/4;
          y = offsetY + 3*half/4;
          DrawCircle(gdi, x, y, radius);
     }
     if (value == 4 || value == 5 || value == 6)
     {
          x = 3*width/4;
          y = offsetY + half/4;
          DrawCircle(gdi, x, y, radius);
          //
          x = width/4;
          y = offsetY + 3*half/4;
          DrawCircle(gdi, x, y, radius);
     }
     if (value == 6)
     {
          x = 3*width/4;
          y = offsetY + half/2;
          DrawCircle(gdi, x, y, radius);
          //
          x = width/4;
          y = offsetY + half/2;
          DrawCircle(gdi, x, y, radius);
     }
}

void Domino::Window_Paint(Win::Event& e)
{
     CG::Gdi gdi(e.hWnd, true, false);
     RECT rect;

     CG::Brush blackBrush(RGB(40, 40, 40));
     const int half = height/2;

     //______________________________________ Line
     gdi.Line(0, half, width, half);
     //
     gdi.Select(blackBrush);
     DrawValue(gdi, _topValue, 0);
     DrawValue(gdi, _bottomValue, half);

     //_____________________________________ Paint The Focus
     if (_hasFocus == true)
     {
          CG::Pen pen;
          pen.Create(PS_DOT, 1, RGB(50, 50, 50));
          gdi.Select(pen);
          gdi.SelectNullBrush(); // No filling

          rect.left = 0;
          rect.top = 0;
          rect.right = width;
          rect.bottom = height;
          gdi.Rectangle(rect);
     }     
}

void Domino::Window_Size(Win::Event& e)
{
}

void Domino::Window_SetFocus(Win::Event& e)
{
     this->_hasFocus = true;
     this->Repaint(NULL, true);
}

void Domino::Window_KillFocus(Win::Event& e)
{
     this->_hasFocus = false;
     this->Repaint(NULL, true);
}

bool Domino::IsEvent(Win::Event& e, int notification)
{
     //if (e.uMsg == WM_NOTIFY)
     //{
     //     NMHDR* pNMHDR= (LPNMHDR)e.lParam;
     //     if (pNMHDR->hwndFrom!=this->GetHWND()) return false;
     //     if (notification == WIN_ALL_EVENTS)
     //     {
     //          // Your code here
     //          return true;
     //     }
     //     if (pNMHDR->code!=notification) return false;
     //     return true;
     //}

     if (e.uMsg!=WM_COMMAND) return false;
     const int id = LOWORD(e.wParam);
     const int notificationd = HIWORD(e.wParam);
     if (id != this->id) return false;
     if (notificationd!=notification) return false;
     return true;
}

Board.cpp
#include "stdafx.h" //________________________________________ Board.cpp
#include "Board.h"

int APIENTRY wWinMain(HINSTANCE hInstance, HINSTANCE , LPTSTR cmdLine, int cmdShow){
     Board app;
     return app.BeginDialog(IDI_BOARD, hInstance);
}

void Board::Window_Open(Win::Event& e)
{
     this->customControlOne.TopValue = 0;
     this->customControlOne.BottomValue = 1;
     //
     this->customControlTwo.TopValue = 2;
     this->customControlTwo.BottomValue = 3;
     //
     this->customControlThree.TopValue = 4;
     this->customControlThree.BottomValue = 5;
     //
     this->customControlFour.TopValue = 6;
     this->customControlFour.BottomValue = 6;
}

BoardFourPieces

Improving Performance

In some cases, it is necessary to improve the performance of a custom control. One easy way to improve performance is by painting only those areas that needs to be painted. In the next problem, it is illustrated how to use Repaint (InvalidateRect) to repaint only a part of the window (or control).
En algunos casos es necesario mejorar el desempeño de un control personalizado. Una forma fácil de mejorar el desempeño es pintando solamente esas áreas que necesitan pintarse. En el problema siguiente se ilustra cómo usar Repaint (InvalidateRect) para repintar solamente una parte de la ventana (o control).

Problem 9
Modify the Domino.cpp file as shown to improve the performance of the Domino custom control.
Modifique el archivo Domino.cpp como se muestra para mejorar el desempeño del control personalizado Domino.

Domino.cpp
...
void Domino::SetTopValue(int value)
{
     if (value < 0 || value > 6) return;
     if (_topValue == value) return;
     _topValue = value;
     // Repaint only the top part of the window
     RECT rect;
     rect.left = 0;
     rect.top = 0;
     rect.right = width;
     rect.bottom = height/2;
     Repaint(&rect, true);
}

int Domino::GetBottomValue()
{
     return _bottomValue;
}

void Domino::SetBottomValue(int value)
{
     if (value < 0 || value > 6) return;
     if (_bottomValue == value) return;
     _bottomValue = value;
     // Repaint only the bottom part of the window
     RECT rect;
     rect.left = 0;
     rect.top = height/2;
     rect.right = width;
     rect.bottom = height;
     Repaint(&rect, true);
}
...


Problem 10
Add the Click Event and the Selected Property to the control. Using Wintempla click in the first Domino control and in the Event tabs, check the Click event as shown. Add the Click event to the other three controls. Modify the Domino.h and Domino.cpp files as shown.
Agregue el Evento de Clic y la propiedad de Selected. Usando Wintempla haga clic en el primer control Domino y en la pestaña de Eventos, marque el evento de Click como se muestra. Agregue el evento de Click a los otros tres controles. Modifique los archivos Domino.h y Domino.cpp como se muestra.

ClickEvent

Domino.h
// Domino.h
#pragma once
#include "resource.h"

class Domino: public Win::Window
{
public:
     Domino();
     ~Domino();
     static CG::Brush brushBackground;
     //______________________________________________ TopValue
     int GetTopValue();
     void SetTopValue(int value);
     __declspec( property( get=GetTopValue, put=SetTopValue ) ) int TopValue;
     //______________________________________________ BottomValue
     int GetBottomValue();
     void SetBottomValue(int value);
     __declspec( property( get=GetBottomValue, put=SetBottomValue ) ) int BottomValue;
     //______________________________________________ Selected
     bool GetSelected();
     void SetSelected(bool selected);
     __declspec( property( get=GetSelected, put=SetSelected ) ) bool Selected;
     //
     bool IsEvent(Win::Event& e, int notification);
private:
     const wchar_t * GetClassName(void){return L"DOMINO";}
     static bool isRegistered;
     bool _selected;
     bool _hasFocus;
     int _topValue;
     int _bottomValue;
     void DrawCircle(CG::Gdi& gdi, int x, int y, int radius);
     void DrawValue(CG::Gdi& gdi, int value, int offsetY);
protected:
     //______ Wintempla GUI manager section begin: DO NOT EDIT AFTER THIS LINE
     void Window_Open(Win::Event& e);
     void Window_Paint(Win::Event& e);
     void Window_Size(Win::Event& e);
     void Window_LButtonDown(Win::Event& e);
     void Window_SetFocus(Win::Event& e);
     void Window_KillFocus(Win::Event& e);
};

Domino.cpp
// Domino.cpp
#include "stdafx.h"
#include "Domino.h"

bool Domino::isRegistered= false;
CG::Brush Domino::brushBackground;

Domino::Domino()
{     
     if (!this->isRegistered)
     {

          brushBackground.CreateSolid(RGB(250, 255, 220));
          this->RegisterClassEx(
               LoadCursor(NULL, IDC_ARROW),
               //(HBRUSH)(COLOR_BTNFACE+1));
               brushBackground.GetHBRUSH()); //Background
          this->isRegistered = true;
     }
     _selected = false;
     _hasFocus = false;
     _topValue = 1;
     _bottomValue = 2;
}

Domino::~Domino()
{
}

int Domino::GetTopValue()
{
     return _topValue;
}

void Domino::SetTopValue(int value)
{
     if (value < 0 || value > 6) return;
     if (_topValue == value) return;
     _topValue = value;
     // Repaint only the top part of the window
     RECT rect;
     rect.left = 0;
     rect.top = 0;
     rect.right = width;
     rect.bottom = height/2;
     Repaint(&rect, true);
}

int Domino::GetBottomValue()
{
     return _bottomValue;
}

void Domino::SetBottomValue(int value)
{
     if (value < 0 || value > 6) return;
     if (_bottomValue == value) return;
     _bottomValue = value;
     // Repaint only the bottom part of the window
     RECT rect;
     rect.left = 0;
     rect.top = height/2;
     rect.right = width;
     rect.bottom = height;
     Repaint(&rect, true);
}

void Domino::Window_Open(Win::Event& e)
{
}

void Domino::DrawCircle(CG::Gdi& gdi, int x, int y, int radius)
{
     //____________________ Compute surrounding box
     RECT box;
     box.left = x-radius-1;
     box.right = x+ radius+1;
     box.top = y-radius-1;
     box.bottom = y+radius+1;

     // if the circle does not overlap with the RC paint, we do not need to paint
     if (gdi.DoRcPaintOverlap(box) == false) return;
     //
     gdi.Circle(x, y, radius);
}

void Domino::DrawValue(CG::Gdi& gdi, int value, int offsetY)
{
     int x, y;
     const int half = height/2;
     const int radius = (int)(MINIMUM(width, height)/20.0 +0.5);
     if (value == 0) return;

     if (value == 1 || value == 3 || value == 5)
     {
          x = width/2;
          y = offsetY + half/2;
          DrawCircle(gdi, x, y, radius);
     }
     if (value == 2 || value == 3 || value == 4 || value == 5 || value == 6)
     {
          x = width/4;
          y = offsetY + half/4;
          DrawCircle(gdi, x, y, radius);
          //
          x = 3*width/4;
          y = offsetY + 3*half/4;
          DrawCircle(gdi, x, y, radius);
     }
     if (value == 4 || value == 5 || value == 6)
     {
          x = 3*width/4;
          y = offsetY + half/4;
          DrawCircle(gdi, x, y, radius);
          //
          x = width/4;
          y = offsetY + 3*half/4;
          DrawCircle(gdi, x, y, radius);
     }
     if (value == 6)
     {
          x = 3*width/4;
          y = offsetY + half/2;
          DrawCircle(gdi, x, y, radius);
          //
          x = width/4;
          y = offsetY + half/2;
          DrawCircle(gdi, x, y, radius);
     }
}

void Domino::Window_Paint(Win::Event& e)
{
     CG::Gdi gdi(e.hWnd, true, false);
     RECT rect;

     CG::Brush blackBrush(RGB(40, 40, 40));
     const int half = height/2;
     //_____________________________________ Paint Selection
     if (Selected== true)
     {
          CG::Brush brushSelected(RGB(230, 230, 180));
          RECT rect;
          rect.left = 0;
          rect.top = 0;
          rect.right = width;
          rect.bottom = height;
          gdi.FillRect(rect, brushSelected);
     }

     //______________________________________ Line
     gdi.Line(0, half, width, half);
     //
     gdi.Select(blackBrush);
     DrawValue(gdi, _topValue, 0);
     DrawValue(gdi, _bottomValue, half);

     //_____________________________________ Paint The Focus
     if (_hasFocus == true)
     {
          CG::Pen pen;
          pen.Create(PS_DOT, 1, RGB(50, 50, 50));
          gdi.Select(pen);
          gdi.SelectNullBrush(); // No filling

          rect.left = 0;
          rect.top = 0;
          rect.right = width;
          rect.bottom = height;
          gdi.Rectangle(rect);
     }
}

void Domino::Window_Size(Win::Event& e)
{
}

void Domino::Window_SetFocus(Win::Event& e)
{
     this->_hasFocus = true;
     this->Repaint(NULL, true);
}

void Domino::Window_KillFocus(Win::Event& e)
{
     this->_hasFocus = false;
     this->Repaint(NULL, true);
}

bool Domino::GetSelected()
{
     return _selected;
}

void Domino::SetSelected(bool selected)
{
     if (_selected == selected) return; //Nothing to do
     _selected = selected;
     this->Repaint(NULL, true);
}

void Domino::Window_LButtonDown(Win::Event& e)
{
     if (Enabled==false) return;
     HWND hWndParent = ::GetParent(hWnd);
     ::SendMessage(hWndParent, WM_COMMAND, MAKEWPARAM(this->id, WIN_CLICK), e.lParam);
     ::SetFocus(hWnd);
}

bool Domino::IsEvent(Win::Event& e, int notification)
{
     //if (e.uMsg == WM_NOTIFY)
     //{
     //     NMHDR* pNMHDR= (LPNMHDR)e.lParam;
     //     if (pNMHDR->hwndFrom!=this->GetHWND()) return false;
     //     if (notification == WIN_ALL_EVENTS)
     //     {
     //          // Your code here
     //          return true;
     //     }
     //     if (pNMHDR->code!=notification) return false;
     //     return true;
     //}

     if (e.uMsg!=WM_COMMAND) return false;
     const int id = LOWORD(e.wParam);
     const int notificationd = HIWORD(e.wParam);
     if (id != this->id) return false;
     if (notificationd!=notification) return false;
     return true;
}

Board.cpp
#include "stdafx.h" //________________________________________ Board.cpp
#include "Board.h"

int APIENTRY wWinMain(HINSTANCE hInstance, HINSTANCE , LPTSTR cmdLine, int cmdShow){
     Board app;
     return app.BeginDialog(IDI_BOARD, hInstance);
}

void Board::Window_Open(Win::Event& e)
{
     this->customControlOne.TopValue = 0;
     this->customControlOne.BottomValue = 1;
     //
     this->customControlTwo.TopValue = 2;
     this->customControlTwo.BottomValue = 3;
     //
     this->customControlThree.TopValue = 4;
     this->customControlThree.BottomValue = 5;
     //
     this->customControlFour.TopValue = 6;
     this->customControlFour.BottomValue = 6;
}

void Board::customControlOne_Click(Win::Event& e)
{
     customControlOne.Selected = (customControlOne.Selected == false);
     customControlOne.TopValue = 6*rand()/RAND_MAX;
     customControlOne.BottomValue = 6*rand()/RAND_MAX;
}

void Board::customControlTwo_Click(Win::Event& e)
{
     customControlTwo.Selected = (customControlTwo.Selected == false);
}

void Board::customControlThree_Click(Win::Event& e)
{
     customControlThree.Selected = (customControlThree.Selected == false);
}

void Board::customControlFour_Click(Win::Event& e)
{
     customControlFour.Selected = (customControlFour.Selected == false);
}

BoardSelected

Problem 11
Modify the Window_Paint function so that the Domino pieces have rounded corners.
Modifique la función Window_Paint para que las piezas de Domino tengan esquinas redondeadas.

Domino.cpp
...
void Domino::Window_Paint(Win::Event& e)
{
     CG::Gdi gdi(e.hWnd, true, false);
     RECT rect;

     CG::Brush brushSelected(RGB(230, 230, 180));
     CG::Brush brushNormal(RGB(250, 255, 220));
     CG::Pen penThin(PS_SOLID, 1, RGB(0, 0, 0));
     CG::Pen penWide(PS_SOLID, 3, RGB(0, 0, 0));

     CG::Brush blackBrush(RGB(40, 40, 40));
     const int half = height/2;
     //_____________________________________ Paint the Piece Border
     rect.left = 0;
     rect.top = 0;
     rect.right = width;
     rect.bottom = height;
     gdi.Select(penWide);
     if (Selected== true)
     {
          gdi.Select(brushSelected);
     }
     else
     {
          gdi.Select(brushNormal);
     }
     gdi.RoundRect(rect, 20, 20);

     //______________________________________ Line
     gdi.Select(penThin);
     gdi.Line(0, half, width, half);
     //
     gdi.Select(blackBrush);
     DrawValue(gdi, _topValue, 0);
     DrawValue(gdi, _bottomValue, half);

     //_____________________________________ Paint The Focus
     if (_hasFocus == true)
     {
          CG::Pen pen;
          pen.Create(PS_DOT, 1, RGB(50, 50, 50));
          gdi.Select(pen);
          gdi.SelectNullBrush(); // No filling

          rect.left = 0;
          rect.top = 0;
          rect.right = width;
          rect.bottom = height;
          gdi.Rectangle(rect);
     }
}
...

RoundDomino

Problem 12
Modify the Domino class so that the custom control appearance merges naturally with the program. First, using Wintempla remove the Border with Sunken Edge property as shown below. Second, remove the static member variable for the brush and modify the constructor to paint the background of the control using a standard color.
Modifique la clase Domine para que la apariencia del control personalizado combine en forma natural con el programa. Primero, usando Wintempla remueva la propiedad de Border with Sunken Edge como se muestra debajo.

SunkenBorder

Domino.h
// Domino.h
#pragma once
#include "resource.h"

class Domino: public Win::Window
{
public:
     Domino();
     ~Domino();
     // DO NOT FORGET TO REMOVE THE brushBackground
     //______________________________________________ TopValue
     int GetTopValue();
     void SetTopValue(int value);
     __declspec( property( get=GetTopValue, put=SetTopValue ) ) int TopValue;
     //______________________________________________ BottomValue
     int GetBottomValue();
     void SetBottomValue(int value);
     __declspec( property( get=GetBottomValue, put=SetBottomValue ) ) int BottomValue;
     //______________________________________________ Selected
     bool GetSelected();
     void SetSelected(bool selected);
     __declspec( property( get=GetSelected, put=SetSelected ) ) bool Selected;
     //
     bool IsEvent(Win::Event& e, int notification);
private:
     const wchar_t * GetClassName(void){return L"DOMINO";}
     static bool isRegistered;
     bool _selected;
     bool _hasFocus;
     int _topValue;
     int _bottomValue;
     void DrawCircle(CG::Gdi& gdi, int x, int y, int radius);
     void DrawValue(CG::Gdi& gdi, int value, int offsetY);
protected:
     //______ Wintempla GUI manager section begin: DO NOT EDIT AFTER THIS LINE
     void Window_Open(Win::Event& e);
     void Window_Paint(Win::Event& e);
     void Window_Size(Win::Event& e);
     void Window_LButtonDown(Win::Event& e);
     void Window_SetFocus(Win::Event& e);
     void Window_KillFocus(Win::Event& e);
};

Domino.cpp
// Domino.cpp
#include "stdafx.h"
#include "Domino.h"

bool Domino::isRegistered= false;
// DO NOT FORGET TO REMOVE THE brushBackground

Domino::Domino()
{     
     if (!this->isRegistered)
     {
          this->RegisterClassEx(
               LoadCursor(NULL, IDC_ARROW),
               (HBRUSH)(COLOR_BTNFACE+1));
          this->isRegistered = true;
     }
     _selected = false;
     _hasFocus = false;
     _topValue = 1;
     _bottomValue = 2;
}

Domino::~Domino()
{
}
...

NoBorderDomino

Uneven Borders

In some graphics, the transition from one object to other object can produce broken or uneven borders. To minimize these effects, there are several techniques. One popular and expensive technique is dithering. On the other hand, a very simple technique consists on reducing the contrast of the graphic in the borders. To do this, the border color must be chosen so that it reduces the transition from one color to the other. For instance, suppose that a black circle is going to be displayed in white background, to hide the border imperfections, the circle must be drawn using a gray border. The same principle can be used to other color combinations.
En algunos gráficos, la transición desde un objeto a otro objeto puede producir rojos o disparejos bordes. Para minimizar estos efectos, hay varias técnicas. Una técnica popular y costosa es el dithering o suavizado. Por otro lado, una técnica muy simple consiste en reducir el contraste del gráfico en los bordes. Para hacer esto, el color del borde debe escogerse para que reduzca la transición de un color al otro. Por ejemplo, suponga que un círculo negro se va a mostrar en fondo blanco, para esconder las imperfecciones del círculo, el círculo se debe dibujar con un borde en color gris. El mismo principio puede ser usado para otras combinaciones de colores.

BorderReduction

Problem 13
Change the colors of the Domino pieces to reduce the contrast and hide the border imperfections.
Cambie los colores de la pieza de Domino para reducir el contraste y esconder las imperfecciones de los bordes.

Domino.cpp
...
void Domino::Window_Paint(Win::Event& e)
{
     CG::Gdi gdi(e.hWnd, true, false);
     RECT rect;

     CG::Brush brushSelected(RGB(230, 230, 180));
     CG::Brush brushNormal(RGB(250, 255, 220));
     CG::Pen penThin(PS_SOLID, 1, RGB(120, 120, 100));
     CG::Pen penWide(PS_SOLID, 3, RGB(120, 120, 100));

     CG::Brush blackBrush(RGB(40, 40, 40));
     const int half = height/2;
     //_____________________________________ Paint the Piece Border
     rect.left = 0;
     rect.top = 0;
     ...


SoftBorders

Flickering

In some applications if a window (or control) is painted several times in a very short period of time, some flickering is produced because the user can see how the objects are being painted in the window. Do eliminate flickering a technique called double buffering can be used. Double buffering creates a bitmap of the same size of the window. Instead of painting in the screen, the program paints in the bitmap. Then, the bitmap is copied to the screen.
En algunas aplicaciones si una ventana (o control) se pinta varias veces en un periodo de tiempo corto, algo de parpadeo se produce porque el usuario puede ver como los objetos son pintados en la ventana. Para eliminar el parpadeo una técnica llamada doble buffer puede ser usada. Doble buffer crea un mapa de bits del mismo tamaño de la ventana. En lugar de pintar en la pantalla, el programa pinta en el mapa de bits. Entonces, el mapa de bits es copiado a la ventana.

Tip
When using double buffering the background color of the window (or control) is not important because the bitmap will be covered the complete window. By default a bitmap is black, thus, the background of the bitmap must be painted with the desired color.
Cuando se usa doble buffer el color de fondo de la ventana (o control) no es importante porque el mapa de bits cubrirá la ventana completa. Por defecto el mapa de bits es negro, así, el fondo del mapa de bits debe ser pintado con el color deseado.

Problem 14
Add double buffering the Domino control. To do this, create the function OnPaintControl and move the code in the function Window_Paint to the function OnPaintControl. Finally, edit the code in the function OnPaintControl as shown below.
Agregue doble buffer al control de Domino. Para esto cree la función OnPaintControl y mueva el código de Window_Paint a la función de OnPaintControl. Finalmente, edite el código de OnPaintControl como se muestra debajo.

Domino.h
// Domino.h
#pragma once
#include "resource.h"

class Domino: public Win::Window
{
public:
     Domino();
     ~Domino();
     //______________________________________________ TopValue
     int GetTopValue();
     void SetTopValue(int value);
     __declspec( property( get=GetTopValue, put=SetTopValue ) ) int TopValue;
     //______________________________________________ BottomValue
     int GetBottomValue();
     void SetBottomValue(int value);
     __declspec( property( get=GetBottomValue, put=SetBottomValue ) ) int BottomValue;
     //______________________________________________ Selected
     bool GetSelected();
     void SetSelected(bool selected);
     __declspec( property( get=GetSelected, put=SetSelected ) ) bool Selected;
     //
     bool IsEvent(Win::Event& e, int notification);
private:
     const wchar_t * GetClassName(void){return L"DOMINO";}
     static bool isRegistered;
     bool _selected;
     bool _hasFocus;
     int _topValue;
     int _bottomValue;
     void DrawCircle(CG::Gdi& gdi, int x, int y, int radius);
     void DrawValue(CG::Gdi& gdi, int value, int offsetY);
     // ___________________________ DOUBLE BUFFERING
     CG::DDBitmap bitmap;
     void OnPaintControl(CG::Gdi& gdi);
protected:
     //______ Wintempla GUI manager section begin: DO NOT EDIT AFTER THIS LINE
     void Window_Open(Win::Event& e);
     void Window_Paint(Win::Event& e);
     void Window_Size(Win::Event& e);
     void Window_LButtonDown(Win::Event& e);
     void Window_SetFocus(Win::Event& e);
     void Window_KillFocus(Win::Event& e);
};

Domino.cpp
// Domino.cpp
#include "stdafx.h"
#include "Domino.h"

bool Domino::isRegistered= false;

Domino::Domino()
{     
     if (!this->isRegistered)
     {
          this->RegisterClassEx(
               LoadCursor(NULL, IDC_ARROW),
               //_________________________________________ DOUBLE BUFFERING
               (HBRUSH)::GetStockObject(NULL_BRUSH)); // Background is transparent
          this->isRegistered = true;
     }
     _selected = false;
     _hasFocus = false;
     _topValue = 1;
     _bottomValue = 2;
}

Domino::~Domino()
{
}
...

void Domino::Window_Paint(Win::Event& e)
{
     CG::Gdi gdi(hWnd, true, false);
     CG::Gdi gdiBitmap(bitmap, gdi.GetRcPaint(), false);
     OnPaintControl(gdiBitmap);
     gdi.DrawDoubleBuffer(bitmap);
}

void Domino::OnPaintControl(CG::Gdi& gdi)
{
     RECT rect;

     CG::Brush brushSelected(RGB(230, 230, 180));
     CG::Brush brushNormal(RGB(250, 255, 220));
     CG::Pen penThin(PS_SOLID, 1, RGB(120, 120, 100));
     CG::Pen penWide(PS_SOLID, 3, RGB(120, 120, 100));
     CG::Brush blackBrush(RGB(40, 40, 40));
     //_________________________________________ DOUBLE BUFFERING
     CG::Brush brushBackground(::GetSysColor(COLOR_BTNFACE));
     CG::Pen penBackground(PS_SOLID, 1, ::GetSysColor(COLOR_BTNFACE));
     const int half = height/2;

     rect.left = 0;
     rect.top = 0;
     rect.right = width;
     rect.bottom = height;
     //_________________________________________ DOUBLE BUFFERING
     //_____________________________________ Paint the Background
     gdi.Select(brushBackground);
     gdi.Select(penBackground);
     gdi.Rectangle(rect);
     //_____________________________________ Paint the Piece Border
     gdi.Select(penWide);
     if (Selected== true)
     {
          gdi.Select(brushSelected);
     }
     else
     {
          gdi.Select(brushNormal);
     }
     gdi.RoundRect(rect, 20, 20);

     //______________________________________ Line
     gdi.Select(penThin);
     gdi.Line(0, half, width, half);
     //
     gdi.Select(blackBrush);
     DrawValue(gdi, _topValue, 0);
     DrawValue(gdi, _bottomValue, half);

     //_____________________________________ Paint The Focus
     if (_hasFocus == true)
     {
          CG::Pen pen;
          pen.Create(PS_DOT, 1, RGB(50, 50, 50));
          gdi.Select(pen);
          gdi.SelectNullBrush(); // No filling

          rect.left = 0;
          rect.top = 0;
          rect.right = width;
          rect.bottom = height;
          gdi.Rectangle(rect);
     }
}

void Domino::Window_Size(Win::Event& e)
{
     bitmap.CreateCompatible(hWnd, width, height);
}

...


RunDoubleBuffering

Size Event (WM_SIZE)

When the main window (or main dialog) is resized the size event (WM_SIZE, Window_Size) is called. During the event, it is possible to resize dynamically all child windows. For instance, it is possible to add the size event to Board class (the main Window class or the main Dialog class) to resize the Domino child windows.
Cuando la ventana principal (o el dialogo principal) cambia de tamaño el evento size (WM_SIZE, Window_Size) es llamado. Durante este evento es posible cambiar de tamaño en forma dinámica las ventanas hijo. Por ejemplo es posible agregar el evento size a la clase Board (la clase de la ventana principal o del dialogo principal) para cambiar de tamaño las ventas hijo de la clase Domino.

Problem 15
Add the size event and the resizing border property to the Border class. 1. Open the Board.h file. 2. Open Wintempla. 3. Make double click anywhere in the window.
Agregue el evento size y la propiedad de borde resizing a la clase Borde. 1. Abra el archivo Board.h. 2. Abra Wintempla. 3. Haga doble clic en cualquier lugar de la ventana.

SizeEvent

ResizingBorder

BoardSize1

BoardSize2

Board.cpp
...
void Board::Window_Size(Win::Event& e)
{
     const int offset = 5;
     const int childWidth = (width - 5*offset)/4;
     const int childHeight = height - 2*offset;
     int x = offset;
     int y = offset;
     customControlOne.Move(x, y, childWidth, childHeight, true);
     x+= (offset+childWidth);
     //
     customControlTwo.Move(x, y, childWidth, childHeight, true);
     x+= (offset+childWidth);
     //
     customControlThree.Move(x, y, childWidth, childHeight, true);
     x+= (offset+childWidth);
     //
     customControlFour.Move(x, y, childWidth, childHeight, true);
     //x+= (offset+childWidth);
     //
     //x = offset;
     //y += (offset+childHeight);
}

Problem 16
Modify the OnPaintControl function to incorporate the Enabled property.
Modifique la función OnPaintControl para incorporar la propiedad de Enabled.

Domino.cpp
...
void Domino::OnPaintControl(CG::Gdi& gdi)
{
     RECT rect;

     CG::Brush brushSelected(Enabled == true ? RGB(230, 230, 180) : RGB(240, 240, 240));
     CG::Brush brushNormal(Enabled == true ? RGB(250, 255, 220) : RGB(220, 220, 220));
     CG::Pen penThin(PS_SOLID, 1, Enabled == true ? RGB(120, 120, 100) : RGB(230, 230, 230));
     CG::Pen penWide(PS_SOLID, 3, Enabled == true ? RGB(120, 120, 100) : RGB(230, 230, 230));
     CG::Brush blackBrush(Enabled == true ? RGB(40, 40, 40) : RGB(180, 180, 180));
     CG::Brush brushBackground(::GetSysColor(COLOR_BTNFACE));
     CG::Pen penBackground(PS_SOLID, 1, ::GetSysColor(COLOR_BTNFACE));
     ...
}
...

Board.cpp
...
void Board::Window_Open(Win::Event& e)
{
     this->customControlOne.TopValue = 0;
     this->customControlOne.BottomValue = 1;
     //
     this->customControlTwo.TopValue = 2;
     this->customControlTwo.BottomValue = 3;
     //
     this->customControlThree.TopValue = 4;
     this->customControlThree.BottomValue = 5;
     //
     this->customControlFour.TopValue = 6;
     this->customControlFour.BottomValue = 6;
     this->customControlFour.Enabled = false;
}
...

EnabledFalse

Tip
To avoid flickering (of child windows) while resizing a Window or Dialog, you must set the "Clip Child Windows" property in the parent of the control to true.
Para evitar el parpadeo (en ventanas hijo) cuando se cuando se cambia de tamaño una Ventana o un Diálogo, usted debe fijar la propiedad "Clip Child Windows" en el padre del control en verdadero.

© Copyright 2000-2019 Wintempla selo. All Rights Reserved. Sep 05 2019. Home