Creating an own control/gadget using Windows API

Just starting out? Need help? Post your questions and find answers here.
User avatar
jacdelad
Addict
Addict
Posts: 1993
Joined: Wed Feb 03, 2021 12:46 pm
Location: Riesa

Creating an own control/gadget using Windows API

Post by jacdelad »

Hi,
I know that Windows offers a ton of APIs and I've already used half a ton of them. I also know that you can create your own controls/gadgets with things like focus frames and such. This is (at least in my eyes) a bit more beautiful that just using a plain canvas (which already opens the doors to do everything else). Now, let's say I want to create a simple two state button (which is what I really want). For now I used a canvas (because the standard button does not offer changing the text color, which is what I need). My button looks quite ok and tries to imitate the standard buttons, but this can get complicated when certain design elements are changed (though GetSysColor_ already provides the colors for the background and the frame). But I want the button to completely look like a standard button, that's why I need the focus frame (and some other things).
Now, the real question: I searched and searched and found pieces of information on that, but no real tutorial or something. Also, I haven't used ownerdrawing by myself (I copied some snips from the forum and such, but with no real understanding). So, does anyone have a link or something to help me with that? Has anyone already worked with the focus frames and such for creating and imitating a control/gadget? I don't want anyone to do my work, but reinventing the wheel is maybe not necessary.

Thanks in advance.
Good morning, that's a nice tnetennba!

PureBasic 6.21/Windows 11 x64/Ryzen 7900X/32GB RAM/3TB SSD
Synology DS1821+/DX517, 130.9TB+50.8TB+2TB SSD
ebs
Enthusiast
Enthusiast
Posts: 557
Joined: Fri Apr 25, 2003 11:08 pm

Re: Creating an own control/gadget using Windows API

Post by ebs »

You might want to take a look at ChrisR's "IceButtons Windows library" to give you some ideas: viewtopic.php?p=608752
User avatar
spikey
Enthusiast
Enthusiast
Posts: 750
Joined: Wed Sep 22, 2010 1:17 pm
Location: United Kingdom

Re: Creating an own control/gadget using Windows API

Post by spikey »

Are you looking for DrawFocusRect? See https://learn.microsoft.com/en-us/windo ... wfocusrect

Code: Select all

Define.i dc, Evt
Define rc.RECT

