CustomTkinter icon indicating copy to clipboard operation
CustomTkinter copied to clipboard

Window changes size with multiple monitors and scalings on Windows

Open TomSchimansky opened this issue 2 years ago • 3 comments

Window changes size when dragged from over to monitor with different scaling one Windows 11, maybe also Windows 10. This only happens when window is hold by the mouse for a second after moving to the other monitor, and is then released by the mouse.

TomSchimansky avatar Oct 15 '22 11:10 TomSchimansky

Try adding this:

import ctypes    
ctypes.windll.shcore.SetProcessDpiAwareness(0)

Akascape avatar Oct 15 '22 11:10 Akascape

I already set the dpi awareness in the scaling tracker class, you can have a look if you are interested. These are the methods that manage the scaling:

@classmethod
    def activate_high_dpi_awareness(cls):
        """ make process DPI aware, customtkinter elements will get scaled automatically,
            only gets activated when CTk object is created """

        if not cls.deactivate_automatic_dpi_awareness:
            if sys.platform == "darwin":
                pass  # high DPI scaling works automatically on macOS

            elif sys.platform.startswith("win"):
                from ctypes import windll
                windll.shcore.SetProcessDpiAwareness(2)
                # Microsoft Docs: https://docs.microsoft.com/en-us/windows/win32/api/shellscalingapi/ne-shellscalingapi-process_dpi_awareness
            else:
                pass  # DPI awareness on Linux not implemented
    @classmethod
    def get_window_dpi_scaling(cls, window) -> float:
        if not cls.deactivate_automatic_dpi_awareness:
            if sys.platform == "darwin":
                return 1  # scaling works automatically on macOS

            elif sys.platform.startswith("win"):
                from ctypes import windll, pointer, wintypes

                DPI100pc = 96  # DPI 96 is 100% scaling
                DPI_type = 0  # MDT_EFFECTIVE_DPI = 0, MDT_ANGULAR_DPI = 1, MDT_RAW_DPI = 2
                window_hwnd = wintypes.HWND(window.winfo_id())
                monitor_handle = windll.user32.MonitorFromWindow(window_hwnd, wintypes.DWORD(2))  # MONITOR_DEFAULTTONEAREST = 2
                x_dpi, y_dpi = wintypes.UINT(), wintypes.UINT()
                windll.shcore.GetDpiForMonitor(monitor_handle, DPI_type, pointer(x_dpi), pointer(y_dpi))
                return (x_dpi.value + y_dpi.value) / (2 * DPI100pc)

            else:
                return 1  # DPI awareness on Linux not implemented
        else:
            return 1
    @classmethod
    def check_dpi_scaling(cls):
        new_scaling_detected = False

        # check for every window if scaling value changed
        for window in cls.window_widgets_dict:
            if window.winfo_exists() and not window.state() == "iconic":
                current_dpi_scaling_value = cls.get_window_dpi_scaling(window)
                if current_dpi_scaling_value != cls.window_dpi_scaling_dict[window]:
                    cls.window_dpi_scaling_dict[window] = current_dpi_scaling_value

                    if sys.platform.startswith("win"):
                        window.attributes("-alpha", 0.15)

                    cls.update_scaling_callbacks_for_window(window)

                    if sys.platform.startswith("win"):
                        window.after(200, lambda: window.attributes("-alpha", 1))

                    new_scaling_detected = True

        # find an existing tkinter object for the next call of .after()
        for app in cls.window_widgets_dict.keys():
            try:
                if new_scaling_detected:
                    app.after(cls.loop_pause_after_new_scaling, cls.check_dpi_scaling)
                else:
                    app.after(cls.update_loop_interval, cls.check_dpi_scaling)
                return
            except Exception:
                continue

        cls.update_loop_running = False

The set scaling method of the CTk window class looks like the following:

    def _set_scaling(self, new_widget_scaling, new_spacing_scaling, new_window_scaling):
        self._window_scaling = new_window_scaling

        # block update_dimensions_event to prevent current_width and current_height to get updated
        self._block_update_dimensions_event = True

        # force new dimensions on window by using min, max, and geometry
        super().minsize(self._apply_window_scaling(self._current_width), self._apply_window_scaling(self._current_height))
        super().maxsize(self._apply_window_scaling(self._current_width), self._apply_window_scaling(self._current_height))

        super().geometry(f"{self._apply_window_scaling(self._current_width)}x{self._apply_window_scaling(self._current_height)}")

        # set new scaled min and max with 400ms delay (otherwise it won't work for some reason)
        self.after(400, self._set_scaled_min_max)

        # release the blocking of update_dimensions_event after a small amount of time (slight delay is necessary)
        def set_block_update_dimensions_event_false():
            self._block_update_dimensions_event = False
        self.after(100, lambda: set_block_update_dimensions_event_false())

Its very difficult to spot any errors in this process I implemented, but maybe this is also just a tk bug, I don't know...

TomSchimansky avatar Oct 15 '22 12:10 TomSchimansky

I will work on this later if I have the time

TomSchimansky avatar Oct 15 '22 13:10 TomSchimansky