ScrollWindowEx not working properly [Windows API]

I have a window that has a bunch of buttons. When the buttons exceed the height of the window, I want to use a scrollbar. The document says that if I set the 4thh argument of of ScrollWindowEx to Null, it will scroll the entire window, but when I set it to None, it throws an ArgumentError. This is the exact error: ctypes.ArgumentError: argument 4: <class 'TypeError'>: expected RECT instance instead of NoneType. If I make a Rect(), it throws the same error, but for argument 5. Again, the documentation says that I may enter Null as 5th argument. When I make a RECT of same size for both arguments and scroll, a chunk of the windows disappears. If you want to run the code on your machine, you will need user32.py

Here is main.py:

from os import listdir
from user32 import *
from button import Button
from pathlib import Path

hInst = windll.kernel32.GetModuleHandleW(0)
buttons = []

BTN_WIDTH = 300
BTN_HEIGHT = 100
LINES = 28

def window_proc(hwnd: HWND, umsg: UINT, wparam: WPARAM, lparam: LPARAM) -> LRESULT:
    si = SCROLLINFO()
    si_pointer = pointer(si)
    match umsg:
        case WindowMessage.CREATE:
            create_list_of_buttons(get_files(), hwnd)
        case WindowMessage.DESTROY:
            PostQuitMessage(0)
            return 0
        
        case WindowMessage.SIZE:
            height = (HIWORD(lparam))
            width = (LOWORD(lparam))

            si.cbSize = sizeof(si)
            si.fMask = ScrollInfoMessage.RANGE | ScrollInfoMessage.PAGE
            si.nMin = 0
            si.nMax = LINES
            si.nPage = int(height.value) // BTN_HEIGHT;
            SetScrollInfo(hwnd, ScrollBarConstants.VERT, si_pointer, True)

        case WindowMessage.COMMAND:
            if wparam >= 100:
                btn_clicked = ([button for button in buttons if button.hMenu == wparam])
                filename = btn_clicked[0].btn_text
                print(filename)
                rect = RECT()
                rect_p = pointer(rect)
                print(GetClientRect(hwnd, rect_p))
                print(rect.top, rect.bottom, rect.left, rect.right)
        case WindowMessage.VSCROLL:
            si.cbSize = sizeof(si)
            si.fMask = ScrollInfoMessage.ALL
            GetScrollInfo(hwnd, ScrollBarConstants.VERT, si_pointer)

            yPos = si.nPos
            match LOWORD(wparam).value:
                case ScrollBarCommands.LINEUP:
                    si.nPos -= 1
                case ScrollBarCommands.THUMBTRACK:
                    si.nPos = si.nTrackPos

            si.fMask = ScrollInfoMessage.POS
            SetScrollInfo(hwnd, ScrollBarConstants.VERT, si_pointer, True)
            GetScrollInfo(hwnd, ScrollBarConstants.VERT, si_pointer)
            if(si.nPos != yPos):
                
                ScrollWindowEx(hwnd, 0, yPos - si.nPos, RECT(0, 0, 561, 567), RECT(0, 0, 561, 567), None, None, 0x0010)
                UpdateWindow(hwnd)

    return DefWindowProcW(hwnd, umsg, wparam, lparam)


def create_window(className, windowName):
    wnd_main = CreateWindowExW(
        0,
        className,
        windowName,
        WindowStyles.OVERLAPPED
        | WindowStyles.CAPTION
        | WindowStyles.SYSMENU
        | WindowStyles.THICKFRAME
        | WindowStyles.MINIMIZEBOX
        | WindowStyles.MAXIMIZEBOX
        | WindowStyles.CAPTION
        | WindowStyles.VSCROLL,
        CW_USEDEFAULT,
        CW_USEDEFAULT,
        600,
        600,
        0,
        0,
        hInst,
        0,
    )
    if not wnd_main:
        print("Window Creation Falid: ", GetLastError())
        return
    return wnd_main


def get_files():
    folder_path = Path(__file__).parent / "log"
    return [file for file in listdir(folder_path) if file.endswith(".txt")]

def create_list_of_buttons(filenames: list[str], parent_window):
    """
    Makes a list of button that have the file's name as their heading
    """
    x, y = 0, 0
    for button_id, filename in enumerate(filenames, 100):
        button = Button(filename, x, y, BTN_WIDTH, BTN_HEIGHT, parent_window, button_id)
        button.create_button()
        buttons.append(button)
        y += BTN_HEIGHT

    return buttons