If OpenWindow(0, 0, 0, 600, 230, "CalendarGadget", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
  CalendarGadget(0, 10, 10, 250, 200)
  rc\top = 14
  rc\bottom = 202
  rc\left = 14
  rc\right = 254
  
  dc = GetDC_(WindowID(0))
  DrawFocusRect_(dc, @rc)
  AddWindowTimer(0, 0, 1500)
  
  Repeat
   Evt = WaitWindowEvent()
   If Evt = #PB_Event_Timer
      DrawFocusRect_(dc, @rc)
    EndIf
  Until Evt = #PB_Event_CloseWindow
EndIf
Hex0r recently published a gadget that uses an image as a base rather than a canvas:
viewtopic.php?p=608455&hilit=slider#p608455

A lot of the examples for a truly native control implementation will be 'C' flavour:
https://learn.microsoft.com/en-us/windo ... rols-intro
https://www.codeproject.com/Articles/55 ... The-Basics
User avatar
jacdelad
Addict
Addict
Posts: 1993
Joined: Wed Feb 03, 2021 12:46 pm
Location: Riesa

Re: Creating an own control/gadget using Windows API

Post by jacdelad »

Aye, thanks you both. I'll try this out with custom button and combobox.
Is it also possible to really give the canvas the focus, like, to work with the tab key?
Good morning, that's a nice tnetennba!

PureBasic 6.21/Windows 11 x64/Ryzen 7900X/32GB RAM/3TB SSD
Synology DS1821+/DX517, 130.9TB+50.8TB+2TB SSD
SMaag
Enthusiast
Enthusiast
Posts: 302
Joined: Sat Jan 14, 2023 6:55 pm
Location: Bavaria/Germany

Re: Creating an own control/gadget using Windows API

Post by SMaag »

What I remember from experiments at VB6 Times
A Button and other standard Gagdets are created with the CreateWindow-API.
You need the correct ClassName for a Button.

It's existing an old implementation in VisualBasic6 from VBaccelerator.com
here the link!
https://www.vbaccelerator.com/home/VB/C ... ttons.html
But actually it is not possible to download it!
I searched in my archives but I don't have the code. But if we want to get this code, I'm sure some guys from
the activevb.de form have it.

But for now, I can supply a C++ Code how to do it with WinAPI. It's from the web, but the original link I don't have.
The example is a complete Class wich is registred in Windows.
The problem is: Setting the Callback Procedure is part of the Class-Registering function.

But for simple using a button, I guess registering a own class is not the way!

But how to get a callback without registering a own Class? I don't know this!

her the link to the CreateWindow API Help
http://winapi.freetechsecrets.com/win32 ... Window.htm

here the C++ Class Code for a owner drawn Button

Code: Select all

// Test_Button.cpp : Defines the entry point for the application.
//

#include "framework.h"
#include "Test_Button.h"
#include <commctrl.h>
#pragma comment(lib,"Comctl32.lib")

#define MAX_LOADSTRING 100
#define IDC_OWNERDRAWBUTTON 101

// Global Variables:
HINSTANCE hInst;                                // current instance
WCHAR szTitle[MAX_LOADSTRING];                  // The title bar text
WCHAR szWindowClass[MAX_LOADSTRING];            // the main window class name

// Forward declarations of functions included in this code module:
ATOM                MyRegisterClass(HINSTANCE hInstance);
BOOL                InitInstance(HINSTANCE, int);
LRESULT CALLBACK    WndProc(HWND, UINT, WPARAM, LPARAM);
INT_PTR CALLBACK    About(HWND, UINT, WPARAM, LPARAM);
LRESULT CALLBACK OwnerDrawButtonProc(HWND hWnd, UINT uMsg, WPARAM wParam,
    LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData);
HWND hWndStatic;
HBITMAP hBmp1;
HBITMAP hBmp;

int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
                     _In_opt_ HINSTANCE hPrevInstance,
                     _In_ LPWSTR    lpCmdLine,
                     _In_ int       nCmdShow)
{
    UNREFERENCED_PARAMETER(hPrevInstance);
    UNREFERENCED_PARAMETER(lpCmdLine);

    // TODO: Place code here.

    // Initialize global strings
    LoadStringW(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
    LoadStringW(hInstance, IDC_TESTBUTTON, szWindowClass, MAX_LOADSTRING);
    MyRegisterClass(hInstance);


    // Perform application initialization:
    if (!InitInstance (hInstance, nCmdShow))
    {
        return FALSE;
    }

    HACCEL hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_TESTBUTTON));

    MSG msg;

    // Main message loop:
    while (GetMessage(&msg, nullptr, 0, 0))
    {
        if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }

    return (int) msg.wParam;
}



//
//  FUNCTION: MyRegisterClass()
//
//  PURPOSE: Registers the window class.
//
ATOM MyRegisterClass(HINSTANCE hInstance)
{
    WNDCLASSEXW wcex;

    wcex.cbSize = sizeof(WNDCLASSEX);

    wcex.style          = CS_HREDRAW | CS_VREDRAW;
    wcex.lpfnWndProc    = WndProc;
    wcex.cbClsExtra     = 0;
    wcex.cbWndExtra     = 0;
    wcex.hInstance      = hInstance;
    wcex.hIcon          = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_TESTBUTTON));
    wcex.hCursor        = LoadCursor(nullptr, IDC_ARROW);
    wcex.hbrBackground  = (HBRUSH)(COLOR_WINDOW+1);
    wcex.lpszMenuName   = MAKEINTRESOURCEW(IDC_TESTBUTTON);
    wcex.lpszClassName  = szWindowClass;
    wcex.hIconSm        = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));

    return RegisterClassExW(&wcex);
}

//
//   FUNCTION: InitInstance(HINSTANCE, int)
//
//   PURPOSE: Saves instance handle and creates main window
//
//   COMMENTS:
//
//        In this function, we save the instance handle in a global variable and
//        create and display the main program window.
//
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
   hInst = hInstance; // Store instance handle in our global variable

   HWND hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
      CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr);

   if (!hWnd)
   {
      return FALSE;
   }

   ShowWindow(hWnd, nCmdShow);
   UpdateWindow(hWnd);

   return TRUE;
}

