Home

History

Blog

'C' Lesson

<<

>>


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.

MinPrg04 : My first own Window Controls


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
MinPrg04 : Source Organization

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...

    MinPrg04 : Object Inside Window

    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.

    MinPrg04 : Data Interface Object

    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).

    MinPrg04 : Our Own Data Interface Object

    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.

    MinPrg04 : Function's Pointer type declaration

    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.

    MinPrg04 : Communication with object

    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).

    MinPrg04 : Windows Handle List



    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(&param_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, &param_2dpanel);
    
       param_2dpanel.hbmp=NULL;
       lpapp->hw_panel2=MYCTRL_Create2DPanel(hw, IDC_2DPANEL_2, 320,165, &param_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__*/
    
    








    Contact Us!

    Privacy Policy

    Social Network:
    VB-Audio On Facebook
    VB-Audio On Instagram
    VB-Audio On Twitter
    VB-Audio On Youtube

    VB-Audio Forums
    VB-Audio Discord Server
    Voicemeeter on Reddit



    Audio Apps:
    VB-Cable
    Hi-Fi Cable & ASIO Bridge
    Voicemeeter
    Voicemeeter Banana
    Voicemeeter Potato
    VBAN Protocol & Tools
    Spectralissime
    VB-Audio Matrix

    About Donationware model...
    About Licensing / Distribution...



    Other web sites:
    VOICEMEETER.COM
    SPECTRALISSIME.COM
    DOWNLOAD.VB-AUDIO.COM

    Freeware:
    LF-Generator
    TimeCalc
    FFX-4

    About us:
    Our history / Blog



    Audio Pro:
    MT32-Splite
    MT64-Standard
    MT128-Pro
    MT128-Integration
    WWW.MT128.COM

    Buy Online:
    VB-Audio WebShop
    General Terms
    Last Newsletter



    Copyright V.Burel ©1998-2023. All rights reserved. All technical specifications and any informations of the products specified on this web site may be subject to change without notice.