9

I am working on an application which is using many widgets (QGroupBox, QVBoxLayout, QHBoxLayout). Initially it was developed on normal HD monitors. But, recently many of us upgraded to 4K resolution monitors. Now some of the buttons and sliders are compressed so small that they are unusable.

Now I tried to make some changes so that the application can be used with both HD and 4K monitors.

I started reading the link below:

https://leomoon.com/journal/python/high-dpi-scaling-in-pyqt5/enter link description here

I thought whenever my window is opened in a particular monitor I can call the following code:

if pixel_x > 1920 and pixel_y > 1080:
  Qapp.setAttribute(Qt.AA_EnableHighDpiScaling, True)
  Qapp.setAttribute(Qt.AA_UseHighDpiPixmaps, True)
else:
  Qapp.setAttribute(Qt.AA_EnableHighDpiScaling, False)
  Qapp.setAttribute(Qt.AA_UseHighDpiPixmaps, False)

Then I tried to get the monitor resolution (pixel_x and pixel_y) using below code from using related post here.

import sys, ctypes

user32 = ctypes.windll.user32
user32.SetProcessDPIAware()
screen_width  = 0 #78
screen_height = 1 #79
[pixel_x , pixel_y ] = [user32.GetSystemMetrics(screen_width), user32.GetSystemMetrics(screen_height)]

screen_width = 0, screen_height = 1 gives me the resolution of my primary monitor(mostly laptops in our case which are HD). screen_width = 78, screen_height = 79 gives me the combined resolution of virtual machines. But I do not understand how I can dynamically get these values depending upon where my application opened.

My application window is developed in such a way that it will open in the same monitor where it was closed last time. The problem is now I want to get the active monitor resolution whenever my GUI is called and adapt to that resolution. I would be glad if someone can help me out.

I am interested to know if I can call the screen resolution calculation every time that I drag my window from an HD monitor to a 4K monitor and Vice versa.

Edit: I have found something similar in this post here But I could not get much from this.

Edit2: Based on @Joe solution, Primary Screen Detection, Why is my primary screen always my laptop resolution even though I run the application on a 4K screen? enter image description here

I just tried to get the dpi of all the screens using the code below:

def screen_selection():
  app = QApplication(sys.argv)
  valid_screens = []
  for index, screen_no in enumerate(app.screens()):
    screen = app.screens()[index]
    dpi = screen.physicalDotsPerInch()
    valid_screens.append(dpi)
  return valid_screens
swimfar
  • 147
  • 6
Rohithsai Sai
  • 468
  • 4
  • 17
  • PEOPLE please see this post, you thank me later: https://stackoverflow.com/questions/20243637/pyqt4-center-window-on-active-screen – Dariyoush Aug 21 '21 at 20:52

3 Answers3

6

One solution I came across is to use a temporary QApplication():

import sys
from PyQt5 import QtWidgets, QtCore, QtGui

# fire up a temporary QApplication
def get_resolution():

    app = QtWidgets.QApplication(sys.argv)

    print(app.primaryScreen())

    d = app.desktop()

    print(d.screenGeometry())
    print(d.availableGeometry())
    print(d.screenCount())    

    g = d.screenGeometry()
    return (g.width(), g.height())

x, y = get_resolution()

if x > 1920 and y > 1080:
  QtWidgets.QApplication.setAttribute(QtCore.Qt.AA_EnableHighDpiScaling, True)
  QtWidgets.QApplication.setAttribute(QtCore.Qt.AA_UseHighDpiPixmaps, True)
else:
  QtWidgets.QApplication.setAttribute(QtCore.Qt.AA_EnableHighDpiScaling, False)
  QtWidgets.QApplication.setAttribute(QtCore.Qt.AA_UseHighDpiPixmaps, False)

# Now your code ...

This function will detect all attached screens:

