PySimpleGUI / PySimpleGUI

Python GUIs for Humans! PySimpleGUI is the top-rated Python application development environment. Launched in 2018 and actively developed, maintained, and supported in 2024. Transforms tkinter, Qt, WxPython, and Remi into a simple, intuitive, and fun experience for both hobbyists and expert users.
https://www.PySimpleGUI.com
Other
13.33k stars 1.84k forks source link

[Enhancement] Resizing of elements #2004

Open PySimpleGUI opened 4 years ago

PySimpleGUI commented 4 years ago

Suppose it was only a matter of time before users wanted to be able to resize elements. Windows were initially meant to be completely static and to confirm to their layouts. The only resizing was the autosizing that happened when elements are first created.

But, it's been a year and the sophistication level of user applications keeps going up. Rather than to try and discourage due to potential problems, I'm suggesting that resizing be enabled with the user living with the consequences.

A new version of PySimpleGUI.py was uploaded with the new method: Element.set_size((width, height))

Note, unlike ALL other times that a size is specified for an element (when creating for example), this method will allow you to change only 1 size if you set the other size to None.

Here is an example use:

import PySimpleGUI as sg

layout = [  [sg.Text('My Window', key='-TEXT-', background_color='lightblue')],
            [sg.Input(key='-IN-'), sg.Text('', key='-OUT-')],
            [sg.Button('Do Something'), sg.Button('Exit')]  ]

window = sg.Window('Window Title', layout, resizable=False)

while True:             # Event Loop
    event, values = window.read()
    print(event, values)
    if event in (None, 'Exit'):
        break
    if event == 'Do Something':
        window['-TEXT-'].set_size((20,3))
        window['-TEXT-'].Update('1\n2\n3')
        window['Exit'].set_size((None,2))       # Change only the height... one of the ONLY times None can be used in a size
window.close()
DrewM1992 commented 4 years ago

Very exciting!

PySimpleGUI commented 4 years ago

One thing to be wary of.... get_size() tends to return pixels even if the original Widget was configured using characters and rows.

Modified the above example to use the get_size call to report the size before and after the change change. The program prints this information:

Do Something {'-IN-': ''}
The text element's size is (72, 20)
The text element's size is (164, 52)
import PySimpleGUI as sg

layout = [  [sg.Text('My Window', key='-TEXT-', background_color='lightblue')],
            [sg.Input(key='-IN-'), sg.Text('', key='-OUT-')],
            [sg.Button('Do Something'), sg.Button('Exit')]  ]

window = sg.Window('Window Title', layout, resizable=False)

while True:             # Event Loop
    event, values = window.read()
    print(event, values)
    if event in (None, 'Exit'):
        break
    if event == 'Do Something':
        print(f"The text element's size is {window['-TEXT-'].get_size()}")
        window['-TEXT-'].set_size((20,3))
        window['-TEXT-'].Update('1\n2\n3')
        window.refresh()
        print(f"The text element's size is {window['-TEXT-'].get_size()}")
        window['Exit'].set_size((None,2))       # Change only the height... one of the ONLY times None can be used in a size
window.close()
PySimpleGUI commented 4 years ago

@DrewM1992 Why is this exciting to you?

I have not found a purpose for this yet to be honest. It's not something that's held me up. But, then again, if you DO need to resize an Element for some reason, it certainly wasn't easy (nor encouraged) to do.

DrewM1992 commented 4 years ago

@PySimpleGUI

I find this useful on implimenting some of the dynamically sized tool bars to span across the screen; but to be able to size it for when its maximized or not.

RobertRautenbach commented 4 years ago

@PySimpleGUI I am currently resizing elements myself for an image viewer that allows the user to choose to fit the image to the screen or view it in its full size. Qt obviously handles this better.

Speaking of which just a question out of pure curiosity :) What is the main reason for maintaining both a Qt and a Tkinter version of PySimpleGUI? Is it for compatibility reasons? I would've assumed moving over to Qt completely at some point and discontinuing the tk development would allow for more resources to target Qt which seems to be a bit more stable across platforms.

PySimpleGUI commented 4 years ago

Speaking of which just a question out of pure curiosity :) What is the main reason for maintaining both a Qt and a Tkinter version of PySimpleGUI? Is it for compatibility reasons? I would've assumed moving over to Qt completely at some point and discontinuing the tk development would allow for more resources to target Qt which seems to be a bit more stable across platforms.

Glad you asked. I think I spoke a little about it in the docs but probably not fully.

There are 4 ports of PySimpleGUI at the moment. PySimpleGUI (tkinter), PySimpleGUIQt, (Pyside2), PySimpleGUIWx (WxPython), PySimpleGUIWeb (Remi). Each port added something the others doesn't have.

