PySimpleGUI / PySimpleGUI/PySimpleGUI

[Enhancement] Modification for option 'grab_anywhere' of sg.Window

Open
#3,791 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

enhancement
Dominant language
Python
Stars
13.8k
Forks
1.8k
PR merge metrics
No merged PRs in 30d

Description

Type of Issues (Enhancement, Error, Bug, Question)

Enhancement

Operating System

WIN 10

Python version

Python 3.8.7

PySimpleGUI Port and Version

Ports = tkinter

PySimpleGUI Version: 4.33.0.2
tkinter version: 8.6.9

Your Experience Levels In Months or Years

2 yrs - Python programming experience
10+ yrs - Programming experience overall
Yes, tkinter - Have used another Python GUI Framework (tkinter, Qt, etc) previously (yes/no is fine)?

You have completed these steps:
  • Read instructions on how to file an Issue
  • Searched through main docs http://www.PySimpleGUI.org for your problem
  • Searched through the readme for your specific port if not PySimpleGUI (Qt, WX, Remi)
  • Looked for Demo Programs that are similar to your goal http://www.PySimpleGUI.com
  • Note that there are also Demo Programs under each port on GitHub
  • Run your program outside of your debugger (from a command line)
  • Searched through Issues (open and closed) to see if already reported
  • Try again by upgrading your PySimpleGUI.py file to use the current one on GitHub. Your problem may have already been fixed but is not yet on PyPI.
Description of Problem / Question / Details

Option grab_anywhere in sg.Window may not work well with some elements, like element with scrollbar, input element, ...
Here, provide idea to handle such issue by set those elements not working to grab anywhere in window.

Event binding

Unless you specify otherwise, the bindings happen in the following order:

if there is a binding directly on the widget it will be fired before any other bindings.
if there is a binding on the widget's class, it is fired next
if there is a binding on the toplevel widget that contains the widget, it is fired next (note: the root window is considered a toplevel window in this context)
if there is a binding on "all" it will fire next.

There should be internal binding for 'grab' on some widgets, like scrollbar,.....
They generate events from '<B1-Motion>', '<ButtonRelease-1>', then '<ButtonPress-1>'. So add one variable to confirm '<ButtonPress-1>' should go first, else variable will be None and no grab action.

Code To Duplicate
import tkinter as tk
from tkinter import ttk
import PySimpleGUI as sg

class Window(sg.Window):

    NO_DRAG_WIDGETS = (tk.Button, tk.Entry, tk.Scrollbar, tk.Listbox, tk.Message,
        tk.PanedWindow, tk.Scale, tk.Text, ttk.Entry, ttk.PanedWindow, ttk.Scale,
        ttk.Scrollbar, ttk.Sizegrip, ttk.Treeview, type(None))
    drag_widget = None

    def _StartMove(self, event):
        """
        Used by "Grab Anywhere" style windows. This function is bound to mouse-down. It marks the beginning of a drag.
        :param event: event information passed in by tkinter. Contains x,y position of mouse
        :type event: (event)
        """
        try:
            self.TKroot.x = event.x
            self.TKroot.y = event.y
            self.drag_widget = event.widget
        except:
            pass

    def _StopMove(self, event):
        """
        Used by "Grab Anywhere" style windows. This function is bound to mouse-up. It marks the ending of a drag.
        Sets the position of the window to this final x,y coordinates
        :param event: event information passed in by tkinter. Contains x,y position of mouse
        :type event: (event)
        """

        try:
            self.TKroot.x = event.x
            self.TKroot.y = event.y
            self.drag_widget = None
        except Exception as e:
            print('stop move error', e, event)

    def _OnMotion(self, event):
        """
        Used by "Grab Anywhere" style windows. This function is bound to mouse motion. It actually moves the window
        :param event: event information passed in by tkinter. Contains x,y position of mouse
        :type event: (event)
        """
        try:
            if isinstance(self.drag_widget, Window.NO_DRAG_WIDGETS):
                return
            deltax = event.x - self.TKroot.x
            deltay = event.y - self.TKroot.y
            x = self.TKroot.winfo_x() + deltax
            y = self.TKroot.winfo_y() + deltay
            self.TKroot.geometry("+%s+%s" % (x, y))  # this is what really moves the window
            # print('{},{}'.format(x,y))

            if Window._move_all_windows:
                for window in Window._active_windows:
                    x = window.TKroot.winfo_x() + deltax
                    y = window.TKroot.winfo_y() + deltay
                    window.TKroot.geometry("+%s+%s" % (x, y))  # this is what really moves the window
        except Exception as e:
            pass


headings = ['President', 'Date of Birth']
data = [
    ['Ronald Reagan', 'February 6'],
    ['Abraham Lincoln', 'February 12'],
    ['George Washington', 'February 22'],
    ['Andrew Jackson', 'March 15'],
    ['Thomas Jefferson', 'April 13'],
    ['Harry Truman', 'May 8'],
    ['John F. Kennedy', 'May 29'],
    ['George H. W. Bush', 'June 12'],
    ['George W. Bush', 'July 6'],
    ['John Quincy Adams', 'July 11'],
    ['Garrett Walker', 'July 18'],
    ['Bill Clinton', 'August 19'],
    ['Jimmy Carter', 'October 1'],
    ['John Adams', 'October 30'],
    ['Theodore Roosevelt', 'October 27'],
    ['Frank Underwood', 'November 5'],
    ['Woodrow Wilson', 'December 28'],
]

layout = [
    [sg.Text("President to search:")],
    [sg.Input(size=(33, 1), key='-INPUT-'), sg.Button('Search')],
    [sg.Table(data, headings=headings, justification='left', key='-TABLE-')],
]
window = Window("Title", layout, grab_anywhere=True, finalize=True)
table = window['-TABLE-']
entry = window['-INPUT-']
entry.bind('<Return>', 'RETURN-')
widget = table.Widget

while True:
    event, values = window.read()
    if event == sg.WINDOW_CLOSED:
        break
    # print(event, values)
    if event in ('Search', '-INPUT-RETURN-'):
        text = values['-INPUT-'].lower()
        if text == '':
            continue
        row_colors = []
        for row, row_data in enumerate(data):
            if text in row_data[0].lower():
                row_colors.append((row, 'green'))
            else:
                row_colors.append((row, sg.theme_background_color()))
        table.update(row_colors=row_colors)
        for iid in widget.get_children():
            print(iid, widget.item(iid))

window.close()

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by tracing how sg.Window implements grab_anywhere and its mouse event bindings, then compare that behavior with the provided Window subclass and Tkinter widget event-order notes. The change is done when widgets such as entries, scrollbars, and tables retain their normal mouse behavior while other window areas can still move the window.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
desktop
Issue type
Feature
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.