New Project Xojo 2020r2 Windows 10 with dual screens with different scaling eg 100% and 200%.
Add
dim m as new MenuItem()
m.Text = "here"
base.AddMenu(m)
return true
To the window’s ConstructContextualMenu event.
When running the popup appears on screen 1 (100%) while app is on screen 2 (200%)
Same problem when trying to do it yourself
In the mouse down event add
if IsContextualClick then
dim b as new MenuItem()
dim m as new MenuItem()
m.Text = "here"
b.AddMenu(m)
call b.PopUp(System.MouseX,System.MouseY)
end if
return true
It also appears on wrong screen.
Same in Xojo 2019r1.1
Guess I will have to look at MBS calls to try adjust to get it to show at correct location
That doesn’t make it a PopupMenu. PopupMenu and MenuItem are entirely separate. You’re supplying the X and Y coordinates to the MenuItem.Popup method. If there’s a bug in positioning on multiple monitors with varying ScaleFactors, you will see this issue.
I would suggest you look through the thread I linked earlier as I believe someone posted a method there to calculate correct coordinates, then open a Feedback case with a project to replicate the issue with MenuItem positioning.
I have tested and cannot see a way to calculate the correct values because as soon as pass in the correct value, times by 2, the MENUITEM appears on the right of the second screen, as if offscreen and windows is bringing it back in bounds.
So screen1 width is 5120, and passing in 5120 * 2 + 1 shows the menu on the right side of screen 2. While 5120 * 2 - 1 shows the menu on the right of screen 1, I cannot see a way to pass in valid values.
It is a nasty problem I was not aware of… While obviously not an easy fix as windows HiDPI handling has been a moving target, it should not have gone this long WITHOUT a fix! Apple keeps changing things and Xojo jumps right on it… But not for Windows!
I write apps for use on both Macs and windows (That’s why I pay money to use RB/RS/Xojo) …
The Macs I use have multiple screens, but not so the Windows machines, but the apps could get used on Windows machines with Multiple monitors (particularly laptops) and I would never see the issue…
If Xojo says it supports HIDPI… After all this time (and this bug was known years ago) that support should be solid X-Platform (at least for Mac and Windows which matter most to the most customers)…
Again this situation should not IMO have been allowed to remain this way for years!!!
Workarounds are OK for the short term, but long term they are huge time sinks for developer who keep stumbling across these types of issues…
I’m still waiting and hoping for a fix on this. From earlier ongoing reading, months ago, it “sounded like” this wouldn’t be difficult for Xojo to fix internally, but the case hasn’t been resolved - and they’ve been busy with what I’d consider higher priority items. I still haven’t found a way to deal with it.
soft declare function TrackPopupMenu lib "user32.dll" (hMenu as integer,uFlags as uint32,x as integer, y as integer, nREs as integer, hWND as integer,prcRect as ptr) as boolean
call TrackPopupMenu(b.Handle(menuitem.HandleType.WindowsParentHMENU),0,5120 + (System.MouseX - 5120) * 2 ,System.MouseY * 2,0,me.Handle,nil)
this is the direction you need to go. It opens the menu at the correct loction on my second screen. Obviously it needs to work off screen sizes etc. Not sure about handling the clicks, maybe addhandler might work in the constructor, or custom menu items
You can use these two methods to get the true position of a window, then calculate your MenuItem.PopUp X and Y coordinates relatively:
Private Function GetTrueLeft(w as Window) as Integer
#if TargetWindows then
Declare Sub GetWindowRect Lib "User32" Alias "GetWindowRect" ( w as Integer, r as Ptr )
dim r as new MemoryBlock( 16 )
GetWindowRect( w.Handle, r )
Return r.Long( 0 )
#endif
End Function
Private Function GetTrueTop(w as Window) as Integer
#if TargetWindows then
Declare Sub GetWindowRect Lib "User32" Alias "GetWindowRect" ( w as Integer, r as Ptr )
dim r as new MemoryBlock( 16 )
GetWindowRect( w.Handle, r )
Return r.Long( 4 )
#endif
End Function
I don’t recall if I wrote these methods or they were suggested by someone else (or from WFS, even), but they do work.
dim d as new Dictionary()
dim b as new MenuItem()
dim m as new MenuItem()
m.Text = "here " + str(Slider1.Value)
d.Value(m.Handle(MenuItem.HandleType.WindowsCommandID)) = m
b.AddMenu(m)
soft declare function TrackPopupMenu lib "user32.dll" (hMenu as integer,uFlags as uint32,x as integer, y as integer, nREs as integer, hWND as integer,prcRect as ptr) as integer
dim res as integer = TrackPopupMenu(b.Handle(menuitem.HandleType.WindowsParentHMENU),&h100,5120 + (System.MouseX - 5120) * 2 ,System.MouseY * 2,0,me.Handle,nil)
if res = 0 then
return true
end if
if not d.HasKey(res) then
return true
end if
m = d.Value(res)
MessageBox(m.Text)
Function MouseDown(X As Integer, Y As Integer) Handles MouseDown as Boolean
if IsContextualClick then
dim d as new Dictionary()
dim b as new MenuItem()
dim m as new MenuItem()
m.Text = "here " + str(Slider1.Value)
d.Value(m.Handle(MenuItem.HandleType.WindowsCommandID)) = m
b.AddMenu(m)
x = x * me.ScaleFactor
y = y * me.ScaleFactor
x = x + getTrueLeft()
y = y + getTrueTop()
soft declare function TrackPopupMenu lib "user32.dll" (hMenu as integer,uFlags as uint32,x as integer, y as integer, nREs as integer, hWND as integer,prcRect as ptr) as integer
dim res as integer = TrackPopupMenu(b.Handle(menuitem.HandleType.WindowsParentHMENU),&h100,x,y,0,me.Handle,nil)
if res = 0 then
//selected id
return true
end if
if not d.HasKey(res) then
return true
end if
m = d.Value(res)
MessageBox(m.Text)
end if
return true
End Function
Using get true left / top, needs to account for window title bar height though
Take a look at how it’s done in WFS. I believe you’re looking for GetClientRect, then you can do some math on the height that Xojo reports for the window.
if IsContextualClick then
#if TargetWindows then
Declare Function GetSystemMetrics Lib "user32" alias "GetSystemMetrics" (nIndex as Integer) as Integer
Declare function TrackPopupMenu lib "user32.dll" (hMenu as integer,uFlags as uint32,x as integer, y as integer, nREs as integer, hWND as integer,prcRect as ptr) as integer
Declare Sub GetWindowRect Lib "User32" Alias "GetWindowRect" ( w as Integer, r as Ptr )
dim winMILookup as new Dictionary()
dim winTL,winTT as integer
dim mb as new MemoryBlock( 16 )
GetWindowRect( me.Handle, mb )
winTL = mb.Long( 0 )
winTT = mb.Long( 4 )
#endif
dim b,m,r as menuitem
b = new MenuItem() //Base menu item to popup
//add menu items
m = new MenuItem()
m.Text = "Menu Item 1"
b.AddMenu(m)
#if TargetWindows then
//keep a reference to menu for windows
winMILookup.Value(m.Handle(MenuItem.HandleType.WindowsCommandID)) = m
#endif
//add more menu items here
//add menu items
m = new MenuItem()
m.Text = "Menu Item 2"
b.AddMenu(m)
#if TargetWindows then
//keep a reference to menu for windows
winMILookup.Value(m.Handle(MenuItem.HandleType.WindowsCommandID)) = m
#endif
#if TargetWindows then
//adjust scale for screen we are on
x = x * me.ScaleFactor
y = y * me.ScaleFactor
//adjust for windows true position
x = x + winTL
y = y + winTT
//adjust X and y based on how window is setup
x = x + GetSystemMetrics(32) * me.ScaleFactor //SM_CXSIZEFRAME
y = y + GetSystemMetrics(15) * me.ScaleFactor//SM_CYMENU
y = y + GetSystemMetrics(4) * me.ScaleFactor//SM_CYCAPTION
//track popup and get result windows command ID or 0 for cancelled, flag &h100 sets this
dim res as integer = TrackPopupMenu(b.Handle(menuitem.HandleType.WindowsParentHMENU),&h100,x,y,0,me.Handle,nil)
//lookup the res in our dictionary
r = winMILookup.lookup(res,nil)
#else
//macOS support
r = b.PopUp(System.MouseX,System.MouseY)
#endif
if r = nil then
//canceled
return true
else
MessageBox(r.Text)
end if
end if
return true
Self contained mouse down event on a window to handle mac and windows popups
Just saw this message, Updated code to use window and client bounds to get offsets.
if IsContextualClick then
#if TargetWindows then
Declare function TrackPopupMenu lib "user32" (hMenu as integer,uFlags as uint32,x as integer, y as integer, nREs as integer, hWND as integer,prcRect as ptr) as integer
Declare Sub GetWindowRect Lib "User32" Alias "GetWindowRect" ( w as Integer, r as Ptr )
declare function GetClientRect lib "user32" (hWnd as integer, r as Ptr ) as boolean
dim winMILookup as new Dictionary()
dim winTL,winTT,winTR,winTB as integer
dim mb as new MemoryBlock( 16 )
GetWindowRect( me.Handle, mb )
winTL = mb.Long(0)
winTT = mb.Long(4)
winTR = mb.Long(8)
winTB = mb.Long(12)
call GetClientRect(me.Handle,mb)
dim winCL,winCT,winCR,winCB as integer
winCL = mb.Long(0)
winCT = mb.Long(4)
winCR = mb.Long(8)
winCB = mb.Long(12)
//calculate the window and client areas
dim wh as integer = winTB - winTT
dim ch as integer = winCB - winCT
dim ww as integer = winTR - winTL
dim cw as integer = winCR - winCL
//calculate the border size from width
dim bw as integer = (ww - cw) / 2
//calculate the header size frame + any menus
dim hH as integer = (wh - ch) - 2 * bw
#endif
dim b,m,r as menuitem
b = new MenuItem() //Base menu item to popup
//add menu items
m = new MenuItem()
m.Text = "Menu Item 1"
b.AddMenu(m)
#if TargetWindows then
//keep a reference to menu for windows
winMILookup.Value(m.Handle(MenuItem.HandleType.WindowsCommandID)) = m
#endif
//add more menu items here
//add menu items
m = new MenuItem()
m.Text = "Menu Item 2"
b.AddMenu(m)
#if TargetWindows then
//keep a reference to menu for windows
winMILookup.Value(m.Handle(MenuItem.HandleType.WindowsCommandID)) = m
#endif
#if TargetWindows then
//adjust scale for screen we are on
x = x * me.ScaleFactor
y = y * me.ScaleFactor
//adjust for windows true position
x = x + winTL + bW
y = y + winTT + HH
//track popup and get result windows command ID or 0 for cancelled, flag &h100 sets this
dim res as integer = TrackPopupMenu(b.Handle(menuitem.HandleType.WindowsParentHMENU),&h100,x,y,0,me.Handle,nil)
//lookup the res in our dictionary
r = winMILookup.lookup(res,nil)
#else
//macOS support
r = b.PopUp(System.MouseX,System.MouseY)
#endif
if r = nil then
//canceled
return true
else
MessageBox(r.Text)
end if
end if
return true