The idea has not been to arrive at a single GUI Framework destination or to boil down to a single "target" that's somehow better than the other. If anything more ports will be added rather than anything being removed.

PySimpleGUI-tkinter was the starting point because it ran pretty much everywhere. Anyone running Python could now write a GUI program easily using PySimpleGUI and run it on their Python system, regardless of platform. The idea was to let everyone into the GUI tent, not just those with a year+ of Python education. It also happened to compile down nicely to a 10MB .EXE file for small programs.

Qt's purpose was to provide an alternative. Yes, perhaps some better layout controls as things like visibility work a little better in Qt. It also has a slightly different ("more modern") style and an addition Element or two (Stretch, Dial). It's biggest feature, however, is the System Tray. This is a rather large feature unavailable on tkinter. PySimpleGUIQt, unfortunately, generally produces a nearly 200MB .EXE file when used with pyinstaller.

When WxPython was added, it brought with it the ability to design GUIs that are more available to sight impaired individuals. Screen readers work well with it evidently. It also provided a System Tray feature. When compared to the footprint of Qt, Wx is a bit smaller. One is able to write a System Tray using less resources.

The most recent addition was Remi. It gives the user the ability to run their GUI in a browser. One fantastic use for this is for embedded systems where there is no display on the device, yet it's still perhaps desirable to run on a GUI when interfacing to your program running on the device. Raspberry Pi's and smaller devices are opened up now to an alternative to tkinter if desired. Plus, PySimpleGUIWeb also runs on Repl.it so that programs can be shared and run virtually.

For the programmer, you can "freely move" across these 4 platforms with no or minimal changes to your program. The result has been that PySimpleGUI is a unified GUI programming environment capable of using a number of GUI backends to render the windows and provide the widget behaviors.

The stated goal of PySimpleGUI continues to be to "provide a GUI solution to 80% of the GUI problems". It's not shooting for 100% of the problem space like say Qt is. Or maybe it's not. I don't know, I just know what PySimpleGUI's goal is.

The implementation reality of this all is that the implementations, the ports, will be out of sync in terms of features completed and perhaps some features will exist that are unique to a particular port (like the Dial and Stretch Elements are to PySimpleGUIQt). Near features continue to flow not into the Qt port, but rather the tkinter port. The "Cross port needed" labels on issues generally mean a port from PySimpleGUI to the Qt, Wx, and Web ports.

Several recent changes were applied to all 4 ports at once. The window[key] construct for example was done to them all. PEP8 conversions was completed for all 4 too. This was needed in order to make the change immediately portable across the ports.

I think the last priority list I made showed the general priority list at the moment is

  1. PySimpleGUI
  2. PySimpleGUIWeb
  3. PySimpleGUIQt
  4. PySimpleGUIWx

Because of all of the documentation efforts and the quick and easy changes made to PySimpleGUI, you wouldn't think that the Web port would be so high, but it is. The attention has simply not been swung that way, yet, recently.

The PEP8 effort cuts across all the ports and a project to "refresh" the Demo Programs for all of the ports is starting up. So that's one that is parallel, hitting them all at once.

Tk is and remains the primary focus of PySimpleGUI. It's the default. It gets the new features first, generally speaking. It runs everywhere, including on the Pi and Repl.it for online emulation. It compiles down to a small EXE. It's emulated on Repl.it and on Android. It's likely to remain the focal point for a long time.

All this said, things can and do happen quickly. The Web port was added and an Engineering level release was written almost overnight once Remi was proposed and the huge benefits it provided were understood.

This project learns from its users. There is no doubt about that. It has from the first weeks of the release. Personally speaking, my education comes from reading as much user PySimpleGUI code as possible and by solving as many of the Reddit GUI problems posed to see what it takes and where things may be lacking.

PySimpleGUI commented 4 years ago

@FlashChaser

I am currently resizing elements myself for an image viewer that allows the user to choose to fit the image to the screen or view it in its full size. Qt obviously handles this better.

I thought that the Image element resized itself automatically to match the size of the image. It does this in both the Qt and tkinter ports.

This demo of using OpenCV to display a webcam in a GUI window demonstrates this nicely. You can change the import to PySimpleGUI or PySimpleGUIQt and it works equally well, with no size indicated on the Image element. The PySimpleGUIWeb version currently flickers with this demo, but there's a fix that I have not yet applied that should make all 3 work and look the same.

import cv2, PySimpleGUIQt as sg
window, cap = sg.Window('Demo Application - OpenCV Integration', [[sg.Image(filename='', key='image')],], location=(800,400)), cv2.VideoCapture(0)
while window(timeout=20)[0] is not None:
    window['image'](data=cv2.imencode('.png', cap.read()[1])[1].tobytes())
