367 lines
20 KiB
HTML
367 lines
20 KiB
HTML
|
<!DOCTYPE html>
|
||
|
<html lang="en">
|
||
|
<head>
|
||
|
<meta charset="utf-8">
|
||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
|
|
||
|
|
||
|
|
||
|
<link rel="shortcut icon" href="../img/favicon.ico">
|
||
|
<title>Architecture - My Docs</title>
|
||
|
<link href="../css/bootstrap-custom.min.css" rel="stylesheet">
|
||
|
<link href="../css/font-awesome.min.css" rel="stylesheet">
|
||
|
<link href="../css/base.css" rel="stylesheet">
|
||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/styles/github.min.css">
|
||
|
<!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
|
||
|
<!--[if lt IE 9]>
|
||
|
<script src="https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
|
||
|
<script src="https://oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js"></script>
|
||
|
<![endif]-->
|
||
|
|
||
|
<script src="../js/jquery-1.10.2.min.js" defer></script>
|
||
|
<script src="../js/bootstrap-3.0.3.min.js" defer></script>
|
||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/highlight.min.js"></script>
|
||
|
<script>hljs.initHighlightingOnLoad();</script>
|
||
|
</head>
|
||
|
|
||
|
<body>
|
||
|
|
||
|
<div class="navbar navbar-default navbar-fixed-top" role="navigation">
|
||
|
<div class="container">
|
||
|
|
||
|
<!-- Collapsed navigation -->
|
||
|
<div class="navbar-header">
|
||
|
<!-- Expander button -->
|
||
|
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
|
||
|
<span class="sr-only">Toggle navigation</span>
|
||
|
<span class="icon-bar"></span>
|
||
|
<span class="icon-bar"></span>
|
||
|
<span class="icon-bar"></span>
|
||
|
</button>
|
||
|
<a class="navbar-brand" href="..">My Docs</a>
|
||
|
</div>
|
||
|
|
||
|
<!-- Expanded navigation -->
|
||
|
<div class="navbar-collapse collapse">
|
||
|
<!-- Main navigation -->
|
||
|
<ul class="nav navbar-nav">
|
||
|
<li >
|
||
|
<a href="..">Home</a>
|
||
|
</li>
|
||
|
<li class="active">
|
||
|
<a href="./">Architecture</a>
|
||
|
</li>
|
||
|
<li >
|
||
|
<a href="../cookbook/">Cookbook</a>
|
||
|
</li>
|
||
|
<li >
|
||
|
<a href="../tutorial/">Tutorial</a>
|
||
|
</li>
|
||
|
</ul>
|
||
|
|
||
|
<ul class="nav navbar-nav navbar-right">
|
||
|
<li>
|
||
|
<a href="#" data-toggle="modal" data-target="#mkdocs_search_modal">
|
||
|
<i class="fa fa-search"></i> Search
|
||
|
</a>
|
||
|
</li>
|
||
|
<li >
|
||
|
<a rel="next" href="..">
|
||
|
<i class="fa fa-arrow-left"></i> Previous
|
||
|
</a>
|
||
|
</li>
|
||
|
<li >
|
||
|
<a rel="prev" href="../cookbook/">
|
||
|
Next <i class="fa fa-arrow-right"></i>
|
||
|
</a>
|
||
|
</li>
|
||
|
</ul>
|
||
|
</div>
|
||
|
</div>
|
||
|
</div>
|
||
|
|
||
|
<div class="container">
|
||
|
<div class="col-md-3"><div class="bs-sidebar hidden-print affix well" role="complementary">
|
||
|
<ul class="nav bs-sidenav">
|
||
|
<li class="main active"><a href="#pysimplegui-architecture">PySimpleGUI Architecture</a></li>
|
||
|
<li class="main "><a href="#what-is-it">What is it?</a></li>
|
||
|
<li><a href="#architectural-decisions-direction-goals">Architectural Decisions / Direction / Goals</a></li>
|
||
|
<li class="main "><a href="#window-definition">Window Definition</a></li>
|
||
|
<li><a href="#widgets-elements-in-pysimplegui-speak">Widgets (Elements in PySimpleGUI-speak)</a></li>
|
||
|
<li><a href="#defining-a-window">Defining a Window</a></li>
|
||
|
<li><a href="#example-code-simple-form-style-window">Example Code - Simple Form-style Window</a></li>
|
||
|
<li class="main "><a href="#event-loops">Event Loops</a></li>
|
||
|
<li><a href="#example-code-window-with-event-loop">Example Code - Window with Event Loop</a></li>
|
||
|
<li class="main "><a href="#widget-interaction">Widget Interaction</a></li>
|
||
|
<li><a href="#reading-a-widget">Reading a widget</a></li>
|
||
|
<li><a href="#changing-a-widgets-value">Changing a widget's value</a></li>
|
||
|
<li><a href="#widget-interaction_1">Widget interaction</a></li>
|
||
|
<li class="main "><a href="#async-designs">Async Designs</a></li>
|
||
|
<li><a href="#example">Example</a></li>
|
||
|
<li class="main "><a href="#wrap-up">Wrap-up</a></li>
|
||
|
<li><a href="#portability">Portability</a></li>
|
||
|
</ul>
|
||
|
</div></div>
|
||
|
<div class="col-md-9" role="main">
|
||
|
|
||
|
<p><img alt="pysimplegui_logo" src="https://user-images.githubusercontent.com/13696193/43165867-fe02e3b2-8f62-11e8-9fd0-cc7c86b11772.png" /></p>
|
||
|
<h1 id="pysimplegui-architecture">PySimpleGUI Architecture</h1>
|
||
|
<p>29-Nov-2018</p>
|
||
|
<p>PySimpleGUI is a "wrapper" for tkinter and Qt, with more on the way. Their are a number of "tricks" / architecture decisions that make this package appealing to both beginners and experienced GUI developers. Both will appreciate the simplicity. Simple doesn't mean shallow. There is considerable depth to the PySimpleGUI architecture.</p>
|
||
|
<h1 id="what-is-it">What is it?</h1>
|
||
|
<p>PySimpleGUI is a code-generator in many ways. When you get and configure a "Text Element (Widget)", PySimpleGUI makes <strong><em>exactly</em></strong> the same tkinter or Qt calls that a developer would.</p>
|
||
|
<p>Here is part of the code that is executed when you create a Text Widget.</p>
|
||
|
<pre><code class="python">element.QT_Label = qlabel = QLabel(element.DisplayText, toplevel_win.QTWindow)
|
||
|
if element.Justification is not None:
|
||
|
justification = element.Justification
|
||
|
elif toplevel_win.TextJustification is not None:
|
||
|
justification = toplevel_win.TextJustification
|
||
|
else:
|
||
|
justification = DEFAULT_TEXT_JUSTIFICATION
|
||
|
if justification[0] == 'c':
|
||
|
element.QT_Label.setAlignment(Qt.AlignCenter)
|
||
|
elif justification[0] == 'r':
|
||
|
element.QT_Label.setAlignment(Qt.AlignRight)
|
||
|
if not auto_size_text:
|
||
|
if element_size[0] is not None:
|
||
|
element.QT_Label.setFixedWidth(element_size[0])
|
||
|
if element_size[1] is not None:
|
||
|
element.QT_Label.setFixedHeight(element_size[1])
|
||
|
</code></pre>
|
||
|
|
||
|
<p>The "beauty" or PySimpleGUI is the code you see above was specified with this line of code</p>
|
||
|
<p><code>Text('Text', justification='left', size=(20,1))</code></p>
|
||
|
<p>You can see just how little effort it took to generate, and configure on your behalf, code that resembles hand-generated Qt code.</p>
|
||
|
<h2 id="architectural-decisions-direction-goals">Architectural Decisions / Direction / Goals</h2>
|
||
|
<ul>
|
||
|
<li>Present a GUI interface that is not object oriented</li>
|
||
|
<li>Run an event loop within the user's application</li>
|
||
|
<li>Events (button clicks, keystrokes, menu choices, etc) are funneled through a single interface, Window.Read()<ul>
|
||
|
<li>Users can use simple 'if' statements to act upon the GUIs events.... if this button press, do this.</li>
|
||
|
<li>There are no callbacks</li>
|
||
|
</ul>
|
||
|
</li>
|
||
|
<li>Can change a widget's settings is a single call </li>
|
||
|
<li>Widget interaction is modeled simply, through simple widget updates<ul>
|
||
|
<li>Change Widget A's value to value of Widget B is a single call</li>
|
||
|
<li>A widget's current value is returned in a dictionary when there is an event</li>
|
||
|
</ul>
|
||
|
</li>
|
||
|
<li>Can duplicate nearly any window layout created by coding directly in tkinter/Qt.</li>
|
||
|
<li>Create complete documentation with a LOT of illustrations</li>
|
||
|
<li>Don't try to solve all GUI problems. <ul>
|
||
|
<li>Be the 80% of 80/20. Leave the difficult 20% to the major frameworks. The the other 80% of GUI problems.</li>
|
||
|
</ul>
|
||
|
</li>
|
||
|
</ul>
|
||
|
<p>This is the magic combination that is PySimpleGUI. It's a unique design that is approachable and enjoyable to use. </p>
|
||
|
<h1 id="window-definition">Window Definition</h1>
|
||
|
<h2 id="widgets-elements-in-pysimplegui-speak">Widgets (Elements in PySimpleGUI-speak)</h2>
|
||
|
<p>Creating widgets and placing them into a window definition is done with a single object, named appropriately in a simple manner. There are no "Label" widgets, but there is a "Text" one.</p>
|
||
|
<p>PySimpleGUI takes advantage of the Python named parameter feature. Object calls and methods are loaded up with lots of potential parameters. An IDE is a must. Let's look at a Text widget.</p>
|
||
|
<pre><code class="python">Text(text, size=(None, None), auto_size_text=None, click_submits=None, relief=None, font=None, text_color=None, background_color=None, justification=None, pad=None, margins=None, key=None, tooltip=None)
|
||
|
</code></pre>
|
||
|
|
||
|
<p>There are 13 different values and settings that can be specified when creating a Text widget. They are set when you create the widget, not several lines away. </p>
|
||
|
<p>The amount of code required to set those 13 values is certainly greater than 1 line of code per value. Closer to 2 or 3. </p>
|
||
|
<p>Not only are you setting the visible settings for a Text widget, but you're setting some behaviors. For example, <code>enable_events</code> will cause this Text widget to inform the application when someone clicks on the text. In 1 parameter we've done the work of several lines of code dealing with callbacks. Callbacks are not something PySimpleGUI have to deal with. There are no callbacks.</p>
|
||
|
<h2 id="defining-a-window">Defining a Window</h2>
|
||
|
<p>Break a window can be broken down into rows like this:</p>
|
||
|
<p><img alt="gui5_1" src="https://user-images.githubusercontent.com/13696193/44160133-6af0df80-a087-11e8-9dec-bb4d4c59393d.JPG" /></p>
|
||
|
<p>Then stack row "rows"up and you've got yourself a window.</p>
|
||
|
<p>If you were to break down the sketched out window into Widgets, you would get something like this:
|
||
|
"Filename"
|
||
|
Input Text, Browse for files
|
||
|
Ok button, Cancel button</p>
|
||
|
<p>To create PySimpleGUI from this, you simply make a list for each row and put those lists together.</p>
|
||
|
<h2 id="example-code-simple-form-style-window">Example Code - Simple Form-style Window</h2>
|
||
|
<pre><code class="python"> import PySimpleGUI as sg
|
||
|
|
||
|
layout = [[sg.Text('Filename')],
|
||
|
[sg.Input(), sg.FileBrowse()],
|
||
|
[sg.OK(), sg.Cancel()] ]
|
||
|
|
||
|
event, values = sg.Window('Get filename example').Layout(layout).Read()
|
||
|
</code></pre>
|
||
|
|
||
|
<p><img alt="snag-0296" src="https://user-images.githubusercontent.com/13696193/49241117-ef683380-f3d4-11e8-9a68-20885ce0af61.jpg" /></p>
|
||
|
<p>Here is a complete program that will show the example window, get the values from the user.</p>
|
||
|
<p>That's all you need. It's "simple" after all.</p>
|
||
|
<p>Your <code>layout</code> is a visual representation of your window. You can clearly see 3 rows of widgets being defined. If you would like your text to be red, then your layout would look like this:</p>
|
||
|
<pre><code class="python">layout = [[sg.Text('Filename', text_color='red')],
|
||
|
[sg.Input(), sg.FileBrowse()],
|
||
|
[sg.OK(), sg.Cancel()]]
|
||
|
</code></pre>
|
||
|
|
||
|
<h1 id="event-loops">Event Loops</h1>
|
||
|
<h2 id="example-code-window-with-event-loop">Example Code - Window with Event Loop</h2>
|
||
|
<p>The event loop in PySimpleGUI is where all the action takes place. There are no callbacks in PySimpleGUI. All of the code is located in 1 place, inside the loop.</p>
|
||
|
<p>Getting user input is achieved by calling Window.Read()</p>
|
||
|
<p>A typical call to Read:
|
||
|
<code>event, values = sg.window.Read()</code></p>
|
||
|
<p><code>event</code> will be the event that happened. values are all of the window's widgets current values, in dictionary format.</p>
|
||
|
<p>Adding an event loop to the previous example results in this code:</p>
|
||
|
<pre><code class="python">import PySimpleGUI as sg
|
||
|
|
||
|
layout = [[sg.Text('Filename', text_color='red')],
|
||
|
[sg.Input(), sg.FileBrowse()],
|
||
|
[sg.OK(), sg.Cancel()]]
|
||
|
|
||
|
window = sg.Window('Get filename example').Layout(layout)
|
||
|
|
||
|
# The Event Loop
|
||
|
while True:
|
||
|
event, values = window.Read()
|
||
|
if event is None:
|
||
|
break
|
||
|
</code></pre>
|
||
|
|
||
|
<p>Within your event loop you take actions based on the event.
|
||
|
Let's say you want to print whatever is in the input field when the user clicks the OK button. The changed event loop is:</p>
|
||
|
<pre><code class="python"># The Event Loop
|
||
|
while True:
|
||
|
event, values = window.Read()
|
||
|
if event is None:
|
||
|
break
|
||
|
if event == 'OK':
|
||
|
print(values[0])
|
||
|
</code></pre>
|
||
|
|
||
|
<p>For buttons, when they are clicked, they return their text as the event, or you can set a "key" that will be what it returned to you when the button is clicked. Adding a key to our OK button is done by adding the <code>key</code> parameter to the <code>OK()</code> call.</p>
|
||
|
<p><code>sg.OK(key='_OK BUTTON_')</code></p>
|
||
|
<p>Now when the OK button is clicked, the event value will be <code>_OK BUTTON_</code>.</p>
|
||
|
<h1 id="widget-interaction">Widget Interaction</h1>
|
||
|
<h2 id="reading-a-widget">Reading a widget</h2>
|
||
|
<p>Any time that an event happens, you are provided a dictionary containing all of your widget's values.<br />
|
||
|
<code>event, values = window.Read()</code></p>
|
||
|
<p>The<code>values</code> return code is a dictionary of the widget's values. Adding a <code>key</code> to the <code>Input</code> widget will cause that key to be what is used to "loop up" the value of that widget in the <code>values</code> variable.</p>
|
||
|
<p>If our <code>Input</code> widget was changed to have a key:
|
||
|
<code>sg.Input(key='_INPUT_')</code></p>
|
||
|
<p>Then the value for that input field can be obtained from the <code>values</code> variable. The value of the Input widget in this case will be:
|
||
|
<code>values['_INPUT_']</code></p>
|
||
|
<h2 id="changing-a-widgets-value">Changing a widget's value</h2>
|
||
|
<p>Every widget has an <code>Update</code> method that will the widget's value or settings.</p>
|
||
|
<p>To update an widget, you must first get the object. You can either save it in a variable when you create it or you can look up a widget by it's key. Remember widgets are called Elements in PySimpleGUI. To get the Input Element in the previous example, you could call <code>Element</code> or <code>FindElement</code>.<br />
|
||
|
<code>window.Element(key)</code></p>
|
||
|
<p>Once you have the element, then you can call the Update function:
|
||
|
<code>window.Element(key).Update(value and settings)</code></p>
|
||
|
<h2 id="widget-interaction_1">Widget interaction</h2>
|
||
|
<p>Building further on the <code>key</code> idea and Updating widgets, let's look at an example where the text 'Filename' is replaced by whatever you type in the input box.</p>
|
||
|
<p>The basic logic:
|
||
|
If button == 'OK':
|
||
|
change text to input field's value</p>
|
||
|
<pre><code class="python">import PySimpleGUI as sg
|
||
|
|
||
|
layout = [[sg.Text('Filename', text_color='red', key='_TEXT_')],
|
||
|
[sg.Input(key='_INPUT_'), sg.FileBrowse()],
|
||
|
[sg.OK(key='_OK BUTTON_'), sg.Cancel()]]
|
||
|
|
||
|
window = sg.Window('Get filename example').Layout(layout)
|
||
|
|
||
|
# The Event Loop
|
||
|
while True:
|
||
|
event, values = window.Read()
|
||
|
if event is None:
|
||
|
break
|
||
|
if event == '_OK BUTTON_':
|
||
|
window.Element('_TEXT_').Update(values['_INPUT_'])
|
||
|
</code></pre>
|
||
|
|
||
|
<p>As you can see, the pseudo-code on the real code look very similar. </p>
|
||
|
<p>Note the statement <code>if event is None</code> is what catches the user clicking the X to close the window. When the user does that, we want to exit the program by breaking from the event loop.</p>
|
||
|
<h1 id="async-designs">Async Designs</h1>
|
||
|
<p>Asynchronous designs are possible using PySimpleGUI. To use async, add a <code>timeout</code> parameter to the <code>window.Read()</code> call., </p>
|
||
|
<h2 id="example">Example</h2>
|
||
|
<p>Let's say you wanted to make a GUI that displays your latest emails, checking every 30 seconds. Rather than spin off a thread for the mail checker, run it within your GUI's event loop. Ideally we want the GUI to run as much as possible so that it's responsive. This is how it's accomplished</p>
|
||
|
<pre><code class="python"># The Event Loop
|
||
|
while True:
|
||
|
event, values = window.Read(timeout=30000)
|
||
|
</code></pre>
|
||
|
|
||
|
<p>If you don't want to GUI to delay at all then set timeout=0. Setting timeout=0 will run in a completely non-blocked, async fashion.</p>
|
||
|
<h1 id="wrap-up">Wrap-up</h1>
|
||
|
<p>The overall architecture was meant to enable someone to duplicate both the GUI at a near-pixel-level and the behavior of a program written directly in the tkinter or Qt framework. PySimpleGUI provides a way of interacting with the native widgets in a more Python-friendly, novice-user-friendly manner. It is not meant for large, commercial applications. Those types of applications are in the 20% not covered by PySimpleGUI.</p>
|
||
|
<h3 id="portability">Portability</h3>
|
||
|
<p>Both PySimpleGUI code and the PySimpleGUI package itself are highly portable. Taking a PySimpleGUI application from tkinter to Qt requires changing the import from <code>import PySimpleGUI</code> to <code>import PySimpleGUIQt</code>. That really is all that is typically required.</p>
|
||
|
<p>The PySimpleGUI module itself is highly portable too. Porting from tkinter to Qt took 1 week to get all of the widgets up and running with their basic operations.</p></div>
|
||
|
</div>
|
||
|
|
||
|
<footer class="col-md-12">
|
||
|
<hr>
|
||
|
<p>Documentation built with <a href="https://www.mkdocs.org/">MkDocs</a>.</p>
|
||
|
</footer>
|
||
|
<script>
|
||
|
var base_url = "..",
|
||
|
shortcuts = {"help": 191, "next": 78, "previous": 80, "search": 83};
|
||
|
</script>
|
||
|
<script src="../js/base.js" defer></script>
|
||
|
<script src="../search/main.js" defer></script>
|
||
|
|
||
|
<div class="modal" id="mkdocs_search_modal" tabindex="-1" role="dialog" aria-labelledby="Search Modal" aria-hidden="true">
|
||
|
<div class="modal-dialog">
|
||
|
<div class="modal-content">
|
||
|
<div class="modal-header">
|
||
|
<button type="button" class="close" data-dismiss="modal"><span aria-hidden="true">×</span><span class="sr-only">Close</span></button>
|
||
|
<h4 class="modal-title" id="exampleModalLabel">Search</h4>
|
||
|
</div>
|
||
|
<div class="modal-body">
|
||
|
<p>
|
||
|
From here you can search these documents. Enter
|
||
|
your search terms below.
|
||
|
</p>
|
||
|
<form role="form">
|
||
|
<div class="form-group">
|
||
|
<input type="text" class="form-control" placeholder="Search..." id="mkdocs-search-query" title="Type search term here">
|
||
|
</div>
|
||
|
</form>
|
||
|
<div id="mkdocs-search-results"></div>
|
||
|
</div>
|
||
|
<div class="modal-footer">
|
||
|
</div>
|
||
|
</div>
|
||
|
</div>
|
||
|
</div><div class="modal" id="mkdocs_keyboard_modal" tabindex="-1" role="dialog" aria-labelledby="Keyboard Shortcuts Modal" aria-hidden="true">
|
||
|
<div class="modal-dialog">
|
||
|
<div class="modal-content">
|
||
|
<div class="modal-header">
|
||
|
<button type="button" class="close" data-dismiss="modal"><span aria-hidden="true">×</span><span class="sr-only">Close</span></button>
|
||
|
<h4 class="modal-title" id="exampleModalLabel">Keyboard Shortcuts</h4>
|
||
|
</div>
|
||
|
<div class="modal-body">
|
||
|
<table class="table">
|
||
|
<thead>
|
||
|
<tr>
|
||
|
<th style="width: 20%;">Keys</th>
|
||
|
<th>Action</th>
|
||
|
</tr>
|
||
|
</thead>
|
||
|
<tbody>
|
||
|
<tr>
|
||
|
<td class="help shortcut"><kbd>?</kbd></td>
|
||
|
<td>Open this help</td>
|
||
|
</tr>
|
||
|
<tr>
|
||
|
<td class="next shortcut"><kbd>n</kbd></td>
|
||
|
<td>Next page</td>
|
||
|
</tr>
|
||
|
<tr>
|
||
|
<td class="prev shortcut"><kbd>p</kbd></td>
|
||
|
<td>Previous page</td>
|
||
|
</tr>
|
||
|
<tr>
|
||
|
<td class="search shortcut"><kbd>s</kbd></td>
|
||
|
<td>Search</td>
|
||
|
</tr>
|
||
|
</tbody>
|
||
|
</table>
|
||
|
</div>
|
||
|
<div class="modal-footer">
|
||
|
</div>
|
||
|
</div>
|
||
|
</div>
|
||
|
</div>
|
||
|
|
||
|
</body>
|
||
|
</html>
|