Search code examples
pythonwinapidrawtext

Python win32 api drawText() and SetTextColor


I have managed to mangle together a program that takes a strings over multiple lines and prints them onto a transparent background. I wanted to know if there is a way to colour individual parts of the string different colours. I know there is but my lack of understanding of win32 is really getting in my way here. Do I need to split the text into two parts and make a call to drawText() or can I can the text colour be changed half way through the string? Any point towards a information or a solution would be great.

example: string = "Username: some message that the user has sent."

I have searched on Stack and multiple other sites and have had no joy as of yet. I usually wouldn't but I have dumped the code as it can be run and you can see what I mean.

I apologise in advance for the lack of comments and the state of the code.

import win32api
import win32con
import win32gui
import time
import threading
from collections import deque


userAndMessage = deque()

def queue(message):
    userAndMessage.append(message)

def getQueue():
    return userAndMessage;

def dequeue():
    return userAndMessage.popleft()

def cleanMessage(message):
    return message.split("\r\n")[0]

def showMessages():
    return userAndMessage[0] + "\n" + userAndMessage[1] + "\n" + 
userAndMessage[2] + "\n" + userAndMessage[3] + "\n" + userAndMessage[4]

#Code example modified from:
#Christophe Keller
#Hello World in Python using Win32

windowText = ''

def main():
    #get instance handle
    hInstance = win32api.GetModuleHandle()
    # the class name
    className = 'SimpleWin32'

    # create and initialize window class
    wndClass                = win32gui.WNDCLASS()
    wndClass.style          = win32con.CS_HREDRAW | win32con.CS_VREDRAW
    wndClass.lpfnWndProc    = wndProc
    wndClass.hInstance      = hInstance
    wndClass.hCursor        = win32gui.LoadCursor(None, win32con.IDC_ARROW)
    wndClass.hbrBackground  = win32gui.GetStockObject(win32con.WHITE_BRUSH)
    wndClass.lpszClassName  = className

    # register window class
    wndClassAtom = None
    try:
        wndClassAtom = win32gui.RegisterClass(wndClass)
    except Exception as e:
        print (e)
        raise e

    exStyle = win32con.WS_EX_COMPOSITED | win32con.WS_EX_LAYERED | 
win32con.WS_EX_NOACTIVATE | win32con.WS_EX_TOPMOST | 
win32con.WS_EX_TRANSPARENT
    style = win32con.WS_DISABLED | win32con.WS_POPUP | win32con.WS_VISIBLE

    hWindow = win32gui.CreateWindowEx(
        exStyle,
        wndClassAtom,
        None, # WindowName
        style,
        20, # x
        900, # y
        1920, # width
        600, # height
        None, # hWndParent
        None, # hMenu
        hInstance,
        None # lpParam
)

    # Show & update the window
    win32gui.SetLayeredWindowAttributes(hWindow, 0x00ffffff, 255, 
win32con.LWA_COLORKEY | win32con.LWA_ALPHA)
    win32gui.SetWindowPos(hWindow, win32con.HWND_TOPMOST, 0, 0, 0, 0,
        win32con.SWP_NOACTIVATE | win32con.SWP_NOMOVE | win32con.SWP_NOSIZE 
| win32con.SWP_SHOWWINDOW)

    win32gui.ShowWindow(hWindow, win32con.SW_SHOWNORMAL)
    win32gui.UpdateWindow(hWindow)

    # New code: Create and start the thread
    thr = threading.Thread(target=customDraw, args=(hWindow,))
    thr.setDaemon(False)
    thr.start()

    # Dispatch messages
    win32gui.PumpMessages()


# New code: Attempt to change the text 1 second later
def customDraw(hWindow): 

    strOne      = "SomeUser: This is test line one"
    strTwo      = "SomeOtherUser: This is test line two"
    strThree    = "AndAnother: This is test line three"
    strFour     = "UserOne: This is test line four"
    strFive     = "AndAgain: This is test line five" 


    queue(strOne)
    queue(strTwo)
    queue(strThree)
    queue(strFour)
    queue(strFive) 

    global windowText
    windowText = showMessages()
    win32gui.RedrawWindow(hWindow, None, None, win32con.RDW_INVALIDATE | 
win32con.RDW_ERASE)


