1

I'll followed this post to include a color picker in tkinter, that remembers last color chosen, and custom colors. It works as expected, but the color chooser window is always created at the top left corner. I want to choose the initial location of the color chooser, and set it to be always on top. Are there any ways to achieve this?

Here's what I have so far:

import ctypes
import ctypes.wintypes as wtypes


"""
https://stackoverflow.com/a/49237204/12471420
thanks!
"""


class CHOOSECOLOR(ctypes.Structure):
    """ " a class to represent CWPRETSTRUCT structure
    https://msdn.microsoft.com/en-us/library/windows/desktop/ms646830(v=vs.85).aspx"""

    _fields_ = [
        ("lStructSize", wtypes.DWORD),
        ("hwndOwner", wtypes.HWND),
        ("hInstance", wtypes.HWND),
        ("rgbResult", wtypes.COLORREF),
        ("lpCustColors", ctypes.POINTER(wtypes.COLORREF)),
        ("Flags", wtypes.DWORD),
        ("lCustData", wtypes.LPARAM),
        ("lpfnHook", wtypes.LPARAM),
        ("lpTemplateName", ctypes.c_char_p),
       
    ]


class ColorChooser:
    """a class to represent Color dialog box
    https://msdn.microsoft.com/en-gb/library/windows/desktop/ms646912(v=vs.85).aspx"""

    CC_SOLIDCOLOR = 0x80
    CC_FULLOPEN = 0x02
    CC_RGBINIT = 0x01
    WS_EX_TOPMOST = 0x08
    custom_color_array = ctypes.c_uint32 * 16
    color_chooser = ctypes.windll.Comdlg32.ChooseColorW
    
    def to_custom_color_array(self, custom_colors):
        custom_int_colors = self.custom_color_array()

        for i in range(16):
            custom_int_colors[i] = (
                rgb_to_int(*custom_colors[i])
                if i < len(custom_colors)
                else rgb_to_int(*(255, 255, 255))
            )

        return custom_int_colors

    def askcolor(self, initialColor, custom_colors):
        struct = CHOOSECOLOR()

        ctypes.memset(ctypes.byref(struct), 0, ctypes.sizeof(struct))
        struct.lStructSize = ctypes.sizeof(struct)
        struct.Flags = self.CC_SOLIDCOLOR | self.CC_FULLOPEN | self.CC_RGBINIT
        struct.lpCustColors = self.to_custom_color_array(custom_colors)
        struct.rgbResult = rgb_to_int(*initialColor)

        if self.color_chooser(ctypes.byref(struct)):
            result = int_to_rgb(struct.rgbResult)
        else:
            result = None

        return result, struct.lpCustColors


def rgb_to_int(red, green, blue):
    return red + (green << 8) + (blue << 16)


def int_to_rgb(int_color):
    red = int_color & 255
    green = (int_color >> 8) & 255
    blue = (int_color >> 16) & 255

    return red, green, blue

goldenotaste
  • 302
  • 2
  • 11
  • You would need to get the *hwnd* then you need to get the *rect* of the hwnd and set the params you want to the rect. Finally you would need to set the style of the hwnd to *topmost*. – Thingamabobs May 01 '21 at 07:32
  • @Atlas435 hi thanks for replying. Can you explain a bit further please? I'm not familiar with these at all. In the original explain, 'hwndOwner' is not set to anything, can I work with this somehow? – goldenotaste May 01 '21 at 07:38
  • Do you have an HWND to assign to hwndParent? – selbie May 01 '21 at 07:39
  • 1
    https://stackoverflow.com/questions/44218662/how-to-get-the-hwnd-of-a-tkinter-window-in-python3-on-windows – selbie May 01 '21 at 07:40
  • Get the HWND associated with the Tkinter window you are hosting. Assign that to hwndOwner on the CHOOSECOLOR object. – selbie May 01 '21 at 07:41
  • from my experience you need to get the parent of the root, like: `hwnd = ctypes.windll.user32.GetParent(root.winfo_id())` – Thingamabobs May 01 '21 at 07:43

2 Answers2

1

You can use lpfnHook to control the generation of the dialog box, and then call SetWindowPos when WM_INITDIALOG to adjust the position of the generated dialog box.

