0

What do I want to achive:

I want to create a window that behaves like a WS_OVERLAPPEDWINDOW | WS_SIZEBOX(can be minimized, maximized, closed from system context menu on Alt+Space, supports Window Snapping, has shadow if it is enabled in system Performance Options -> [x] Enable shadow under window and so on). It also should be borderless (without title bar, icon, application name in title bar and so on), just client region and nothing else.

So main goals are:

  • Has no border;
  • Supports default window functionality;
  • Drops shadow underneath.

Don't say that it is impossible. The most simple example of such a window is Telegram Desktop. They use Qt library. Don't say I should use it. I want to learn how they did this trick. Their window is borderless, without bugs (that are described below). I know that Qt uses OpenGL inside for their window. But I also know that it should be possible to draw with GDI+ only without OpenGL.

What I have already tried:

  • github:melak47/BorderlessWindow This window has a tiny border around. You can see it if you resize it really quickly holding by the top or left edge of the window. We can also set margins to be like MARGINS m{0,0,0,1} for DwmExtendFrameIntoClientArea(hWnd, &m);. But window will still remain the same border which could be seen if resize.

  • github:rossy/borderless-window This solution is better. It propose two variants to struggle with this problem.

    1. The border here is hidden by enabling WS_EX_LAYERED window style with chroma keying by color (e.g. magenta). But it is obvious that now we need to perform checks every time we draw something on bitmap for a specified chroma color. If user draws clear magenta RGB(255, 0, 255) we can then replace it with RGB(254, 0, 255) to avoid holes inside. But I think it is a very bad solution that cause unneccessary checks for each pixel in bitmap.

    2. The border is now considered as a part of client area. We disable WM_EX_LAYERED and color keying and using GDI+ to draw transparent colors on top border. The this is that you can't draw pure opaque color on this border (e.g. graphics.FillRect(&Gdiplus::SolidBrush{Gdiplus::Color{255, 255, 0, 0}}, Gdiplus::Rect{0, 0, windowWidht, windowHeight}) will not cover top border by red color - the border will be white or another color (I don't know what it depends on but I think it is window system color preference, like Personalize -> Colors -> [x] Title bars and window borders)). By the way you can cover it any color you want just by setting opacity of this color to 254. This will work. But the problem is still there: if we'll try to resize the window guess what.. we have this white border on top (or what you set this when MARGINS m{0,0,0,1} for DwmExtendFrameIntoClientArea(hWnd, &m);. And also notice that it will be a lot of pain when you will forget that you can't draw pure opaque color and draw something with GDI. So I come up to the next solution.

  • My idea is to draw with alpha value of 254 only the top most scanline of bitmap. Becase I also perform double buffering I can do it with AlphaBlend(hdc, 0, 0, width, 1, memHdc, 0, 0, width, 1, {AC_SRC_OVER, 0, 254, AC_SRC_ALPHA});. But this solution is still got it's own bug. When resize window we still got this border because it's been drawn faster than our window content. Also we got weird pixels on the left side if we resize the window by the top edge. And the last bug is that this alpha dissapears sometimes to and the top border is visible not only in undrawn client regions but also on top of validated regions.

    The code of WM_PAINT procedure:

    LRESULT wmPaint(HWND hWnd, WPARAM wParam, LPARAM lParam)
    {
        PAINTSTRUCT ps = {};
        HDC hdc = BeginPaint(hWnd, &ps);

        auto [width, height] = dimensions.normal;

        HDC memHdc = CreateCompatibleDC(hdc);
        HBITMAP hBitmap = CreateCompatibleBitmap(hdc, width, height);
        SelectObject(memHdc, hBitmap);

        // Children perform their drawings on "back buffer" - memDc
        handleChildrenDraw(memDc);


        // Copy top line as transparent and other as always to paint over non-client area as it was client

        BLENDFUNCTION bf = {};
        bf.BlendOp = AC_SRC_OVER;
        bf.SourceConstantAlpha = 254;
        bf.AlphaFormat = AC_SRC_ALPHA;
        auto alphaSuccess = AlphaBlend(hdc, 0, 0, width, 1, memHdc, 0, 0, width, 1, bf);
        expect(alphaSuccess == TRUE);
        auto copySuccess = BitBlt(hdc, 1, 0, width, height, memHdc, 1, 0, SRCCOPY);
        expect(copySuccess == TRUE);

        EndPaint(hWnd, &ps);

        DeleteObject(memHdc);
        DeleteObject(hBitmap);

        return 0;
    }

Sreenshot: window rendered with a white srtipe on top (border has been rendered for some reasons)

Question:

Can you please tell me how to fix this top border drawing bug or any better solution to draw borderless window with WinAPI? Thanks in advance.

maxnevans
  • 1
  • 3
  • But built-in shadow dropping is implemented via border rendering... – user7860670 Apr 13 '20 at 12:37
  • @user7860670 I know. But I want to know how does Telegram Desktop app hide this border. Try to resize Telegram Desktop quickly (by top or left edge). You will find out that there is no border drawn underneath client rectangle. – maxnevans Apr 13 '20 at 12:46
  • Its been reverse-engineered, type "steam borderless" in the search box at the top of this page to find the posts back. – Hans Passant Apr 13 '20 at 13:07
  • @HansPassant Did you mean this topic? [borderless-window-using-areo-snap-shadow-minimize-animation-and-shake](https://stackoverflow.com/questions/16765561/borderless-window-using-areo-snap-shadow-minimize-animation-and-shake?rq=1) I have already seen it. But I don't understand what did they mean by _So you might have to put a non transparent widget, brush or something behind the transparent element._ – maxnevans Apr 13 '20 at 15:14
  • @HansPassant I've just tested to draw with GDI+ on top of the border with `Gdiplus::Color{255, 255, 0, 0}` but it does not applies to the border. But if I draw with `Gdiplus::Color{254, 255, 0, 0}` it draws well on top. But I can't always control that whenever somebody draws on top of the border must draw with 254 alpha channel instead of 255. – maxnevans Apr 13 '20 at 15:35
  • It seems that the window has a border because of the working principle of DWM, please refer to [this](https://stackoverflow.com/a/53004460/11128312). And I found the same situation in Google Chrome.By the way, there is a solution in the link, you can see if it helps. – Strive Sun Apr 15 '20 at 03:44
  • Telegram fakes the shadow. The main window is surrounded by 4 other windows which draw the shadow (see https://github.com/desktop-app/lib_ui/blob/master/ui/platform/win/ui_window_shadow_win.cpp), they don't even use the real shadow images from the DWMWindow theme data. – gix Aug 09 '23 at 14:23

0 Answers0