//
//  FUNCTION: WndProc(HWND, UINT, WPARAM, LPARAM)
//
//  PURPOSE: Processes messages for the main window.
//
//  WM_COMMAND  - process the application menu
//  WM_PAINT    - Paint the main window
//  WM_DESTROY  - post a quit message and return
//
//
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{

    switch (message)
    {
    case WM_CREATE:
    {
        hBmp = (HBITMAP)LoadImage(GetModuleHandle(NULL),
            MAKEINTRESOURCE(IDB_BITMAP1),
            IMAGE_BITMAP,
            NULL,
            NULL,
            LR_DEFAULTCOLOR);
        hBmp1 = (HBITMAP)LoadImage(GetModuleHandle(NULL),
            MAKEINTRESOURCE(IDB_BITMAP2),
            IMAGE_BITMAP,
            NULL,
            NULL,
            LR_DEFAULTCOLOR);
        hWndStatic = CreateWindowEx(0, L"button", NULL, WS_CHILD | WS_VISIBLE | BS_OWNERDRAW, 500, 400, 46, 29, hWnd, (HMENU)IDC_OWNERDRAWBUTTON, hInst, NULL);
        SetWindowSubclass(hWndStatic, &OwnerDrawButtonProc, IDC_OWNERDRAWBUTTON, 0);     
    }
    break;
    case WM_COMMAND:
        {
            int wmId = LOWORD(wParam);
            // Parse the menu selections:
            switch (wmId)
            {          
            case IDM_ABOUT:
                DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
                break;
            case IDM_EXIT:
                DestroyWindow(hWnd);
                break;
            default:
                return DefWindowProc(hWnd, message, wParam, lParam);
            }
        }
        break;
    case WM_PAINT:
        {
            PAINTSTRUCT ps;
            HDC hdc = BeginPaint(hWnd, &ps);
            // TODO: Add any drawing code that uses hdc here...

            EndPaint(hWnd, &ps);
        }
        break;
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    default:
        return DefWindowProc(hWnd, message, wParam, lParam);
    }
    return 0;
}

// Message handler for about box.
INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
    UNREFERENCED_PARAMETER(lParam);
    switch (message)
    {
    case WM_INITDIALOG:
        return (INT_PTR)TRUE;

    case WM_COMMAND:
        if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)
        {
            EndDialog(hDlg, LOWORD(wParam));
            return (INT_PTR)TRUE;
        }
        break;
    }
    return (INT_PTR)FALSE;
}

LRESULT CALLBACK OwnerDrawButtonProc(HWND hWnd, UINT uMsg, WPARAM wParam,
    LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData)
{
    BITMAP          bitmap01;
    HDC             hdcMem01;
    HGDIOBJ         oldBitmap01;
    switch (uMsg)
    {
    case WM_LBUTTONDOWN:
    {
        RECT rc;
        HDC hdc = GetDC(hWnd);
        hdcMem01 = CreateCompatibleDC(hdc);
        oldBitmap01 = SelectObject(hdcMem01, hBmp1);

        GetObject(hBmp1, sizeof(bitmap01), &bitmap01);
        BitBlt(hdc, 0, 0, bitmap01.bmWidth, bitmap01.bmHeight, hdcMem01, 0, 0, SRCCOPY);

        SelectObject(hdcMem01, oldBitmap01);  
        DeleteDC(hdcMem01);
    }
    break;
    case WM_PAINT:
    {
        RECT rc;
        PAINTSTRUCT ps;
        HDC hdc = BeginPaint(hWnd, &ps);
        hdcMem01 = CreateCompatibleDC(hdc);
        oldBitmap01 = SelectObject(hdcMem01, hBmp);

        GetObject(hBmp, sizeof(bitmap01), &bitmap01);
        BitBlt(hdc, 0, 0, bitmap01.bmWidth, bitmap01.bmHeight, hdcMem01, 0, 0, SRCCOPY);

        SelectObject(hdcMem01, oldBitmap01);
        DeleteDC(hdcMem01);
        EndPaint(hWnd, &ps);   
    }
    break;
    case WM_LBUTTONUP:
    {
        HDC hdc = GetDC(hWnd);
        hdcMem01 = CreateCompatibleDC(hdc);
        oldBitmap01 = SelectObject(hdcMem01, hBmp);

        GetObject(hBmp, sizeof(bitmap01), &bitmap01);
        BitBlt(hdc, 0, 0, bitmap01.bmWidth, bitmap01.bmHeight, hdcMem01, 0, 0, SRCCOPY);

        SelectObject(hdcMem01, oldBitmap01);
        DeleteDC(hdcMem01);
    }
    break;
    case WM_NCDESTROY:
        RemoveWindowSubclass(hWnd, &OwnerDrawButtonProc, 1);
        break;
    }
    return DefSubclassProc(hWnd, uMsg, wParam, lParam);
}
SMaag
Enthusiast
Enthusiast
Posts: 302
Joined: Sat Jan 14, 2023 6:55 pm
Location: Bavaria/Germany

