You are on page 1of 16

Designing Tkinter forms with Page/Tcl

If you are familiar with designing forms for Visual Basic and C# using Visual Studio, or even Eclipse
FormsBuilder for Java, you will appreciate the convenience of these IDE development tools.

When it comes to Python GUIs then it becomes a whole different ballgame. There are a number of
GUI designers but they all have one major problem, which is they require different libraries that
need to be installed to use them and run the code afterwards.

This is OK at home, but what about at a school, where you do not have priveleges to install these
libraries? Usually the only GUI there is Tkinter, as it is part of the original Python distribution.

Luckily there is a solution, which does not need anything to be ‘installed’ ie registry entries, .dll files
in System32, desktop icons etc. etc. It runs from a USB drive, or any location where you can run .exe
executable files. This often includes your Documents user space!

If you want to set it up yourself from scratch here’s how:

Download the following:

1) Get Tcl from http://www.activestate.com/activetcl/downloads version 8.6.4.1 at the time


of writing. Use either 32bit or 64bit depending on your system.

2) Get PAGE (Python Automatic (gui ) GEnerator) from


https://sourceforge.net/projects/page/files/latest/download version 4.8.6 at the time of
writing

3) Documentation on http://page.sourceforge.net/html/index.html Useful tutorial on


http://page.sourceforge.net/pdf/python-page.pdf

4) If you want to use SharpDevelop to create your own Page/Tcl launcher in C# for use in the
PortableApps distribution, and to test the comparisons detailed below go to:
http://www.icsharpcode.net/OpenSource/SD/Default.aspx This is a 50MB application and
runs from any location, including USB

The documentation and tutorial links above are helpful, but for a quick guide to setting up and use
read on:

Page 1 of 16
This work is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License
http://creativecommons.org/licenses/by-nc-sa/4.0/
‘Install’ ActiveTcl by double-clicking the executable file:

Ignore all the text as usual and click ‘Next’

Ignore the text again, click the ‘Accept’ radio button and click ‘Next’

You cannot change any options here except the path, so leave it at C:\Tcl (as it is going to be moved
later anyway), unless on a school system, when you should use choose the root of your documents
folder (usually U:\ or N:\ which represent mapped drive letters to your Documents share on the
server)

Click ‘Next’

Leave the demos directory alone. (C:\Tcl\Demos or YourDocumentsMappedDrive:\Tcl\demos)

Page 2 of 16
This work is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License
http://creativecommons.org/licenses/by-nc-sa/4.0/
Click ‘Next’. Click ‘Next’ again You may get errors at school due to not being able to modify the start
menu

All Done. The highlighted text informs you the only way to un-install is through the uninstall entry
added to the start menu. Nonsense: Select the folder and press the Delete key… (Only if you want to
‘uninstall’ it!)

Hit ‘Finish’

Install Page-4.8.6.exe by double-clicking it:

Click ‘Yes’

A full-window installer emerges with only the option for changing the path.

Leave it at C:\page unless on a school system then use the root of your Documents, similar to Tcl
above.

Again there may be errors due to shortcut and start menu creation on school systems.

Once completed drag’n’drop the Tcl folder into the page folder. The complete page folder should
contain the following:

Page 3 of 16
This work is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License
http://creativecommons.org/licenses/by-nc-sa/4.0/
If you managed to get a desktop icon for page. The properties page for this icon (right-click
 properties) show the following Target:

C:\page\winpage.bat

When the icon is double-clicked it says:

Open winpage.bat in a text editor:

start /min wish.exe c:\page\page.tcl %1

Change it to: start /min Tcl\bin\wish.exe page.tcl %1

Save ad try again.

This time a bunch of separate dialogs will open at the corners of the screen.

As all absolute pathnames have been removed, this will now work wherever you move the page
folder.

Copy the page folder to a usb or any convenient location and you can run it directly from there.

Page 4 of 16
This work is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License
http://creativecommons.org/licenses/by-nc-sa/4.0/
Adding Page to the PortableApps distribution

If you use PortableApps, you will be famiiar with the interface:

If you drop the page folder into the PortableApps folder, it will not show up in the menu, as there is
no associated .exe file with a suitable icon.

You have to make your own using C# in either SharpDevelop or Visual Studio.

Create a new C# Console project in the page directory called PageLauncher.

Use the icon located in \page\WIN_INSTALL as the project icon and use the following code in
Program.cs:

using System;
using System.Linq;
using System.IO;
using System.Threading;

