0

I'm trying to create splitter 'windows' and have a provisional splitter where I'm using WM_NCHITTEST to return HTCAPTION and subsequently restricting axial movement by fixing the X or Y coordinates in WM_WINDOWPOSCHANING. The splitter is a child of the main window.

The problem I'm having however is that when moving the splitter the borders smear. I can't produce a gif because the smearing effect isn't picked up, but it can be seen on any draggable child window (e.g. Open MMC, 'restore' the MDI child window and drag it around whilst observing the borders).

I need to overcome this issue for the splitters to work properly so I'm wondering how this issue might be addressed? An alternative I suppose would be to take a similar approach to this, but make the splitter control invisible. The border issue would still stand though.

Minimum reproducible example:

#include "framework.h"
#include "painttest.h"

#define MAX_LOADSTRING 100

HINSTANCE hInst;
WCHAR szTitle[MAX_LOADSTRING];
WCHAR szWindowClass[MAX_LOADSTRING];

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 Bproc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);

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

    LoadStringW(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
    LoadStringW(hInstance, IDC_PAINTTEST, szWindowClass, MAX_LOADSTRING);
    MyRegisterClass(hInstance);

    if (!InitInstance (hInstance, nCmdShow))
    {
        return FALSE;
    }

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

    MSG msg;

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

    return (int) msg.wParam;
}


WNDPROC g_ogp;
ATOM MyRegisterClass(HINSTANCE hInstance)
{
    WNDCLASSEXW wcex;

    wcex.cbSize = sizeof(WNDCLASSEX);

    wcex.style          = 0;
    wcex.lpfnWndProc    = WndProc;
    wcex.cbClsExtra     = 0;
    wcex.cbWndExtra     = 0;
    wcex.hInstance      = hInstance;
    wcex.hIcon          = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_PAINTTEST));
    wcex.hCursor        = LoadCursor(nullptr, IDC_ARROW);
    wcex.hbrBackground  = NULL;
    wcex.lpszMenuName   = MAKEINTRESOURCEW(IDC_PAINTTEST);
    wcex.lpszClassName  = szWindowClass;
    wcex.hIconSm        = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));

    return RegisterClassExW(&wcex);
}

BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
   hInst = hInstance;

   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;
}

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)
    {
    case WM_COMMAND:
        {
            int wmId = LOWORD(wParam);
            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_CREATE:
    {
        HWND t = CreateWindowEx(0, L"BUTTON", L"TEST", WS_CHILD | WS_VISIBLE, 20, 20, 100, 20, hWnd, 0, hInst, 0);
        g_ogp = (WNDPROC)SetWindowLongPtr(t, GWLP_WNDPROC, (UINT_PTR)Bproc);
        break;
    }
    case WM_PAINT:
        {
        PAINTSTRUCT paint_structure;
        RECT client_rect;
        HDC paint_device_context, paint_dc;
        HBITMAP bitmap;

        paint_device_context = BeginPaint(hWnd, &paint_structure);
        //FillRect(paint_device_context, &paint_structure.rcPaint,(HBRUSH)COLOR_WINDOW);
        EndPaint(hWnd, &paint_structure);
        }
        break;
    case WM_ERASEBKGND:
        return 1;
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    default:
        return DefWindowProc(hWnd, message, wParam, lParam);
    }
    return 0;
}

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 Bproc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)
    {
    case WM_NCHITTEST:
    {
        return HTCAPTION;
    }

    default:
        return CallWindowProc(g_ogp, hWnd, message, wParam, lParam);
    }

    return CallWindowProc(g_ogp, hWnd, message, wParam, lParam);
}

Notice that when moving the button, the border sears even when the parent background isn't being painted.

  • To resize/reposition multiple windows synchronously, use [`DeferWindowPos`](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-deferwindowpos). Though without a [mcve] it's hard to understand the problem. Please provide one. – IInspectable Mar 08 '22 at 18:34
  • I'm not sure that using the system to do the resizing is a good idea. The dragging loop that `HTCAPTION` puts you into is designed for top-level windows, not child windows. Wouldn't it be just as easy to handle it yourself? You can use `ClipCursor()` to constrain the mouse position. – Jonathan Potter Mar 08 '22 at 19:47
  • Handle myself as in checking for `WM_LBUTTONDOWN` when on a certain area or? My main goal is working towards creating an MFC like 'view' pane (e.g. class view, properties view) system. In MFC the seperators are seperate objects but which are moved when the `WM_LBUTTONUP` is received after the drag. I'd like to process mine such that it is shown straight away (akin to "Show window contents while dragging"). – Synthetic Ascension Mar 08 '22 at 19:58
  • I may not have fully understood, Could you tell me where is the child window? – Junjie Zhu - MSFT Mar 10 '22 at 09:58
  • In the example it's the button – Synthetic Ascension Mar 10 '22 at 10:47
  • When I move the button, there will be several residual buttons, Could you tell me what you expect? – Junjie Zhu - MSFT Mar 18 '22 at 08:28
  • Could you tell me how do you use MMC? Could you provide specific steps? And what effect do you expect to get, but what is the current situation, could you provide some screenshots? This is a screenshot of my test. What does this show? https://ibb.co/R4pr797 – Junjie Zhu - MSFT Mar 21 '22 at 06:32
  • There are several residual buttons, but you can still see when moving the button that the border sears. If you uncomment `FillRect(` in `WM_PAINT` the effect is easier to see. If you drag the button fast enough it disappears, but I can't provide a screenshot because it doesn't pick up the effect. I might try filming the screen though. MMC can be accessed by just running "mmc" from the run command interface. If you 'restore' the MDI child window and drag it you can see the border searing. – Synthetic Ascension Mar 25 '22 at 18:45

0 Answers0