Programmer en 'C' sous Windows
MinPrg04: Mes premiers contrôles Windows.
Windows permet de créer ses propres contrôles, et de les utiliser ensuite pour
élaborer ses interfaces utilisateur graphiques spécialisées et sur mesure.
Fabriquer un nouveau contrôle s'apparente à créer une nouvelle classe de
fenêtre (comme nous l'avons fais dès la première leçon pour notre fenêtre
principale) mais en s'organisant de manière à ce que cette fenêtre s'utilise
comme un contrôle Windows, c'est à dire un composant qui est autonome dans son
affichage et dans la gestion des actions utilisateur, mais qui nous communique le
résultat de ces actions. L'exemple qui suit met en œuvre deux contrôles, un bouton
poussoir qui respecte la philosophie Windows et un panel 2D permettant de modifier 2
paramètres en même temps qui utilise un jeu de fonctions spécifiques et un
mécanisme de communication direct.
Les Fichiers |
Organisation du Source Code |
MiniPrg_04.exe (300KB 3-APR-2008)
MiniPrg_04.zip (92KB 3-APR-2008)
minprg.h
minprg.c
minprg.rc
tools.h
tools.c
myctrl.h
myctrl.c
|
|
Que fait ce programme ? : |
Nous continuons avec l'intégralité du code source de l'exemple précédent,
et nous affichons l'heure système en temps réel grace à un Timer Windows et nous ajoutons nos nouveaux
contrôles dans notre fenêtre principale :
Un bouton poussoir qui ouvre la boite "About"
Un bouton poussoir qui permet de vider la List-Box.
Trois petit boutons poussoirs permettant de changer la valeur du Slider (-24, 0, +24).
un Panel 2D utilisant une bitmap de fond qui change les composantes couleurs Bleues et Rouges de l'affiche de l'heure.
un second Panel 2D sans bitmap de fond qui change les composantes couleurs Rouges et Vertes de l'affiche de l'heure.
|
Les grands principes évoqués : |
L'un des deux grands principes mis en lumière dans notre exemple est l'orientation objet de nos
contrôles, car l'enjeu principale consiste à attacher des données à une
fenêtre windows. En effet quand on crée 5 boutons poussoirs on crée 5
fenêtres, donc 5 objets qui ont des données différentes (label différent,
Ident différent etc...) mais qui utilisent le même code (on parle aussi d'instanciation,
on crée 5 instances du même objet). Donc on a besoin d'organiser la gestion de notre
fenêtre sous forme d'objet multi-instanciable, et le système a prévu cela en mettant
à notre disposition un peu de mémoire libre dans un objet HWND (fenêtre). C'est le
champs "cbWndExtra" qui permet d'allouer cette espace quand on définit la classe de
fenêtre. Cette espace va nous permettre de rattacher à la fenêtre par exemple un pointeur
sur notre structure de données (T_MYCTRL_BUTTONCTX). Ainsi quand la Callback du contrôle sera sollicitée,
nous pourrons récupérer un pointeur sur notre structure (voir TOOL_RecallPointerFromWindow), on
dit que l'on récupère notre contexte, c'est à dire l'ensemble des données qui
nous permettent de définir entièrement le comportement d'une instance d'objet...
Données, interface et objet.
Dans la fabrication d'un composant logiciel, en terme d'organisation il n'y a 3 grands concepts qui doivent
être distinctement définis :
La partie fonctionnelle de l'objet, du composant, son cœur (core en anglais) son moteur, son comportement.
l'interface, qui va permettre de modifier, contrôler, surveiller le comportement de notre objet
(usuellement sous forme d'un jeu de fonctions).
Le protocole et les données qui permettent de discuter avec un composant logiciel.
Dans un contrôle Windows, on retrouve ces 3 principes où le cœur d'un contrôle est
représenté par le code de la fonction Callback et de tout ce qui s'y rattache. L'interface est
constituées de diverses fonctions permettant de créer, détruire, positionner, cacher,
mettre à jour un contrôle, envoyer/recevoir des messages... et le protocole réside dans la
manière dont on va utiliser toutes ses fonctions, dans quel ordre, et en utilisant quelles données.
Quand nous fabriquons nos propre contrôles, si l'on est obligé de respecter la logique de Windows
pour la partie fonctionnelle (la Callback reste le cœur de la gestion d'une fenêtre), on peut se
créer une interface et un protocole spécifique pour communiquer entre nos composants, qui ne passe
ni par les fonctions SendMessage ni par la Callback Windows. C'est un peu ce que
font certaines librairies spécialisées (MFC, Borland, QT etc...) en fournissant une autre interface
que celles proposées par les API Windows. Ainsi peut on changer la manière de mettre en œuvre
un Contrôle Windows, par exemple on ne répond plus à un Message WM_PAINT dans un Callback,
mais on implémente une méthode OnPaint dans un objet. Le fait est que ces librairies, historiquement
faites pour fournir une interface C++, constituent une couche logiciel supplémentaire qui n'apportent pas
vraiment de simplifications.
Nous, nous allons créer notre propre interface pour gérer notre second contrôle
(MYCTRL_CLASSNAME_PANEL), un panel 2D qui permet donc de modifier deux données en même temps
(abscisses et ordonnées), dans le but de simplifier sa mise en œuvre. Plus généralement,
vous verrez qu'il y a souvent grand intérêt à faire une interface spécifique pour
simplifier la mise en œuvre de contrôles complexes (ne serait-ce que pour la clarté et la
lisibilité du code source).
Donc, on réalise pour ce contrôle (2DPanel) une sorte de surcharge de l'objet
HWND Windows en étoffant certaines fonctions. Nous allons fournir une
fonction de création spécifique (MYCTRL_Create2Dpanel) et une méthode de mise à jour
des paramètres (xvalue, yvalue) qui ne passe pas par un SendMessage, mais par une fonction directe
(MYCTRL_Set2DpanelValue). Pour la notification à la fenêtre père, nous ne passerons pas non
plus par la Callback Window et un message WM_COMMAND, mais par une Callback spécifique qui permet de
communiquer nos deux données (xvalue et yvalue).
Le but des deux exemples de contrôles Windows (_CLASSNAME_BUTTON, et _CLASSNAME_PANEL) est
de montrer d'une part la facilité et l'intérêt qu'il y a à se créer des
contrôles spécifiques, sur mesure, et d'autre part à amener progressivement l'idée
que pour chaque objet (au sens large du terme), on peut se créer son interface à soi, parfaitement
adaptée à ses besoins, et plus tard envisager même d'élaborer une couche logicielle
d'abstraction. C'est à dire une interface présentant un jeu de fonctions spécifiques
(à soi) dont le but est généralement de permettre de programmer rapidement un type
d'applications donné. Enfin, et j'espère que cela vous apparaît comme une
révélation, à travers l'objet Windows, que nous mettons en oeuvre de
différentes manières depuis la première leçon, mine de rien, nous avons mis en
lumière la plupart des concepts de la programmation orientée objet (POO).
|
Quelques points du source : |
Nous continuons d'étoffer l'exemple précédent. Les codes sources des 2 contrôles sont
dans les fichiers myctrl.h / myctrl.c. Toujours dans notre fonction CreateOurControls, nous créons 5
boutons poussoirs supplémentaires en utilisant notre classe _CLASSNAME_BUTTON et 2 controles 2DPanel en
utilisant notre fonction spécifique _Create2DPanel. Avec cette fonction nous pouvons définir
plusieurs paramètres pour configurer notre contrôle, nous avons même crée une structure
spécifiques (T_MYCTRL_2DPANELPARAM) pour renseigner divers champs : hbmp (si l'on utilise une Bitmap
de fond), font (pour l'affichage des valeurs), xmin, xmax, ymin, ymax (pour définir l'étendue
logique de notre panel 2D), lpCallback et lpuser (pour dire quelle callback appeler quand l'utilisateur
agit sur le contrôle).
Le bouton poussoir classique
Nous avons d'abord codé un contrôle classique appelé "MyOwnCtrl_Button" qui a un comportement
similaire à un "button" Windows (en gros on a refait le Push Button Windows). L'essentiel de son comportement
se trouve dans la fonction MYCTRL_ButtonCallback qui définit les fonctionnalités de notre bouton, son
affichage et son interaction avec l'utilisateur via les événements souris ou clavier. On retrouve les
mêmes choses que vu précédemment, nous avons déjà fait de l'affichage dans une
fenêtre, nous avons déjà traité des événements souris. La seule
nouveauté réside dans la manière d'organiser les données relatives à une instance
de bouton. Tout se passe ici dans la section WM_CREATE, on alloue de la mémoire pour notre structure de
données (malloc), on l'initialise, et on stocke notre pointeur (lpctx) dans notre fenêtre
via notre fonction TOOL_StorePointerInWindow. Ensuite on récupère notre contexte de données
dès que l'on en a besoin, par exemple en réponse au message WM_SETFONT on appelle notre fonction
TOOL_RecallPointerFromWindow. Ainsi pouvons nous créer plusieurs instances du même contrôle
(et dans notre exemple on crée 5 boutons).
Le contrôle 2DPanel
Pour notre second contrôle : le 2DPanel, même si les fonctionnalités sont différentes,
l'implémentation suit les mêmes principes, sauf que nous commençons par allouer de la mémoire
pour notre structure de données, avant même de créer notre fenêtre (voir fonction MYCTRL_Create2DPanel).
Donc il faut communiquer notre pointeur sur notre structure T_MYCTRL_2DPANELCTX à la fenêtre pour qu'il y soit
associé. Heureusement Microsoft a tout prévu : il suffit de donner notre pointeur en dernier paramètre de la fonction CreateWindow
pour pouvoir le récupérer au WM_CREATE dans le second paramètre de la Callback (LPARAM) qui dans ce cas,
est un pointeur sur un type système dénommé CREATESTRUCT qui contient notre lpctx. Ensuite tout se
passe comme pour notre premier contrôle, on utilise nos fonctions TOOL_StorePointerInWindow / TOOL_RecallPointerFromWindow
pour gérer notre pointeur de contexte.
Toujours pour notre second contrôle, nous ne passons pas par la boucle de messages pour notifier à la
fenêtre parent que l'utilisateur a modifié les valeurs courantes du 2DPanel. Nous utilisons notre Callback
à nous, dont le pointeur nous a été transmis dans les paramètres de création du contrôle.
L'intérêt d'utiliser une Callback spécifique est double : on peut communiquer ce qu'on veut, sans subir
les contraintes de la fonction SendMessage, et on ne passe pas par la boucle de messages, donc on gagne du temps. Beaucoup de
composants logiciels utilisent ce mécanisme avec des Callback spécifiques, car c'est le meilleur moyen de permettre
une communication bi-directionnelle hiérarchisée entre l'applicatif et les objets qu'il utilise.
Affichage de l'heure temps-réel
Pour l'affichage de l'heure, on crée dans la section WM_CREATE de notre fenêtre principale un Timer Windows
( SetTimer) à 30ms qui demande au système de nous envoyer le message WM_TIMER environ 30 fois par seconde.
Dès que nous recevons ce message, nous affichons l'heure système (voir fonction DisplayTimerStuff). Notons
que le message WM_TIMER passe aussi par la boucle de messages et qu'aucun Thread n'est créé, notre application reste
'mono-thread', ce qui garantie que deux parties de notre code ne peuvent pas être exécutées en même temps.
Quand nous affichons notre heure système par exemple, il n'y a aucune possibilité pour que dans le même temps
nous recevions un appel de notre contrôle 2DPanel qui modifie la couleur de ce même affichage. Comme notre Timer passe
par la boucle de messages, il peut subir des irrégularités car il n'est pas plus prioritaire qu'un autre message.
Le fait de manipuler notre fenêtre par exemple peut temporairement geler l'affichage de l'heure.
NOTE : une fois notre application lancée, l'usage de l'outil Microsoft Spy++ (ou équivalent)
permet de voir la liste des Handle de fenêtre (HWND) attachés à notre
Thread Primaire. Nos contrôles apparaissent tels qu'on les a défini dans myctrl.h :
"MyOwnCtrl_Button" et "MyOwnCtrl_Panel" (la liste affiche les WindowText et ClassName de chaque fenêtre Windows).
|
Questions / Réponses: |
Pourquoi la classe _CLASSNAME_PANEL (voir fonction MYCTRL_InitLib) est-elle définie avec le style CS_DBLCLKS ?
Pour pouvoir recevoir le message de double-clic souris (WM_LBUTTONDBLCLK). Autrement la fenêtre ne le recoit pas.
Dans notre cas, nous pourrions par exemple repositionner le curseur de notre 2Dpanel au milieu quand l'utilisateur fait un double-clic.
Pourquoi nous continuons de recevoir le message WM_TIMER quand nous ouvrons une boite de dialogue type MessageBox ?
En effet les "Message-Box" (boite 'About' ou boite de confirmation de fermeture de notre application) sont des boites de
dialogues dites 'modales' qui bloquent l'exécution du programme jusqu'à ce que l'utilisateur réponde en
fermant cette fenêtre. Si notre code attend la réponse de l'utilisateur à travers l'appel à MessageBox(),
il ne peut plus faire tourner notre boucle de messages (celle que l'on a définie dans notre fonction WinMain() dès
la première leçon). En fait quand on ouvre une boite de dialogue modale, on fait rentrer notre code dans une
autre boucle de Messages, celle de la Modal-Dialog-Box et c'est cette boucle qui va permettre la diffusion des messages
(tels que WM_TIMER, WM_PAINT etc... ) à notre Callback de fenêtre principale via sa fonction DispatchMessage()
|
Source : minprg.c (quelques parties nouvelles) |
/*--------------------------------------------------------------------------------*/
/* 'C' Programming Under WIN32 V.Burel (c)2008*/
/* */
/* WEB : http://pagesperso-orange.fr/vb-audio/fr/pub/programming/index.htm */
/*--------------------------------------------------------------------------------*/
/* This MinPrg is a minimal program under windows (like a Hello World) */
/* To demonstrate how to make custom Windows Control */
/*--------------------------------------------------------------------------------*/
/* To compile With Microsoft VC2005 you need to create an empty Win32 project */
/* with the following options : */
/* - Configuration Properties / General : Char Set = NOT SET */
/* - Configuration Properties / C/C++ / Preprocessor : _CRT_SECURE_NO_DEPRECATE */
/*--------------------------------------------------------------------------------*/
[...]
static long CreateOurControls(LPT_APP_CONTEXT lpapp, HWND hw)
{
T_MYCTRL_2DPANELPARAM param_2dpanel;
char sss[256];
//create a list box on the right.
lpapp->hw_ListBox=CreateWindowEx(WS_EX_CLIENTEDGE,"listbox",NULL,
WS_CHILD | WS_VISIBLE | ES_LEFT | WS_TABSTOP | WS_VSCROLL
| LBS_NOTIFY | LBS_NOINTEGRALHEIGHT,
507,0,100,300,
hw,(HMENU)IDC_LISTBOX,G_hinstance,NULL);
if (lpapp->hw_ListBox ==NULL) return -1;
SendMessage(lpapp->hw_ListBox,WM_SETFONT,(WPARAM)lpapp->font_small,MAKELPARAM(1,0));
//Create an Edit Box on the bottom :
strcpy(sss,"0.0");
lpapp->hw_EditBox=CreateWindowEx(WS_EX_CLIENTEDGE,"edit",sss,
WS_CHILD | WS_VISIBLE | ES_LEFT | WS_TABSTOP,
10,150,100,30,
hw,(HMENU)IDC_EDITBOX,G_hinstance,NULL);
if (lpapp->hw_EditBox ==NULL) return -1;
SendMessage(lpapp->hw_EditBox,WM_SETFONT,(WPARAM)lpapp->font_med,MAKELPARAM(1,0));
SendMessage(lpapp->hw_EditBox,EM_SETMARGINS,EC_LEFTMARGIN | EC_RIGHTMARGIN,(LPARAM) MAKELONG(2,2));
//Create a combo box
lpapp->hw_ComboBox=CreateWindowEx(WS_EX_CLIENTEDGE,"combobox",NULL,
WS_VSCROLL | WS_CHILD | WS_VISIBLE | WS_TABSTOP | CBS_DROPDOWNLIST,
120,150,150,300,
hw,(HMENU)IDC_COMBOBOX,G_hinstance,NULL);
if (lpapp->hw_ComboBox ==NULL) return -1;
SendMessage(lpapp->hw_ComboBox,WM_SETFONT,(WPARAM)lpapp->font_med,MAKELPARAM(1,0));
strcpy(sss,"Min Value");
SendMessage(lpapp->hw_ComboBox,CB_ADDSTRING,0,(LPARAM)sss);
strcpy(sss,"Intermediate...");
SendMessage(lpapp->hw_ComboBox,CB_ADDSTRING,0,(LPARAM)sss);
strcpy(sss,"Max Value");
SendMessage(lpapp->hw_ComboBox,CB_ADDSTRING,0,(LPARAM)sss);
SendMessage(lpapp->hw_ComboBox,CB_SETCURSEL ,1,0L);
//Create Push Button
lpapp->hw_PushButton=CreateWindow("button","About Box",WS_CHILD | WS_VISIBLE | WS_TABSTOP,
300,150,150,30,
hw,(HMENU)IDM_ABOUT,G_hinstance,NULL);
if (lpapp->hw_PushButton ==NULL) return -1;
SendMessage(lpapp->hw_PushButton,WM_SETFONT,(WPARAM)lpapp->font_med,MAKELPARAM(1,0));
//our button
lpapp->hw_mybutton_1=CreateWindow(MYCTRL_CLASSNAME_BUTTON,"Display About Box",
WS_CHILD | WS_VISIBLE | WS_TABSTOP,
10,160,150,30,
hw,(HMENU)IDM_ABOUT,G_hinstance,NULL);
lpapp->hw_mybutton_2=CreateWindow(MYCTRL_CLASSNAME_BUTTON,"Reset List Box",
WS_CHILD | WS_VISIBLE | WS_TABSTOP,
10,200,150,30,
hw,(HMENU)IDM_RESETLISTBOX,G_hinstance,NULL);
SendMessage(lpapp->hw_mybutton_2,WM_SETFONT,(WPARAM)lpapp->font_med,MAKELPARAM(1,0));
//our 3 small button
lpapp->hw_mybutton_3=CreateWindow(MYCTRL_CLASSNAME_BUTTON,"-24",WS_CHILD | WS_VISIBLE | WS_TABSTOP,
10,240,45,20,
hw,(HMENU)IDM_BUTTONMIN,G_hinstance,NULL);
SendMessage(lpapp->hw_mybutton_3,WM_SETFONT,(WPARAM)lpapp->font_small,MAKELPARAM(1,0));
lpapp->hw_mybutton_4=CreateWindow(MYCTRL_CLASSNAME_BUTTON,"0",WS_CHILD | WS_VISIBLE | WS_TABSTOP,
60,240,45,20,
hw,(HMENU)IDM_BUTTONMID,G_hinstance,NULL);
SendMessage(lpapp->hw_mybutton_4,WM_SETFONT,(WPARAM)lpapp->font_small,MAKELPARAM(1,0));
lpapp->hw_mybutton_5=CreateWindow(MYCTRL_CLASSNAME_BUTTON,"+24",WS_CHILD | WS_VISIBLE | WS_TABSTOP,
110,240,45,20,
hw,(HMENU)IDM_BUTTONMAX,G_hinstance,NULL);
SendMessage(lpapp->hw_mybutton_5,WM_SETFONT,(WPARAM)lpapp->font_small,MAKELPARAM(1,0));
//our 2D panel
TOOL_MemZERO(¶m_2dpanel, sizeof(T_MYCTRL_2DPANELPARAM ));
param_2dpanel.hbmp=lpapp->bmp_panel;
param_2dpanel.font=lpapp->font_small;
param_2dpanel.xmin=0.0f;
param_2dpanel.xmax=200.0f;
param_2dpanel.ymin=0.0f;
param_2dpanel.ymax=+200.0f;
param_2dpanel.lpCallback=My2DPanelCallback;
param_2dpanel.lpuser=lpapp;
lpapp->hw_panel1=MYCTRL_Create2DPanel(hw, IDC_2DPANEL_1, 200,160, ¶m_2dpanel);
param_2dpanel.hbmp=NULL;
lpapp->hw_panel2=MYCTRL_Create2DPanel(hw, IDC_2DPANEL_2, 320,165, ¶m_2dpanel);
MYCTRL_Set2DPanelValue(lpapp->hw_panel2, (float)lpapp->color_rrr, (float)lpapp->color_ggg);
MYCTRL_Set2DPanelValue(lpapp->hw_panel1, (float)lpapp->color_bbb, (float)lpapp->color_rrr);
//Create Contextual Popup Menu
lpapp->hPopupMenu=CreatePopupMenu();
if (lpapp->hPopupMenu ==NULL) return -1;
strcpy(sss,"info...");
AppendMenu(lpapp->hPopupMenu,MF_STRING,IDM_CTXPOPUP_INFO,sss);
strcpy(sss,"See VB's Web Pages...");
AppendMenu(lpapp->hPopupMenu,MF_STRING,IDM_CTXPOPUP_WEB,sss);
strcpy(sss,"E-mail me...");
AppendMenu(lpapp->hPopupMenu,MF_STRING,IDM_CTXPOPUP_MAIL,sss);
return 0;
}
[...]
void __stdcall My2DPanelCallback(void * lpuser,long Ident,float xvalue, float yvalue)
{
LPT_APP_CONTEXT lpapp;
lpapp=(LPT_APP_CONTEXT)lpuser;
switch(Ident)
{
case IDC_2DPANEL_1:
lpapp->color_bbb = (long)xvalue;
lpapp->color_rrr = (long)yvalue;
MYCTRL_Set2DPanelValue(lpapp->hw_panel2, (float)lpapp->color_rrr, (float)lpapp->color_ggg);
break;
case IDC_2DPANEL_2:
lpapp->color_rrr = (long)xvalue;
lpapp->color_ggg = (long)yvalue;
MYCTRL_Set2DPanelValue(lpapp->hw_panel1, (float)lpapp->color_bbb, (float)lpapp->color_rrr);
break;
}
}
[...]
|
Source : tools.c (quelques parties nouvelles) |
[...]
void TOOL_StorePointerInWindow(HWND hw,LPVOID pp)
{
#ifdef WIN32
SetWindowLong(hw,0,(LONG)pp);
#endif
}
LPVOID TOOL_RecallPointerFromWindow(HWND hw)
{
#ifdef WIN32
return (LPVOID)GetWindowLong(hw,0);
#endif
}
[...]
|
Source : myctrl.c |
/*****************************************************************/
/* MYCTRL V.Burel */
/*****************************************************************/
/* Contains 2 custom windows control */
/*****************************************************************/
#ifndef __cplusplus
#ifndef STRICT
#define STRICT
#endif
#endif
#include <windows.h>
#include <stdio.h>
#include "myctrl.h"
#include "tools.h"
static HINSTANCE G_hinstance=NULL;
/*---------------------------------------------------------------*/
/* BASIC BUTTON */
/*---------------------------------------------------------------*/
typedef struct tagMYCTRL_BUTTONCTX
{
HWND hw;
HFONT font;
BOOL status;
HWND LastCapture;
long action;
} T_MYCTRL_BUTTONCTX, *PT_MYCTRL_BUTTONCTX, *LPT_MYCTRL_BUTTONCTX;
#define ACTION_BY_MOUSE 1
#define ACTION_BY_KEYBOARD 2
static void DrawMyButton(HWND hw,HDC dc)
{
LPT_MYCTRL_BUTTONCTX lpctx;
char sss[256]="-";
RECT rect;
HPEN oldpen;
HBRUSH oldbrush;
HFONT oldfont;
lpctx=TOOL_RecallPointerFromWindow(hw);
if (lpctx == NULL) return;
GetClientRect(hw,&rect);
SetBkMode(dc,TRANSPARENT);
oldfont=(HFONT)SelectObject(dc,lpctx->font);
if (lpctx->status == TRUE)
{
oldpen=(HPEN)SelectObject(dc,GetStockObject(BLACK_PEN));
oldbrush=(HBRUSH)SelectObject(dc,GetStockObject(BLACK_BRUSH));
SetTextColor(dc,RGB(255,255,255));
}
else
{
oldpen=(HPEN)SelectObject(dc,GetStockObject(WHITE_PEN));
oldbrush=(HBRUSH)SelectObject(dc,GetStockObject(WHITE_BRUSH));
SetTextColor(dc,RGB(0,0,0));
}
//Draw Background first
Rectangle(dc,rect.left,rect.top,rect.right,rect.bottom);
SelectObject(dc,GetStockObject(NULL_BRUSH));
if (lpctx->status == FALSE) SelectObject(dc,GetStockObject(BLACK_PEN));
else SelectObject(dc,GetStockObject(WHITE_PEN));
RoundRect(dc,rect.left+1,rect.top+1,rect.right-1,rect.bottom-1,4,4);
//Draw Text
GetWindowText(hw,sss,255);
sss[255]=0;
DrawText(dc,sss,(int)strlen(sss),&rect,DT_SINGLELINE | DT_CENTER | DT_VCENTER);
//draw focus if any
if (GetFocus() == hw)
{
rect.left +=3;
rect.top+=3;
rect.right -=3;
rect.bottom -=3;
DrawFocusRect(dc,&rect);
}
//deselect GDI object
SelectObject(dc,oldpen);
SelectObject(dc,oldbrush);
SelectObject(dc,oldfont);
}
LRESULT CALLBACK MYCTRL_ButtonCallback(HWND hw,//handle of the window.
UINT msg, //Message Ident.
WPARAM p1, //parameter 1.
LPARAM p2) //parameter 2
{
RECT rect;
HWND hparent;
long hwIdent,xx,yy;
LPT_MYCTRL_BUTTONCTX lpctx;
HDC dc;
PAINTSTRUCT ps;
switch (msg)
{
//here we create our structure
case WM_CREATE:
lpctx=(LPT_MYCTRL_BUTTONCTX)malloc(sizeof(T_MYCTRL_BUTTONCTX));
if (lpctx == NULL) return -1;
TOOL_MemZERO(lpctx,sizeof(T_MYCTRL_BUTTONCTX));
lpctx->hw=hw;
lpctx->font=GetStockObject(DEFAULT_GUI_FONT);
lpctx->status = FALSE;
TOOL_StorePointerInWindow(hw,lpctx);
break;
//on mouse click we get the focus.
case WM_MOUSEACTIVATE:
if (GetFocus()!=hw) SetFocus(hw);
return MA_ACTIVATE;
//we can even implement SET_FONT.
case WM_SETFONT:
lpctx=TOOL_RecallPointerFromWindow(hw);
if (lpctx != NULL)
{
lpctx->font = (HFONT)p1;
if (LOWORD(p2) != 0) InvalidateRect(hw,NULL,FALSE);
}
break;
//on Focus change we redraw the control
case WM_SETFOCUS:
case WM_KILLFOCUS:
InvalidateRect(hw,NULL,FALSE);
return 0;
//here we draw all the control
case WM_PAINT:
dc=BeginPaint(hw,&ps);
DrawMyButton(hw,dc);
EndPaint(hw,&ps);
return 0;
//mouse behavior
case WM_LBUTTONDOWN:
lpctx=TOOL_RecallPointerFromWindow(hw);
if (lpctx == NULL) break;
if (lpctx->action != 0) break;
lpctx->action=ACTION_BY_MOUSE;
lpctx->status=TRUE;
lpctx->LastCapture=SetCapture(hw);
dc=GetDC(hw);
DrawMyButton(hw,dc);
ReleaseDC(hw,dc);
break;
case WM_LBUTTONUP:
lpctx=TOOL_RecallPointerFromWindow(hw);
if (lpctx == NULL) break;
if (lpctx->action != ACTION_BY_MOUSE) break;
if (lpctx->status == FALSE) break;
xx=(long)(short int)LOWORD(p2);
yy=(long)(short int)HIWORD(p2);
//redraw button
ReleaseCapture();
if (lpctx->LastCapture != NULL) SetCapture(lpctx->LastCapture);
lpctx->status = FALSE;
lpctx->action = 0;
dc=GetDC(hw);
DrawMyButton(hw,dc);
ReleaseDC(hw,dc);
//notify parent if ok
GetClientRect(hw,&rect);
if ((xx>= rect.left) && (xx<rect.right) && (yy>=rect.top) && (yy<rect.bottom))
{
hparent=(HWND)GetWindowLong(hw,GWL_HWNDPARENT);
hwIdent=(long)GetWindowLong(hw,GWL_ID);
SendMessage(hparent, WM_COMMAND, hwIdent,0);
}
break;
//Keyboard Behavior
case WM_KEYDOWN:
if ((p1 == VK_SPACE) || (p1 == VK_RETURN))
{
lpctx=TOOL_RecallPointerFromWindow(hw);
if (lpctx == NULL) break;
if (lpctx->action != 0) break;
lpctx->action=ACTION_BY_KEYBOARD;
//redraw button
lpctx->status = TRUE;
dc=GetDC(hw);
DrawMyButton(hw,dc);
ReleaseDC(hw,dc);
//notify parent
hparent=(HWND)GetWindowLong(hw,GWL_HWNDPARENT);
hwIdent=(long)GetWindowLong(hw,GWL_ID);
SendMessage(hparent, WM_COMMAND, hwIdent,0);
}
return 0;
case WM_KEYUP:
if ((p1 == VK_SPACE) || (p1 == VK_RETURN))
{
lpctx=TOOL_RecallPointerFromWindow(hw);
if (lpctx == NULL) break;
if (lpctx->action != ACTION_BY_KEYBOARD) break;
lpctx->status = FALSE;
lpctx->action = 0;
//redraw button
dc=GetDC(hw);
DrawMyButton(hw,dc);
ReleaseDC(hw,dc);
}
return 0;
//close and destroy.
case WM_CLOSE:
DestroyWindow(hw);
return 0;
case WM_DESTROY:
lpctx=TOOL_RecallPointerFromWindow(hw);
if (lpctx != NULL)
{
free(lpctx);
TOOL_StorePointerInWindow(hw,NULL);
}
break;
}
return DefWindowProc(hw,msg,p1,p2);
}
/*---------------------------------------------------------------*/
/* 2D PANEL */
/*---------------------------------------------------------------*/
typedef struct tagMYCTRL_2DPANELCTX
{
HWND hw;
long dx,dy;
float xvalue,yvalue;
long action;
HWND LastCapture;
HBITMAP tempbmp;
T_MYCTRL_2DPANELPARAM param;
} T_MYCTRL_2DPANELCTX, *PT_MYCTRL_2DPANELCTX, *LPT_MYCTRL_2DPANELCTX;
static void Draw2DPanel(LPT_MYCTRL_2DPANELCTX lpctx, HDC dc)
{
RECT rect;
char sss[64];
LPT_MYCTRL_2DPANELPARAM lpp;
HFONT oldfont;
HPEN oldpen;
HBRUSH oldbrush;
HBITMAP oldbmp;
HDC dcmem;
float ratio;
long xx,yy;
long xf,yf;
lpp=&(lpctx->param);
//we don't draw directly in screen.
//to avoid flickering we first build a bitmap in memory
dcmem=CreateCompatibleDC(dc);
if (lpctx->tempbmp ==NULL) lpctx->tempbmp=CreateCompatibleBitmap(dc,lpctx->dx,lpctx->dy);
oldbmp=(HBITMAP)SelectObject(dcmem,lpctx->tempbmp);
//Draw background per bitmap if any
if (lpctx->param.hbmp != NULL)
{
TOOL_DrawBitmap(dcmem, 0, 0, lpctx->param.hbmp);
}
//Draw background per drawing function
else
{
oldpen=(HPEN)SelectObject(dcmem,GetStockObject(BLACK_PEN));
oldbrush=(HBRUSH)SelectObject(dcmem,GetStockObject(WHITE_BRUSH));
Rectangle(dcmem,0,0,lpctx->dx,lpctx->dy);
SelectObject(dcmem,oldpen);
SelectObject(dcmem,oldbrush);
}
//draw value intersection
ratio=(float)(lpctx->dx-1)/(lpp->xmax-lpp->xmin);
xx=(long)((lpctx->xvalue-lpp->xmin)*ratio);
ratio=(float)(lpctx->dy-1)/(lpp->ymax-lpp->ymin);
yy=(long)((lpctx->yvalue-lpp->ymin)*ratio);
yy=lpctx->dy-yy;
//draw vertical line
oldpen=(HPEN)SelectObject(dcmem,GetStockObject(WHITE_PEN));
MoveToEx(dcmem,xx,0,NULL);
LineTo(dcmem,xx,lpctx->dy);
SelectObject(dcmem,GetStockObject(BLACK_PEN));
MoveToEx(dcmem,xx+1,0,NULL);
LineTo(dcmem,xx+1,lpctx->dy);
//draw horizontal line
SelectObject(dcmem,GetStockObject(WHITE_PEN));
MoveToEx(dcmem,0,yy,NULL);
LineTo(dcmem,lpctx->dx,yy);
SelectObject(dcmem,GetStockObject(BLACK_PEN));
MoveToEx(dcmem,0,yy+1,NULL);
LineTo(dcmem,lpctx->dy,yy+1);
SelectObject(dcmem,oldpen);
//display value
if (lpp->font != NULL)
{
oldfont=(HFONT)SelectObject(dcmem,lpp->font);
SetBkMode(dcmem,OPAQUE);
SetTextColor(dcmem,RGB(255,255,255));
SetBkColor(dcmem,RGB(100,100,100));
xf=5;
yf=5;
if (xx<(lpctx->dx>>1)) xf=(lpctx->dx>>1)+5 ;
if (yy<(lpctx->dy>>1)) yf=(lpctx->dy>>1)+5;
sprintf(sss," %0.1f ",lpctx->xvalue);
TextOut(dcmem,xf,yf,sss,(int)strlen(sss));
sprintf(sss," %0.1f ",lpctx->yvalue);
TextOut(dcmem,xf,yf+13,sss,(int)strlen(sss));
SelectObject(dcmem,oldfont);
}
//draw focus if any
if (GetFocus() == lpctx->hw)
{
GetClientRect(lpctx->hw,&rect);
rect.left +=3;
rect.top+=3;
rect.right -=3;
rect.bottom -=3;
DrawFocusRect(dcmem,&rect);
}
//display bitmap on screen at the end.
BitBlt(dc,0,0,lpctx->dx,lpctx->dy,dcmem,0,0,SRCCOPY);
SelectObject(dcmem,oldbmp);
DeleteDC(dcmem);
}
LRESULT CALLBACK MYCTRL_2DPanelCallback(HWND hw,//handle of the window.
UINT msg, //Message Ident.
WPARAM p1, //parameter 1.
LPARAM p2) //parameter 2
{
LPT_MYCTRL_2DPANELPARAM lpp;
LPCREATESTRUCT lpcs;
LPT_MYCTRL_2DPANELCTX lpctx;
HDC dc;
PAINTSTRUCT ps;
long xx,yy,hwIdent;
float ratio;
switch (msg)
{
//here we create our structure
case WM_CREATE:
lpcs=(LPCREATESTRUCT)p2;
lpctx=(LPT_MYCTRL_2DPANELCTX)lpcs->lpCreateParams;
lpctx->hw=hw;
TOOL_StorePointerInWindow(hw,lpctx);
break;
//on mouse click we get the focus.
case WM_MOUSEACTIVATE:
if (GetFocus()!=hw) SetFocus(hw);
return MA_ACTIVATE;
//on Focus change we redraw the control
case WM_SETFOCUS:
case WM_KILLFOCUS:
InvalidateRect(hw,NULL,FALSE);
return 0;
//here we draw all the control
case WM_PAINT:
dc=BeginPaint(hw,&ps);
lpctx=(LPT_MYCTRL_2DPANELCTX)TOOL_RecallPointerFromWindow(hw);
if (lpctx != NULL) Draw2DPanel(lpctx, dc);
EndPaint(hw,&ps);
return 0;
//mouse behavior
case WM_LBUTTONDOWN:
lpctx=(LPT_MYCTRL_2DPANELCTX)TOOL_RecallPointerFromWindow(hw);
if (lpctx == NULL) break;
if (lpctx->action != 0) break;
lpctx->action=ACTION_BY_MOUSE;
lpctx->LastCapture=SetCapture(hw);
lpp=&(lpctx->param);
xx=(long)(short int)LOWORD(p2);
yy=(long)(short int)HIWORD(p2);
//compute value according coordinates
ratio=(float)(lpctx->dx-1)/(lpp->xmax-lpp->xmin);
lpctx->xvalue=lpp->xmin+((float)xx/ratio);
yy=lpctx->dy-yy;
ratio=(float)(lpctx->dy-1)/(lpp->ymax-lpp->ymin);
lpctx->yvalue=lpp->ymin+((float)yy/ratio);
//display panel
dc=GetDC(hw);
Draw2DPanel(lpctx, dc);
ReleaseDC(hw,dc);
//notify to parent if any
if (lpp->lpCallback != NULL)
{
hwIdent=(long)GetWindowLong(hw,GWL_ID);
lpp->lpCallback(lpp->lpuser, hwIdent,lpctx->xvalue, lpctx->yvalue);
}
break;
case WM_MOUSEMOVE:
xx=(long)(short int)LOWORD(p2);
yy=(long)(short int)HIWORD(p2);
lpctx=(LPT_MYCTRL_2DPANELCTX)TOOL_RecallPointerFromWindow(hw);
if (lpctx == NULL) break;
if (lpctx->action != ACTION_BY_MOUSE) break;
lpp=&(lpctx->param);
if (xx<0) xx=0;
if (xx>=lpctx->dx) xx=lpctx->dx-1;
if (yy<0) yy=0;
if (yy>=lpctx->dy) yy=lpctx->dy-1;
//compute value according coordinates
ratio=(float)(lpctx->dx-1)/(lpp->xmax-lpp->xmin);
lpctx->xvalue=lpp->xmin+((float)xx/ratio);
yy=lpctx->dy-yy;
ratio=(float)(lpctx->dy-1)/(lpp->ymax-lpp->ymin);
lpctx->yvalue=lpp->ymin+((float)yy/ratio);
//display panel
dc=GetDC(hw);
Draw2DPanel(lpctx, dc);
ReleaseDC(hw,dc);
//notify to parent if any
if (lpp->lpCallback != NULL)
{
hwIdent=(long)GetWindowLong(hw,GWL_ID);
lpp->lpCallback(lpp->lpuser, hwIdent,lpctx->xvalue, lpctx->yvalue);
}
break;
case WM_LBUTTONUP:
lpctx=(LPT_MYCTRL_2DPANELCTX)TOOL_RecallPointerFromWindow(hw);
if (lpctx == NULL) break;
if (lpctx->action != ACTION_BY_MOUSE) break;
ReleaseCapture();
if (lpctx->LastCapture != NULL) SetCapture(lpctx->LastCapture);
lpctx->action = 0;
break;
case WM_CLOSE:
DestroyWindow(hw);
return 0;
case WM_DESTROY:
lpctx=TOOL_RecallPointerFromWindow(hw);
if (lpctx != NULL)
{
if (lpctx->tempbmp !=NULL) DeleteObject(lpctx->tempbmp);
free(lpctx);
TOOL_StorePointerInWindow(hw,NULL);
}
break;
}
return DefWindowProc(hw,msg,p1,p2);
}
void MYCTRL_Set2DPanelValue(HWND hw, float xvalue, float yvalue)
{
LPT_MYCTRL_2DPANELPARAM lpp;
LPT_MYCTRL_2DPANELCTX lpctx;
lpctx=(LPT_MYCTRL_2DPANELCTX)TOOL_RecallPointerFromWindow(hw);
if (lpctx == NULL) return;
lpp=&(lpctx->param);
if (xvalue < lpp->xmin) xvalue=lpp->xmin;
if (xvalue > lpp->xmax) xvalue=lpp->xmax;
lpctx->xvalue=xvalue;
if (yvalue < lpp->ymin) yvalue=lpp->ymin;
if (yvalue > lpp->ymax) yvalue=lpp->ymax;
lpctx->yvalue=yvalue;
InvalidateRect(lpctx->hw,NULL,FALSE);
}
HWND MYCTRL_Create2DPanel(HWND hParent, long Ident, long x0,long y0, LPT_MYCTRL_2DPANELPARAM lpparam)
{
HWND hw;
long wstyle;
LPT_MYCTRL_2DPANELCTX lpctx;
//we allocate memory for our structure
lpctx=(LPT_MYCTRL_2DPANELCTX)malloc(sizeof(T_MYCTRL_2DPANELCTX));
if (lpctx == NULL) return NULL;
TOOL_MemZERO(lpctx,sizeof(T_MYCTRL_2DPANELCTX));
if (lpparam == NULL) return NULL;
lpctx->param = *lpparam;
//define control size according bitmap.
if (lpparam->hbmp != NULL) TOOL_GetBitmapSize(lpparam->hbmp,&(lpctx->dx),&(lpctx->dy));
else
{
lpctx->dx=100;
lpctx->dy=100;
}
//set current values
lpctx->xvalue=(lpctx->param.xmin+lpctx->param.xmax)*0.5f;
lpctx->yvalue=(lpctx->param.ymin+lpctx->param.ymax)*0.5f;
//we create window with window-creation data
wstyle=WS_CHILD | WS_VISIBLE | WS_TABSTOP;
hw=CreateWindow(MYCTRL_CLASSNAME_PANEL, NULL,
wstyle,x0,y0,
lpctx->dx, // window width
lpctx->dy, // window height
hParent, // parent Windows Handle.
(HMENU)Ident, // child-window identifier.
G_hinstance, // handle of application instance
lpctx); // we communicate our pointer (see WM_CREATE)
return hw;
}
/*---------------------------------------------------------------*/
/* INIT / END */
/*---------------------------------------------------------------*/
long MYCTRL_InitLib(HINSTANCE hinst)
{
long rep;
WNDCLASS wc;
G_hinstance=hinst;
//register class for our custom push button
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc =(WNDPROC)MYCTRL_ButtonCallback; //Adresse of the related callback
wc.cbClsExtra =0; //0 per default.
wc.cbWndExtra =sizeof(void*); //alloc some byte to store pointer inside window.
wc.hInstance =hinst; //application hinstance.
wc.hIcon =NULL; //no Icon
wc.hCursor =LoadCursor(NULL,IDC_ARROW); //handle on cursor mouse.
wc.hbrBackground=NULL; //no background
wc.lpszMenuName =NULL; //no menu.
wc.lpszClassName=MYCTRL_CLASSNAME_BUTTON;
rep=RegisterClass(&wc);
if (rep==0) return -1;
//register class for our custom 2D Panel
wc.style = CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW;//allows double click
wc.lpfnWndProc =(WNDPROC)MYCTRL_2DPanelCallback; //Adresse of the related callback
wc.cbClsExtra =0; //0 per default.
wc.cbWndExtra =sizeof(void*); //alloc some byte to store pointer inside window.
wc.hInstance =hinst; //application hinstance.
wc.hIcon =NULL; //no Icon
wc.hCursor =LoadCursor(NULL,IDC_UPARROW); //handle on cursor mouse.
wc.hbrBackground=NULL; //no background
wc.lpszMenuName =NULL; //no menu.
wc.lpszClassName=MYCTRL_CLASSNAME_PANEL;
rep=RegisterClass(&wc);
if (rep==0) return -1;
return 0;
}
void MYCTRL_EndLib(void)
{
UnregisterClass(MYCTRL_CLASSNAME_BUTTON,G_hinstance);
UnregisterClass(MYCTRL_CLASSNAME_PANEL,G_hinstance);
}
|
Source : myctrl.h |
/*****************************************************************/
/* MYCTRL V.Burel */
/*****************************************************************/
/* Contains 2 custom windows control */
/*****************************************************************/
#ifndef __MYCTRL_H__
#define __MYCTRL_H__
#ifdef __cplusplus
extern "C" {
#endif
#define MYCTRL_CLASSNAME_BUTTON "MyOwnCtrl_Button"
#define MYCTRL_CLASSNAME_PANEL "MyOwnCtrl_Panel"
//Initialize library
long MYCTRL_InitLib(HINSTANCE hinst);
void MYCTRL_EndLib(void);
/*---------------------------------------------------------------*/
/* 2D PANEL */
/*---------------------------------------------------------------*/
typedef void (__stdcall *MYCTRL_LPT_2DPANELCALLBACK)(void * lpuser,long Ident,float xvalue, float yvalue);
typedef struct tagMYCTRL_2DPANELPARAM
{
HBITMAP hbmp;
HFONT font;
float xmin,xmax;
float ymin,ymax;
MYCTRL_LPT_2DPANELCALLBACK lpCallback;
void * lpuser;
} T_MYCTRL_2DPANELPARAM, *PT_MYCTRL_2DPANELPARAM, *LPT_MYCTRL_2DPANELPARAM;
HWND MYCTRL_Create2DPanel(HWND hParent, long Ident, long x0,long y0, LPT_MYCTRL_2DPANELPARAM lpparam);
void MYCTRL_Set2DPanelValue(HWND hw, float xvalue, float yvalue);
//End Of Header.
#ifdef __cplusplus
}
#endif
#endif /*__MYCTRL_H__*/
|
|