Some C++ code:

UINT_PTR CALLBACK Lpcchookproc(
    HWND hWnd,
    UINT message,
    WPARAM wParam,
    LPARAM lParam
)

{
    if (message == WM_INITDIALOG)
    {
        SetWindowPos(hWnd, HWND_TOPMOST, 400, 400, 0, 0, SWP_NOSIZE);
    }
    return 0;
}

int main()
{
    CHOOSECOLOR cc;                 // common dialog box structure 
    static COLORREF acrCustClr[16]; // array of custom colors 
    HBRUSH hbrush;                  // brush handle
    static DWORD rgbCurrent;        // initial color selection
    // Initialize CHOOSECOLOR 
    ZeroMemory(&cc, sizeof(cc));
    cc.lStructSize = sizeof(cc);
    cc.lpCustColors = (LPDWORD)acrCustClr;
    cc.Flags = CC_FULLOPEN | CC_RGBINIT | CC_ENABLEHOOK;
    cc.lpfnHook = Lpcchookproc;
    ChooseColor(&cc) == TRUE;
    return 0;
}
Zeus
  • 3,703
  • 3
  • 7
  • 20
0

Thanks you to all who replied, here's what I ended up using.


import ctypes
import ctypes.wintypes as wtypes


"""
https://stackoverflow.com/a/49237204/12471420

a color chooser for tkinter with initial color, and custom color settings.
"""


class CHOOSECOLOR(ctypes.Structure):
    """ " a class to represent CWPRETSTRUCT structure
    https://msdn.microsoft.com/en-us/library/windows/desktop/ms646830(v=vs.85).aspx"""

    _fields_ = [
        ("lStructSize", wtypes.DWORD),
        ("hwndOwner", wtypes.HWND),
        ("hInstance", wtypes.HWND),
        ("rgbResult", wtypes.COLORREF),
        ("lpCustColors", ctypes.POINTER(wtypes.COLORREF)),
        ("Flags", wtypes.DWORD),
        ("lCustData", wtypes.LPARAM),
        ("lpfnHook", wtypes.LPARAM),
        ("lpTemplateName", ctypes.c_char_p),
    ]


class ColorChooser:
    """a class to represent Color dialog box
    https://msdn.microsoft.com/en-gb/library/windows/desktop/ms646912(v=vs.85).aspx"""

    CC_SOLIDCOLOR = 0x80
    CC_FULLOPEN = 0x02
    CC_RGBINIT = 0x01
    WS_EX_TOPMOST = 0x08
    custom_color_array = ctypes.c_uint32 * 16
    color_chooser = ctypes.windll.Comdlg32.ChooseColorW

    def to_custom_color_array(self, custom_colors):
        custom_int_colors = self.custom_color_array()

        for i in range(16):
            custom_int_colors[i] = (
                rgb_to_int(*custom_colors[i])
                if i < len(custom_colors)
                else rgb_to_int(*(255, 255, 255))
            )

        return custom_int_colors

    def askcolor(self, hostId, initialColor, custom_colors):
        #hostId is .winfo_id() of parent window, colors are hex rgb string, and 
        #custom_colors accepts up to 16 hex strings.
        struct = CHOOSECOLOR()
        ctypes.memset(ctypes.byref(struct), 0, ctypes.sizeof(struct))
        struct.lStructSize = ctypes.sizeof(struct)
        struct.Flags = self.CC_SOLIDCOLOR | self.CC_FULLOPEN | self.CC_RGBINIT
        struct.lpCustColors = self.to_custom_color_array(custom_colors)
        struct.rgbResult = rgb_to_int(*initialColor)
        struct.hwndOwner = hostId
        if self.color_chooser(ctypes.byref(struct)):
            result = int_to_rgb(struct.rgbResult)
        else:
            result = None

        return result, [int_to_rgb(struct.lpCustColors[i]) for i in range(16)]


def rgb_to_int(red, green, blue):
    return red + (green << 8) + (blue << 16)


def int_to_rgb(int_color):
    red = int_color & 255
    green = (int_color >> 8) & 255
    blue = (int_color >> 16) & 255

    return red, green, blue


goldenotaste
  • 302
  • 2
  • 11