Output element - changed to be a subclass of the Multiline as part of the ttk scrollbar switch. It also greatly reduces complexity as they had very different implementations.
This commit is contained in:
parent
a0c5aafd47
commit
1ecae38613
410
PySimpleGUI.py
410
PySimpleGUI.py
|
@ -1,6 +1,6 @@
|
|||
#!/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 = """
|
||||
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
|
||||
4.59.0.21
|
||||
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
|
||||
|
@ -4259,117 +4262,119 @@ class TKProgressBar():
|
|||
return False
|
||||
return True
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------- #
|
||||
# TKOutput #
|
||||
# New Type of TK Widget that's a Text Widget in disguise #
|
||||
# Note that it's inherited from the TKFrame class so that the #
|
||||
# Scroll bar will span the length of the frame #
|
||||
# ---------------------------------------------------------------------- #
|
||||
class TKOutput(tk.Frame):
|
||||
"""
|
||||
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
|
||||
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):
|
||||
"""
|
||||
:param parent: The "Root" that the Widget will be in
|
||||
:type parent: tk.Tk | tk.Toplevel
|
||||
:param width: Width in characters
|
||||
:type width: (int)
|
||||
:param height: height in rows
|
||||
:type height: (int)
|
||||
:param bd: Border Depth. How many pixels of border to show
|
||||
:type bd: (int)
|
||||
:param background_color: color of background
|
||||
:type background_color: (str)
|
||||
:param text_color: color of the text
|
||||
: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
|
||||
: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
|
||||
: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)
|
||||
:type pad: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) | int
|
||||
"""
|
||||
self.frame = tk.Frame(parent)
|
||||
tk.Frame.__init__(self, self.frame)
|
||||
self.output = tk.Text(self.frame, width=width, height=height, bd=bd, font=font)
|
||||
if background_color and background_color != COLOR_SYSTEM_DEFAULT:
|
||||
self.output.configure(background=background_color)
|
||||
self.frame.configure(background=background_color)
|
||||
if text_color and text_color != COLOR_SYSTEM_DEFAULT:
|
||||
self.output.configure(fg=text_color)
|
||||
self.output.configure(insertbackground=text_color)
|
||||
self.vsb = tk.Scrollbar(self.frame, orient="vertical", command=self.output.yview)
|
||||
self.output.configure(yscrollcommand=self.vsb.set)
|
||||
self.output.pack(side="left", fill="both", expand=True)
|
||||
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.previous_stdout = sys.stdout
|
||||
self.previous_stderr = sys.stderr
|
||||
self.parent = parent
|
||||
self.echo_stdout_stderr = echo_stdout_stderr
|
||||
|
||||
sys.stdout = self
|
||||
sys.stderr = self
|
||||
self.pack()
|
||||
|
||||
def write(self, txt):
|
||||
"""
|
||||
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
|
||||
|
||||
:param txt: text of output
|
||||
:type txt: (str)
|
||||
"""
|
||||
try:
|
||||
self.output.insert(tk.END, str(txt))
|
||||
self.output.see(tk.END)
|
||||
self.parent.update()
|
||||
except:
|
||||
pass
|
||||
|
||||
try:
|
||||
if self.echo_stdout_stderr:
|
||||
self.previous_stdout.write(txt)
|
||||
except:
|
||||
pass
|
||||
|
||||
def Close(self):
|
||||
"""
|
||||
Called when wanting to restore the old stdout/stderr
|
||||
"""
|
||||
sys.stdout = self.previous_stdout
|
||||
sys.stderr = self.previous_stderr
|
||||
|
||||
def flush(self):
|
||||
"""
|
||||
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.
|
||||
"""
|
||||
try:
|
||||
if self.echo_stdout_stderr:
|
||||
self.previous_stdout.flush()
|
||||
except:
|
||||
pass
|
||||
|
||||
def __del__(self):
|
||||
"""
|
||||
If this Widget is deleted, be sure and restore the old stdout, stderr
|
||||
"""
|
||||
sys.stdout = self.previous_stdout
|
||||
sys.stderr = self.previous_stderr
|
||||
#
|
||||
# # ---------------------------------------------------------------------- #
|
||||
# # TKOutput #
|
||||
# # New Type of TK Widget that's a Text Widget in disguise #
|
||||
# # Note that it's inherited from the TKFrame class so that the #
|
||||
# # Scroll bar will span the length of the frame #
|
||||
# # ---------------------------------------------------------------------- #
|
||||
# class TKOutput(tk.Frame):
|
||||
# """
|
||||
# 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
|
||||
# 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):
|
||||
# """
|
||||
# :param parent: The "Root" that the Widget will be in
|
||||
# :type parent: tk.Tk | tk.Toplevel
|
||||
# :param width: Width in characters
|
||||
# :type width: (int)
|
||||
# :param height: height in rows
|
||||
# :type height: (int)
|
||||
# :param bd: Border Depth. How many pixels of border to show
|
||||
# :type bd: (int)
|
||||
# :param background_color: color of background
|
||||
# :type background_color: (str)
|
||||
# :param text_color: color of the text
|
||||
# :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
|
||||
# :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
|
||||
# :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)
|
||||
# :type pad: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) | int
|
||||
# """
|
||||
# self.frame = tk.Frame(parent)
|
||||
# tk.Frame.__init__(self, self.frame)
|
||||
# self.output = tk.Text(self.frame, width=width, height=height, bd=bd, font=font)
|
||||
# if background_color and background_color != COLOR_SYSTEM_DEFAULT:
|
||||
# self.output.configure(background=background_color)
|
||||
# self.frame.configure(background=background_color)
|
||||
# if text_color and text_color != COLOR_SYSTEM_DEFAULT:
|
||||
# self.output.configure(fg=text_color)
|
||||
# self.output.configure(insertbackground=text_color)
|
||||
# self.vsb = tk.Scrollbar(self.frame, orient="vertical", command=self.output.yview)
|
||||
# self.output.configure(yscrollcommand=self.vsb.set)
|
||||
# self.output.pack(side="left", fill="both", expand=True)
|
||||
# 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.previous_stdout = sys.stdout
|
||||
# self.previous_stderr = sys.stderr
|
||||
# self.parent = parent
|
||||
# self.echo_stdout_stderr = echo_stdout_stderr
|
||||
#
|
||||
# sys.stdout = self
|
||||
# sys.stderr = self
|
||||
# self.pack()
|
||||
#
|
||||
# def write(self, txt):
|
||||
# """
|
||||
# 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
|
||||
#
|
||||
# :param txt: text of output
|
||||
# :type txt: (str)
|
||||
# """
|
||||
# try:
|
||||
# self.output.insert(tk.END, str(txt))
|
||||
# self.output.see(tk.END)
|
||||
# self.parent.update()
|
||||
# except:
|
||||
# pass
|
||||
#
|
||||
# try:
|
||||
# if self.echo_stdout_stderr:
|
||||
# self.previous_stdout.write(txt)
|
||||
# except:
|
||||
# pass
|
||||
#
|
||||
# def Close(self):
|
||||
# """
|
||||
# Called when wanting to restore the old stdout/stderr
|
||||
# """
|
||||
# sys.stdout = self.previous_stdout
|
||||
# sys.stderr = self.previous_stderr
|
||||
#
|
||||
# def flush(self):
|
||||
# """
|
||||
# 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.
|
||||
# """
|
||||
# try:
|
||||
# if self.echo_stdout_stderr:
|
||||
# self.previous_stdout.flush()
|
||||
# except:
|
||||
# pass
|
||||
#
|
||||
# def __del__(self):
|
||||
# """
|
||||
# If this Widget is deleted, be sure and restore the old stdout, stderr
|
||||
# """
|
||||
# sys.stdout = self.previous_stdout
|
||||
# sys.stderr = self.previous_stderr
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------- #
|
||||
# Output #
|
||||
# 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.
|
||||
|
||||
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
|
||||
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,
|
||||
|
@ -4417,103 +4425,103 @@ class Output(Element):
|
|||
:type metadata: (Any)
|
||||
"""
|
||||
|
||||
self._TKOut = self.Widget = None # type: TKOutput
|
||||
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
|
||||
self.RightClickMenu = right_click_menu
|
||||
key = key if key is not None else k
|
||||
self.echo_stdout_stderr = echo_stdout_stderr
|
||||
sz = size if size != (None, None) else s
|
||||
pad = pad if pad is not None else p
|
||||
self.expand_x = expand_x
|
||||
self.expand_y = expand_y
|
||||
# self._TKOut = self.Widget = None # type: TKOutput
|
||||
# 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
|
||||
# self.RightClickMenu = right_click_menu
|
||||
# key = key if key is not None else k
|
||||
# self.echo_stdout_stderr = echo_stdout_stderr
|
||||
# sz = size if size != (None, None) else s
|
||||
# pad = pad if pad is not None else p
|
||||
# self.expand_x = expand_x
|
||||
# self.expand_y = expand_y
|
||||
|
||||
super().__init__(ELEM_TYPE_OUTPUT, size=sz, background_color=bg, text_color=fg, pad=pad, font=font,
|
||||
tooltip=tooltip, key=key, visible=visible, metadata=metadata)
|
||||
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,
|
||||
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
|
||||
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__()
|
||||
|
||||
TKOut = tk_out
|
||||
Update = update
|
||||
Get = get
|
||||
# TKOut = tk_out
|
||||
# Update = update
|
||||
# Get = get
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------- #
|
||||
|
|
Loading…
Reference in New Issue