def main():
    wclassName = ctypes.c_wchar_p("My")
    wname = ctypes.c_wchar_p("Left")
    wndClass = WNDCLASSEXW()
    wndClass.cbSize = sizeof(WNDCLASSEXW)
    wndClass.style = ClassStyles.HREDRAW | ClassStyles.VREDRAW
    wndClass.lpfnWndProc = WNDPROC(window_proc)
    wndClass.cbClsExtra = 0
    wndClass.cbWndExtra = 0
    wndClass.hInstance = hInst
    wndClass.hIcon = 0
    wndClass.hCursor = 0
    wndClass.hBrush = windll.gdi32.GetStockObject(0)
    wndClass.lpszMenuName = 0
    wndClass.lpszClassName = wclassName
    wndClass.hIconSm = 0
    RegisterClassExW(byref(wndClass))
    wnd_main = create_window(wclassName, wname)
    ShowWindow(wnd_main, 5)
    UpdateWindow(wnd_main)

    msg = MSG()
    lpmsg = pointer(msg)
    while (GetMessageW(lpmsg, 0, 0, 0)) != 0:
        TranslateMessage(lpmsg)
        DispatchMessageW(lpmsg)


main()

Looking at user32.py, I see the following line

ScrollWindowEx.argtypes = [HWND, c_int, c_int, RECT, RECT, HRGN, LPRECT, UINT]

The documentation at ScrollWindowEx function (winuser.h) - Win32 apps | Microsoft Docs indicates that the 3rd and 4th arguments should be pointers to RECT, not RECT structures themselves. Try editing this line so that the 3rd and 4th arguments are LPRECT, then modify your main.py to pass pointers into this function.

I wrote LPRECT in user32.py and when I passed in None, it didn’t throw any error, but everything still disappears. What am I doing wrong?

I’m not sure. One thing you could try is to write an equivalent version or a small portion of your program in C/C++ that uses the Windows API directly and see whether you experience the same problem. That would tell you whether the issue is specific to Python.

oh okay. I have a different problem now. I changed the flags from 0x0010 (SW_SMOOTHSCROLL) to 0x0001 (SW_SCROLLCHILDREN). It kind of works. The buttons don’t disappear, but when I scroll, things don’t get rendered properly. For example, when I scroll down, the button at the bottom of the screen doesn’t move up, instead it’s height increases and when I hover over the buttons, the new buttons get rendered. I am not sure what is causing that behavior.

from user32 import *
from button import Button
from pathlib import Path

hInst = windll.kernel32.GetModuleHandleW(0)
buttons = []

BTN_WIDTH = 300
BTN_HEIGHT = 100
LINES = 28

def window_proc(hwnd: HWND, umsg: UINT, wparam: WPARAM, lparam: LPARAM) -> LRESULT:
    si = SCROLLINFO()
    si_pointer = pointer(si)
    match umsg:
        case WindowMessage.CREATE:
            create_list_of_buttons(get_files(), hwnd)
        case WindowMessage.DESTROY:
            PostQuitMessage(0)
            return 0
        
        case WindowMessage.SIZE:
            height = (HIWORD(lparam))
            width = (LOWORD(lparam))

            si.cbSize = sizeof(si)
            si.fMask = ScrollInfoMessage.RANGE | ScrollInfoMessage.PAGE
            si.nMin = 0
            si.nMax = LINES
            si.nPage = height * BTN_HEIGHT;
            SetScrollInfo(hwnd, ScrollBarConstants.VERT, si_pointer, True)

        case WindowMessage.COMMAND:
            if wparam >= 100:
                btn_clicked = ([button for button in buttons if button.hMenu == wparam])
                filename = btn_clicked[0].btn_text
                print(filename)
                rect = RECT()
                rect_p = pointer(rect)
                print(GetClientRect(hwnd, rect_p))
                print(rect.top, rect.bottom, rect.left, rect.right)
        case WindowMessage.VSCROLL:
            si.cbSize = sizeof(si)
            si.fMask = ScrollInfoMessage.ALL
            GetScrollInfo(hwnd, ScrollBarConstants.VERT, si_pointer)

            yPos = si.nPos
            match LOWORD(wparam).value:
                case ScrollBarCommands.LINEUP:
                    si.nPos -= 1
                case ScrollBarCommands.TOP:
                    si.nPos = si.nMin
                case ScrollBarCommands.BOTTOM:
                    si.nPos = si.nMax
                case ScrollBarCommands.LINEUP:
                    si.nPos -= 1
                case ScrollBarCommands.LINEDOWN:
                    si.nPos += 1
                case ScrollBarCommands.PAGEUP:
                    si.nPos -= si.nPage
                case ScrollBarCommands.PAGEDOWN:
                    si.nPos += si.nPage
                case ScrollBarCommands.THUMBTRACK:
                    print(si.nPos)
                    si.nPos = si.nTrackPos
                    print(si.nPos)

            si.fMask = ScrollInfoMessage.POS
            SetScrollInfo(hwnd, ScrollBarConstants.VERT, si_pointer, True)
            GetScrollInfo(hwnd, ScrollBarConstants.VERT, si_pointer)
            if(si.nPos != yPos):
                ScrollWindowEx(hwnd, 0, (yPos - si.nPos), None, None, None, None, 0x0001)
                UpdateWindow(hwnd)

    return DefWindowProcW(hwnd, umsg, wparam, lparam)