namespace PageLauncher
{
class Program
{
public static void Main(string[] args)
{
// Make sure the source code of this development is located within the \page folder
// else paths/files will not be found!
Console.WriteLine("C# Launcher for Page");
string driveLetter = Path.GetPathRoot(Environment.CurrentDirectory); // X:\
Console.WriteLine("Running from drive: " + driveLetter);
/* full startup of .exe eg in \bin folder
possible locations:
X:\PortableApps\page\PageLauncher.exe
X:\PortableApps\page\PageLauncher\PageLauncher\bin\Debug\PageLauncher.exe
X:\page\PageLauncher.exe
*/
string AppPath =
Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);

Page 5 of 16
This work is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License
http://creativecommons.org/licenses/by-nc-sa/4.0/
Console.WriteLine("PageLauncher.exe (C# launcher) located at:\n\t" + AppPath +
@"\PageLauncher.exe");
string PagePath = AppPath.Substring(0, AppPath.IndexOf(@"\page")) + @"\page";
string TclPath = PagePath + @"\Tcl";
if(Directory.Exists(TclPath))
{
Console.WriteLine("Wish.exe (executable) located at:\n\t" + TclPath +
@"\bin\wish.exe");
Console.WriteLine("\nLaunching Page\n\nHold tight!");
try
{
//Mimic batch file contents: start /min Tcl\bin\wish.exe page.tcl %1
System.Diagnostics.Process startPage = new System.Diagnostics.Process();
startPage.StartInfo.FileName = TclPath + @"\bin\wish.exe";
startPage.StartInfo.Arguments = PagePath + @"\page.tcl";
startPage.Start();
Console.WriteLine("\nClosing in 10 seconds...");
Thread.Sleep(10000);
}
catch (Exception e)
{
Console.WriteLine(e.Message);
Console.WriteLine("Press enter to exit");
Console.ReadLine();
}
}
else
{
Console.WriteLine("Cannot find directory " + TclPath);
Console.WriteLine("Install or copy TCL into page directory");
Console.WriteLine("Available from: http://www.activestate.com/activetcl/downloads");
Console.WriteLine("Press enter to exit");
Console.ReadLine();
}
}
}
}

Copy the compiled .exe from the \bin folder to the root of the \page folder.

It will now have an entry in the PortableApps Menu:

Page 6 of 16
This work is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License
http://creativecommons.org/licenses/by-nc-sa/4.0/
Comparison of the use of Page for those familiar with Visual Studio and Java Forms
Builder

C#
Take an example of a simple form with a button, label and textbox:

This was made on the SharpDevelop IDE in a few minutes. (Launch from PortableApps)

- Use the default 300 x 300 design form


- Add a button and set the following properties from the property panel:
Name btnDemo
Font Segoe UI, 9.75pt
Location 70,35
Size 150,40
Text Click Me!
Click (Event) BtnDemoClick

- Add a TextBox and set the propeties:


Name txtDemo
Font Segoe UI, 8.25pt
Location 12,100
Size 270,22

- Add a label:
Name lblDemo
BorderStyle Fixed3D
Font Segoe UI, 8.25pt
Location 12,170
Size 270,30
Text Clicking the button will copy the textbox text here
TextAlign MiddleCenter

Page 7 of 16
This work is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License
http://creativecommons.org/licenses/by-nc-sa/4.0/
This generates the designer code behind the scenes as follows:

private System.Windows.Forms.TextBox txtDemo;


private System.Windows.Forms.Label lblDemo;
private System.Windows.Forms.Button btnDemo;

private void InitializeComponent()


{
this.txtDemo = new System.Windows.Forms.TextBox();
this.lblDemo = new System.Windows.Forms.Label();
this.btnDemo = new System.Windows.Forms.Button();
this.SuspendLayout();
// txtDemo
this.txtDemo.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Regular,
System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.txtDemo.Location = new System.Drawing.Point(12, 100);
this.txtDemo.Name = "txtDemo";
this.txtDemo.Size = new System.Drawing.Size(270, 22);
this.txtDemo.TabIndex = 0;
// lblDemo
this.lblDemo.BorderStyle = System.Windows.Forms.BorderStyle.Fixed3D;
this.lblDemo.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Regular,
System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.lblDemo.Location = new System.Drawing.Point(12, 170);
this.lblDemo.Name = "lblDemo";
this.lblDemo.Size = new System.Drawing.Size(270, 30);
this.lblDemo.TabIndex = 1;
this.lblDemo.Text = "Clicking the button will copy the textbox text here";
this.lblDemo.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
// btnDemo
this.btnDemo.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular,
System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.btnDemo.Location = new System.Drawing.Point(70, 35);
this.btnDemo.Name = "btnDemo";
this.btnDemo.Size = new System.Drawing.Size(150, 40);
this.btnDemo.TabIndex = 2;
this.btnDemo.Text = "Click Me!";
this.btnDemo.UseVisualStyleBackColor = true;
this.btnDemo.Click += new System.EventHandler(this.BtnDemoClick);
// MainForm
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(296, 261);
this.Controls.Add(this.btnDemo);
this.Controls.Add(this.lblDemo);
this.Controls.Add(this.txtDemo);
this.Name = "MainForm";
this.Text = "Cross Platform Demo";
this.ResumeLayout(false);
this.PerformLayout();
}