Re: Creating an own control/gadget using Windows API

Post by SMaag »

here the link to the complete vbAccelartaor-Archive on github!
maybe here you can find some code for owner drawn controls
https://github.com/tannerhelland/vbAcce ... ree/master
User avatar
jacdelad
Addict
Addict
Posts: 1993
Joined: Wed Feb 03, 2021 12:46 pm
Location: Riesa

Re: Creating an own control/gadget using Windows API

Post by jacdelad »

Hi SMaag,
I've already done that, that's no problem for me. It's just really about simulating "missing" features for existing controls, so...mimicking them as close as possible. But thanks!
Good morning, that's a nice tnetennba!

PureBasic 6.21/Windows 11 x64/Ryzen 7900X/32GB RAM/3TB SSD
Synology DS1821+/DX517, 130.9TB+50.8TB+2TB SSD
AZJIO
Addict
Addict
Posts: 2143
Joined: Sun May 14, 2017 1:48 am

Re: Creating an own control/gadget using Windows API

Post by AZJIO »

Launcher6-OWNER.pb -> viewtopic.php?p=579465#p579465
Find in the source #WM_DRAWITEM
breeze4me
Enthusiast
Enthusiast
Posts: 633
Joined: Thu Mar 09, 2006 9:24 am
Location: S. Kor

Re: Creating an own control/gadget using Windows API

Post by breeze4me »

If you just want to change the text color of any button, you can do something similar to the way in the post below.

CheckBox & Option Color Theme ?
viewtopic.php?t=78966

Note that if you want to create a button with a canvas gadget, the #PB_Canvas_DrawFocus and #PB_Canvas_Keyboard flags will be helpful.

Code: Select all

DeclareModule API_HookEngine
  Declare.i Hook(*OldFunctionAddress, *NewFunctionAddress)
  Declare.i UnHook(*hook_ptr)
  Declare.i ProcAddress(ModuleName$, ProcName$)
EndDeclareModule