# fire up a temporary QApplication
def get_resolution_multiple_screens():

    app = QtGui.QGuiApplication(sys.argv)
    #QtWidgets.QtGui
    all_screens = app.screens()

    for s in all_screens:

        print()
        print(s.name())
        print(s.availableGeometry())
        print(s.availableGeometry().width())
        print(s.availableGeometry().height())
        print(s.size())
        print(s.size().width())
        print(s.size().height())

    print()
    print('primary:', app.primaryScreen())
    print('primary:', app.primaryScreen().availableGeometry().width())
    print('primary:', app.primaryScreen().availableGeometry().height())

    # now choose one

You can use the hints here and here to get the screen where the application is running.

But I think primaryScreen should also return this:

primaryScreen : QScreen* const

This property holds the primary (or default) screen of the application.

This will be the screen where QWindows are initially shown, unless otherwise specified.

(https://doc.qt.io/qt-5/qguiapplication.html#primaryScreen-prop)

Joe
  • 6,758
  • 2
  • 26
  • 47
  • Thanks for the answer. Unfortunately this is not working. I tried to implement the above function in Spyder IDE. I run the application in 4K monitor but still it returns my laptop resolutions. – Rohithsai Sai Dec 10 '19 at 12:05
  • Was the 4K attached to the laptop as second screen? – Joe Dec 10 '19 at 12:10
  • yes and my active application is opened in 4K monitor – Rohithsai Sai Dec 10 '19 at 12:10
  • Please try the other function I attached. Also check the `app.primaryScreen()` to see which screen is used. – Joe Dec 10 '19 at 12:25
  • your second code is working fine in detecting all screens. I find that, though I run my application in 4K screen (connected monitor), my laptop screen is detecting as primary screen. Please find the edit part to find a attachment – Rohithsai Sai Dec 10 '19 at 14:41
  • 1
    added links to my answer – Joe Dec 10 '19 at 15:08
  • Unfortunately not, it is showing my laptop screen as primary screen always, irrespective of where I run my program. – Rohithsai Sai Dec 11 '19 at 08:46
  • Is it maybe related to the primary Windows screen? – Joe Dec 11 '19 at 08:55
  • Exactly, In this link it mentioned, primary screen is where OS is installed, may be it is detecting same https://www.bestusbpoweredmonitor.com/2017/12/11/how-to-change-primary-monitor/ – Rohithsai Sai Dec 11 '19 at 09:03
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/204035/discussion-between-joe-and-rohithsai-sai). – Joe Dec 11 '19 at 09:10
4

Well, after creating the MainWindow, you can just call QMainWindow.screen(). This returns the current screen the MainWindow is on. This would at least allow you to check the screen resolution at the start of your application. Right now there is no such thing as a screenChangeEvent. However i am sure you can create one by subclassing the MainWindow and overloading the QMainWindow.moveEvent

For example:

    class MainWindow(QtWidgets.QMainWindow):
        screenChanged = QtCore.pyqtSignal(QtGui.QScreen, QtGui.QScreen)

        def moveEvent(self, event):
            oldScreen = QtWidgets.QApplication.screenAt(event.oldPos())
            newScreen = QtWidgets.QApplication.screenAt(event.pos())

            if not oldScreen == newScreen:
                self.screenChanged.emit(oldScreen, newScreen)

            return super().moveEvent(event)

This checks if the screen has changed. If it has it emits a signal. Now you only need to connect this signal to a function that sets your dpi attributes. The event gives you access to the old and to the new screen.

Warning:

One of the screen can be None at the start of your application because there is no oldScreen when you first start your application. So please check this.