RobertRautenbach commented 4 years ago

Apologies, should have been clearer. Due to the images being displayed being larger than the screen resolution I have the image inside of a scrollable column. It's the column that I'm currently resizing in a manual hacky way at the moment because they don't resize along with the image always. Qt columns seem to behave differently between OSX and Windows.

Zciurus-Alt-Del commented 4 years ago

I'm having trouble resizing columns. It works fine for other elements, like Buttons, but when I try to change the height of a column, the window briefly changes to the correct size for a fraction of a second and then returns back to the default size. The same problem occurs when using Frames. Is there any reason this shouldn't work for containers?

Edit: Heres example code that reproduces the problem


layout = [
    [sg.Frame("I'm a frame",[[sg.Button(button_text='Change size')]],key='body')]
]

window = sg.Window('Window',layout)
window.finalize()

while True:
    event, values = window.read()
    if event in (None, 'Cancel'):
        break
    if event == 'Change size':
        window['Change size'].set_size(size=(200, 200))
        window.refresh()
PySimpleGUI commented 4 years ago

Columns are different. There's no way at the moment to resize them using existing calls. There are several other elements, usually those with scrollbars, that doesn't support being resized.

Containers are really tricky because they don't always size to a fixed size to begin with. Columns that don't scroll are different than scrollable ones as well.

I'll get a workaround together for you. Are you Columns scrollable?

Zciurus-Alt-Del commented 4 years ago

No they aren't (weren't) scrollable. My plan was to resize some elements in the window when the actual window size changes by having those elements inside a column. I figured out a workaround for that, so resizing columns is not really necessary for me anymore.

Still, thanks for your help!

Edit: To be more specific, my problem was that I wanted to have a table change size when the window size changes. I didn't you could do this with .expand() as discussed in #2196, which did the trick for me.

mdopearce commented 4 years ago

I hope this is the right place to ask. I have two main questions, one related (probably) to me being new to python and learning, the other related to resizing.

I am trying to create a window that has a frame, in said frame, there will be a text display of a list.

This list begins empty, and is populated as the person uses the program.

I have managed to get it to update, but when I do, it retains its original size. In order to display the new information, I need to either manually resize it, or have it automatically register the new necessary capacity (I'm fine with either).

To try give a simplified version:

units=[]
colUnits=sg.Frame("Your units",[[sg.Column([[sg.Text(units,key="--colUnits")]])],[sg.Text("")]])

layout=[[sg.Text("The window")],[colUnits],[sg.Button("Buy a unit")]]
win=sg.Window("Title Bar",layout=layout)

while True: 
    event, values=win.Read(timeout=100)
    if event in (None):
        break
    win["--colUnits"].update(units)
    if event in ("Buy a unit"):
        units+=["Bob"]

win.close()

So the issues I'm having are: 1) The resizing of the frame to show all the "Bob" in the units list. 2) Pretty sure I've buggered up the sg.Frame() entirely, but if I take out any of the elements the entire thing doesn't display at all. Similarly, the sg.Column(). Both appear to get very unhappy if I don't include the number of lists within lists, or layers of lists as I have. I definitely don't understand why, this is just what I have realised from tinkering with it for some time.

Apologies for how messy this is, I've spent the better part of the day looking around the github documents to try figure out where I've gone wrong, to no avail as yet.

PySimpleGUI commented 4 years ago

"Dynamic" layouts are not an easy thing to do in PySimpleGUI. It wasn't originally designed for this purposes. Resize is likely not what you need but rather extending the layout by more individual lines. "Extending" layouts isn't all that difficult. Shrinking them isn't a possibility if you're extending.

You're into areas that are relatively new and may not be quite so straightforward to work with. There are multiple ways to achieve windows that appear to grow dynamically.

I would caution you that what you're ultimately after may not be possible... I don't know how to answer that just yet as I've not gone through your example in detail. I'm talking generalities here. Simply a word of caution that the footing in this area isn't as firm as other features.

New window approach

What I generally do for these kinds of programs that need to add stuff is make a new window.

This recent project is an example of that.

https://github.com/PySimpleGUI/PySimpleGUI-COVID19

Rather than extending the layout or trying to modify the existing layout when the number of graphs change, I instead close the window, make a new layout and open a new window.

tkinter itself struggles with expanding things sometimes. Qt does a better job of hiding and unhiding.

Hidden elements approach

Another way to go about it is to create a lot of elements ahead of times that are created invisibly. Then as new ones are needed, they are made visible one row at a time. This works well for layouts that extend downward, row by row.