Merge pull request #5387 from PySimpleGUI/Dev-latest
Output element - changed to be a subclass of the Multiline as part of…
This commit is contained in:
commit
1646a15914
410
PySimpleGUI.py
410
PySimpleGUI.py
|
@ -1,6 +1,6 @@
|
||||||
#!/usr/bin/python3
|
#!/usr/bin/python3
|
||||||
|
|
||||||
version = __version__ = "4.59.0.21 Released 5-Apr-2022"
|
version = __version__ = "4.59.0.22 Released 5-Apr-2022"
|
||||||
|
|
||||||
_change_log = """
|
_change_log = """
|
||||||
Changelog since 4.59.0 released to PyPI on 5-Apr-2022
|
Changelog since 4.59.0 released to PyPI on 5-Apr-2022
|
||||||
|
@ -79,7 +79,10 @@ _change_log = """
|
||||||
irony - when you accidently leave debug prints in your debug print code
|
irony - when you accidently leave debug prints in your debug print code
|
||||||
4.59.0.21
|
4.59.0.21
|
||||||
Additional exception handling needed for debug window closure at any point
|
Additional exception handling needed for debug window closure at any point
|
||||||
|
4.59.0.22
|
||||||
|
"Ding-dong the Output's dead" - finally replaced the Output element with Multiline by subclassing Multiline
|
||||||
|
This solves a bunch of problems including duplication of funcationality using 2 different techniques.
|
||||||
|
Problem may be in backward compatibility if anyone is using internal Output element member variables, etc.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__version__ = version.split()[0] # For PEP 396 and PEP 345
|
__version__ = version.split()[0] # For PEP 396 and PEP 345
|
||||||
|
@ -4259,117 +4262,119 @@ class TKProgressBar():
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
#
|
||||||
# ---------------------------------------------------------------------- #
|
# # ---------------------------------------------------------------------- #
|
||||||
# TKOutput #
|
# # TKOutput #
|
||||||
# New Type of TK Widget that's a Text Widget in disguise #
|
# # New Type of TK Widget that's a Text Widget in disguise #
|
||||||
# Note that it's inherited from the TKFrame class so that the #
|
# # Note that it's inherited from the TKFrame class so that the #
|
||||||
# Scroll bar will span the length of the frame #
|
# # Scroll bar will span the length of the frame #
|
||||||
# ---------------------------------------------------------------------- #
|
# # ---------------------------------------------------------------------- #
|
||||||
class TKOutput(tk.Frame):
|
# class TKOutput(tk.Frame):
|
||||||
"""
|
# """
|
||||||
tkinter style class. Inherits Frame class from tkinter. Adds a tk.Text and a scrollbar together.
|
# tkinter style class. Inherits Frame class from tkinter. Adds a tk.Text and a scrollbar together.
|
||||||
Note - This is NOT a user controlled class. Users should NOT be directly using it unless making an extention
|
# Note - This is NOT a user controlled class. Users should NOT be directly using it unless making an extention
|
||||||
to PySimpleGUI by directly manipulating tkinter.
|
# to PySimpleGUI by directly manipulating tkinter.
|
||||||
"""
|
# """
|
||||||
|
#
|
||||||
def __init__(self, parent, width, height, bd, background_color=None, text_color=None, echo_stdout_stderr=False, font=None, pad=None):
|
# def __init__(self, parent, width, height, bd, background_color=None, text_color=None, echo_stdout_stderr=False, font=None, pad=None):
|
||||||
"""
|
# """
|
||||||
:param parent: The "Root" that the Widget will be in
|
# :param parent: The "Root" that the Widget will be in
|
||||||
:type parent: tk.Tk | tk.Toplevel
|
# :type parent: tk.Tk | tk.Toplevel
|
||||||
:param width: Width in characters
|
# :param width: Width in characters
|
||||||
:type width: (int)
|
# :type width: (int)
|
||||||
:param height: height in rows
|
# :param height: height in rows
|
||||||
:type height: (int)
|
# :type height: (int)
|
||||||
:param bd: Border Depth. How many pixels of border to show
|
# :param bd: Border Depth. How many pixels of border to show
|
||||||
:type bd: (int)
|
# :type bd: (int)
|
||||||
:param background_color: color of background
|
# :param background_color: color of background
|
||||||
:type background_color: (str)
|
# :type background_color: (str)
|
||||||
:param text_color: color of the text
|
# :param text_color: color of the text
|
||||||
:type text_color: (str)
|
# :type text_color: (str)
|
||||||
:param echo_stdout_stderr: If True then output to stdout will be output to this element AND also to the normal console location
|
# :param echo_stdout_stderr: If True then output to stdout will be output to this element AND also to the normal console location
|
||||||
:type echo_stdout_stderr: (bool)
|
# :type echo_stdout_stderr: (bool)
|
||||||
:param font: specifies the font family, size, etc. Tuple or Single string format 'name size styles'. Styles: italic * roman bold normal underline overstrike
|
# :param font: specifies the font family, size, etc. Tuple or Single string format 'name size styles'. Styles: italic * roman bold normal underline overstrike
|
||||||
:type font: (str or (str, int[, str]) or None)
|
# :type font: (str or (str, int[, str]) or None)
|
||||||
:param pad: Amount of padding to put around element in pixels (left/right, top/bottom) or ((left, right), (top, bottom)) or an int. If an int, then it's converted into a tuple (int, int)
|
# :param pad: Amount of padding to put around element in pixels (left/right, top/bottom) or ((left, right), (top, bottom)) or an int. If an int, then it's converted into a tuple (int, int)
|
||||||
:type pad: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) | int
|
# :type pad: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) | int
|
||||||
"""
|
# """
|
||||||
self.frame = tk.Frame(parent)
|
# self.frame = tk.Frame(parent)
|
||||||
tk.Frame.__init__(self, self.frame)
|
# tk.Frame.__init__(self, self.frame)
|
||||||
self.output = tk.Text(self.frame, width=width, height=height, bd=bd, font=font)
|
# self.output = tk.Text(self.frame, width=width, height=height, bd=bd, font=font)
|
||||||
if background_color and background_color != COLOR_SYSTEM_DEFAULT:
|
# if background_color and background_color != COLOR_SYSTEM_DEFAULT:
|
||||||
self.output.configure(background=background_color)
|
# self.output.configure(background=background_color)
|
||||||
self.frame.configure(background=background_color)
|
# self.frame.configure(background=background_color)
|
||||||
if text_color and text_color != COLOR_SYSTEM_DEFAULT:
|
# if text_color and text_color != COLOR_SYSTEM_DEFAULT:
|
||||||
self.output.configure(fg=text_color)
|
# self.output.configure(fg=text_color)
|
||||||
self.output.configure(insertbackground=text_color)
|
# self.output.configure(insertbackground=text_color)
|
||||||
self.vsb = tk.Scrollbar(self.frame, orient="vertical", command=self.output.yview)
|
# self.vsb = tk.Scrollbar(self.frame, orient="vertical", command=self.output.yview)
|
||||||
self.output.configure(yscrollcommand=self.vsb.set)
|
# self.output.configure(yscrollcommand=self.vsb.set)
|
||||||
self.output.pack(side="left", fill="both", expand=True)
|
# self.output.pack(side="left", fill="both", expand=True)
|
||||||
self.vsb.pack(side="left", fill="y", expand=False)
|
# self.vsb.pack(side="left", fill="y", expand=False)
|
||||||
self.frame.pack(side="left", padx=pad[0], pady=pad[1], expand=True, fill='y')
|
# self.frame.pack(side="left", padx=pad[0], pady=pad[1], expand=True, fill='y')
|
||||||
self.previous_stdout = sys.stdout
|
# self.previous_stdout = sys.stdout
|
||||||
self.previous_stderr = sys.stderr
|
# self.previous_stderr = sys.stderr
|
||||||
self.parent = parent
|
# self.parent = parent
|
||||||
self.echo_stdout_stderr = echo_stdout_stderr
|
# self.echo_stdout_stderr = echo_stdout_stderr
|
||||||
|
#
|
||||||
sys.stdout = self
|
# sys.stdout = self
|
||||||
sys.stderr = self
|
# sys.stderr = self
|
||||||
self.pack()
|
# self.pack()
|
||||||
|
#
|
||||||
def write(self, txt):
|
# def write(self, txt):
|
||||||
"""
|
# """
|
||||||
Called by Python (not tkinter?) when stdout or stderr wants to write
|
# Called by Python (not tkinter?) when stdout or stderr wants to write
|
||||||
Refreshes the window after the write so that the change is immediately displayed
|
# Refreshes the window after the write so that the change is immediately displayed
|
||||||
|
#
|
||||||
:param txt: text of output
|
# :param txt: text of output
|
||||||
:type txt: (str)
|
# :type txt: (str)
|
||||||
"""
|
# """
|
||||||
try:
|
# try:
|
||||||
self.output.insert(tk.END, str(txt))
|
# self.output.insert(tk.END, str(txt))
|
||||||
self.output.see(tk.END)
|
# self.output.see(tk.END)
|
||||||
self.parent.update()
|
# self.parent.update()
|
||||||
except:
|
# except:
|
||||||
pass
|
# pass
|
||||||
|
#
|
||||||
try:
|
# try:
|
||||||
if self.echo_stdout_stderr:
|
# if self.echo_stdout_stderr:
|
||||||
self.previous_stdout.write(txt)
|
# self.previous_stdout.write(txt)
|
||||||
except:
|
# except:
|
||||||
pass
|
# pass
|
||||||
|
#
|
||||||
def Close(self):
|
# def Close(self):
|
||||||
"""
|
# """
|
||||||
Called when wanting to restore the old stdout/stderr
|
# Called when wanting to restore the old stdout/stderr
|
||||||
"""
|
# """
|
||||||
sys.stdout = self.previous_stdout
|
# sys.stdout = self.previous_stdout
|
||||||
sys.stderr = self.previous_stderr
|
# sys.stderr = self.previous_stderr
|
||||||
|
#
|
||||||
def flush(self):
|
# def flush(self):
|
||||||
"""
|
# """
|
||||||
Flush parameter was passed into a print statement.
|
# Flush parameter was passed into a print statement.
|
||||||
For now doing nothing. Not sure what action should be taken to ensure a flush happens regardless.
|
# For now doing nothing. Not sure what action should be taken to ensure a flush happens regardless.
|
||||||
"""
|
# """
|
||||||
try:
|
# try:
|
||||||
if self.echo_stdout_stderr:
|
# if self.echo_stdout_stderr:
|
||||||
self.previous_stdout.flush()
|
# self.previous_stdout.flush()
|
||||||
except:
|
# except:
|
||||||
pass
|
# pass
|
||||||
|
#
|
||||||
def __del__(self):
|
# def __del__(self):
|
||||||
"""
|
# """
|
||||||
If this Widget is deleted, be sure and restore the old stdout, stderr
|
# If this Widget is deleted, be sure and restore the old stdout, stderr
|
||||||
"""
|
# """
|
||||||
sys.stdout = self.previous_stdout
|
# sys.stdout = self.previous_stdout
|
||||||
sys.stderr = self.previous_stderr
|
# sys.stderr = self.previous_stderr
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------- #
|
# ---------------------------------------------------------------------- #
|
||||||
# Output #
|
# Output #
|
||||||
# Routes stdout, stderr to a scrolled window #
|
# Routes stdout, stderr to a scrolled window #
|
||||||
# ---------------------------------------------------------------------- #
|
# ---------------------------------------------------------------------- #
|
||||||
class Output(Element):
|
class Output(Multiline):
|
||||||
"""
|
"""
|
||||||
|
** NOTE - It's recommended to use Multiline Element instead **
|
||||||
|
|
||||||
Output Element - a multi-lined text area where stdout and stderr are re-routed to.
|
Output Element - a multi-lined text area where stdout and stderr are re-routed to.
|
||||||
|
|
||||||
The Multiline Element is the superior and recommended method for showing the output of stdout.
|
The Multiline Element is the superior and recommended method for showing the output of stdout.
|
||||||
|
@ -4378,6 +4383,9 @@ class Output(Element):
|
||||||
|
|
||||||
Of course, Output Element continues to operate and be backwards compatible, but you're missing out on
|
Of course, Output Element continues to operate and be backwards compatible, but you're missing out on
|
||||||
features such as routing the cprint output to the element.
|
features such as routing the cprint output to the element.
|
||||||
|
|
||||||
|
In Apr 2022, the Output Element was switched to be a subclass of the Multiline so that more code will be in common. Nowever
|
||||||
|
you will not get all of the parms unless you switch to the Multline Specifically
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, size=(None, None), s=(None, None), background_color=None, text_color=None, pad=None, p=None, echo_stdout_stderr=False, font=None, tooltip=None,
|
def __init__(self, size=(None, None), s=(None, None), background_color=None, text_color=None, pad=None, p=None, echo_stdout_stderr=False, font=None, tooltip=None,
|
||||||
|
@ -4417,103 +4425,103 @@ class Output(Element):
|
||||||
:type metadata: (Any)
|
:type metadata: (Any)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self._TKOut = self.Widget = None # type: TKOutput
|
# self._TKOut = self.Widget = None # type: TKOutput
|
||||||
bg = background_color if background_color else DEFAULT_INPUT_ELEMENTS_COLOR
|
# bg = background_color if background_color else DEFAULT_INPUT_ELEMENTS_COLOR
|
||||||
fg = text_color if text_color is not None else DEFAULT_INPUT_TEXT_COLOR
|
# fg = text_color if text_color is not None else DEFAULT_INPUT_TEXT_COLOR
|
||||||
self.RightClickMenu = right_click_menu
|
# self.RightClickMenu = right_click_menu
|
||||||
key = key if key is not None else k
|
# key = key if key is not None else k
|
||||||
self.echo_stdout_stderr = echo_stdout_stderr
|
# self.echo_stdout_stderr = echo_stdout_stderr
|
||||||
sz = size if size != (None, None) else s
|
# sz = size if size != (None, None) else s
|
||||||
pad = pad if pad is not None else p
|
# pad = pad if pad is not None else p
|
||||||
self.expand_x = expand_x
|
# self.expand_x = expand_x
|
||||||
self.expand_y = expand_y
|
# self.expand_y = expand_y
|
||||||
|
|
||||||
super().__init__(ELEM_TYPE_OUTPUT, size=sz, background_color=bg, text_color=fg, pad=pad, font=font,
|
super().__init__(size=size, s=s, background_color=background_color, text_color=text_color, pad=pad, p=p, echo_stdout_stderr=echo_stdout_stderr, font=font, tooltip=tooltip,
|
||||||
tooltip=tooltip, key=key, visible=visible, metadata=metadata)
|
key=key, k=k, right_click_menu=right_click_menu, write_only=True, reroute_stdout=True, reroute_stderr=True, autoscroll=True, expand_x=expand_x, expand_y=expand_y, visible=visible, metadata=metadata)
|
||||||
|
#
|
||||||
|
# @property
|
||||||
|
# def tk_out(self):
|
||||||
|
# """
|
||||||
|
# Returns the TKOutput object used to create the element
|
||||||
|
#
|
||||||
|
# :return: The TKOutput object
|
||||||
|
# :rtype: (TKOutput)
|
||||||
|
# """
|
||||||
|
# if self._TKOut is None:
|
||||||
|
# print('*** Did you forget to call Finalize()? Your code should look something like: ***')
|
||||||
|
# print('*** form = sg.Window("My Form").Layout(layout).Finalize() ***')
|
||||||
|
# return self._TKOut
|
||||||
|
#
|
||||||
|
# def update(self, value=None, visible=None):
|
||||||
|
# """
|
||||||
|
# Changes some of the settings for the Output Element. Must call `Window.Read` or `Window.Finalize` prior
|
||||||
|
#
|
||||||
|
# Changes will not be visible in your window until you call window.read or window.refresh.
|
||||||
|
#
|
||||||
|
# If you change visibility, your element may MOVE. If you want it to remain stationary, use the "layout helper"
|
||||||
|
# function "pin" to ensure your element is "pinned" to that location in your layout so that it returns there
|
||||||
|
# when made visible.
|
||||||
|
#
|
||||||
|
# :param value: string that will replace current contents of the output area
|
||||||
|
# :type value: (str)
|
||||||
|
# :param visible: control visibility of element
|
||||||
|
# :type visible: (bool)
|
||||||
|
# """
|
||||||
|
# if not self._widget_was_created(): # if widget hasn't been created yet, then don't allow
|
||||||
|
# return
|
||||||
|
#
|
||||||
|
# if value is not None:
|
||||||
|
# self._TKOut.output.delete('1.0', tk.END)
|
||||||
|
# self._TKOut.output.insert(tk.END, value)
|
||||||
|
# if visible is False:
|
||||||
|
# self._pack_forget_save_settings(self._TKOut.frame)
|
||||||
|
# elif visible is True:
|
||||||
|
# self._pack_restore_settings(self._TKOut.frame)
|
||||||
|
#
|
||||||
|
# if visible is not None:
|
||||||
|
# self._visible = visible
|
||||||
|
#
|
||||||
|
# def get(self):
|
||||||
|
# """
|
||||||
|
# Returns the current contents of the output. Similar to Get method other Elements
|
||||||
|
# :return: the current value of the output
|
||||||
|
# :rtype: (str)
|
||||||
|
# """
|
||||||
|
# return self._TKOut.output.get(1.0, tk.END)
|
||||||
|
#
|
||||||
|
# def expand(self, expand_x=False, expand_y=False, expand_row=True):
|
||||||
|
# """
|
||||||
|
# Causes the Element to expand to fill available space in the X and Y directions. Can specify which or both directions
|
||||||
|
#
|
||||||
|
# :param expand_x: If True Element will expand in the Horizontal directions
|
||||||
|
# :type expand_x: (bool)
|
||||||
|
# :param expand_y: If True Element will expand in the Vertical directions
|
||||||
|
# :type expand_y: (bool)
|
||||||
|
# """
|
||||||
|
#
|
||||||
|
# if expand_x and expand_y:
|
||||||
|
# fill = tk.BOTH
|
||||||
|
# elif expand_x:
|
||||||
|
# fill = tk.X
|
||||||
|
# elif expand_y:
|
||||||
|
# fill = tk.Y
|
||||||
|
# else:
|
||||||
|
# return
|
||||||
|
#
|
||||||
|
# self._TKOut.output.pack(expand=True, fill=fill)
|
||||||
|
# self._TKOut.frame.pack(expand=True, fill=fill)
|
||||||
|
# self.ParentRowFrame.pack(expand=expand_row, fill=fill)
|
||||||
|
#
|
||||||
|
# def __del__(self):
|
||||||
|
# """
|
||||||
|
# Delete this element. Normally Elements do not have their delete method specified, but for this one
|
||||||
|
# it's important that the underlying TKOut object get deleted so that the stdout will get restored properly
|
||||||
|
# """
|
||||||
|
# self._TKOut.__del__()
|
||||||
|
|
||||||
@property
|
# TKOut = tk_out
|
||||||
def tk_out(self):
|
# Update = update
|
||||||
"""
|
# Get = get
|
||||||
Returns the TKOutput object used to create the element
|
|
||||||
|
|
||||||
:return: The TKOutput object
|
|
||||||
:rtype: (TKOutput)
|
|
||||||
"""
|
|
||||||
if self._TKOut is None:
|
|
||||||
print('*** Did you forget to call Finalize()? Your code should look something like: ***')
|
|
||||||
print('*** form = sg.Window("My Form").Layout(layout).Finalize() ***')
|
|
||||||
return self._TKOut
|
|
||||||
|
|
||||||
def update(self, value=None, visible=None):
|
|
||||||
"""
|
|
||||||
Changes some of the settings for the Output Element. Must call `Window.Read` or `Window.Finalize` prior
|
|
||||||
|
|
||||||
Changes will not be visible in your window until you call window.read or window.refresh.
|
|
||||||
|
|
||||||
If you change visibility, your element may MOVE. If you want it to remain stationary, use the "layout helper"
|
|
||||||
function "pin" to ensure your element is "pinned" to that location in your layout so that it returns there
|
|
||||||
when made visible.
|
|
||||||
|
|
||||||
:param value: string that will replace current contents of the output area
|
|
||||||
:type value: (str)
|
|
||||||
:param visible: control visibility of element
|
|
||||||
:type visible: (bool)
|
|
||||||
"""
|
|
||||||
if not self._widget_was_created(): # if widget hasn't been created yet, then don't allow
|
|
||||||
return
|
|
||||||
|
|
||||||
if value is not None:
|
|
||||||
self._TKOut.output.delete('1.0', tk.END)
|
|
||||||
self._TKOut.output.insert(tk.END, value)
|
|
||||||
if visible is False:
|
|
||||||
self._pack_forget_save_settings(self._TKOut.frame)
|
|
||||||
elif visible is True:
|
|
||||||
self._pack_restore_settings(self._TKOut.frame)
|
|
||||||
|
|
||||||
if visible is not None:
|
|
||||||
self._visible = visible
|
|
||||||
|
|
||||||
def get(self):
|
|
||||||
"""
|
|
||||||
Returns the current contents of the output. Similar to Get method other Elements
|
|
||||||
:return: the current value of the output
|
|
||||||
:rtype: (str)
|
|
||||||
"""
|
|
||||||
return self._TKOut.output.get(1.0, tk.END)
|
|
||||||
|
|
||||||
def expand(self, expand_x=False, expand_y=False, expand_row=True):
|
|
||||||
"""
|
|
||||||
Causes the Element to expand to fill available space in the X and Y directions. Can specify which or both directions
|
|
||||||
|
|
||||||
:param expand_x: If True Element will expand in the Horizontal directions
|
|
||||||
:type expand_x: (bool)
|
|
||||||
:param expand_y: If True Element will expand in the Vertical directions
|
|
||||||
:type expand_y: (bool)
|
|
||||||
"""
|
|
||||||
|
|
||||||
if expand_x and expand_y:
|
|
||||||
fill = tk.BOTH
|
|
||||||
elif expand_x:
|
|
||||||
fill = tk.X
|
|
||||||
elif expand_y:
|
|
||||||
fill = tk.Y
|
|
||||||
else:
|
|
||||||
return
|
|
||||||
|
|
||||||
self._TKOut.output.pack(expand=True, fill=fill)
|
|
||||||
self._TKOut.frame.pack(expand=True, fill=fill)
|
|
||||||
self.ParentRowFrame.pack(expand=expand_row, fill=fill)
|
|
||||||
|
|
||||||
def __del__(self):
|
|
||||||
"""
|
|
||||||
Delete this element. Normally Elements do not have their delete method specified, but for this one
|
|
||||||
it's important that the underlying TKOut object get deleted so that the stdout will get restored properly
|
|
||||||
"""
|
|
||||||
self._TKOut.__del__()
|
|
||||||
|
|
||||||
TKOut = tk_out
|
|
||||||
Update = update
|
|
||||||
Get = get
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------- #
|
# ---------------------------------------------------------------------- #
|
||||||
|
|
Loading…
Reference in New Issue