If you study this code it follows the pattern:

- Declare the GUI element as an object variable: private System.Windows.Forms.Button btnDemo;


- Create the object: this.btnDemo = new System.Windows.Forms.Button();
- Set the properties of this variable: this.btnDemo.Name = "btnDemo"; etc. etc.
- Create the event handler: this.btnDemo.Click += new System.EventHandler(this.BtnDemoClick);
- Add the GUI element to the layout: this.Controls.Add(this.btnDemo);

Page 8 of 16
This work is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License
http://creativecommons.org/licenses/by-nc-sa/4.0/
Repeat using Eclipse WindowBuilder

Java
The same project created with the same properties takes a bit longer, but gives the following GUI
designer code (The declarations have been moved out of ‘ initialise()’ so they can be accessed from
outside the function. Highlighted lines have been added or modified):

private JFrame frame;


private JTextField txtDemo;
private JButton btnDemo;
private JLabel lblDemo;
private void initialize()
{
frame = new JFrame();
frame.setTitle("Cross Platform Demo");
frame.setResizable(false);
frame.setBounds(100, 100, 310, 300);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().setLayout(null);

btnDemo = new JButton("Click Me!");


btnDemo.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent arg0)
{
lblDemo.setText(txtDemo.getText());
}
}); btnDemo.setFont(new Font("Segoe UI", Font.PLAIN, 12));
btnDemo.setBounds(70, 35, 150, 40);
frame.getContentPane().add(btnDemo);

lblDemo = new JLabel("Clicking the button will copy the textbox text here");
lblDemo.setBorder(new BevelBorder(BevelBorder.LOWERED, null, null, null, null));
lblDemo.setHorizontalTextPosition(SwingConstants.CENTER);
lblDemo.setHorizontalAlignment(SwingConstants.CENTER);
lblDemo.setBounds(12, 170, 270, 30);
frame.getContentPane().add(lblDemo);

txtDemo = new JTextField();


txtDemo.setFont(new Font("Segoe UI", Font.PLAIN, 11));
txtDemo.setBounds(12, 100, 270, 22);
frame.getContentPane().add(txtDemo);
txtDemo.setColumns(10);
}

If you study this code it follows the pattern:

- Declare the GUI element as an object variable: private JButton btnDemo;


- Create the object: btnDemo = new JButton("Click Me!");
- Set the properties of this variable: btnDemo.setBounds(70, 35, 150, 40);
- Create the event handler:
btnDemo.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent arg0){}});
- Add the GUI element to the layout: frame.getContentPane().add(btnDemo);

The main difference here is the events for the button Click in C# are declared in the designer, but
coded in the MainForm source file, to encourage programmers NOT to alter the design code.

In Java it is all thrown together and left to the programmer to create new classes or functions to
handle the event code. The line lblDemo.setText(txtDemo.getText()); is written directly into the event

Page 9 of 16
This work is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License
http://creativecommons.org/licenses/by-nc-sa/4.0/
handler. You can of course write a new function to do this, and call the function in the event handler
instead. The equivalent line lblDemo.Text = txtDemo.Text; in C# is found in the separate source file
Program.cs:

using System;
using System.Windows.Forms;

namespace Cross_Platform_Demo
{
public partial class MainForm : Form
{
public MainForm()
{
InitializeComponent();
this.CenterToScreen();
}
void BtnDemoClick(object sender, EventArgs e)
{
lblDemo.Text = txtDemo.Text;
}
}
}

Repeat using Page and Tkinter

Python/Tkinter
Start Page and click the Toplevel icon in the Widget Toolbar:

Find the Attribute Editor and change the following:

Widget: Alias MainForm


Attributes: title Cross Platform Demo
Geometry:width 300
Geometry:height 270
Geometry:resize width No
Geometry:resize height No

Go back to the Widget Toolbar and Click on the Themed widgets Tbutton icon

Click inside your new window, now resized and re-titled. The button will be drawn.

From the Attribute Editor change:

Page 10 of 16
This work is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License
http://creativecommons.org/licenses/by-nc-sa/4.0/
Alias btnDemo
command btnDemoClick
text Click Me!
Geometry: x position 70
Geometry: y position 35
Geometry:width 150
Geometry:height 40

You should now have:

Go back to the Widget Toolbar and Click on the Themed widgets TEntry icon (The Tkinter designers
obviously did not think of using TextBox as a name used since the invention of Visual basic in the
1980s)

Click in your design window to draw the textbox (Entry).

From the Attribute Editor change:

Alias txtDemo
font Segoe UI 10
Geometry: x position 12
Geometry: y position 100
Geometry:width 270
Geometry:height 30

Page 11 of 16
This work is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License
http://creativecommons.org/licenses/by-nc-sa/4.0/
Go back to the Widget Toolbar and Click on the Themed widgets TLabel icon

Click in your design window to draw the Label

From the Attribute Editor change:

Alias lblDemo
Anchor center
relief sunken
text Clicking the button will copy the
textbox text here
Geometry: x position 12
Geometry: y position 170
Geometry:width 270
Geometry:height 30

On the main PAGE menu  Gen_Python  Generate Python Gui

This will also open a file selector dialog prompting you to save the project.
Page 12 of 16
This work is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License
http://creativecommons.org/licenses/by-nc-sa/4.0/
Choose an appropriate folder and filename to save the design in .tcl format. (I used demo as the
filename)

The Python file will open with the same name as the .tcl file you just saved. (demo.py)

Go back to the menu and use Gen_Python  Generate Support Module.

Click the Save button on both these Python code windows.

Going through the code in sections:

#! /usr/bin/env python
#
# GUI module generated by PAGE version 4.8.6
# In conjunction with Tcl version 8.6
# Jan 14, 2017 07:33:42 PM
import sys
The above code is not required and can be deleted
try:
from Tkinter import *
except ImportError:
from tkinter import *

try:
import ttk
py3 = 0
except ImportError:
import tkinter.ttk as ttk
py3 = 1

The above code checks which version of Python you are using. Assuming you are on Python 3 this
can be changed to:

from tkinter import *


import tkinter.ttk as ttk

The next statement imports the second Python file created by Page:

import demo_support

The following three functions are used when starting from the support module:

def vp_start_gui():
'''Starting point when module is the main routine.'''
global val, w, root
root = Tk()
top = Cross_Platfom_Demo (root)
demo_support.init(root, top)
root.mainloop()

w = None
def create_Cross_Platfom_Demo(root, *args, **kwargs):
'''Starting point when module is imported by another program.'''
global w, w_win, rt
rt = root
w = Toplevel (root)
top = Cross_Platfom_Demo (w)
demo_support.init(w, top, *args, **kwargs)
return (w, top)

def destroy_Cross_Platfom_Demo():
global w
w.destroy()
w = None

Page 13 of 16
This work is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License
http://creativecommons.org/licenses/by-nc-sa/4.0/
The above lines can be deleted, as the Demo project will always start from the main module.
The class below is the impotrant bit:

class Cross_Platfom_Demo:
def __init__(self, top=None):
'''This class configures and populates the toplevel window.
top is the toplevel containing window.'''
self._bgcolor = '#d9d9d9' # X11 color: 'gray85'
self._fgcolor = '#000000' # X11 color: 'black'
self._compcolor = '#d9d9d9' # X11 color: 'gray85'
self._ana1color = '#d9d9d9' # X11 color: 'gray85'
self._ana2color = '#d9d9d9' # X11 color: 'gray85'
self.font3 = "-family {Segoe UI} -size 10 -weight normal -slant roman -underline 0 -overstrike
0"
self.style = ttk.Style()
if sys.platform == "win32":
self.style.theme_use('winnative')
self.style.configure('.',background=self._bgcolor)
self.style.configure('.',foreground=self._fgcolor)
self.style.configure('.',font="TkDefaultFont")
self.style.map('.',background=[('selected', self._compcolor), ('active',self._ana2color)])

top.geometry("300x270+0+0")
top.title("Cross Platfom Demo")
top.configure(background="#d9d9d9")
top.configure(highlightbackground="#d9d9d9")
top.configure(highlightcolor="black")

self.btnDemo = ttk.Button(top)
self.btnDemo.place(relx=0.23, rely=0.12, height=40, width=150)
self.btnDemo.configure(command=demo_support.btnDemoClick)
self.btnDemo.configure(takefocus="")
self.btnDemo.configure(text='''Click Me!''')