Module API_HookEngine  
  EnableExplicit
  
  Structure opcode
    CompilerIf #PB_Compiler_Processor = #PB_Processor_x64
      mov.u
    CompilerElse
      mov.a
    CompilerEndIf
    addr.i
    push.a
    ret.a
  EndStructure
  
  Structure hookstruct
    addr.i
    hook.opcode
    orig.a[SizeOf(opcode)]
  EndStructure
  
  CompilerIf #PB_Compiler_Unicode
    Import "kernel32.lib"
      GetProcAddress(hModule, lpProcName.p-ascii)
    EndImport
  CompilerElse
    Import "kernel32.lib"
      GetProcAddress(hModule, lpProcName.s)
    EndImport
  CompilerEndIf
  
  Procedure.i ProcAddress(ModuleName$, ProcName$)
    Protected moduleH.i
    
    moduleH = GetModuleHandle_(ModuleName$)
    If moduleH = #Null
      moduleH = LoadLibrary_(ModuleName$)
      If moduleH = #Null
        ProcedureReturn #Null
      EndIf
    EndIf
    
    ProcedureReturn GetProcAddress(moduleH, ProcName$)
  EndProcedure
  
  Procedure Hook(*OldFunctionAddress, *NewFunctionAddress)
    Protected *hook_ptr.hookstruct
    
    If Not *OldFunctionAddress
      ProcedureReturn #Null
    EndIf
    
    *hook_ptr = AllocateMemory(SizeOf(hookstruct))
    *hook_ptr\addr = *OldFunctionAddress
    CompilerIf #PB_Compiler_Processor = #PB_Processor_x64
      *hook_ptr\hook\mov = $B848
    CompilerElse
      *hook_ptr\hook\mov = $B8
    CompilerEndIf
    *hook_ptr\hook\addr = *NewFunctionAddress
    *hook_ptr\hook\push = $50
    *hook_ptr\hook\ret = $C3
     
    CopyMemory(*OldFunctionAddress, @*hook_ptr\orig, SizeOf(opcode))
    If Not WriteProcessMemory_(GetCurrentProcess_(), *OldFunctionAddress, @*hook_ptr\hook, SizeOf(opcode), #Null)
      FreeMemory(*hook_ptr)
      ProcedureReturn #Null
    Else
      ProcedureReturn *hook_ptr
    EndIf
  EndProcedure
  
  Procedure.i UnHook(*hook_ptr.hookstruct)
    Protected retValue.i
    
    If *hook_ptr
      If *hook_ptr\addr
        If WriteProcessMemory_(GetCurrentProcess_(), *hook_ptr\addr, @*hook_ptr\orig, SizeOf(opcode), #Null)
          retValue = *hook_ptr\addr
          FreeMemory(*hook_ptr)
          ProcedureReturn retValue
        EndIf
      EndIf
    EndIf
    
    ProcedureReturn #Null
  EndProcedure
  
  Procedure DisableHook(*hook_ptr.hookstruct)
    If *hook_ptr
      If *hook_ptr\addr
        If WriteProcessMemory_(GetCurrentProcess_(), *hook_ptr\addr, @*hook_ptr\orig, SizeOf(opcode), #Null)
          ProcedureReturn *hook_ptr\addr
        EndIf
      EndIf
    EndIf
    ProcedureReturn #Null
  EndProcedure
  
  Procedure EnableHook(*hook_ptr.hookstruct)
    If *hook_ptr
      If *hook_ptr\hook\addr
        If WriteProcessMemory_(GetCurrentProcess_(), *hook_ptr\addr, @*hook_ptr\hook, SizeOf(opcode), #Null)
          ProcedureReturn *hook_ptr\addr
        EndIf
      EndIf
    EndIf
    ProcedureReturn #Null
  EndProcedure
EndModule



UseModule API_HookEngine

EnumerationBinary
  #DTT_TEXTCOLOR       ;(1 << 0)      ;// crText has been specified
  #DTT_BORDERCOLOR     ;(1 << 1)      ;// crBorder has been specified
  #DTT_SHADOWCOLOR     ;(1 << 2)      ;// crShadow has been specified
  #DTT_SHADOWTYPE      ;(1 << 3)      ;// iTextShadowType has been specified
  #DTT_SHADOWOFFSET    ;(1 << 4)      ;// ptShadowOffset has been specified
  #DTT_BORDERSIZE      ;(1 << 5)      ;// iBorderSize has been specified
  #DTT_FONTPROP        ;(1 << 6)      ;// iFontPropId has been specified
  #DTT_COLORPROP       ;(1 << 7)      ;// iColorPropId has been specified
  #DTT_STATEID         ;(1 << 8)      ;// IStateId has been specified
  #DTT_CALCRECT        ;(1 << 9)      ;// Use pRect as and in/out parameter
  #DTT_APPLYOVERLAY    ;(1 << 10)     ;// fApplyOverlay has been specified
  #DTT_GLOWSIZE        ;(1 << 11)     ;// iGlowSize has been specified
  #DTT_CALLBACK        ;(1 << 12)     ;// pfnDrawTextCallback has been specified
  #DTT_COMPOSITED      ;(1 << 13)     ;// Draws text with antialiased alpha (needs a DIB section)
EndEnumeration

#DTT_VALIDBITS = #DTT_TEXTCOLOR | #DTT_BORDERCOLOR | #DTT_SHADOWCOLOR | #DTT_SHADOWTYPE | #DTT_SHADOWOFFSET |
                 #DTT_BORDERSIZE | #DTT_FONTPROP | #DTT_COLORPROP | #DTT_STATEID | #DTT_CALCRECT |
                 #DTT_APPLYOVERLAY | #DTT_GLOWSIZE | #DTT_COMPOSITED

#BP_PUSHBUTTON = 1
#BP_RADIOBUTTON = 2
#BP_CHECKBOX = 3

Structure DTTOPTS
  dwSize.l
  dwFlags.l
  crText.l
  crBorder.l
  crShadow.l
  iTextShadowType.l
  ptShadowOffset.POINT
  iBorderSize.l
  iFontPropId.l
  iColorPropId.l
  iStateId.l
  fApplyOverlay.l
  iGlowSize.l
  *pfnDrawTextCallback
  lParam.l
EndStructure

Prototype DrawThemeTextEx(hTheme, hdc, iPartId, iStateId, *pszText, cchText, dwTextFlags.l, *pRect, *pOptions)
Global DrawThemeTextEx.DrawThemeTextEx

Structure ColoredGadgetInfo
  FrontColor.i
  hBrushBackColor.i
EndStructure

Global NewMap ColoredGadget.ColoredGadgetInfo()

#App_BrushWidth = 2000         ;Set the value large enough in consideration of changing a gadget width.

Procedure MainWindow_Callback(hWnd, uMsg, wParam, lParam)
  Protected Gadget, Result = #PB_ProcessPureBasicEvents
  
  Select uMsg
    Case #WM_CTLCOLORBTN
      Gadget = GetProp_(lParam, "pb_id")
      If IsGadget(Gadget) And GadgetType(Gadget) = #PB_GadgetType_Button
        If FindMapElement(ColoredGadget(), Str(lParam))
          ;SetBkMode_(wParam, #TRANSPARENT)
          Result = ColoredGadget()\hBrushBackColor
        EndIf
      EndIf
  EndSelect
  ProcedureReturn Result
EndProcedure

Procedure CreateBackgroundBrush(hwnd)
  Protected hBrush, Img
  If hwnd = 0 : ProcedureReturn 0 : EndIf
  Img = CreateImage(#PB_Any, #App_BrushWidth, 1, 24, GetSysColor_(#COLOR_3DFACE))
  If Img
    If StartDrawing(ImageOutput(Img))
      ;Write the handle value in the last few bytes of the pattern brush.
      Plot(#App_BrushWidth - 1, 0, hwnd & $FFFF)
      Plot(#App_BrushWidth - 2, 0, (hwnd >> 16) & $FFFF)
      CompilerIf #PB_Compiler_Processor = #PB_Processor_x64
        Plot(#App_BrushWidth - 3, 0, (hwnd >> 32) & $FFFF)
        Plot(#App_BrushWidth - 4, 0, (hwnd >> 48) & $FFFF)
      CompilerEndIf
      StopDrawing()
      hBrush = CreatePatternBrush_(ImageID(Img))
    EndIf
    FreeImage(Img)
  EndIf
  ProcedureReturn hBrush
EndProcedure

Procedure SetButtonGadgetTextColor(Gadget, FrontColor = #PB_Default)
  Protected Result, hwnd
  
  If IsGadget(Gadget)
    If GadgetType(Gadget) <> #PB_GadgetType_Button
      ProcedureReturn 0
    EndIf
    
    hwnd = GadgetID(Gadget)
    If hwnd
      SendMessage_(hwnd, #WM_SETREDRAW, 0, 0)
      
      Repeat
        With ColoredGadget()
          
          ;Check if the gadget already exists in the map.
          If FindMapElement(ColoredGadget(), Str(hwnd))
            
            If FrontColor = #PB_Default
              ;Remove the gadget from the map.
              DeleteObject_(\hBrushBackColor)
              DeleteMapElement(ColoredGadget())
              Result = 1
              Break
            EndIf
            
            If \FrontColor <> FrontColor
              \FrontColor = FrontColor
              If \hBrushBackColor
                DeleteObject_(\hBrushBackColor)
                \hBrushBackColor = 0
              EndIf
              \hBrushBackColor = CreateBackgroundBrush(hwnd)
            EndIf
            Result = 1
            Break
          EndIf
          
          ;If not, add it to the map.
          If AddMapElement(ColoredGadget(), Str(hwnd))
            \FrontColor = FrontColor
            \hBrushBackColor = CreateBackgroundBrush(hwnd)
            Result = 1
          EndIf
          
          Break
        EndWith
      ForEver
      
      SendMessage_(hwnd, #WM_SETREDRAW, 1, 0)
      RedrawWindow_(hwnd, 0, 0, #RDW_ERASE | #RDW_FRAME | #RDW_INVALIDATE | #RDW_ALLCHILDREN)
    EndIf
  EndIf
  ProcedureReturn Result
EndProcedure

Procedure My_DrawThemeText(hTheme, hdc, iPartId, iStateId, *pszText, cchText, dwTextFlags, dwTextFlags2, *pRect)
  Protected opt.DTTOPTS\dwSize = SizeOf(DTTOPTS)
  Protected hBrush, BrushInfo.LOGBRUSH, hdcTemp, rt.RECT, hBitmap, old, hwnd
  
  If iPartId = #BP_PUSHBUTTON
    hBrush = GetCurrentObject_(hdc, #OBJ_BRUSH)
    If hBrush
      If GetObject_(hBrush, SizeOf(LOGBRUSH), @BrushInfo)
        If BrushInfo\lbStyle = #BS_PATTERN
          hdcTemp = CreateCompatibleDC_(hdc)
          If hdcTemp
            hBitmap = CreateCompatibleBitmap_(hdc, #App_BrushWidth, 1)
            If hBitmap
              old = SelectObject_(hdcTemp, hBitmap)
              rt\right = #App_BrushWidth
              rt\bottom = 1
              FillRect_(hdcTemp, rt, hBrush)
              CompilerIf #PB_Compiler_Processor = #PB_Processor_x64
                hwnd = (GetPixel_(hdcTemp, #App_BrushWidth - 1, 0) & $FFFF) | ((GetPixel_(hdcTemp, #App_BrushWidth - 2, 0) & $FFFF) << 16) |
                       ((GetPixel_(hdcTemp, #App_BrushWidth - 3, 0) & $FFFF) << 32) | ((GetPixel_(hdcTemp, #App_BrushWidth - 4, 0) & $FFFF) << 48)
              CompilerElse
                hwnd = (GetPixel_(hdcTemp, #App_BrushWidth - 1, 0) & $FFFF) | ((GetPixel_(hdcTemp, #App_BrushWidth - 2, 0) & $FFFF) << 16)
              CompilerEndIf
              
              If FindMapElement(ColoredGadget(), Str(hwnd))
                If ColoredGadget()\FrontColor <> #PB_Default
                  opt\dwFlags = #DTT_TEXTCOLOR
                  opt\crText = ColoredGadget()\FrontColor
                EndIf
              EndIf
              
              DeleteObject_(SelectObject_(hdcTemp, old))
            EndIf
            DeleteDC_(hdcTemp)
          EndIf
        EndIf
      EndIf
    EndIf
    
  EndIf
  ProcedureReturn DrawThemeTextEx(hTheme, hdc, iPartId, iStateId, *pszText, cchText, dwTextFlags, *pRect, @opt)
EndProcedure


If OpenLibrary(0, "UxTheme.dll")
  DrawThemeTextEx = GetFunction(0, "DrawThemeTextEx")   ;There is no DrawThemeTextEx in Windows XP, so this method is not available.
EndIf

If DrawThemeTextEx
  *DrawThemeText = Hook(ProcAddress("UxTheme.dll", "DrawThemeText"), @My_DrawThemeText())
EndIf

Enumeration Window
  #MainWindow
EndEnumeration

Enumeration Gadgets
  #g_0
  #g_1
  #g_2
  #g_3
  #g_4
  #g_5
  #g_6
EndEnumeration

If OpenWindow(#MainWindow, 0, 0, 400, 350, "Button Text Color", #PB_Window_SystemMenu | #PB_Window_SizeGadget | #PB_Window_ScreenCentered)
  SetWindowColor(#MainWindow, RGB(54, 54, 54))
  
  ButtonGadget(#g_0, 20, 20, 160, 30, "Default")
  ButtonGadget(#g_1, 220, 20, 160, 30, "#Yellow")
  ContainerGadget(#g_2, 10, 60, 390, 90, #PB_Container_Flat)
    SetGadgetColor(#g_2, #PB_Gadget_BackColor, RGB(128, 128, 128))
    TextGadget(#g_3, 5, 5, 160, 20, "Container_1")
    SetGadgetColor(#g_3, #PB_Gadget_BackColor, GetGadgetColor(#g_2, #PB_Gadget_BackColor)) 
    
    ButtonGadget(#g_4, 10, 30, 160, 30, "#Blue")
    ButtonGadget(#g_5, 190, 30, 180, 40, "#Magenta")
  CloseGadgetList()
  
  CheckBoxGadget(#g_6, 20, 230, 180, 40, "enable/disable")
  
  hFont = LoadFont(0, "arial", 20, #PB_Font_Bold)
  SetGadgetFont(#g_5, hFont)
  
  
  SetButtonGadgetTextColor(#g_1, #Yellow)
  SetButtonGadgetTextColor(#g_4, #Blue)
  SetButtonGadgetTextColor(#g_5, #Magenta)
  
  ;SetButtonGadgetTextColor(#g_4)
  ;SetButtonGadgetTextColor(#g_5)
  ;DisableGadget(#g_5, 1)
  
  SetWindowCallback(@MainWindow_Callback(), #MainWindow)
  
  Repeat
    e = WaitWindowEvent()
    If e = #PB_Event_Gadget And EventGadget() = #g_6
      If GetGadgetState(#g_6)
        SetButtonGadgetTextColor(#g_5)
      Else
        SetButtonGadgetTextColor(#g_5, #Magenta)
      EndIf
      DisableGadget(#g_5, GetGadgetState(#g_6))
    EndIf
  Until e = #PB_Event_CloseWindow
EndIf

If *DrawThemeText
  UnHook(*DrawThemeText)
EndIf

CloseLibrary(0)

ForEach ColoredGadget()
  DeleteObject_(ColoredGadget()\hBrushBackColor)
Next
User avatar
jacdelad
Addict
Addict
Posts: 1993
Joined: Wed Feb 03, 2021 12:46 pm
Location: Riesa

Re: Creating an own control/gadget using Windows API

Post by jacdelad »

Thanks to all, I'll try this...when I have power again. There's currently a power outage where I live...again (it's annoying).
Also, I'm still working on the Gerber module, so I have to split my time. But that's all great suggestions to work with.
Good morning, that's a nice tnetennba!

PureBasic 6.21/Windows 11 x64/Ryzen 7900X/32GB RAM/3TB SSD
Synology DS1821+/DX517, 130.9TB+50.8TB+2TB SSD
BarryG
Addict
Addict
Posts: 4128
Joined: Thu Apr 18, 2019 8:17 am

Re: Creating an own control/gadget using Windows API

Post by BarryG »

Is this correct?

Code: Select all

old = SelectObject_(hdcTemp, hBitmap)
[...]
DeleteObject_(SelectObject_(hdcTemp, old))
Why are you selecting the object again? Shouldn't deleting it be like this:

Code: Select all

DeleteObject_(old)
I don't know, hence why I'm asking. It returns 1 for success when doing it my way.

(Trying to find why my app has increasing GDI objects in Task Manager).
breeze4me
Enthusiast
Enthusiast
Posts: 633
Joined: Thu Mar 09, 2006 9:24 am
Location: S. Kor

Re: Creating an own control/gadget using Windows API

Post by breeze4me »

BarryG wrote: Tue Jul 23, 2024 5:45 am Is this correct?

Code: Select all

old = SelectObject_(hdcTemp, hBitmap)
[...]
DeleteObject_(SelectObject_(hdcTemp, old))
Why are you selecting the object again? Shouldn't deleting it be like this:

Code: Select all

DeleteObject_(old)
I don't know, hence why I'm asking. It returns 1 for success when doing it my way.

(Trying to find why my app has increasing GDI objects in Task Manager).
The result is the same as the following code.

Code: Select all

old = SelectObject_(hdcTemp, hBitmap)
[...]
SelectObject_(hdcTemp, old)  ; The return value is the same as hBitmap.
DeleteObject_(hBitmap)
BarryG
Addict
Addict
Posts: 4128
Joined: Thu Apr 18, 2019 8:17 am

Re: Creating an own control/gadget using Windows API

Post by BarryG »

So what should I be doing? Just "DeleteObject_(hBitmap)" then?
Fred
Administrator
Administrator
Posts: 18162
Joined: Fri May 17, 2002 4:39 pm
Location: France
Contact:

Re: Creating an own control/gadget using Windows API

Post by Fred »

You need to select the old object back before deleting the new one, so the code is correct.
breeze4me
Enthusiast
Enthusiast
Posts: 633
Joined: Thu Mar 09, 2006 9:24 am
Location: S. Kor

Re: Creating an own control/gadget using Windows API

Post by breeze4me »

BarryG wrote: Tue Jul 23, 2024 9:24 am So what should I be doing? Just "DeleteObject_(hBitmap)" then?
The two codes are actually the same code.
I ran my code and checked it in the task manager and there are no GDI object leaks, so you don't need to modify anything about the code above.
Post Reply