Tim Körner
  • 385
  • 2
  • 9
  • Körnet thank you for the solution. But, there is no method called QMainWindow.screen() in PyQt5 atleast what I have understood. If am wrong kindly show me some link for python (PyQt5) – Rohithsai Sai Dec 13 '19 at 14:43
  • I also couldn't find it in the documentation. However every `QWidget` supports this method as you can see [here](https://doc.qt.io/qt-5/qwidget.html#screen). `QMainWindow` subclasses `QWidget.` I have also tested my code and it worked. Please just checks if it works for you as well. – Tim Körner Dec 13 '19 at 14:47
  • I would also recommend using the solution with subclassing the `QMainWindow` and overloading the `moveEvent`. This works 100% and provides more functionality for you. You can just take the code i provided and use it. The piece of code works fine and i get correct `screenChanged` signal whenever i move the ``MainWindow` on another screen. – Tim Körner Dec 13 '19 at 14:49
  • I am interrested to know how you said tested and worked. Firstly when I use QMainWindow.screen() it throws an error saying it has no attribute. Secondly for moveEvent getting an error 'QApplication' has no attribute 'screenAt'. I want to understand if you have implemmented in Python. – Rohithsai Sai Dec 13 '19 at 15:06
  • Yes of course i am using python. My `PyQt5` version is 5.13.2 You can see right [here](https://www.riverbankcomputing.com/static/Docs/PyQt5/api/qtgui/qguiapplication.html) that the official PyQt5 reference also lists the `QApplication.screenAt` method. – Tim Körner Dec 13 '19 at 15:11
3

Though i could not get the direct solution I am able to develop a method to get what I was looking. With the help of few links and previous post I am able to achieve. with this post I got an idea of tracking the mouse event.

I developed a method to track all the monitors and respective staring positions. if my variable naming is not appropriate I am happy to accept the changes

def get_screen_resolution():
  app = QApplication(sys.argv)
  screen_count = QGuiApplication.screens()
  resolutions_in_x = []

  for index, screen_names in enumerate(screen_count):
    resolution = screen_count[index].size()
    height = resolution.height()
    width = resolution.width()
    resolutions_in_x.append(width)

  low_resolution_monitors = {}
  high_resolution_monitors = {}

  for i, wid_res in enumerate(resolutions_in_x):
    if wid_res > 1920:
      high_resolution_monitors.update({i: wid_res})
    else:
      low_resolution_monitors.update({'L': wid_res})    
  temp_value = 0
  high_res_monitors_x_position = []
  low_res_monitors_x_position = []
  for i in range(len(screen_count)):
    temp_value = temp_value+resolutions_in_x[i]
      if resolutions_in_x[i] in high_resolution_monitors.values():
        high_res_monitors_x_position.append(temp_value-resolutions_in_x[i])
      else:
        low_res_monitors_x_position.append(temp_value-resolutions_in_x[i])

  total_width_res = []
  pixel_value = 0
  first_pixel = 0
  for i, wid_value in enumerate(resolutions_in_x):
    pixel_value = pixel_value + wid_value
    total_width_res.append(tuple((first_pixel, pixel_value-1)))
    first_pixel = pixel_value

  return high_res_monitors_x_position, low_res_monitors_x_position, total_width_res


def moveEvent(self, event):

screen_pos = self.pos()
screen_dimensions = [screen_pos.x(),screen_pos.y()]
super(MainWindow, self).moveEvent(event)

Window_starting_pt = screen_pos.x()
for i, value in enumerate(self.total_width_res):
  if value[0]<=Window_starting_pt+30 <=value[1] or value[0]<=Window_starting_pt-30 <=value[1]: #taking 30pixels as tolerance since widgets are staring at some negative pixel values
    if value[0] in self.high_res_monitors_x_position:
      QtWidgets.QApplication.setAttribute(QtCore.Qt.AA_EnableHighDpiScaling, True)
      QtWidgets.QApplication.setAttribute(QtCore.Qt.AA_UseHighDpiPixmaps, True)
    else:
      QtWidgets.QApplication.setAttribute(QtCore.Qt.AA_EnableHighDpiScaling, False)
      QtWidgets.QApplication.setAttribute(QtCore.Qt.AA_UseHighDpiPixmaps, False)

With aboe two functions am able to track my application(window) position and also able to track when ever it is dragged among windows

Rohithsai Sai
  • 468
  • 4
  • 17