def create_window(className, windowName):
    wnd_main = CreateWindowExW(
        0,
        className,
        windowName,
        WindowStyles.OVERLAPPED
        | WindowStyles.CAPTION
        | WindowStyles.SYSMENU
        | WindowStyles.THICKFRAME
        | WindowStyles.MINIMIZEBOX
        | WindowStyles.MAXIMIZEBOX
        | WindowStyles.CAPTION
        | WindowStyles.VSCROLL,
        CW_USEDEFAULT,
        CW_USEDEFAULT,
        600,
        600,
        0,
        0,
        hInst,
        0,
    )
    if not wnd_main:
        print("Window Creation Falid: ", GetLastError())
        return
    return wnd_main


def get_files():
    folder_path = Path(__file__).parent / "log"
    return [file for file in listdir(folder_path) if file.endswith(".txt")]

def create_list_of_buttons(filenames: list[str], parent_window):
    """
    Makes a list of button that have the file's name as their heading
    """
    x, y = 0, 0
    for button_id, filename in enumerate(filenames, 100):
        button = Button(filename, x, y, BTN_WIDTH, BTN_HEIGHT, parent_window, button_id)
        button.create_button()
        buttons.append(button)
        y += BTN_HEIGHT

    return buttons




def main():
    wclassName = ctypes.c_wchar_p("My")
    wname = ctypes.c_wchar_p("Left")
    wndClass = WNDCLASSEXW()
    wndClass.cbSize = sizeof(WNDCLASSEXW)
    wndClass.style = ClassStyles.HREDRAW | ClassStyles.VREDRAW
    wndClass.lpfnWndProc = WNDPROC(window_proc)
    wndClass.cbClsExtra = 0
    wndClass.cbWndExtra = 0
    wndClass.hInstance = hInst
    wndClass.hIcon = 0
    wndClass.hCursor = 0
    wndClass.hBrush = windll.gdi32.GetStockObject(0)
    wndClass.lpszMenuName = 0
    wndClass.lpszClassName = wclassName
    wndClass.hIconSm = 0
    RegisterClassExW(byref(wndClass))
    wnd_main = create_window(wclassName, wname)
    ShowWindow(wnd_main, 5)
    UpdateWindow(wnd_main)

    msg = MSG()
    lpmsg = pointer(msg)
    while (GetMessageW(lpmsg, 0, 0, 0)) != 0:
        TranslateMessage(lpmsg)
        DispatchMessageW(lpmsg)


main()

This is the updated code

And this is updated user32.py:

import ctypes
import enum
from ctypes import *
from ctypes.wintypes import *

WNDPROC = ctypes.WINFUNCTYPE(c_int, HWND, c_uint, WPARAM, LPARAM)
LRESULT = c_long
LONG_PTR = LPARAM
CW_USEDEFAULT = 0x80000000
WNDENUMPROC = ctypes.WINFUNCTYPE(BOOL, HWND, LPARAM)
user32 = windll.user32