def wndProc(hWnd, message, wParam, lParam):
    if message == win32con.WM_PAINT:
        hDC, paintStruct = win32gui.BeginPaint(hWnd)


        fontSize = 26
        lf = win32gui.LOGFONT()
        lf.lfFaceName = "Stencil"
        lf.lfHeight = fontSize
        lf.lfWeight = 600

        lf.lfQuality = win32con.NONANTIALIASED_QUALITY
        hf = win32gui.CreateFontIndirect(lf)
        win32gui.SelectObject(hDC, hf)
        win32gui.SetTextColor(hDC,win32api.RGB(240,0,50))

        rect = win32gui.GetClientRect(hWnd)
        win32gui.DrawText(hDC,windowText,-1, rect, win32con.DT_CALCRECT); 
        win32gui.DrawText(
            hDC,
            windowText,
            -1,
            rect,
            win32con.DT_NOCLIP | win32con.DT_VCENTER | 
win32con.DT_EXPANDTABS
        )
        win32gui.EndPaint(hWnd, paintStruct)
        return 0

    elif message == win32con.WM_DESTROY:
        print('Being destroyed')
        win32gui.PostQuitMessage(0)
        return 0

    else:
        return win32gui.DefWindowProc(hWnd, message, wParam, lParam)

if __name__ == '__main__':
    main()

there may be some indentation out of line, this is not the case in the program its just that I had to press the spacebar 4 times on each line of text.

Thanks


Solution

  • Yes, you have to use SetTextColor to change the color before calling DrawText

    You are correctly calling DrawText with DT_CALCRECT option. This doesn't draw anything, it just calculates the height of the rectangle (based on width...) Python's DrawText will return a tuple for the calculated rectangle.

    Then call DrawText again, with the same text format, without DT_CALCRECT flag. Then offset the rectangle, change color, and draw the next text.

    Note, this can get very messy in pywin32, it might be easier to try it out in C/C++ first.

    if message == win32con.WM_PAINT:
        hDC, paintStruct = win32gui.BeginPaint(hWnd)
    
        fontSize = 16
        lf = win32gui.LOGFONT()
        lf.lfFaceName = "Stencil"
        lf.lfHeight = fontSize
        lf.lfWeight = 600
    
        lf.lfQuality = win32con.NONANTIALIASED_QUALITY
        hf = win32gui.CreateFontIndirect(lf)
        win32gui.SelectObject(hDC, hf)
    
        text1 = 'line1'
        text2 = 'line2'
        text3 = 'line3'
        rect = win32gui.GetClientRect(hWnd)
    
        textformat = win32con.DT_LEFT | win32con.DT_TOP
    
        win32gui.SetTextColor(hDC,win32api.RGB(255,0,0))
        drawrect = win32gui.DrawText(hDC, text1, -1, rect, textformat | win32con.DT_CALCRECT);
        win32gui.DrawText(hDC, text1, -1, rect, textformat)
    
        l = drawrect[1][0]
        t = drawrect[1][1]
        r = drawrect[1][2]
        b = drawrect[1][3]
        height = b - t
        rect = (l, t + height, r, b + height)
    
        win32gui.SetTextColor(hDC,win32api.RGB(0,255,0))
        drawrect = win32gui.DrawText(hDC, text2, -1, rect, textformat | win32con.DT_CALCRECT);
        win32gui.DrawText(hDC, text2, -1, rect, textformat)
    
        l = drawrect[1][0]
        t = drawrect[1][1]
        r = drawrect[1][2]
        b = drawrect[1][3]
        height = b - t
        rect = (l, t + height, r, b + height)
    
        win32gui.SetTextColor(hDC,win32api.RGB(0,0,255))
        drawrect = win32gui.DrawText(hDC, text3, -1, rect, textformat | win32con.DT_CALCRECT);
        win32gui.DrawText(hDC, text3, -1, rect, textformat)
    
        win32gui.EndPaint(hWnd, paintStruct)
        return 0