Question

I've made a screensaver that simply scrolls user-defined text from right to left, automatically jumping back to the right if it exceeds the left boundary.

It works with multiple monitors flawlessly, barring one exception: if the 'Main Display' is on the right (i.e. Monitor #2 is primary), then I do not get the scrolling text, however the monitor IS blacked out by the code. If the main display is #1, there's no problem.

I've been poring over the code for hours and cannot identify at what stage the issue arises; I can confirm the text is in the right position (I inserted logging code that verifies its current position), but it's as if one of the API calls simply erases it. I've read the documentation for them and all looks ok.

I create a custom DC in WM_CREATE via:

if (( hDC = CreateDC(TEXT("DISPLAY"), NULL, NULL, NULL)) == NULL )

To prevent flicker, I create compatible objects to update:

void
TickerScreensaver::Paint_Prep(HDC hDC)
{
    _devcon_mem = CreateCompatibleDC(hDC);
    _devcon_orig = hDC;
    _bmp_mem = CreateCompatibleBitmap(hDC, _width, _height);
}

and when painting in WM_PAINT (after BeginPaint, etc.), do a bit-block transfer to the actual device context:

void
TickerScreensaver::Paint(HDC hDC, RECT rect)
{
    _bmp_orig = (HBITMAP)SelectObject(_devcon_mem, _bmp_mem);

    FillRect(_devcon_mem, &rect, (HBRUSH)GetStockObject(BLACK_BRUSH));

    if ( _gdiplus_token != NULL )
    {
        Graphics    graphics(_devcon_mem);
        SolidBrush  brush(cfg.display.font_colour); 
        FontFamily  font_family(cfg.display.font_family.c_str());
        Font        font(&font_family, cfg.display.font_size, FontStyleRegular, UnitPixel);
        PointF      point_f((f32)cfg.display.text_pos.x, (f32)cfg.display.text_pos.y);
        RectF       layout_rect(0, 0, 0, 0);
        RectF       bound_rect;

        graphics.SetTextRenderingHint(TextRenderingHintAntiAlias);

        graphics.MeasureString(cfg.display.text.c_str(), cfg.display.text.length(), &font, layout_rect, &bound_rect);
        cfg.display.offset.x = (DWORD)(0 - bound_rect.Width);
        cfg.display.offset.y = (DWORD)(bound_rect.Height / 2);

        graphics.DrawString(cfg.display.text.c_str(), cfg.display.text.length(), &font, point_f, &brush);
    }

    BitBlt(hDC, 0, 0, _width, _height, _devcon_mem, 0, 0, SRCCOPY);

    SelectObject(_devcon_mem, _bmp_orig);
}

I calculate the dimensions like so:

void
TickerScreensaver::GetFullscreenRect(HDC hDC, RECT *rect)
{
    RECT    s = { 0, 0, 0, 0 };

    if ( EnumDisplayMonitors(hDC, NULL, EnumMonitorCallback, (LPARAM)&s) )
    {
        CopyRect(rect, &s);

        s.left < 0 ?
            _width = s.right + (0 + -s.left) :
            _width = s.right;

        s.top < 0 ?
            _height = s.bottom + (0 + -s.top) :
            _height = s.bottom;
    }
}

Please note that the calculated width, height, etc., are all 100% accurate; it is purely the drawing code that doesn't appear to be working on the main display, only when it is on the right (which sets the origin to {0,0}, monitor #1 then being negative values). It is also reproduceable on a tri-display, with the main being in the center.

Was it helpful?

Solution

Well, turns out it is nice and simple - in Paint(), we should use a rect using the real width and height, not the one retrieved containing the negative values (the one actually retrieved from the API functions):

RECT r = { 0, 0, _width, _height }; 

_bmp_orig = (HBITMAP)SelectObject(_devcon_mem, _bmp_mem);

FillRect(_devcon_mem, &r, (HBRUSH)GetStockObject(BLACK_BRUSH));
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top