class WindowStyles(enum.IntFlag):
    BORDER = 0x00800000
    CAPTION = 0x00C00000
    CHILD = 0x40000000
    CHILDWINDOW = 0x40000000
    CLIPCHILDREN = 0x02000000
    CLIPSIBLINGS = 0x04000000
    DISABLED = 0x08000000
    DLGFRAME = 0x00400000
    GROUP = 0x00020000
    HSCROLL = 0x00100000
    ICONIC = 0x20000000
    MAXIMIZE = 0x01000000
    MAXIMIZEBOX = 0x00010000
    MINIMIZE = 0x20000000
    MINIMIZEBOX = 0x00020000
    OVERLAPPED = 0x00000000
    SYSMENU = 0x00080000
    THICKFRAME = 0x00040000
    TILED = 0x00000000
    VISIBLE = 0x10000000
    VSCROLL = 0x00200000
    SIZEBOX = 0x00040000
    TABSTOP = 0x00010000
    POPUP = 0x80000000
    TILEDWINDOW = (
        OVERLAPPED | CAPTION | SYSMENU | THICKFRAME | MINIMIZEBOX | MAXIMIZEBOX
    )
    OVERLAPPEDWINDOW = (
        OVERLAPPED | CAPTION | SYSMENU | THICKFRAME | MINIMIZEBOX | MAXIMIZEBOX
    )
    POPUPWINDOW = POPUP | BORDER | SYSMENU


class WindowMessage(enum.IntEnum):
    SETFOCUS = 0x0007
    KILLFOCUS = 0x0006
    ENABLE = 0x000A
    SETREDRAW = 0x000B
    SETTEXT = 0x000C
    SETFONT = 0x0030
    GETFONT = 0x0031
    GETTEXT = 0x000D
    GETTEXTLENGTH = 0x000E
    PAINT = 0x000F
    CLOSE = 0x00010
    QUIT = 0x0012
    SHOWWINDOW = 0x0018
    NULL = 0x0000
    CREATE = 0x0001
    DESTROY = 0x0002
    MOVE = 0x0003
    SIZE = 0x0005
    ACTIVATE = 0x0006
    COMMAND = 0x0111
    NOTIFY = 0x004E
    VSCROLL = 0x0115


class ClassStyles(enum.IntFlag):
    VREDRAW = 0x0001
    HREDRAW = 0x0002
    DBLCLKS = 0x0008
    OWNDC = 0x0020
    CLASSDC = 0x0040
    PARENTDC = 0x0080
    NOCLOSE = 0x0200
    SAVEBITS = 0x0800
    BYTEALIGNCLIENT = 0x1000
    BYTEALIGNWINDOW = 0x2000
    GLOBALCLASS = 0x4000


class ButtonStyle(enum.IntFlag):
    PUSHBUTTON = 0x00000000
    DEFPUSHBUTTON = 0x00000001
    CHECKBOX = 0x00000002
    AUTOCHECKBOX = 0x00000003
    RADIOBUTTON = 0x00000004
    _3STATE = 0x00000005
    AUTO3STATE = 0x00000006
    GROUPBOX = 0x00000007
    USERBUTTON = 0x00000008
    AUTORADIOBUTTON = 0x00000009
    PUSHBOX = 0x0000000A
    OWNERDRAW = 0x0000000B
    TYPEMASK = 0x0000000F
    LEFTTEXT = 0x00000020
    TEXT = 0x00000000
    ICON = 0x00000040
    BITMAP = 0x00000080
    LEFT = 0x00000100
    RIGHT = 0x00000200
    CENTER = 0x00000300
    TOP = 0x00000400
    BOTTOM = 0x00000800
    VCENTER = 0x00000C00
    PUSHLIKE = 0x00001000
    MULTILINE = 0x00002000
    NOTIFY = 0x00004000
    FLAT = 0x00008000
    RIGHTBUTTON = LEFTTEXT


class WNDCLASSEXW(Structure):
    _fields_ = [
        ("cbSize", c_uint),
        ("style", c_uint),
        ("lpfnWndProc", WNDPROC),
        ("cbClsExtra", c_int),
        ("cbWndExtra", c_int),
        ("hInstance", HANDLE),
        ("hIcon", HANDLE),
        ("hCursor", HANDLE),
        ("hBrush", HANDLE),
        ("lpszMenuName", LPCWSTR),
        ("lpszClassName", LPCWSTR),
        ("hIconSm", HANDLE),
    ]


class MSG(Structure):
    _fields_ = [
        ("hwnd", HWND),
        ("message", c_int),
        ("wParam", WPARAM),
        ("lParam", LPARAM),
        ("time", DWORD),
        ("pt", POINT),
        ("lPrivate", DWORD),
    ]


