I want to create a window that spans across all monitors, in my case I have 3. The window is a semi-transparent background with a draggable square. The square is used to provide a position for a different part of my code.
When I make the window span all 3 monitors, the square is being created on my left one which is not my primary and the position is not corresponding well. When I change the code and only make focus on the primary screen, everything works but the background no longer spans all 3 monitors.
Here is the code where all 3 monitors have the background, and the square is placed INCORRECTLY. I want the effect of spanning across all 3 monitors but not the badly placed square.
class DynamicAlignmentWindow(QWidget):
positionSelected = Signal(int, int)
def __init__(self, main_window, width, height):
super().__init__()
self.main = main_window
self.main.hide()
self.setWindowFlags(Qt.WindowStaysOnTopHint | Qt.FramelessWindowHint)
self.setWindowOpacity(0.5)
self.setStyleSheet("background-color: rgba(0, 0, 255, 38);")
# Set the geometry to span all monitors
total_screen_rect = calculate_total_geometry()
self.setGeometry(total_screen_rect)
# Get the geometry of the primary screen
primary_screen_geometry = QApplication.primaryScreen().geometry()
self.lastPosition = None # Initialize last known position
print(f"Total Screen Rect: {total_screen_rect}") # Debugging output for total geometry
print(f"Primary Screen Rect: {primary_screen_geometry}") # Debugging output for primary screen
# Calculate center position for the square on the primary screen
center_x = primary_screen_geometry.x() + (primary_screen_geometry.width() - width) // 2
center_y = primary_screen_geometry.y() + (primary_screen_geometry.height() - height) // 2
print(f"Centering at: X={center_x}, Y={center_y}") # Debugging output for centering
# Initialize the square and center it on the primary screen
self.rubber_band = QRubberBand(QRubberBand.Rectangle, self)
self.rubber_band.setGeometry(QRect(QPoint(center_x, center_y), QSize(width, height)))
self.rubber_band.show()
self.dragging = False
self.drag_position = QPoint()
I just want the 3 monitors background from here.
Here is the code where only the primary monitor has the background and the square is placed correctly. I want all 3 monitors to have the background like the above example but this square position.
class DynamicAlignmentWindow(QWidget):
positionSelected = Signal(int, int)
def __init__(self, main_window, width, height):
super().__init__()
self.main = main_window
self.main.hide()
self.setWindowFlags(Qt.WindowStaysOnTopHint | Qt.FramelessWindowHint)
self.setWindowOpacity(0.5)
self.setStyleSheet("background-color: rgba(0, 0, 255, 38);")
primary_screen_geometry = QApplication.primaryScreen().geometry()
self.setGeometry(primary_screen_geometry)
self.lastPosition = None # Initialize last known position
print(f"Primary Screen Rect: {primary_screen_geometry}") # Debugging output
center_x = primary_screen_geometry.x() + (primary_screen_geometry.width() - width) // 2
center_y = primary_screen_geometry.y() + (primary_screen_geometry.height() - height) // 2
print(f"Centering at: X={center_x}, Y={center_y}") # More debugging output
self.rubber_band = QRubberBand(QRubberBand.Rectangle, self)
self.rubber_band.setGeometry(QRect(QPoint(center_x, center_y), QSize(width, height)))
self.rubber_band.show()
self.dragging = False
self.drag_position = QPoint()
I just want the position of the square here.
I dont think this below function is the problem but im adding it in case it helps solving this problem:
Finding geometry for all screens
def calculate_total_geometry():
total_rect = QRect()
for screen in QApplication.screens():
total_rect = total_rect.united(screen.geometry())
return total_rect
UPDATE
When I Use the below code, I believe Im getting the correct primary screen. I am getting:
Total Screen Rect: PySide6.QtCore.QRect(-1920, 0, 5760, 1081)
However, this centers the square on my leftmost screen which is not my primary. I wouldn't mind this however, the position is not corresponding later on. My app displays an overlay using a selected image. When the square is being centered on the left screen and I change the its position, the image is being overlayed one screen down.
For example, if I place the square top left on left screen, its overlaying top left center screen. So the position does not correspond correctly. I am not sure exactly why this happens, but I think if I get the sqaure to generate on the main center screen or the users primary screen, the position will correspond correctly.
self.lastPosition = None # Initialize last known position
self.setGeometry(calculate_total_geometry())
total_screen_rect = calculate_total_geometry()
print(f"Total Screen Rect: {total_screen_rect}") # Debugging output
center_x = total_screen_rect.x() + (total_screen_rect.width() - width) // 2
center_y = total_screen_rect.y() + (total_screen_rect.height() - height) // 2
print(f"Centering at: X={center_x}, Y={center_y}") # More debugging output
You need to check the the screen geometries more carefully: according to your output, the joined rectangle starts at -1920, indicating a negative x in global coordinates: while the primary screen begins at 0, the leftmost one begins at -1920.
But then you use the same geometry of the primary screen as a relative coordinate in order to get a reference position for the center, which is wrong: widget coordinates are always relative to the top left corner of the parent, which always is 0, 0
, but your coordinate system should actually consider the whole geometry, which has negative global positions.
In order to get the accurate position, you need to get the translated geometry of the primary screen using 0-based coordinates. Consider the following image:
The simplified formula also shows why it could be applied for the left screen while obtaining the same result, since its geometry is at -1920:
LeftScreen.x + LeftScreen.width / 2 - Desktop.x = Xcenter
-1920 + 1920 / 2 - -1920 = 960
While you could simply add the width of the left screen, a more appropriate solution should work independently than the screen layout, so that it will always be consistent no matter if the overall screen geometry does start at 0, 0
or not: it may be negative, like in your case, but even positive, and the layout might also have a different vertical offset if you use screens with different heights or laid out vertically.
Qt simplifies all this since it provides access functions to QRect corner points, meaning that we can simply translate the geometry to 0, 0
(widget) based coordinates using the negative of the top left corner: screen.translated(-desktop.topLeft())
.
# this may have an origin (top/left) different than 0, 0
total_screen_rect = calculate_total_geometry()
self.setGeometry(total_screen_rect)
# get the origin point of the screen geometry
origin = total_screen_rect.topLeft()
primary_screen_geometry = QApplication.primaryScreen().geometry()
# translate the geometry in "widget coordinates"; note the minus sign!
local_screen_geometry = primary_screen_geometry.translated(-origin)
center = local_screen_geometry.center()
self.rubber_band = QRubberBand(QRubberBand.Rectangle, self)
self.rubber_band.setGeometry(
center.x() - width // 2, center.y() - height // 2, width, height)
Note that there's no point in doing QRect(QPoint(x, y), QSize(w, h))
, since Qt would internally do the same as QRect(x, y, w, h)
, making the creation of QPoint and QSize objects pointless; you could even skip the QRect creation for setGeometry()
(as I did above) if you don't need that rectangle somewhere else, since Qt would create it anyway with the setGeometry(x, y, w, h)
overload.
Finally, you should call show()
on a child widget only if the parent has been already shown.