I am trying to create a GUI for the analyzeMFT python program. So far this is what i have
#!/usr/bin/python
# -*- coding: utf-8 -*-
from Tkinter import *
from ttk import *
import analyzeMFT
class Example(Frame):
def __init__(self, parent):
Frame.__init__(self, parent)
self.parent = parent
self.filename = ""
self.initUI()
def initUI(self):
self.parent.title("Mtf Analyzer")
#initializing and configuring menubar
menubar = Menu(self.parent)
self.parent.config(menu=menubar)
fileMenu = Menu(menubar)
fileMenu.add_command(label="Open file", command=self.fileOpen)
fileMenu.add_command(label="Exit", command=self.onExit)
menubar.add_cascade(label="File", menu=fileMenu)
#specify grid row and column spaces
self.pack(fill=BOTH, expand=True)
self.columnconfigure(1, weight=1)
self.columnconfigure(3, pad=7)
self.rowconfigure(3, weight=1)
self.rowconfigure(5, pad=7)
lbl = Label(self, text="File Name")
lbl.grid(row=1, column=0, sticky=W, pady=4, padx=5)
filename_area = Entry(self)
filename_area.grid(row=1, column=1, columnspan=3,
padx=5)
analize_button = Button(self, text="Analize", command=self.processFile)
analize_button.grid(row=1, column=4, padx=5)
area = Text(self)
area.grid(row=2, column=1, columnspan=2, rowspan=4,
padx=5, sticky=E+W+S+N)
#configure the raw output view
def onExit(self):
self.quit()
#this function selects and opens the file to analize
def fileOpen(self):
from tkFileDialog import askopenfilename
Tk().withdraw()
self.filename = askopenfilename()
#populate the filename field
#do the processing of the file obtained. Populate the file NAME entry or
#send the filename to the analyzeMFT.py
def processFile(self):
filei = "-f "+self.filename
arguments = [filei, '-o '+self.filename+' .csv']
analyzeMFT.main(arguments)
#get and set methods for the entry field
def get(self):
return self.filename_area.get()
def set(self, value):
filename_area.set(value)
def main():
root = Tk()
root.geometry("250x150+300+300")
app = Example(root)
root.mainloop()
if __name__ == '__main__':
main()
This code creates a gui that i can chose a file to analyze using the analyzeMFT program. There are two problems.
1.analyzeMFT is meant to be run on command line. I am not able to pass the file name I get from the GUI to the analyzeMFT.py Here are the contents of analyzeMFT.py
#!/usr/bin/python
try:
from analyzemft import mftsession
except:
from .analyzemft import mftsession
def main():
session = mftsession.MftSession()
session.mft_options()
session.open_files()
session.process_mft_file()
if __name__ == "__main__":
main()
Next, when i run the analyzerMFT in cmd and enable debug mode, it prints every detail on the screen. How can I direct that out to a window i have shown below
I want it somehow to look like this
Sorry if the explanation is very long. I've been working on it for days.
Instead of using print like you would normally use to display results to the console you can use insert() on your text box.
EDIT:
First change:
area = Text(self)
area.grid(row=2, column=1, columnspan=2, rowspan=4,
padx=5, sticky=E+W+S+N)
To:
self.area = Text(self)
self.area.grid(row=2, column=1, columnspan=2, rowspan=4,
padx=5, sticky=E+W+S+N)
This way your text box is set up as a class attribute that we can use.
Then use:
self.area.delete(1.0, "end") # to clear text box
self.area.insert("end", your_output variable or string) # to insert content
Related
I'm a little stuck here. I've read lots of stack overflows threads but not getting any further on that topic.
My goal is to have a tinter GUI that at some point launches a function in a new Process and redirects every print in that function to the Guis Text widget. There a Pipes and Queues but im not to familiar with how to use them correctly. I have found a working solution here but that only applies to Python 3. Unfortunately I have to use Python 2.7...
Can anybody help?
my sample code:
from Tkinter import *
import multiprocessing as mp
import time
import sys
class Gui(object):
def __init__(self):
self.a=Tk()
b1=Button(self.a, text="Process 1", command=self.func)
b1.grid(row=0, column=0, pady=10, padx=10, sticky=SE)
self.messages=Text(
self.a, height=2.5, width=30, bg="light cyan", state=NORMAL)
self.messages.grid(row=1, column=0, columnspan=3)
sys.stdout = self.StdoutRedirector(self.messages)
sys.stderr = self.StdoutRedirector(self.messages)
self.a.mainloop()
class StdoutRedirector(object):
def __init__(self, text_widget):
self.output = text_widget
def write(self, string):
self.output.config(state=NORMAL)
self.output.update_idletasks()
self.output.insert('end', string)
self.output.see('end')
self.output.config(state=DISABLED)
def flush(self):
pass
def func(self):
print("test")
proc=mp.Process(target=go)
proc.start()
def go():
for i in range(0,10):
time.sleep((1))
print(i)
if __name__ == "__main__":
Gui()
The issue in your code is that you are changing the content of a tkinter widget outside of the mainloop, which is not possible. One solution I have found is to redirect the stdout and stderr to Queues and periodically read the content of these queues in the mainloop to update the text widget. The periodical reading is done using the method .after(<delay in ms>, <callback>).
from Tkinter import *
import multiprocessing as mp
import time
import sys
class StdoutRedirector(object):
def __init__(self, queue):
self.output = queue
def write(self, string):
self.output.put(string)
def flush(self):
pass
class Gui(object):
def __init__(self):
self.a=Tk()
b1 = Button(self.a, text="Process 1", command=self.func)
b1.grid(row=0, column=0, pady=10, padx=10, sticky=SE)
self.messages=Text(self.a, height=2.5, width=30, bg="light cyan",
state=NORMAL)
self.messages.grid(row=1, column=0, columnspan=3)
self.messages.tag_configure('error', foreground='red')
# queues to get the stdout and stderr
self.stdout = mp.Queue()
self.stderr = mp.Queue()
self.proc = None # process
self.a.mainloop()
def stdout_update(self):
self.messages.configure(state=NORMAL)
while not self.stdout.empty(): # display stdout
self.messages.insert('end', self.stdout.get(False))
while not self.stderr.empty(): # display stderr
self.messages.insert('end', self.stderr.get(False), 'error')
self.messages.configure(state=DISABLED)
if self.proc.is_alive(): # the process is not finished, schedule the next display update
self.a.after(100, self.stdout_update)
def func(self):
self.proc = mp.Process(target=go, args=(self.stdout, self.stderr))
self.proc.start()
self.stdout_update() # launch display update
def go(queue_out, queue_err):
# redirect stdout and stderr to queues
sys.stdout = StdoutRedirector(queue_out)
sys.stderr = StdoutRedirector(queue_err)
for i in range(0, 2):
time.sleep(1)
print(i)
1/0 # to test the stderr
if __name__ == "__main__":
Gui()
I am trying to develop a GUI using Tkinter and Python2.7 by following a few tutorials and their documentation. Right now i am stuck on how to keep my frame a constant size (Because i want to have a nice background).
I have tried to use geometry() function from their documentation but always returns with an error - geometry attribute not found
import Tkinter as tk
import ttk
class Application(tk.Tk):
def __init__(self, *args, **kwargs):
# Initialize Tkinter
tk.Tk.__init__(self, *args, **kwargs)
tk.Tk.wm_title(self, "ScraperBot")
tk.Tk.geometry('250x250')
# Make a container
container = tk.Frame(self)
container.pack(side="top", fill="both", expand=True)
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
# Define Frames
self.frames = {}
for myFrame in (HomePage, PageOne, PageTwo):
frame = myFrame(container, self)
self.frames[myFrame] = frame
frame.grid(row=0, column=0, sticky="nsew")
# Which frame do we wanna display?
self.show_frame(HomePage)
def show_frame(self, cont):
frame = self.frames[cont]
frame.tkraise()
class HomePage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
# Make a Label
label = ttk.Label(self, text="HomePage", font=LARGE_FONT)
label.pack(pady=10, padx=10) # Padding on top and bottom
# Add a Button to navigate from one page to another
button1 = tk.Button(self, text="Go to Page_1", fg="red",
command= lambda: controller.show_frame(PageOne))
button1.pack()
# Add a Button to navigate from one page to another
button6 = tk.Button(self, text="Go to Page_2", fg="green",
command= lambda: controller.show_frame(PageTwo))
button6.pack()
def button_Function(parameter):
print(parameter)
app = Application()
app.mainloop()
I have a class called app.py within that class there's a method called print_raw_records_screen here's the part of the class and the method
app.py
#!/usr/bin/python
# -*- coding: utf-8 -*-
from Tkinter import *
from ttk import *
import os
import mftsession
class Example(Frame):
def __init__(self, parent):
Frame.__init__(self, parent)
self.parent = parent
self.filename = ""
self.initUI()
#defining a function that that receives the raw records from the mftsession.py and print it to the screen
#this script will be called by the mftsession.py in
#process_mftfile()
def print_raw_records_screen(self,records):
self.area.delete(1.0, "end")
self.area.insert('1.0',records)
def initUI(self):
self.parent.title("Mtf Analyzer")
#initializing and configuring menubar
menubar = Menu(self.parent)
self.parent.config(menu=menubar)
fileMenu = Menu(menubar)
fileMenu.add_command(label="Open file", command=self.fileOpen)
fileMenu.add_command(label="Exit", command=self.onExit)
menubar.add_cascade(label="File", menu=fileMenu)
#specify grid row and column spaces
self.pack(fill=BOTH, expand=True)
self.columnconfigure(1, weight=1)
self.columnconfigure(3, pad=7)
self.rowconfigure(3, weight=1)
self.rowconfigure(5, pad=7)
lbl = Label(self, text="File Name")
lbl.grid(row=1, column=0, sticky=W, pady=4, padx=5)
self.filename_area = Entry(self)
self.filename_area.grid(row=1, column=1, columnspan=5, padx=5, sticky=E+W+S+N)
analize_button = Button(self, text="Analize", command=self.processFile)
analize_button.grid(row=1, column=6, padx=5)
self.area = Text(self)
self.area.grid(row=2, column=1, columnspan=2, rowspan=4,
padx=5, sticky=E+W+S+N)
#configure the raw output view
def onExit(self):
self.quit()
#this function selects and opens the file to analize
def fileOpen(self):
from tkFileDialog import askopenfilename
Tk().withdraw()
self.filename = askopenfilename()
#populate the filename field
self.set( self.filename)
#do the processing of the file obtained. Populate the file NAME entry or
#send the filename to the analyzeMFT.py
def processFile(self):
arguments = "analyzeMFT.py -f "+self.filename+" -d --bodyfull -l -o "+self.filename+".csv"
os.system(arguments)
mftsession.MftSession.process_mft_file(self)
#get and set methods for the entry field
def get(self):
return self.filename_area.get()
def set(self, value):
self.filename_area.delete(0, END)
self.filename_area.insert(0,value)
def main():
root = Tk()
root.geometry("450x350+500+500")
app = Example(root)
root.mainloop()
if __name__ == '__main__':
main()
This method is called by another external script like this
mftsession.py
import csv
import json
import os
import sys
from optparse import OptionParser
import mft
import app
from Tkinter import *
from ttk import *
class MftSession:
"""Class to describe an entire MFT processing session"""
#staticmethod
def fmt_excel(date_str):
return '="{}"'.format(date_str)
#staticmethod
def fmt_norm(date_str):
return date_str
def __init__(self):
self.mft = {}
self.fullmft = {}
self.folders = {}
self.debug = False
self.mftsize = 0
def process_mft_file(self):
root = Tk()
appi = app.Example(root)
self.sizecheck()
self.build_filepaths()
# reset the file reading
self.num_records = 0
self.file_mft.seek(0)
raw_record = self.file_mft.read(1024)
if self.options.output is not None:
self.file_csv.writerow(mft.mft_to_csv(None, True, self.options))
while raw_record != "":
record = mft.parse_record(raw_record, self.options)
if self.options.debug:
print record
appi.print_raw_records_screen(raw_record ) #THIS FUNCTION WHILE INVOKED THIS WAY IS NOT WORKING
..........
The script analyzeMFT.py called by the app.py through the subrouting
#!/usr/bin/python
import sys
from os import path
def main():
session = mftsession.MftSession()
session.mft_options()
session.open_files()
session.process_mft_file()
if __name__ == '__main__':
if __package__ is None:
sys.path.append( path.dirname( path.dirname( path.abspath(__file__) ) ) )
import mftsession
main()
else:
import mftsession
main()
When called this way it prints nothing to the window, but when i invoke it for testing within its own class it prints the test
def print_raw_records_screen(self,records):
self.area.delete(1.0, "end")
self.area.insert('1.0', records)
and invoke like print_raw_records_screen("any test"): in the app.py
An image can tell alot. and summarize
What am i doing wrong? I sense it's the instantiation am doing wrong. I need diretions please
From what I can see here you have a few issues causing problems.
you are importing app.py on mftsession.py instead importing mftsession.py on app.py.
You are trying to use appi.print_raw_records_screen(raw_record) on a completely different instance of the Example() class with appi = app.Example(root) remove that part all together.
It is bad practice to importing inside a function. Import at the start of each py file.
There is so many things going on in your code I had to create a Minimal, Complete, and Verifiable example example of my own to illustrate the relation between files.
Here is a simple example of how the 2 files can interact and the way I think you are trying to do things.
Here I have created a main file called app.py:
from Tkinter import *
# You might need to import the py file with the package name as well.
# Change the package name to the package your python files are located in.
import PACKAGE_NAME.file_b
class Example(Frame):
def __init__(self, parent):
Frame.__init__(self, parent)
self.parent = parent
self.filename = ""
analize_button = Button(self.parent, text="Analize", command=self.processFile)
analize_button.pack()
self.area = Text(self.parent, height = 2, width = 40)
self.area.pack()
def print_raw_records_screen(self,records):
self.area.delete(1.0, "end")
self.area.insert('1.0',records)
def processFile(self):
PACKAGE_NAME.file_b.process_mft_file(self)
if __name__ == "__main__":
root = Tk()
app = Example(root)
root.mainloop()
Here I have created a file called file_b.py
from Tkinter import *
def process_mft_file(self):
record = "Some values assigned to a variable"
self.print_raw_records_screen(record)
The results look something like this:
I've been having difficulty with my PySide program for a few days now. I don't think the problem is incredibly difficult because there are answer out there. Problem I have is none of them seem to work for me.
I want to 'listen' to the file objects stdout and stderr and output the contents to QText Edit widget while my PySide program is running. Now, I already realise this question (or something similar) has been asked before on here but like I said, can't get it to work for me for some reason and most other solutions out there are based on the one that I can't get working, so a very frustrating last few days for me. This solution (OutLog), is included in my code snippet below, just in case one of you guys can see a botched implementation on my part.
Things to remember:
1 I'm doing this on Windows 7(duuuh, da, da, duh)
2 I'm using eclipse and running it from inside the IDE(duh, da, da, duh, DUUUUH: It would be really handy if the suggestions worked with either commandline or an IDE)
3 I really just want to duplicate the output of stdout and stderr to the widget while the program runs. For this to happen line-by-line would be a dream but even if it all comes out as a chunk at the end of a loop or something, that would be fab.
4 Oh, and also regarding OutLog, could somebody tell me how, if self.out is set to 'None' in the init, this class can actually work? I mean, self.out is always a NoneType object, right???
Any help would be appreciated, even if it's just pointers to where I could find more information. I've been trying to build my own solution (I'm a bit of a sadist that way) but I've found it hard to find relevant info on how these objects work to do that.
Anyway, whine over. Here's my code:
#!/usr/bin/env python
import sys
import logging
import system_utilities
log = logging.getLogger()
log.setLevel("DEBUG")
log.addHandler(system_utilities.SystemLogger())
import matplotlib
matplotlib.use("Qt4Agg")
matplotlib.rcParams["backend.qt4"] = "PySide"
import subprocess
import plot_widget
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
from PySide import QtCore, QtGui
class MainWindow(QtGui.QMainWindow):
"""This is the main window class and displays the primary UI when launched.
Inherits from QMainWindow.
"""
def __init__(self):
"""Init function.
"""
super(MainWindow, self).__init__()
self.x = None
self.y = None
self.data_plot = None
self.plot_layout = None
self.terminal = None
self.setup_plot()
self.setup_interface()
def setup_plot(self):
"""Member function to setup the graph window in the main UI.
"""
#Create a PlotWidget object
self.data_plot = plot_widget.PlotWidget()
#Create a BoxLayout element to hold PlotWidget
self.plot_layout = QtGui.QVBoxLayout()
self.plot_layout.addWidget(self.data_plot)
def setup_interface(self):
"""Member function to instantiate and build the composite elements of the
UI."""
#Main widget houses layout elements (Layout cannot be placed directly in a QMainWindow).
central_widget = QtGui.QWidget()
test_splitter = QtGui.QSplitter(QtCore.Qt.Vertical)
button_splitter = QtGui.QSplitter(QtCore.Qt.Horizontal)
#UI BoxLayout elements
central_layout = QtGui.QVBoxLayout()
#button_layout = QtGui.QHBoxLayout()
#UI PushButton elements
exit_button = QtGui.QPushButton("Close")
run_button = QtGui.QPushButton("Run...")
#UI Text output
self.editor = QtGui.QTextEdit()
self.editor.setReadOnly(True)
self.terminal = QtGui.QTextBrowser()
self.terminal.setReadOnly(True)
#UI PushButton signals
run_button.clicked.connect(self.run_c_program)
run_button.clicked.connect(self.data_plot.redraw_plot)
exit_button.clicked.connect(QtCore.QCoreApplication.instance().quit)
#Build the UI from composite elements
central_layout.addLayout(self.plot_layout)
central_layout.addWidget(self.editor)
button_splitter.addWidget(run_button)
button_splitter.addWidget(exit_button)
test_splitter.addWidget(button_splitter)
test_splitter.addWidget(self.terminal)
test_splitter.setCollapsible(1, True)
central_layout.addWidget(test_splitter)
central_widget.setLayout(central_layout)
self.setCentralWidget(central_widget)
self.show()
class OutLog:
def __init__(self, edit, out=None, color=None):
"""(edit, out=None, color=None) -> can write stdout, stderr to a
QTextEdit.
edit = QTextEdit
out = alternate stream ( can be the original sys.stdout )
color = alternate color (i.e. color stderr a different color)
"""
self.edit = edit
self.out = None
self.color = color
def write(self, m):
if self.color:
tc = self.edit.textColor()
self.edit.setTextColor(self.color)
self.edit.moveCursor(QtGui.QTextCursor.End)
log.debug("this is m {}".format(m))
self.edit.insertPlainText( m )
if self.color:
self.edit.setTextColor(tc)
if self.out:
self.out.write(m)
def main():
app = QtGui.QApplication(sys.argv)
log.debug("Window starting.")
window = MainWindow()
sys.stdout = OutLog(window.terminal, sys.stdout)
sys.stderr = OutLog(window.terminal, sys.stderr, QtGui.QColor(255,0,0))
window.show()
sys.exit(app.exec_())
log.info("System shutdown.")
if __name__ == '__main__':
main()
"Help me Obi-Wan..."
Thanks in advance guys (and gals :-))
It seems that all you need to do is override sys.stderr and sys.stdout with a wrapper object that emits a signal whenever output is written.
Below is a demo script that should do more or less what you want. Note that the wrapper class does not restore sys.stdout/sys.stderr from sys.__stdout__/sys.__stderr__, because the latter objects may not be same as the ones that were orignally replaced.
PyQt5:
import sys
from PyQt5 import QtWidgets, QtGui, QtCore
class OutputWrapper(QtCore.QObject):
outputWritten = QtCore.pyqtSignal(object, object)
def __init__(self, parent, stdout=True):
super().__init__(parent)
if stdout:
self._stream = sys.stdout
sys.stdout = self
else:
self._stream = sys.stderr
sys.stderr = self
self._stdout = stdout
def write(self, text):
self._stream.write(text)
self.outputWritten.emit(text, self._stdout)
def __getattr__(self, name):
return getattr(self._stream, name)
def __del__(self):
try:
if self._stdout:
sys.stdout = self._stream
else:
sys.stderr = self._stream
except AttributeError:
pass
class Window(QtWidgets.QMainWindow):
def __init__(self):
super().__init__( )
widget = QtWidgets.QWidget(self)
layout = QtWidgets.QVBoxLayout(widget)
self.setCentralWidget(widget)
self.terminal = QtWidgets.QTextBrowser(self)
self._err_color = QtCore.Qt.red
self.button = QtWidgets.QPushButton('Test', self)
self.button.clicked.connect(self.handleButton)
layout.addWidget(self.terminal)
layout.addWidget(self.button)
stdout = OutputWrapper(self, True)
stdout.outputWritten.connect(self.handleOutput)
stderr = OutputWrapper(self, False)
stderr.outputWritten.connect(self.handleOutput)
def handleOutput(self, text, stdout):
color = self.terminal.textColor()
self.terminal.moveCursor(QtGui.QTextCursor.End)
self.terminal.setTextColor(color if stdout else self._err_color)
self.terminal.insertPlainText(text)
self.terminal.setTextColor(color)
def handleButton(self):
if QtCore.QTime.currentTime().second() % 2:
print('Printing to stdout...')
else:
print('Printing to stderr...', file=sys.stderr)
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
window = Window()
window.setGeometry(500, 300, 300, 200)
window.show()
sys.exit(app.exec_())
PyQt4:
import sys
from PyQt4 import QtGui, QtCore
class OutputWrapper(QtCore.QObject):
outputWritten = QtCore.pyqtSignal(object, object)
def __init__(self, parent, stdout=True):
QtCore.QObject.__init__(self, parent)
if stdout:
self._stream = sys.stdout
sys.stdout = self
else:
self._stream = sys.stderr
sys.stderr = self
self._stdout = stdout
def write(self, text):
self._stream.write(text)
self.outputWritten.emit(text, self._stdout)
def __getattr__(self, name):
return getattr(self._stream, name)
def __del__(self):
try:
if self._stdout:
sys.stdout = self._stream
else:
sys.stderr = self._stream
except AttributeError:
pass
class Window(QtGui.QMainWindow):
def __init__(self):
QtGui.QMainWindow.__init__(self)
widget = QtGui.QWidget(self)
layout = QtGui.QVBoxLayout(widget)
self.setCentralWidget(widget)
self.terminal = QtGui.QTextBrowser(self)
self._err_color = QtCore.Qt.red
self.button = QtGui.QPushButton('Test', self)
self.button.clicked.connect(self.handleButton)
layout.addWidget(self.terminal)
layout.addWidget(self.button)
stdout = OutputWrapper(self, True)
stdout.outputWritten.connect(self.handleOutput)
stderr = OutputWrapper(self, False)
stderr.outputWritten.connect(self.handleOutput)
def handleOutput(self, text, stdout):
color = self.terminal.textColor()
self.terminal.moveCursor(QtGui.QTextCursor.End)
self.terminal.setTextColor(color if stdout else self._err_color)
self.terminal.insertPlainText(text)
self.terminal.setTextColor(color)
def handleButton(self):
if QtCore.QTime.currentTime().second() % 2:
print('Printing to stdout...')
else:
sys.stderr.write('Printing to stderr...\n')
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
window = Window()
window.setGeometry(500, 300, 300, 200)
window.show()
sys.exit(app.exec_())
NB:
Instances of the OutputWrapper should be created as early as possible, so as to ensure that other modules that need sys.stdout/sys.stderr (such as the logging module) use the wrapped versions wherever necessary.
self.out = None is probably a typo and should be self.out = out. That way, you can see anything that printed in the console as well. This is the first step to be sure that the code prints anything at all.
The next thing is that you need to realize which output you're redirecting. A subprocess get its own stdio, so no amount of redirection of the parent's stdout is going to have any effect.
Getting stdio correct with a subprocess isn't trivial. I suggest to start with subprocess.communicate() which gives you all the output as a single string. That's usually good enough.
I was testing an app I was writing but I just get a blank window and no widgets.
from Tkinter import*
class App(Frame):
def _init_(self, master):
frame = Frame(master)
frane.pack()
self.instruction = Label(frame, text = 'Password:')
self.instruction.pack()
self.button = Button(frame, text = 'Enter', command = self.reveal)
self.button.pack()
root = Tk()
root.title('Password')
root.geometry('350x250')
App(root)
root.mainloop()
You have a couple of typos. The first is in the name of the constructor method:
def _init_(self, master):
Should read:
def __init__(self, master):
Note the double underscore - see the docs for Python objects.
The second is inside your constructor:
frane.pack()
You're also missing a declaration for a method named 'reveal' in your App class:
self.button = Button(frame, text="Enter", command=self.reveal)
The working example reads:
from Tkinter import *
class App(Frame):
def __init__(self, master):
Frame.__init__(self, master)
self.pack()
frame = Frame()
frame.pack()
self.instruction = Label(frame, text="Password:")
self.instruction.pack()
self.button = Button(frame, text="Enter", command=self.reveal)
self.button.pack()
def reveal(self):
# Do something.
pass
root = Tk()
root.title("Password")
root.geometry("350x250")
App(root)
root.mainloop()
See also: The Tkinter documentation.