class GetWindowLong(enum.IntEnum):
    EXSTYLE = -20
    HINSTANCE = -6
    HWNDPARENT = -8
    ID = -12
    STYLE = -16
    USERDATA = -21
    WNDPROC = -4


class SCROLLINFO(Structure):
    _fields_ = [
        ("cbSize", UINT),
        ("fMask", UINT),
        ("nMin", c_int),
        ("nMax", c_int),
        ("nPage", UINT),
        ("nPos", c_int),
        ("nTrackPos", c_int),
    ]


class ScrollInfoMessage(enum.IntEnum):
    RANGE = 0x0001
    PAGE = 0x0002
    POS = 0x0004
    DISABLENOSCROLL = 0x0008
    TRACKPOS = 0x0010
    ALL = RANGE | PAGE | POS | TRACKPOS


class ScrollBarConstants(enum.IntEnum):
    HORZ = 0
    VERT = 1
    CTL = 2
    BOTH = 3


class ScrollBarCommands(enum.IntEnum):
    LINEUP = 0
    LINELEFT = 0
    LINEDOWN = 1
    LINERIGHT = 1
    PAGEUP = 2
    PAGELEFT = 2
    PAGEDOWN = 3
    PAGERIGHT = 3
    THUMBPOSITION = 4
    THUMBTRACK = 5
    TOP = 6
    LEFT = 6
    BOTTOM = 7
    RIGHT = 7
    ENDSCROLL = 8


def HIWORD(l):
    return WORD((l >> 16) & 0xFFFF)


def LOWORD(l):
    return WORD(l & 0xFFFF)


LPMSG = POINTER(MSG)
LPCSCROLLINFO = POINTER(SCROLLINFO)


DefWindowProcW = user32.DefWindowProcW
DefWindowProcW.argtypes = [HWND, UINT, WPARAM, LPARAM]
DefWindowProcW.restype = LRESULT

CreateWindowExW = user32.CreateWindowExW
CreateWindowExW.argtypes = [
    DWORD,
    LPCWSTR,
    LPCWSTR,
    DWORD,
    c_int,
    c_int,
    c_int,
    c_int,
    HWND,
    HMENU,
    HINSTANCE,
    LPVOID,
]
CreateWindowExW.restype = HWND


LPWNDCLASSW = POINTER(WNDCLASSEXW)
RegisterClassExW = user32.RegisterClassExW
RegisterClassExW.argtypes = [LPWNDCLASSW]
RegisterClassExW.restype = ATOM

ShowWindow = user32.ShowWindow
ShowWindow.argtypes = [HWND, c_int]
ShowWindow.restype = BOOL

UpdateWindow = user32.UpdateWindow
UpdateWindow.argtypes = [HWND]
UpdateWindow.restype = BOOL

GetMessageW = user32.GetMessageW
GetMessageW.argtypes = [LPMSG, HWND, UINT, UINT]
GetMessageW.restype = BOOL

TranslateMessage = user32.TranslateMessage
TranslateMessage.argtypes = [LPMSG]
TranslateMessage.restype = BOOL

DispatchMessageW = user32.DispatchMessageW
DispatchMessageW.argtypes = [LPMSG]
DispatchMessageW.restype = LRESULT

PostQuitMessage = user32.PostQuitMessage
PostQuitMessage.argtypes = [c_int]
PostQuitMessage.restype = None

GetWindowLongPtrW = user32.GetWindowLongPtrW
GetWindowLongPtrW.argtypes = [HWND, c_int]
GetWindowLongPtrW.restype = LONG_PTR

GetWindow = user32.GetWindow
GetWindow.argtypes = [HWND, UINT]
GetWindow.restype = HWND

ScrollWindowEx = user32.ScrollWindowEx
ScrollWindowEx.argtypes = [HWND, c_int, c_int, LPRECT, LPRECT, HRGN, LPRECT, UINT]
ScrollWindowEx.restype = int

SetScrollInfo = user32.SetScrollInfo
SetScrollInfo.argtypes = [HWND, c_int, LPCSCROLLINFO, BOOL]
SetScrollInfo.restype = int

GetScrollInfo = user32.GetScrollInfo
GetScrollInfo.argtypes = [HWND, c_int, LPCSCROLLINFO]
GetScrollInfo.restype = BOOL

GetClientRect = user32.GetClientRect
GetClientRect.argtypes = [HWND, LPRECT]
GetClientRect.restype = BOOL