wxWidgets
wxWidgets copied to clipboard
when drag the wxAUI window edge, there are some ugly lines shows in the desktop like edge shadows
I'm on Windows 10 64bit, and by using wx 3.2.3, I see this issue, see the image below:
The ugly lines normally are parallel with the dragged wxAUI window edge, but shown in other places.
This issue happens not only on the wxAUI sample code, I see this in Code::Blocks from time to time. This is not a new issue in wx 3.2.3, but it happens for a very long time.
In the above image shot, my desk setting has NO DPI scale, I mean it is 100% on a screen 1920*1080 resolution.
Maybe, this issue is DPI related, see:
github issue: New AUI notebook splitter drawn at incorrect position when dragged on scaled display Issue #18098 wxWidgets/wxWidgets
wx-user maillist discussion: wrong xor line is drawn when I drag the wxSplitterWindow' sash bar with wxSP_LIVE_UPDATE disabled
C::B forum discussion: ugly lines when I drag the aui panel in Code::Blocks
Sorry, this is rather confusing: why do you think this is DPI-related if you're using normal DPI? The linked issues don't seem to have anything to do with this, especially because one of them is about wxSplitterWindow
and not wxAUI at all.
What is really needed is a way to reproduce this reliably. I don't see the problem myself in master. Before I retest with 3.2.3, could you please at least tell if you see it when using live update (this can be toggled in the sample) or not and, also, if you have any precise instructions for reproducing it in the sample?
Sorry, this is rather confusing: why do you think this is DPI-related if you're using normal DPI? The linked issues don't seem to have anything to do with this, especially because one of them is about
wxSplitterWindow
and not wxAUI at all.
This is only my guess, if I remember correctly, the sash bar in wxSplitterWindow also draw such kinds of gray shadow lines. In my mentioned github issues, it is DPI related. Anyway, this is only my guess.
What is really needed is a way to reproduce this reliably. I don't see the problem myself in master. Before I retest with 3.2.3, could you please at least tell if you see it when using live update (this can be toggled in the sample) or not
I just tried the "Live Resize Update" option enabled or disabled in wxAUI sample. I can only tell you that this option is disabled by default, so my screen shot (showing the ugly lines) was in the condition with this option disabled.
But sorry, I can't precisely reproduce this issue. Sometimes, it happens, and sometimes it doesn't. I will try to find a way to reproduce this bug.
Hi, I think I can find a clean way to reproduce this bug.
This happens when I have multiple monitors. I have two monitors, the main monitor is my notebook monitor A 19201080 with 100%DPI, and another extended monitor B, it is 1600900 with 100 DPI scale.
Now if I start C::B or wxAUI sample in the monitor A, and than I drag the application from the monitor A to monitor B, and later drag the AUI window edge to resize it, I see that the shadow lines will be drawn in the monitor A.
EDIT: the shadow lines won't happen if I enable the "Live Resize Update" option in the wxAUI sample.
My guess is that the shadow line drawing system has some cache. The cache may remember the monitor(display) where it was initially started. So, when I drag edges in monitor B, it still draws in monitor A.
I suffer this issue also on my application just shaking the splitter long enough. The "live resize update" option reduces probability, but it is not zero.
I am using one monitor on MSW10 with wxWidgets 3.2.3.
If this is really multi-monitor specific I'm going to have trouble testing this because I don't have a multi-monitor Windows system any more (it's supposed to work with QEMU/KVM but it just doesn't for me... if anybody has any hints about setting this up, they'd be very welcome).
@wh11204 Just to make sure, do you mean AUI splitter or wxSplitterWindow
?
I mean the AUI splitter, as seen in the OP's image.
I found another issue, in my two monitor condition, when I drag the AUI splitter in monitor A, there is NO shadow lines(hint lines) shown around the mouse position. My guess is that in this case, the shadow lines were drawn out of the the screen.
Those shadow lines(hint lines) are drawn by the wxAUI system, so the position calculation is wrong here.
wxWidgets-3.2.3\src\aui\framemanager.cpp
static void DrawResizeHint(wxDC& dc, const wxRect& rect)
{
wxBitmap stipple = wxPaneCreateStippleBitmap();
wxBrush brush(stipple);
dc.SetBrush(brush);
#ifdef __WXMSW__
wxMSWDCImpl *impl = (wxMSWDCImpl*) dc.GetImpl();
PatBlt(GetHdcOf(*impl), rect.GetX(), rect.GetY(), rect.GetWidth(), rect.GetHeight(), PATINVERT);
#else
dc.SetPen(*wxTRANSPARENT_PEN);
dc.SetLogicalFunction(wxXOR);
dc.DrawRectangle(rect);
#endif
}
I just did some text search on the source files, the above function looks like to draw the hint lines.
void wxAuiManager::OnMotion(wxMouseEvent& event)
{
// sometimes when Update() is called from inside this method,
// a spurious mouse move event is generated; this check will make
// sure that only real mouse moves will get anywhere in this method;
// this appears to be a bug somewhere, and I don't know where the
// mouse move event is being generated. only verified on MSW
wxPoint mouse_pos = event.GetPosition();
if (m_lastMouseMove == mouse_pos)
return;
m_lastMouseMove = mouse_pos;
if (m_action == actionResize)
{
// It's necessary to reset m_actionPart since it destroyed
// by the Update within DoEndResizeAction.
if (m_currentDragItem != -1)
m_actionPart = & (m_uiParts.Item(m_currentDragItem));
else
m_currentDragItem = m_uiParts.Index(* m_actionPart);
if (m_actionPart)
{
wxPoint pos = m_actionPart->rect.GetPosition();
if (m_actionPart->orientation == wxHORIZONTAL)
pos.y = wxMax(0, event.m_y - m_actionOffset.y);
else
pos.x = wxMax(0, event.m_x - m_actionOffset.x);
if (HasLiveResize())
{
m_frame->ReleaseMouse();
DoEndResizeAction(event);
m_frame->CaptureMouse();
}
else
{
wxRect rect(m_frame->ClientToScreen(pos),
m_actionPart->rect.GetSize());
wxScreenDC dc;
if (!m_actionHintRect.IsEmpty())
{
// remove old resize hint
DrawResizeHint(dc, m_actionHintRect);
m_actionHintRect = wxRect();
}
// draw new resize hint, if it's inside the managed frame
wxRect frameScreenRect = m_frame->GetScreenRect();
if (frameScreenRect.Contains(rect))
{
DrawResizeHint(dc, rect);
m_actionHintRect = rect;
}
}
}
}
The code above may draw the hint lines. My guess is that the
wxScreenDC dc;
Is not correct?
wxWidgets: wxScreenDC Class Reference
It said:
A wxScreenDC can be used to paint on the screen.
This should normally be constructed as a temporary stack object; don't store a wxScreenDC object.
When using multiple monitors, wxScreenDC corresponds to the entire virtual screen composed of all of them. Notice that coordinates on wxScreenDC can be negative in this case, see wxDisplay::GetGeometry() for more.
So, this is the whole desktop???
We just need a single desktop where the application frame locates.
I read some source code especially in src/aui/framemanager.cpp
, and I see that when it try to draw something(hint lines or hint rectangles) on the wxScreenDC, the coordinates were translated to virtual screen coordinates by the wxWindow::ClientToScreen()
. Maybe this function got the wrong coordinates.
I just build a debug version of wx 3.2.3, and try to debug it. I found that the reason is ClientToScreen
function only return the coordinates of the current desktop(screen), not the whole desktop. But when it try to draw the rectangle, it use the wxScreenDC, and which is in the whole desktop. So, it draws on the wrong place.
I'm not sure why the ClientToScreen
failed, I see it correctly pass the window HWND, the coordinates of relative to the current wxFrame to the Windows API.
Maybe this article is related, ScreenToVirtual, also this article: The Virtual Screen - Win32 apps | Microsoft Learn
It looks like the virtual screen coordinates' origin are the top left point of the primary screen. (As mentioned in the above Microsoft link)
But when wx try to draw on the wxScreenDC, the origin is on my other screen's top left point.
I just upload the image here, suppose I have monitor 1 and monitor 2.
In the image, the red arrow is the origin of the ClientToScreen
returned coordinates.
While when we draw the rectangle, it looks like the wxScreenDC has the origin pointed by blue arrow.
Are you sure about wxScreenDC
origin part? I'd expect it to be at the top left corner of the primary monitor too.
This is, of course, easy to test: just write a tiny program drawing a line from (0,0) to (1000,1000) using wxScreenDC
and check where does it appear.
This is the screen shot of my primary monitor 1, a very simple wx program
void OnMouseMotion(wxMouseEvent& event)
{
if (event.Dragging() && event.LeftIsDown())
{
wxScreenDC dc;
wxPen pen(wxColor(255, 0, 0), 5);
dc.SetPen(pen);
dc.DrawLine(0, 0, 3000, 500);
}
}
My monitor 1 is 1920x1080 with 100%DPI, and the extended monitor 2 is 1600x900 with 100 DPI scale.
Please note that in my test case, I have only monitor 1. I have unplugged the monitor 2, which means I only have one monitor. But it looks like the wxScreenDC are still using the top left corner of the monitor 2 as the origin.
I don't understand how can wxScreenDC
"know" about the other monitor if you don't see it in Windows any more. We do need to stop using wxScreenDC
anyhow, but this is not that simple to do, unfortunately.
My guess is maybe wxScreenDC caches some states that I had two monitors.
I can reproduce it too with a monitor setup like in the image from @asmwarrior
I can fix drawing on the main monitor by using:
rect.x += GetSystemMetrics(SM_XVIRTUALSCREEN);
rect.y += GetSystemMetrics(SM_YVIRTUALSCREEN);
DrawResizeHint(dc, rect);
m_actionHintRect = rect;
Note that the hint is drawn with PatBlt
, and not wxScreenDC
directly: https://github.com/wxWidgets/wxWidgets/blob/9bae94022c6aa8bb2b1b207f0f64bc362e5cd6af/src/aui/framemanager.cpp#L283-L286
Also I'm not able to get it to draw hints on the top-left monitor at all.
After logging out and in again, this patch doesn't work anymore, but the unmodified code works fine. Seems like it is only an issue when you change monitor layout without logging out/in or restarting.
Hi, @MaartenBent thanks for the test.
Do we need some method to detect the display changes? I mean when a monitor added or removed, we should know the display got changed?
Search on the internet, I found this:
Multiple Display Monitors - Win32 apps | Microsoft Learn
Maybe, there are some API's to query the values, or maybe we can catch some event.
Thanks.
I can fix drawing on the main monitor by using:
rect.x += GetSystemMetrics(SM_XVIRTUALSCREEN); rect.y += GetSystemMetrics(SM_YVIRTUALSCREEN); DrawResizeHint(dc, rect); m_actionHintRect = rect;
Note that the hint is drawn with
PatBlt
, and notwxScreenDC
directly:https://github.com/wxWidgets/wxWidgets/blob/9bae94022c6aa8bb2b1b207f0f64bc362e5cd6af/src/aui/framemanager.cpp#L283-L286
Also I'm not able to get it to draw hints on the top-left monitor at all.
With the above code patch, I see you just "Add" a X,Y coordinates shift to the rect's position.
Do you mean that by adding this shift, the PatBlt
function just sets the monitor 2's top left corner as the origin?
After logging out and in again, this patch doesn't work anymore, but the unmodified code works fine. Seems like it is only an issue when you change monitor layout without logging out/in or restarting.
I haven't tested your patch, but it looks like if I log out and log in, if I have only one monitor, the GetSystemMetrics(SM_XVIRTUALSCREEN)
and GetSystemMetrics(SM_YVIRTUALSCREEN)
should be 0.
GetSystemMetrics function (winuser.h) - Win32 apps | Microsoft Learn
OK, I wrote a simple program(which is mostly generated by chatGPT)
#include <Windows.h>
#include <iostream>
int main()
{
int numMonitors = GetSystemMetrics(SM_CMONITORS);
std::cout << "Number of Monitors: " << numMonitors << std::endl;
int virtualScreenWidth = GetSystemMetrics(SM_CXVIRTUALSCREEN);
int virtualScreenHeight = GetSystemMetrics(SM_CYVIRTUALSCREEN);
std::cout << "Virtual Screen Width: " << virtualScreenWidth << std::endl;
std::cout << "Virtual Screen Height: " << virtualScreenHeight << std::endl;
int virtualScreenX = GetSystemMetrics(SM_XVIRTUALSCREEN);
int virtualScreenY = GetSystemMetrics(SM_YVIRTUALSCREEN);
std::cout << "Virtual Screen X: " << virtualScreenX << std::endl;
std::cout << "Virtual Screen Y: " << virtualScreenY << std::endl;
return 0;
}
The result is:
Number of Monitors: 2
Virtual Screen Width: 3520
Virtual Screen Height: 1080
Virtual Screen X: -1600
Virtual Screen Y: 0
So, I think we need the GetSystemMetrics(SM_XVIRTUALSCREEN);
and GetSystemMetrics(SM_YVIRTUALSCREEN)
instead of the SM_CXVIRTUALSCREEN
and SM_CYVIRTUALSCREEN
. Which is the X,Y position of the monitor 2. When I have only one monitor, those two values should be 0.
I wrote another test:
void OnMouseMotion(wxMouseEvent& event)
{
if (event.Dragging() && event.LeftIsDown())
{
wxScreenDC dc;
wxPen pen(wxColor(255, 0, 0), 5);
dc.SetPen(pen);
int virtualScreenX = GetSystemMetrics(SM_XVIRTUALSCREEN);
int virtualScreenY = GetSystemMetrics(SM_YVIRTUALSCREEN);
dc.DrawLine(0 + virtualScreenX, 0 + virtualScreenY, 1000 + virtualScreenX, 500 + virtualScreenY);
}
}
It looks like in this case, I can draw the line from (0,0) to (1000, 500) in the expected position in the monitor 1, see below image shot:
But it looks like the wxScreenDC has some issue, and it mistakenly copies monitor 2's area to monitor 1, so you can see the blue background.
https://github.com/wxWidgets/wxWidgets/blob/9bae94022c6aa8bb2b1b207f0f64bc362e5cd6af/src/msw/dc.cpp#L976
When debugging, I see this line has a typo, whether
.
I did a pure Windows C++ application, which is also generated by chatGPT, I try to draw a line from (0,0)
to (virtualScreenWidth, virtualScreenHeight)
, in my test case, it is (0,0) to (3520, 1080).
#include <Windows.h>
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_PAINT:
{
HDC hdc = GetDC(nullptr); // Get the desktop DC
// Get the virtual screen dimensions
int virtualScreenWidth = GetSystemMetrics(SM_CXVIRTUALSCREEN);
int virtualScreenHeight = GetSystemMetrics(SM_CYVIRTUALSCREEN);
// Create a pen and select it into the device context
HPEN pen = CreatePen(PS_SOLID, 2, RGB(255, 0, 0));
SelectObject(hdc, pen);
// Draw a line from top-left to bottom-right of the virtual screen
MoveToEx(hdc, 0, 0, nullptr);
LineTo(hdc, virtualScreenWidth, virtualScreenHeight);
// Clean up
DeleteObject(pen);
ReleaseDC(nullptr, hdc);
return 0;
}
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
// Register the window class
const wchar_t CLASS_NAME[] = L"MyWindowClass";
WNDCLASS wc = { 0 };
wc.lpfnWndProc = WindowProc;
wc.hInstance = hInstance;
wc.lpszClassName = CLASS_NAME;
RegisterClass(&wc);
// Create the window
HWND hwnd = CreateWindowEx(
0, // Optional window styles
CLASS_NAME, // Window class name
L"Draw on Virtual Screen", // Window title
WS_OVERLAPPEDWINDOW, // Window style
CW_USEDEFAULT, // X position
CW_USEDEFAULT, // Y position
CW_USEDEFAULT, // Width
CW_USEDEFAULT, // Height
nullptr, // Parent window
nullptr, // Menu
hInstance, // Instance handle
nullptr // Additional application data
);
if (hwnd == nullptr)
return 0;
ShowWindow(hwnd, nCmdShow);
// Run the message loop
MSG msg;
while (GetMessage(&msg, nullptr, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return 0;
}
But the result screen shot shows that the line is NOT start from the (0,0) of the monitor 1(the top left of monitor 1).
See image shot below of the monitor 1
I just skimmed the thread, so I apologize in advance if I missed something.
Are you sure that the top left coordinate of your left monitor is 0,0? I have two monitors side by side, the right one being my primary display (DISPLAY1 in the screenshot below) and it has display coordinates starting with 0,0 but my left screen (DISPLAY2) has negative x coordinates:
I think if one really wants the top-left coordinate of the whole virtual screen (not sure if this is the case here), this should be obtained with
::GetSystemMetrics(SM_XVIRTUALSCREEN)
and ::GetSystemMetrics(SM_YVIRTUALSCREEN)
.
EDIT Removed the edits created in confusion.
I tried the win32 line example with SM_XVIRTUALSCREEN
, SM_YVIRTUALSCREEN
instead of 0
, 0
. I moved monitor 2 from the right to the position in the screenshot. Virtual screen coordinates are -1920;-1040
It won't draw the line on monitor 2. The line is flipped and shown on monitor 1 instead. And all kinds of weird artifacts show.
It works correct after I logout and login again (with the same monitor layout). Line is shown on both monitors and no artifacts. The virtual screen coordinates and size are still the same. It must be something in Windows that messes things up.
The aui, splitter and sash examples all show the same problem. Because they use wxScreenDC
. I have no idea how to fix this. Or even how to detect that this is happening.
Hi, @MaartenBent I try to fix this issue today, and I have take several hours to debug and test the code to find the reason.
I first try to debug the wx source code, and later I see this issue also happens on a pure Win32 code.
Now, even a simple Win32 line drawing on the desktop fails, see my comment, screen shot and source code here: https://github.com/wxWidgets/wxWidgets/issues/23982#issuecomment-1793614165
The strange thing is:
Case A: If I put the primary monitor in the left, and the second monitor in the right, I don't see the issue in Win32 line drawing code.
Case B: But if I put the primary monitor in the right, and the second monitor in the left, I can see the issue.
Now, I think this is a bug from either the video driver or the Windows 10 itself, so I try to upgrade my Win10. In-fact, the upgrade feature is disable by myself, so I have to enable it, I'm not finishing the upgrade, but it looks like after running some update, I see that my "Win32 line drawing" issue in Case A is gone!!!
I will do more test tomorrow.
I can't say I understand the virtual screen coordinates. Running this:
#include <wx/wx.h>
class MyFrame: public wxFrame
{
public:
MyFrame(wxWindow* parent = nullptr) : wxFrame(parent, wxID_ANY, "Test")
{
wxPanel* mainPanel = new wxPanel(this);
wxBoxSizer* mainPanelSizer = new wxBoxSizer(wxVERTICAL);
wxButton* button = new wxButton(mainPanel, wxID_ANY, "Draw Screen Lines");
mainPanelSizer->Add(button, wxSizerFlags().Expand().Border());
button->Bind(wxEVT_BUTTON, &MyFrame::DrawScreenLines, this);
wxTextCtrl* logCtrl = new wxTextCtrl(mainPanel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_MULTILINE | wxTE_READONLY | wxTE_RICH2);
wxLog::SetActiveTarget(new wxLogTextCtrl(logCtrl));
mainPanelSizer->Add(logCtrl, wxSizerFlags().Proportion(1).Expand().Border());
mainPanel->SetSizer(mainPanelSizer);
}
private:
void DrawScreenLines(wxCommandEvent&)
{
HDC screenDC = ::GetDC(nullptr);
const int x = ::GetSystemMetrics(SM_XVIRTUALSCREEN);
const int y = ::GetSystemMetrics(SM_YVIRTUALSCREEN);
const int width = ::GetSystemMetrics(SM_CXVIRTUALSCREEN);
const int height = ::GetSystemMetrics(SM_CYVIRTUALSCREEN);
HPEN pen = ::CreatePen(PS_SOLID, 2, RGB(255, 0, 0));
HPEN oldPen = (HPEN)::SelectObject(screenDC, pen);
if ( !screenDC )
wxLogError("Could not obtain the screen DC!");
if ( !::MoveToEx(screenDC, x, y, nullptr) )
wxLogError("MoveToEx() failed!");
if ( !::LineTo(screenDC, width, height) )
wxLogError("LineTo() failed!");
::SelectObject(screenDC, oldPen);
::DeleteObject(pen);
::ReleaseDC(nullptr, screenDC);
wxLogMessage("Screen DC x = %d, y = %d, width = %d, height = %d", x, y, width, height);
}
};
class MyApp : public wxApp
{
bool OnInit() override
{
(new MyFrame())->Show();
return true;
}
}; wxIMPLEMENT_APP(MyApp);
as DPI PMv2 aware application on Win10 setup where the primary screen (right) is 2560x1440@125% DPI and secondary 1680x1050@100 (left, vertically centered), I get this
I understand that I have different resolutions on the two displays, so there are "virtual height" pixels (black area on the top and bottom on the left), but I still find surprising that the line does not hit either corner.
Hi, @PBfordev, thanks for the help!
I think I have the same behavior as yours.
I just run the code in your comment, I got the result:
Here is my monitor layout:
And here is the result:
You see, you draw a line from (x,y) to the (width, height).
The (x,y) is the top left corner of the virtual desktop, and the width is the total width of the desktop.
So, the above red line is correct.
If I make the monitors layout like below:
I think it is still correct, I mean the top left corner is in the result image below(I pointed it by a red arrow)
Since the (0,0) is always the top left corner of the primary monitor, the (width, height) point exceeds the right boundary of the primary monitor, so it cannot be displayed.
After the update of my Win10, I think I don't see the ugly lines now. So maybe Microsoft has fixed such drawing issue in the update.