I am trying to develop a GUI using Enthought. I'm a little confused as to how to use Traits and return a value from a function to be displayed on the GUI. Below I wrote a simple GUI displaying my problems.
from traits.api import HasTraits, Instance, String, Float, Enum, Button, Str, List, Bool, Int
from traitsui.api import Handler, View, Item, Group, HSplit, NoButtons, VGroup, VGrid, HGroup, EnumEditor, Action, ProgressEditor, ButtonEditor
from multiprocessing.pool import ThreadPool
import simple_GUI_function
class Input_Panel(HasTraits):
Name = Str(name='Name', label="Name")
Iterations = Str(name='Iterations',label="Iterations")
#~ #Create the User Information panel
User_Information_Panel = View(VGroup(
VGrid(
Item('Name'),
Item('Iterations'),
),
show_border=True, label="Information"
)
)
class Output_Panel(HasTraits):
counter_out = Int(0)
User_Output_Panel = View(VGrid(
Item('counter_out', width=-10, style='readonly',resizable=False
),
)
)
class Program_Execute_Thread(Handler):
wants_abort = False
pool = ThreadPool(processes=1)
def process(self, iterations):
try:
if self.processing_job.isAlive():
return
except AttributeError:
pass
self.processing_job = Thread(target = process, args = (iterations))
self.processing_job.start()
def run(self):
while not self.wants_abort:
print("here")
async_result = pool.apply_async(simple_GUI_function.Gui_func.Func_test(simple_GUI_function.Gui_func(), iterations))
return_val = async_result.get()
class Start_Panel(HasTraits):
Program_Execute = Instance(Program_Execute_Thread)
Start_Button = Button(name = 'Start', action='_Start_Button', tooltip = 'Start')
Quit_Button = Button(name = 'Exit Program', action='_Quit_Button', tooltip = 'Quit')
Button_Panel = View(HGroup(
Item('Start_Button', show_label = False),
Item('Quit_Button', show_label = False),
)
)
def _Start_Button_fired(self):
if self.Program_Execute and self.Program_Execute.isAlive():
self.Program_Execute.wants_abort = True
else:
self.Program_Execute = Program_Execute_Thread()
self.Program_Execute.start()
print("Start Button pushed")
def _Quit_Button_fired(self):
print("Quit Button pushed")
class MainWindowHandler(Handler):
def close(self, info, is_OK):
#~ if (info.object.button_panel.Program_Execute_Thread and \
#~ info.object.button_panel.Program_Execute_Thread.isAlive()):
#~ info.object.button_panel.Program_Execute_Thread.wants_abort = True
#~ while info.object.Program_Execute_Thread.isAlive():
#~ sleep(0.1)
#~ GUI.process_events()
return True
class MainWindow(HasTraits):
user_input = Instance(Input_Panel, ())
user_output = Instance(Output_Panel, ())
button_panel = Instance(Start_Panel, ())
view = View(VGroup(Item('user_input', style = 'custom', show_label = False),
Item('user_output', style = 'custom', show_label = False),
Item('button_panel', style = 'custom', show_label = False),
show_labels = False),
resizable = True, handler = MainWindowHandler(),
)
if __name__ == '__main__':
MainWindow().configure_traits()
Here is the simple function the code is trying to call:
class Gui_func:
def Func_test(self, iteration):
import time
print("Inside the function")
time.sleep(2)
print("Now leaving the function")
p = iteration + 1
return p
The code will register the Start button begin pushed but keeps generating the error "Program_Execute_Thread" object has no attribute 'start'. I looked at the Camera.py threading example built using Traits and built my code around that example. It seems like Python is not recognizing my Program_Execute_Thread as a "Thread." Anybody have any ideas?
Cheers,
Shivels
Solved my own problem. Looks like I forgot to declare Program_Execute_Thread as "Program_Execute_Thread(Thread)" and also I had to start the thread.
Related
I am using PyViz / Panel in a notebook. Now I want to use a toggle button inside a class, and bind a callback to it.
This code - outside a class - is working:
import panel as pn
import panel.widgets as pnw
pn.extension()
toggle = pn.widgets.Toggle(name='Toggle')
def callback(*events):
if toggle.active is True: toggle.name = 'Active'
else: toggle.name = 'Toggle'
watcher = toggle.param.watch(callback, 'active')
toggle.param.set_param(active=False)
toggle.param.trigger('active')
pn.Row(toggle)
It produces a toggle button, and when clicked, the text changes.
No I tried to put everything inside a class definition:
class ToggleInClass():
def __init__(self):
self.toggle = pn.widgets.Toggle(name='Toggle')
self.watcher = self.toggle.param.watch(callback, 'active')
self.toggle.param.set_param(active=False)
self.toggle.param.trigger('active')
def callback(self, *events):
if toggle.active is True: toggle.name = 'Active'
else: toggle.name = 'Toggle'
toggle_in_class = ToggleInClass()
pn.Row(toggle_in_class.toggle)
Again a button is produced, but this time the callback does not seem to work: The text never changes.
The watcher seems to be ok:
toggle_in_class.watcher gives Watcher(inst=Toggle(), cls=<class 'panel.widgets.Toggle'>, fn=<function callback at 0x000001EC8419E510>, mode='args', onlychanged=True, parameter_names=('active',)).
The output of toggle_in_class.toggle.active alternates according the toggle state.
What is wrong with my callback / class definition?
After correcting some stupid typos this code is working for me:
class ToggleInClass():
def __init__(self):
self.toggle = pn.widgets.Toggle(name='Toggle')
self.watcher = self.toggle.param.watch(self.callback, 'active')
self.toggle.param.set_param(active=False)
def callback(self, *events):
if self.toggle.active is True: self.toggle.name = 'Active'
else: self.toggle.name = 'Toggle'
toggle_in_class = ToggleInClass()
pn.Row(toggle_in_class.toggle)
I tried to get an event if the windows visibility is changed.
I found out that there is an event called "Visibility".
The Operating System is Windows 64bit.
So I implemented in the following way:
root.bind('<Visibility>', visibilityChanged)
But I always got the state "VisibilityUnobscured" no matter if there is a window over it or not. What is the normal behaviour of this event? How can I implement a feature like that?
Example Prog:
import tkinter as tk
class GUI:
def __init__(self, master):
self.master = master
master.title("Test GUI")
self.master.bind('<Visibility>', self.visibilityChanged)
self.label = tk.Label(master, text="GUI")
self.label.pack()
self.close_button = tk.Button(master, text="Close", command=master.quit)
self.close_button.pack()
def visibilityChanged(self, event):
if (str(event.type) == "Visibility"):
print(event.state)
root = tk.Tk()
my_gui = GUI(root)
root.mainloop()
What is the normal behaviour of this event?
It's well described in the docs:The X server generates VisibilityNotify event whenever the visibility changes state and for any window.
How can I implement a feature like that?
It depends on how far you are going to go in your wishes, since this isn't a trivial task. Thus, don't treat that answer as a complete solution, but as a problem overview and a set of suggestions.
The event problem
Windows OS uses a message-passing model - the system communicates with your application window via messages, where each message is a numeric code that designates a particular event. Application window has an associated window procedure — a function that processes (responds or ignores) all messages sent.
The most generic solution is to set a hook to catch certain events/messages and it's possible either via SetWindowsHookEx or pyHook.
The main problem is to get event, because the Windows WM has no such message as VisibilityNotify. As I said in comment section - one option, on which we can rely, is the z-order
(there's possibility to check Visibility of the window, whenever this window changes it's position in z-order).Therefore our target message is either WM_WINDOWPOSCHANGING or WM_WINDOWPOSCHANGED.
A naive implementation:
import ctypes
import ctypes.wintypes as wintypes
import tkinter as tk
class CWPRETSTRUCT(ctypes.Structure):
''' a class to represent CWPRETSTRUCT structure
https://msdn.microsoft.com/en-us/library/windows/desktop/ms644963(v=vs.85).aspx '''
_fields_ = [('lResult', wintypes.LPARAM),
('lParam', wintypes.LPARAM),
('wParam', wintypes.WPARAM),
('message', wintypes.UINT),
('hwnd', wintypes.HWND)]
class WINDOWPOS(ctypes.Structure):
''' a class to represent WINDOWPOS structure
https://msdn.microsoft.com/en-gb/library/windows/desktop/ms632612(v=vs.85).aspx '''
_fields_ = [('hwnd', wintypes.HWND),
('hwndInsertAfter', wintypes.HWND),
('x', wintypes.INT),
('y', wintypes.INT),
('cx', wintypes.INT),
('cy', wintypes.INT),
('flags', wintypes.UINT)]
class App(tk.Tk):
''' generic tk app with win api interaction '''
wm_windowposschanged = 71
wh_callwndprocret = 12
swp_noownerzorder = 512
set_hook = ctypes.windll.user32.SetWindowsHookExW
call_next_hook = ctypes.windll.user32.CallNextHookEx
un_hook = ctypes.windll.user32.UnhookWindowsHookEx
get_thread = ctypes.windll.kernel32.GetCurrentThreadId
get_error = ctypes.windll.kernel32.GetLastError
get_parent = ctypes.windll.user32.GetParent
wnd_ret_proc = ctypes.WINFUNCTYPE(ctypes.c_long, wintypes.INT, wintypes.WPARAM, wintypes.LPARAM)
def __init__(self):
''' generic __init__ '''
super().__init__()
self.minsize(350, 200)
self.hook = self.setup_hook()
self.protocol('WM_DELETE_WINDOW', self.on_closing)
def setup_hook(self):
''' setting up the hook '''
thread = self.get_thread()
hook = self.set_hook(self.wh_callwndprocret, self.call_wnd_ret_proc, wintypes.HINSTANCE(0), thread)
if not hook:
raise ctypes.WinError(self.get_error())
return hook
def on_closing(self):
''' releasing the hook '''
if self.hook:
self.un_hook(self.hook)
self.destroy()
#staticmethod
#wnd_ret_proc
def call_wnd_ret_proc(nCode, wParam, lParam):
''' an implementation of the CallWndRetProc callback
https://msdn.microsoft.com/en-us/library/windows/desktop/ms644976(v=vs.85).aspx'''
# get a message
msg = ctypes.cast(lParam, ctypes.POINTER(CWPRETSTRUCT)).contents
if msg.message == App.wm_windowposschanged and msg.hwnd == App.get_parent(app.winfo_id()):
# if message, which belongs to owner hwnd, is signaling that windows position is changed - check z-order
wnd_pos = ctypes.cast(msg.lParam, ctypes.POINTER(WINDOWPOS)).contents
print('z-order changed: %r' % ((wnd_pos.flags & App.swp_noownerzorder) != App.swp_noownerzorder))
return App.call_next_hook(None, nCode, wParam, lParam)
app = App()
app.mainloop()
As you can see, this implementation has a similar behavior as a "broken" Visibility event.
This problem stems from the fact, that you can catch only thread-specified messages, hence application doesn't know about changes in the stack. It's just my assumptions, but I think that the cause of the broken Visibility is same.
Of course, we can setup a global hook for all messages, regardless a thread, but this approach requires a DLL injection, which is an another story for sure.
The visibility problem
It's not a problem to determine obscuration of the window, since we can rely on Graphical Device Interface.
The logic is simple:
Represent window (and each visible window, which is higher in the z-order) as a rectangle.
Subtract from main rectangle each rectangle and store result.
If final geometrical subtraction is:
... an empty rectangle — return 'VisibilityFullyObscured'
... a set of rectangles — return 'VisibilityPartiallyObscured'
... a single rectangle:
if geometrical difference between result and original rectangle is:
... an empty rectangle — return 'VisibilityUnobscured'
... a single rectangle — return 'VisibilityPartiallyObscured'
A naive implementation (with self-scheduled loop):
import ctypes
import ctypes.wintypes as wintypes
import tkinter as tk
class App(tk.Tk):
''' generic tk app with win api interaction '''
enum_windows = ctypes.windll.user32.EnumWindows
is_window_visible = ctypes.windll.user32.IsWindowVisible
get_window_rect = ctypes.windll.user32.GetWindowRect
create_rect_rgn = ctypes.windll.gdi32.CreateRectRgn
combine_rgn = ctypes.windll.gdi32.CombineRgn
del_rgn = ctypes.windll.gdi32.DeleteObject
get_parent = ctypes.windll.user32.GetParent
enum_windows_proc = ctypes.WINFUNCTYPE(wintypes.BOOL, wintypes.HWND, wintypes.LPARAM)
def __init__(self):
''' generic __init__ '''
super().__init__()
self.minsize(350, 200)
self.status_label = tk.Label(self)
self.status_label.pack()
self.after(100, self.continuous_check)
self.state = ''
def continuous_check(self):
''' continuous (self-scheduled) check '''
state = self.determine_obscuration()
if self.state != state:
# mimic the event - fire only when state changes
print(state)
self.status_label.config(text=state)
self.state = state
self.after(100, self.continuous_check)
def enumerate_higher_windows(self, self_hwnd):
''' enumerate window, which has a higher position in z-order '''
#self.enum_windows_proc
def enum_func(hwnd, lParam):
''' clojure-callback for enumeration '''
rect = wintypes.RECT()
if hwnd == lParam:
# stop enumeration if hwnd is equal to lParam (self_hwnd)
return False
else:
# continue enumeration
if self.is_window_visible(hwnd):
self.get_window_rect(hwnd, ctypes.byref(rect))
rgn = self.create_rect_rgn(rect.left, rect.top, rect.right, rect.bottom)
# append region
rgns.append(rgn)
return True
rgns = []
self.enum_windows(enum_func, self_hwnd)
return rgns
def determine_obscuration(self):
''' determine obscuration via CombineRgn '''
hwnd = self.get_parent(self.winfo_id())
results = {1: 'VisibilityFullyObscured', 2: 'VisibilityUnobscured', 3: 'VisibilityPartiallyObscured'}
rgns = self.enumerate_higher_windows(hwnd)
result = 2
if len(rgns):
rect = wintypes.RECT()
self.get_window_rect(hwnd, ctypes.byref(rect))
# region of tk-window
reference_rgn = self.create_rect_rgn(rect.left, rect.top, rect.right, rect.bottom)
# temp region for storing diff and xor rgn-results
rgn = self.create_rect_rgn(0, 0, 0, 0)
# iterate over stored results
for _ in range(len(rgns)):
_rgn = rgn if _ != 0 else reference_rgn
result = self.combine_rgn(rgn, _rgn, rgns[_], 4)
self.del_rgn(rgns[_])
if result != 2:
# if result isn't a single rectangle
# (NULLREGION - 'VisibilityFullyObscured' or COMPLEXREGION - 'VisibilityPartiallyObscured')
pass
elif self.combine_rgn(rgn, reference_rgn, rgn, 3) == 1:
# if result of XOR is NULLREGION - 'VisibilityUnobscured'
result = 2
else:
# 'VisibilityPartiallyObscured'
result = 3
# clear up regions to prevent memory leaking
self.del_rgn(rgn)
self.del_rgn(reference_rgn)
return results[result]
app = App()
app.mainloop()
Unfortunately, this approach is far from a working solution, but it's tweakable in a perspective.
I've recently needed to do some GUI work with Python and stumbled on Tkinter. For the most part, I like it; it's clean and mostly intuitive and brief. There is, however, one little sticking point for me: sometimes it crashes out of nowhere. The program will run normally five times in a row and then the sixth time, halfway through it will freeze and I will get the error
Tcl_AsyncDelete: async handler deleted by the wrong thread
My efforts to find a solution to this problem, both on this website and others, all have to do with multithreading, but I don't use multiple threads. Not explicitly, anyway. I suspect the fact that I have a timer in the GUI is to blame, but I have not been able to figure out why the error pops up, or indeed why it is so infrequent.
What follows is the shortest tkinter program I've written. This happens in all of them, so I suspect the problem will be easiest to see here. Any and all help is appreciated, just don't point me toward another solution without telling me how it applies to my code, because I assure you, I have looked at it, and it either doesn't apply or I didn't understand how it did. I don't mind having missed the answer, but I'm new to tkinter (and to multithreading in general) so I might need it told more explicitly.
This code is for a simulation of a game show I saw online. Most of it can probably be safely ignored, but I'm pasting it all here because I don't know where the error is from.
import re
from tkinter import Tk, Frame, DISABLED, Button, Label, font, NORMAL, ttk
from random import choice
import winsound
class Question:
def __init__(self, t, a, o):
self.text = t.strip().capitalize() + "?"
self.answer = a.strip().title()
self.options = o
self.firstIsRight = self.answer.lower() == self.options[0].lower()
assert self.firstIsRight or self.answer.lower() == self.options[1].lower(), self
def __eq__(self, other):
return self.text == other.text
def __repr__(self):
return "{1} or {2}, {0}".format(self.text, self.options[0], self.options[1])
class Application(Frame):
def __init__(self, master=None):
self.setup()
Frame.__init__(self, master)
self.grid()
self.customFont = font.Font(family="Times New Roman", size=30)
self.createWidgets()
def setup(self):
self.questions = []
with open("twentyone.txt",'r') as file:
for line in file:
groups = re.split("[,\.\?]",line)
answers = re.split(" or ",groups[0])
self.questions.append(Question(groups[1], groups[2], answers))
def createWidgets(self):
self.gamePanel = Frame(self)
self.gamePanel.grid(column=0,row=0)
self.displayPanel = Frame(self)
self.displayPanel.grid(column=0,row=1)
self.buttonPanel = Frame(self)
self.buttonPanel.grid(column=0,row=2)
self.QUIT = Button(self.buttonPanel,text="QUIT",font=self.customFont,command=self.quit)
self.QUIT.grid(row=0,column=2)
self.BEGIN = Button(self.buttonPanel, text="BEGIN",font=self.customFont, command = self.begin)
self.BEGIN.grid(row=0,column=0)
self.STOP = Button(self.buttonPanel, text="STOP",font=self.customFont, command = self.stop)
self.STOP.grid(row=0,column=1)
self.STOP["state"] = DISABLED
self.TITLE = Label(self.gamePanel,text="21 Questions Wrong",font=self.customFont,bg="Black",fg="White")
self.TITLE.grid(columnspan=2)
self.questionText = Label(self.gamePanel,text="Questions go here",font=self.customFont,wraplength=400)
self.questionText.grid(row=1,columnspan=2)
self.leftChoice = Button(self.gamePanel,text="Option 1",font=self.customFont)
self.leftChoice.grid(row=2,column=0)
self.rightChoice = Button(self.gamePanel,text="Option 2",font=self.customFont)
self.rightChoice.grid(row=2,column=1)
self.timerText = Label(self.displayPanel, text="150",font=self.customFont)
self.timerText.grid(row=0)
self.progress = ttk.Progressbar(self.displayPanel, length=100,maximum=22)
self.progress.grid(row=0,column=1,padx=10)
def begin(self):
self.timer(250)
self.asked = []
self.STOP["state"] = NORMAL
self.leftChoice["state"] = NORMAL
self.rightChoice["state"] = NORMAL
self.restart = False
self.askNewQuestion()
def askNewQuestion(self):
if self.restart:
self.currentQuestion = self.asked[int(self.progress["value"])]
else:
self.currentQuestion = choice([i for i in self.questions if i not in self.asked])
self.asked.append(self.currentQuestion)
self.questionDisplay()
def questionDisplay(self):
self.questionText["text"] = self.currentQuestion.text
self.leftChoice["text"] = self.currentQuestion.options[0]
self.rightChoice["text"] = self.currentQuestion.options[1]
if self.currentQuestion.firstIsRight:
self.leftChoice["command"] = self.correct
self.rightChoice["command"] = self.wrong
else:
self.leftChoice["command"] = self.wrong
self.rightChoice["command"] = self.correct
def correct(self):
self.progress.step()
if self.progress["value"] >= 21:
self.gameOver(True, 21)
else:
if self.progress["value"] >= len(self.asked):
self.restart = False
self.askNewQuestion()
def wrong(self):
self.restart = True
self.progress["value"] = 0
winsound.Beep(750,700)
self.askNewQuestion()
def stop(self):
self.after_cancel(self.timerAfter)
self.BEGIN["state"] = NORMAL
self.STOP["state"] = DISABLED
def gameOver(self, success, longest):
self.after_cancel(self.timerAfter)
self.BEGIN["state"] = NORMAL
self.STOP["state"] = DISABLED
self.questionText["text"] = "Congratulations!" if success else "Too Bad!"
self.leftChoice["text"] = "Game"
self.leftChoice["state"] = DISABLED
self.rightChoice["text"] = "Over"
self.rightChoice["state"] = DISABLED
self.showPoints(success, longest)
def showPoints(self, s, l):
if s:
timeTaken = max(0, 100-int(self.timerText["text"]))
print("You scored {0} points".format(1000-10*timeTaken))
else:
print("You scored no points")
def timer(self, time):
self.BEGIN["state"] = DISABLED
self.STOP["state"] = NORMAL
self.runTimer(time)
def runTimer(self, current=None, resume=False):
if current is not None:
self.current = current
self.timerText["text"] = self.current
if self.current == 0:
self.gameOver(False, len(self.asked)-1)
else:
self.current -= 1
self.timerAfter = self.after(1000,self.runTimer)
root = Tk()
app = Application(master=root)
app.master.title("Score Calculator!")
app.anchor("center")
root.mainloop()
root.destroy()
I have a simple program that gets an text input from the user, and displays it on an instrument (Keithley). However, the attribute does not seem to change. That is, when I run the Start method, the output is "Wowee", even if I change Display in a pop-up window. Do I have to make it editable? I didn't think so. I am basically following Gael Varoquaux's intro to traits.
COntrolPanel.py:
from traits.api import *
from traitsui.api import *
import Keithley3706A_module
class ControlPanel(HasTraits):
keithley2430settings = Instance(Keithley2430.Keithley2430Settings, ())
keithley3706Asettings = Instance(Keithley3706A_module.Keithley3706ASettings, ())
start = Button("Start Measurements")
clear_3706A_display = Button("Clear K3706A Display")
k3706A_settings = Keithley3706A_module.Keithley3706ASettings()
k3706A = Keithley3706A_module.Keithley3706A()
view = View(Item('start', show_label=False,style='custom' ),
Item('clear_3706A_display', show_label=False,style='custom' ),
Item('keithley2430settings',style='custom'),
Item('keithley3706Asettings',style='simple'))
def _start_fired(self):
print "hello %s" % self.k3706A_settings.display
self.k3706A.message(self.k3706A_settings.display)
def _clear_3706A_display_fired(self):
self.k3706A.clear()
if __name__ == '__main__':
ControlPanel().configure_traits()
Keithley3706A.py:
from traits.api import *
from traitsui.api import *
import visa
import time
class Keithley3706ASettings(HasTraits):
display = String("Wowee")
class Keithley3706A(HasTraits):
def __init__(self):
self.Keithley3706AUSB = visa.instrument("USB0::0x05E6::0x3706::04019447::INSTR")
def message(self,foo):
s="display.settext('%s')" % foo
self.Keithley3706AUSB.write("display.clear()")
self.Keithley3706AUSB.write(s)
def clear(self):
self.Keithley3706AUSB.write("display.clear()")
In the ControlPanel class, you have created two different instances of Keithley3706ASettings, namely keithley3706Asettings and k3706A_settings. If I delete the latter, and replace the use of k3706A_settings with keithley3706Asettings, it works. Here's my version (with references to the 2430 device removed, the k3706A trait declared as an Instance of Keithley3706A, and a few other irrelevant UI changes):
class ControlPanel(HasTraits):
keithley3706Asettings = Instance(Keithley3706A_module.Keithley3706ASettings, ())
k3706A = Instance(Keithley3706A_module.Keithley3706A, ())
start = Button("Start Measurements")
clear_3706A_display = Button("Clear K3706A Display")
view = View(UItem('start'),
UItem('clear_3706A_display'),
Item('keithley3706Asettings', style='simple'))
def _start_fired(self):
print "hello %s" % self.keithley3706Asettings.display
self.k3706A.message(self.keithley3706Asettings.display)
def _clear_3706A_display_fired(self):
self.k3706A.clear()
I have a simple PyQt4 program that takes in 3 Particulars (Name,Gender & Address) whenever i clicked on OK button and save it as a binary file (3 particulars are hard coded in program for testing purpose). Then later will load that information back and display it in QTableWidget.
This is the layout of my program:
It has 2 scripts: DContainer.py and Data_Main.py
Dcontainer.py
import bisect
from PyQt4 import QtCore
class Person(object):
def __init__(self, Name = None, Gender = None , Address = None ):
self.Name = Name
self.Gender = Gender
self.Address = Address
class PersonContainer(object):
def __init__(self):
self.__fname = QtCore.QString("mydatabase.mqb")
self.__persons = []
self.__personFromId = {}
def __iter__(self):
for pair in iter(self.__persons):
yield pair[1]
def __len__(self):
return len(self.__persons)
def Clear(self):
self.__persons = []
self.__personFromId ={}
def add(self,person):
if id(person)in self.__personFromId:
return False
key = person.Name
bisect.insort_left(self.__persons, [key,person])
self.__personFromId[id(person)] = person
return True
def save(self):
fh = QtCore.QFile(self.__fname)
if not fh.open(QtCore.QIODevice.WriteOnly):
raise IOError , unicode(fh.errorString())
stream = QtCore.QDataStream(fh)
for key, person in self.__persons:
stream << person.Name << person.Gender << person.Address
def load(self):
fh = QtCore.QFile(self.__fname)
if not fh.open(QtCore.QIODevice.ReadOnly):
raise IOError , unicode(fh.errorString())
stream = QtCore.QDataStream(fh)
while not stream.atEnd():
Name = QtCore.QString()
Gender = QtCore.QString()
Address = QtCore.QString()
stream >> Name >> Gender >> Address
self.add(Person(Name,Gender,Address))
Data_Main.py
import sys
from PyQt4 import QtCore,QtGui
import DContainer
class MainDialog(QtGui.QDialog):
def __init__(self, parent = None):
super(MainDialog,self).__init__(parent)
self.InitGui()
self.persons = DContainer.PersonContainer()
self.Update()
def InitGui(self):
buttonbox = QtGui.QDialogButtonBox(QtGui.QDialogButtonBox.Ok|QtGui.QDialogButtonBox.Cancel)
self.table = QtGui.QTableWidget()
layout = QtGui.QVBoxLayout()
layout.addWidget(self.table)
layout.addWidget(buttonbox)
self.setLayout(layout)
self.connect(buttonbox.button(buttonbox.Ok), QtCore.SIGNAL("clicked()"),self.OK)
def OK(self):
NewPerson = DContainer.Person(QtCore.QString('This is another test'),QtCore.QString('Male'),QtCore.QString('Strand Road'))
self.persons.add(NewPerson)
self.persons.save()
self.Update()
def Update(self):
self.table.clear()
self.persons.load()
self.table.setRowCount(len(self.persons))
self.table.setColumnCount(3)
for row,person in enumerate(self.persons):
item = QtGui.QTableWidgetItem(person.Name)
self.table.setItem(row,0,item)
def Main():
app = QtGui.QApplication(sys.argv)
dialog = MainDialog()
dialog.show()
app.exec_()
if __name__ == "__main__":
Main()
My Problem is whenever i clicked on OK button, it create multiple table entries
After second click
It should not create multiple table entries as i have used
if id(person)in self.__personFromId:
return False
in my Add method in Dcontainer.py.
Rightfully, it should only show one item in the table unless i give the new person object with different name.
What is causing the problem?
The PersonContainer.add method is called twice when you click the OK button:
Directly from the MainDialog.OK method
Indirectly from the MainDialog.Update method, with self.persons.load()
You can add an optional argument to the Update method to trigger the call to load:
def Update(self, load=False):
self.table.clear()
if load:
self.persons.load()
And call this method with load set to True in the __init__ method:
def __init__(self, parent = None):
super(MainDialog,self).__init__(parent)
self.InitGui()
self.persons = DContainer.PersonContainer()
self.Update(True)
By the way, the old style signal/slot is no longer supported with PyQt5. This is how to write in the new style:
buttonbox.accepted.connect(self.OK)
buttonbox.rejected.connect(self.reject)