self.txtDemo = ttk.Entry(top)
self.txtDemo.place(relx=0.03, rely=0.33, relheight=0.1, relwidth=0.9)
self.txtDemo.configure(font=self.font3)
self.txtDemo.configure(takefocus="")
self.txtDemo.configure(cursor="ibeam")

self.lblDemo = ttk.Label(top)
self.lblDemo.place(relx=0.03, rely=0.57, height=30, width=270)
self.lblDemo.configure(background="#d9d9d9")
self.lblDemo.configure(foreground="#000000")
self.lblDemo.configure(relief=SUNKEN)
self.lblDemo.configure(anchor=CENTER)
self.lblDemo.configure(text='''Clicking the button will copy the textbox text here''')

The code below is used to check if the script is launched from this module, then runs the function
above: vp_start_gui()

if __name__ == '__main__':
vp_start_gui()

At first, using the two scripts seems to run along the same lines as the C# version, with the GUI
design in one, and the events in the other, but it does not work well when trying to fill the label with
the contents of the textbox, as you are constantly cross-referencing the two modules.

It makes much more sense to keep all the code used in the GUI design class, including events, similar
to the Java equivalent used earlier

Page 14 of 16
This work is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License
http://creativecommons.org/licenses/by-nc-sa/4.0/
To make this script work change the Demo.py script:

1) Move the text in the function vp_start_gui() to the bottom of the script and delete the
function definition and the call to it after if __name__ == '__main__':
2) Delete everything above the class declaration except the top two lines highlighted
3) Modify self.btnDemo.configure() as highlighted
4) Add the function def btnDemoClick() as highlighted below (It can be cut from the
Demo_support.py file and modified)

from tkinter import *


import tkinter.ttk as ttk

class Cross_Platfom_Demo:
def __init__(self, top=None):
'''This class configures and populates the toplevel window.
top is the toplevel containing window.'''
self._bgcolor = '#d9d9d9' # X11 color: 'gray85'
self._fgcolor = '#000000' # X11 color: 'black'
self._compcolor = '#d9d9d9' # X11 color: 'gray85'
self._ana1color = '#d9d9d9' # X11 color: 'gray85'
self._ana2color = '#d9d9d9' # X11 color: 'gray85'
self.font3 = "-family {Segoe UI} -size 10 -weight normal -slant roman -underline 0 -overstrike
0"
self.style = ttk.Style()
if sys.platform == "win32":
self.style.theme_use('winnative')
self.style.configure('.',background=self._bgcolor)
self.style.configure('.',foreground=self._fgcolor)
self.style.configure('.',font="TkDefaultFont")
self.style.map('.',background=[('selected', self._compcolor), ('active',self._ana2color)])

top.geometry("300x270+0+0")
top.title("Cross Platfom Demo")
top.configure(background="#d9d9d9")
top.configure(highlightbackground="#d9d9d9")
top.configure(highlightcolor="black")

self.btnDemo = ttk.Button(top)
self.btnDemo.place(relx=0.23, rely=0.12, height=40, width=150)
self.btnDemo.configure(command=self.btnDemoClick)
self.btnDemo.configure(takefocus="")
self.btnDemo.configure(text='''Click Me!''')

self.txtDemo = ttk.Entry(top)
self.txtDemo.place(relx=0.03, rely=0.33, relheight=0.1, relwidth=0.9)
self.txtDemo.configure(font=self.font3)
self.txtDemo.configure(takefocus="")
self.txtDemo.configure(cursor="ibeam")

self.lblDemo = ttk.Label(top)
self.lblDemo.place(relx=0.03, rely=0.57, height=30, width=270)
self.lblDemo.configure(background="#d9d9d9")
self.lblDemo.configure(foreground="#000000")
self.lblDemo.configure(relief=SUNKEN)
self.lblDemo.configure(anchor=CENTER)
self.lblDemo.configure(text='''Clicking the button will copy the textbox text here''')

def btnDemoClick(self):
self.lblDemo.configure(text=self.txtDemo.get())

if __name__ == '__main__':
global root
root = Tk()
top = Cross_Platfom_Demo(root)
root.mainloop()

Page 15 of 16
This work is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License
http://creativecommons.org/licenses/by-nc-sa/4.0/
The Demo_support.py file can now be deleted. You now have a skeleton design, ready for filling in
the class with additional methods to handle the widgets you place in the designer.

The output from the three versions side by side:

C# Java Tkinter

The use of the Tkinter .place() method is close to the positioning methods used in C# and Java
WindowBuilder.

Page 16 of 16
This work is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License
http://creativecommons.org/licenses/by-nc-sa/4.0/

You might also like