1

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

Paul Hashmi
  • 456
  • 5
  • 18

2 Answers2

2

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
Barmak Shemirani
  • 30,904
  • 6
  • 40
  • 77
  • Hi Barmak, Thanks for the reply. Thats almost it and im pretty sure I can get it from this into what I need...You are very right it is going to get very messy but the rest of the app is wrote in python already. I had the same thought to port it all to c++ but If I can help it I really dont want to...I need to have an individual line with two colours on it so in the example string above the username is one color and the text is another...when I get a minute after work I will have a mess around and accept your answer when I get it done. Thanks again, Turtle – Paul Hashmi Feb 01 '19 at 17:27
  • Agonisingly close now...I will pop my code up in a minute...Thanks for your help it seems I should be able to get it working! – Paul Hashmi Feb 02 '19 at 00:13
  • Got it, your answer helped me get their so I have added your comment as correct. Thanks for your help! :) – Paul Hashmi Feb 02 '19 at 00:50
  • *"I had the same thought to port it all to c++"* Actually, I wasn't recommending what language you should use, since I don't know your project. I was suggesting to make a test program in C++, then convert from C++ back to Python. That might be easier, or maybe not. – Barmak Shemirani Feb 02 '19 at 01:28
1

@Barmak, here is the code I got from your help...I marked you as correct, if you hadn't of posted I would still be struggling with this. If you run it you can see it working, Your a legend!

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



messagePrompt = ' :'
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

# New code: Define globaL
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)

    thr = threading.Thread(target=customDraw, args=(hWindow,))
    thr.setDaemon(False)
    thr.start()

    # Dispatch messages
    win32gui.PumpMessages()

def customDraw(hWindow):
    win32gui.RedrawWindow(hWindow, None, None, win32con.RDW_INVALIDATE | 
win32con.RDW_ERASE)



queue(("Dave: ", "Daves message was important"))
queue(("Chris: ", "Chris is asleep again"))
queue(("Suzy: ", "Suzy has had way to much cake"))
queue(("Sarah: ", "Sarah is shockingly beautiful"))
queue(("Steve: ", "Steve likes to eat dog treats")) 


def wndProc(hWnd, message, wParam, lParam):
    textFormat = win32con.DT_NOCLIP | win32con.DT_VCENTER | win32con.DT_EXPANDTABS
    if message == win32con.WM_PAINT:
        hDC, paintStruct = win32gui.BeginPaint(hWnd)
        fontSize = 20

        lf = win32gui.LOGFONT()
        lf.lfFaceName = "Times New Roman"
        lf.lfHeight = fontSize
        lf.lfWeight = 300

        lf.lfQuality = win32con.NONANTIALIASED_QUALITY
        hf = win32gui.CreateFontIndirect(lf)
        win32gui.SelectObject(hDC, hf)

        if len(userAndMessage) > 4:
            win32gui.SetTextColor(hDC,win32api.RGB(255,0,0))
            rect = win32gui.GetClientRect(hWnd)
            drawRect = win32gui.DrawText(hDC,userAndMessage[0][0],-1, rect, 
win32con.DT_CALCRECT); 
            win32gui.DrawText(hDC, userAndMessage[0][0], -1, rect, textFormat)

            win32gui.SetTextColor(hDC,win32api.RGB(240,240,240))
            drawrect = win32gui.DrawText(hDC, userAndMessage[0][1], -1, rect, 
win32con.DT_CALCRECT);
            rect = (drawRect[1][0] + drawRect[1][2], drawRect[1][1], drawRect[1][2], 
drawRect[1][3])
            win32gui.DrawText(hDC, userAndMessage[0][1], -1, rect, textFormat)


#####################################################################################
            win32gui.SetTextColor(hDC,win32api.RGB(255,0,0))
            rect = (0, drawRect[1][1] + drawRect[1][3], drawRect[1][2], drawRect[1] 
[3])
            drawRect = win32gui.DrawText(hDC,userAndMessage[1][0],-1, rect, 
win32con.DT_CALCRECT); 
            win32gui.DrawText(hDC, userAndMessage[1][0], -1, rect, textFormat)

            win32gui.SetTextColor(hDC,win32api.RGB(240,240,240))
            drawrect = win32gui.DrawText(hDC, userAndMessage[1][1], -1, rect, 
win32con.DT_CALCRECT);
            rect = (drawRect[1][0] + drawRect[1][2], drawRect[1][1], drawRect[1][2], 
drawRect[1][3])
            win32gui.DrawText(hDC, userAndMessage[1][1], -1, rect, textFormat)


#####################################################################################
            win32gui.SetTextColor(hDC,win32api.RGB(255,0,0))
            rect = (0, drawRect[1][1] + (drawRect[1][3] // 2), drawRect[1][2], 
drawRect[1][3])
            drawRect = win32gui.DrawText(hDC,userAndMessage[2][0],-1, rect, 
win32con.DT_CALCRECT); 
            win32gui.DrawText(hDC, userAndMessage[2][0], -1, rect, textFormat)

            win32gui.SetTextColor(hDC,win32api.RGB(240,240,240))
            drawrect = win32gui.DrawText(hDC, userAndMessage[2][1], -1, rect, 
win32con.DT_CALCRECT);
            rect = (drawRect[1][0] + drawRect[1][2], drawRect[1][1], drawRect[1][2], 
drawRect[1][3])
            win32gui.DrawText(hDC, userAndMessage[2][1], -1, rect, textFormat)

        #####################################################################################
            win32gui.SetTextColor(hDC,win32api.RGB(255,0,0))
            rect = (0, drawRect[1][1] + (drawRect[1][3] // 3), drawRect[1][2], 
drawRect[1][3])
            drawRect = win32gui.DrawText(hDC,userAndMessage[3][0],-1, rect, 
win32con.DT_CALCRECT); 
            win32gui.DrawText(hDC, userAndMessage[3][0], -1, rect, textFormat)

            win32gui.SetTextColor(hDC,win32api.RGB(240,240,240))
            drawrect = win32gui.DrawText(hDC, userAndMessage[3][1], -1, rect, 
win32con.DT_CALCRECT);
            rect = (drawRect[1][0] + drawRect[1][2], drawRect[1][1], drawRect[1][2], 
drawRect[1][3])
            win32gui.DrawText(hDC, userAndMessage[3][1], -1, rect, textFormat)


#####################################################################################
            win32gui.SetTextColor(hDC,win32api.RGB(255,0,0))
            rect = (0, drawRect[1][1] + (drawRect[1][3] // 4), drawRect[1][2], 
drawRect[1][3])
            drawRect = win32gui.DrawText(hDC,userAndMessage[4][0],-1, rect, 
win32con.DT_CALCRECT); 
            win32gui.DrawText(hDC, userAndMessage[4][0], -1, rect, textFormat)

            win32gui.SetTextColor(hDC,win32api.RGB(240,240,240))
            drawrect = win32gui.DrawText(hDC, userAndMessage[4][1], -1, rect, 
win32con.DT_CALCRECT);
            rect = (drawRect[1][0] + drawRect[1][2], drawRect[1][1], drawRect[1][2], 
drawRect[1][3])
            win32gui.DrawText(hDC, userAndMessage[4][1], -1, rect, textFormat)

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

Thanks again, Turtle

Paul Hashmi
  • 456
  • 5
  • 18