Some PyQt5 Checkboxes are unclickable after being moved - user-interface

I'm creating a to-do list where a checkbox, textbox (QLineEdit), and QComboBox (to set Priority 1, 2, etc.) are added each time the "Add Task" button is clicked. When the checkbox for each corresponding task is clicked, the task would move to the bottom of the list of tasks. Conversely, if a priority is set, then the task is moved to the top of the list of tasks.
The problem is that in certain circumstances, when I click on the checkbox, nothing happens. The signal that identifies that the checkbox state has changed is never sent. It's almost like the checkbox isn't there, even though it (or at least, the image of it) is. This problem happens if none of the priorities are set with unchecking—checking any given task works, but certain tasks cannot be unchecked. Other tasks are able to be unchecked, and sometimes if I go back to the task that couldn't be unchecked after unchecking something else, it'll be checkable again. If I were to set some tasks to have a priority and then go and try to check the checkboxes, some checkboxes won't be able to be checked at all. Again, if I were to check some other task and then go back to the task that wasn't able to be checked, it might be able to be checked this time around.
I think the error might be coming from moving the checkboxes... for some reason the functionality behind some of the checkboxes is lost going through the code. One notable thing is tabbing through the items in the GUI. Once items are moved, hitting tab will go through the items in the same order that it was originally set up in (so the cursor will essentially jump around the textboxes instead of going in order from top to bottom). Not sure if that has anything to do with the error; it's just something I've noticed. I'm don't know if this is an error within PyQt5 itself, or if it's a fixable error within the code.
import sys
from PyQt5.QtWidgets import (QWidget, QComboBox,
QPushButton, QApplication, QLabel, QLineEdit, QTextEdit, QMainWindow, QAction, QShortcut, QCheckBox)
from PyQt5.QtGui import (QKeySequence)
from PyQt5 import QtWidgets, QtCore
from PyQt5.QtCore import pyqtSlot, QObject
class App(QMainWindow):
def __init__(self):
super().__init__()
self.i = 40
self.j = 80
self.counter = 1
self.list_eTasks = []
self.initUI()
def initUI(self):
#sets up window
self.setWindowTitle('To-Do List')
self.setGeometry(300, 300, 800, 855)
#button
self.btn1 = QPushButton("Add Task", self)
self.btn1.clicked.connect(self.add_task_button_clicked)
#close the window via keyboard shortcuts
self.exit = QShortcut(QKeySequence("Ctrl+W" or "Ctrl+Q"), self)
self.exit.activated.connect(self.close)
self.show()
#pyqtSlot()
def add_task_button_clicked(self):
self.label = QLabel(self)
self.label.setText(str(self.counter))
self.label.move(5, self.i)
self.btn1.move(50, self.j)
self.textbox = QLineEdit(self)
self.textbox.resize(280, 40)
self.checkbox = QCheckBox(self)
self.checkbox.stateChanged.connect(self.click_box)
self.combobox = QComboBox(self)
self.combobox.addItem("No Priority")
self.combobox.addItem("Priority 1")
self.combobox.addItem("Priority 2")
self.combobox.addItem("Priority 3")
self.combobox.activated[str].connect(self.combobox_changed)
self.obj = eTask(self.checkbox, self.textbox, self.combobox)
self.list_eTasks.append(self.obj)
self.textbox.show()
self.label.show()
self.checkbox.show()
self.combobox.show()
self.i += 40
self.j += 40
self.counter += 1
self.sort_eTasks()
def click_box(self, state):
self.sort_eTasks()
def move_everything(self, new_list):
count = 0
for item in new_list:
y_value = ((40)*(count + 1))
item.check.move(20, y_value)
item.text.move(50, y_value)
item.combo.move(350, y_value)
count += 1
return
def combobox_changed(self, state):
#make new list to not include any already checked off items
for task in self.list_eTasks:
if task.combo.currentText() == "Priority 1":
task.priority = 1
elif task.combo.currentText() == "Priority 2":
task.priority = 2
elif task.combo.currentText() == "Priority 3":
task.priority = 3
else:
task.priority = 10
self.sort_eTasks()
def sort_eTasks(self):
self.list_eTasks.sort(key=lambda eTask: eTask.getRank())
self.move_everything(self.list_eTasks)
for task in self.list_eTasks:
print(str(task.priority) + " pos: " + str(task.check.pos()))
print("-----------------------------------")
class eTask:
check = ""
text = ""
combo = ""
priority = 10
def __init__(self, c, t, co):
self.check = c
self.text = t
self.combo = co
def setP(self, p):
self.priority = p
def getRank(self):
if self.check.isChecked():
return self.priority + 100
else:
return self.priority
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = App()
sys.exit(app.exec_())

There are various problems with your code, I'll try to address all of them.
The sorting mechanism is not good: it only happens when an item changes the priority (while it should also apply whenever the user clicks the checkbox)
Using low values like those results in unexpected behavior: if I have a priority 1 checked its priority is augmented by 10, so why should it go under a priority 2 or even 3?
The sorting mechanism should be centralized, instead of being split into more functions that don't do anything else: "one function to rule them all" ;-) Don't check for the combobox contents and then sort basing on another external function call: just call a single function that checks for the combobox and the checkbox, then sort the contents; remember: while OOP is about modularity, you should avoid unnecessary fragmentation (remember the KISS principle);
You should probably not connect to the activated signal, and you should certainly not use the [str] overload (checking for a string is rarely a good idea for index based widgets); use currentIndex() and its currentIndexChanged() signal instead;
Using fixed geometries for child widgets is something that should be avoided as much as possible; layout managers exist for lots of very good reasons (for example, I couldn't click on the QLineEdit if the mouse cursor was too close to the check box, even if it's over the line edit); note that you should avoid using setGeometry for the top level window also;
You should never set instance attributes that are going to be overwritten by a different object: you're continuously overwriting them (self.label, self.textbox, etc.), and the result is that making them instance attributes is completely useless; since you're already creating them with a parent widget, the garbage collector won't delete them at the end of the function, so you should only use local variables instead (label = QLabel(), etc...);
Using a class like you did is not very useful: you're going to group all those widgets anyway, so it's better to use a container widget which not only will manage its children (see the point above about layouts), but will "centralize" all necessary programming logic;
Avoid similar names for class and variables if they refer to different object types: in your case, app refers to the QApplication instance, but App is a QMainWidget subclass;
Here's a possible reimplementation of your code:
from PyQt5 import QtCore, QtWidgets
class TaskWidget(QtWidgets.QWidget):
priorityChanged = QtCore.pyqtSignal()
lastChanged = QtCore.QDateTime.currentDateTime()
def __init__(self, title='', parent=None):
super().__init__(parent)
layout = QtWidgets.QHBoxLayout(self)
self.checkBox = QtWidgets.QCheckBox()
layout.addWidget(self.checkBox)
self.textBox = QtWidgets.QLineEdit(title)
layout.addWidget(self.textBox)
self.textBox.setMinimumWidth(240)
self.priorityCombo = QtWidgets.QComboBox()
layout.addWidget(self.priorityCombo)
self.priorityCombo.addItems([
'No priority',
'Priority 1',
'Priority 2',
'Priority 3',
])
self.priorityCombo.currentIndexChanged.connect(self.updatePriority)
self.checkBox.toggled.connect(self.updatePriority)
def updatePriority(self):
# update the "lastChanged" variable, ensuring that the sorting puts on
# top the last changed task
self.lastChanged = QtCore.QDateTime.currentDateTime()
self.priorityChanged.emit()
def priority(self):
priority = self.priorityCombo.currentIndex()
# set an arbitrary (but still reasonable) priority that would be fine for
# more items in the same priority
if priority > 0:
priority *= 100
else:
priority = self.priorityCombo.count() * 100
if self.checkBox.isChecked():
priority -= 1
return priority, self.lastChanged.msecsTo(QtCore.QDateTime.currentDateTime())
class TaskApp(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
central = QtWidgets.QWidget()
layout = QtWidgets.QVBoxLayout(central)
self.setCentralWidget(central)
self.taskLayout = QtWidgets.QGridLayout()
layout.addLayout(self.taskLayout)
self.addTaskButton = QtWidgets.QPushButton('Add task')
layout.addWidget(self.addTaskButton)
self.addTaskButton.clicked.connect(self.addTask)
self.tasks = []
self.addTask()
def addTask(self):
task = TaskWidget('Task no. {}'.format(len(self.tasks) + 1))
if not self.taskLayout.count():
# this is for the first item only; we cannot use rowCount, since it
# always returns at least 1, even if it's empty
row = 0
else:
row = self.taskLayout.rowCount()
self.taskLayout.addWidget(QtWidgets.QLabel(str(row + 1)))
self.taskLayout.addWidget(task, row, 1)
self.tasks.append(task)
task.priorityChanged.connect(self.sortTasks)
def sortTasks(self):
tasks = self.tasks[:]
self.tasks.clear()
# since we're using a QGridLayout, we don't need to relate to
# insertWidget(), which is index based and might result in some
# inconsistencies while "regenerating" the UI; in this case, the
# grid will be the same, we're only going to rearrange the child widgets
for row, taskWidget in enumerate(sorted(tasks, key=lambda w: w.priority())):
self.taskLayout.addWidget(taskWidget, row, 1)
self.tasks.append(taskWidget)
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
w = TaskApp()
w.show()
sys.exit(app.exec_())
I just guessed the priority implementation, but that's not the point.
I also added a datetime check, so that the last changed item with the same priority will always be on top.

Related

Updating a kivy label on a screen when dynamically generating it through a list?

Firstly, disclaimer: I am terribly new to programming and am trying to build my understanding with this project. Additionally, let me say I have searched the forums and found similar posts to mine, but none have the issue of updating a label that has been dynamically generated through a list.
My question is in my code, commented out, but to summarize: I generate buttons and labels for each item in a list. Then the buttons should add and subtract from the linked value in a dictionary. Currently the code does this, but the labels on screen don't update to reflect the new values. Can someone please assist with updating the value for "ordlayout.add_widget(ordlayout.lbl[str(i)])" when calling to updateup and updatedown?
import kivy
kivy.require('1.10.0')
from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.button import Button
from kivy.uix.widget import Widget
from kivy.properties import ObjectProperty
from kivy.uix.behaviors import ButtonBehavior
from kivy.uix.gridlayout import GridLayout
from kivy.uix.scrollview import ScrollView
from kivy.uix.textinput import TextInput
from kivy.lang import Builder
from kivy.uix.dropdown import DropDown
from kivy.base import runTouchApp
from kivy.uix.screenmanager import ScreenManager, Screen, FadeTransition
from functools import partial
#The first screen the app opens to. Contains all other screen branches.
class MainScreen(Screen):
pass
#NewOrder will be the screen used for choosing which
#items/and how many of each the customer wants added.
class NewOrder(Screen):
def __init__(self, **kwargs):
super(NewOrder, self).__init__(**kwargs)
#This will eventually read/create a list of strings from a user-modified file.
self.foods = ["Puppy", "Cat", "Fish"]
#I create a dictionary linking a number to each item.
self.countfoods = {}
for i in self.foods:
self.countfoods[i] = 0
#Now I create a grid layout to put on the screen.
ordlayout = GridLayout()
ordlayout.cols = 8
ordlayout.row_default_height=20
ordlayout.buttons={}
ordlayout.btns1 = {}
ordlayout.lbl = {}
#The items I want on the screen are 1.)First item from list. 2.) Minus button.
#3.) Current number of the item. 4.) Plus button.
#I want these four buttons for each item.
for i in self.countfoods:
#Adds text for first item.
ordlayout.add_widget(Label(text=i))
#Adds a button for minus, linked to a unique dict value.
ordlayout.buttons[str(i)] = Button(text="-")
ordlayout.lbl[str(i)] = Label(text=str((self.countfoods[i])))
#The below assigns the specific object location of each label
#to a variable for passing to ocuntup and countdown.
tempPlacement = str(ordlayout.lbl[str(i)])
ordlayout.buttons[str(i)].bind(on_press=partial(self.updatedown, i))
ordlayout.add_widget(ordlayout.buttons[str(i)])
#Add the value that I want to update.
ordlayout.add_widget(ordlayout.lbl[str(i)])
#Adds a button for addition, but doesn't properly link it to a specific value.
ordlayout.btns1[str(i)] = Button(text="+")
ordlayout.btns1[str(i)].bind(on_press=partial(self.updateup, i))
ordlayout.add_widget(ordlayout.btns1[str(i)])
#Add that grid wit
h values to the screen.
self.add_widget(ordlayout)
#Function used to change value down by one.
def updatedown(self, event, i):
self.countfoods[event] -= 1
print (self.countfoods)
#Function used to change value up by one.
def updateup(self, event, i):
self.countfoods[event] += 1
print (self.countfoods)
#AdminOpt will be the screen used for
class AdminOpt(Screen):
def __init__(self, **kwargs):
super(AdminOpt, self).__init__(**kwargs)
#Will allow for opening and checking of created orders.
class OrdHist(Screen):
pass
#This is purely the class used for managing the other screens.
class ScreenManagement(ScreenManager):
pass
Main = Builder.load_file("Order Handler2.kv")
class Customer:
def __init__(self, name, pricelist):
self.name = name
self.pricelist = pricelist
class SimpleKivy(App):
def build(self):
return Main
if __name__== "__main__":
SimpleKivy().run()
Haven't been able to test this (your question is missing your kv file), but something like this might work:
#Function used to change value down by one.
def updatedown(self, i, button):
self.countfoods[i] -= 1
self.ordlayout.lbl[str(i)].text = str(self.countfoods[i])
print (self.countfoods)
#Function used to change value up by one.
def updateup(self, i, button):
self.countfoods[i] += 1
self.ordlayout.lbl[str(i)].text = str(self.countfoods[i])
print (self.countfoods)
You will also need to replace every occurence of ordlayout with self.ordlayout in the __init__() method.
As an aside, you don't need to do str(i) for your dictionary keys. In fact, you can use lists instead of dictionaries, if you prefer.

GUI plot refreshing incorrectly on wxpython panel

Ok, so I've been tasked with creating a VERY simple GUI at work (I'm an intern). The task is to eventually connect to a machine and process real data, but right now I'm working on randomly generated sine data with noise. I've chose to work in Python 3.0, and use wxpython to create my GUI components.
As I want everything to appear on the same window, I'm using panels (hence wx.lib.plot.PlotCanvas rather than something like matplotlib.pyplot)
The problem that I have is that over time, the plot seems to 'expand' off of the panel. This is temporarily solved when I manually resize the window, but resumes again immediately after (you need to run the code to see what I mean).
Expansion over time in panel
Another problem (that has bugged me since I have started writing the code) is that sometimes when I resize the window (manually) or minimize it and then maximize it again, the timer randomly starts and stops.
I have tried all sorts of things (changing padding in sizers, extra arguments, changing time between refreshes GetBestSize()) but I believe that I simply don't understand wxpython well enough to identify where the problem is
I would really appreciate any help you can shed on either of these problems (I don't know, they might even be linked to each other).
FYI: I am not an experienced coder, and my code is not finished (I have more functions to code, but I feel like I should resolve this first). I have constructed this code by looking at different techniques from various tutorials and websites like stackoverflow, so I know it's not formatted well and could definitely be made more efficient. Also, I have removed some parts just to be safe about confidentiality - nothing important, just strings in messages.
PS: If you do have an easier way to do the whole plot/update thing that doesn't have this problem (preferably still in wx) I would be thrilled to hear that as well
And here's my code:
EDIT: Solved the expanding problem by using self.p2.SetSize((W+0,L+0)) instead of (self.p2.GetBestSize())
EDIT: Made transitions much smoother by just regenerating data and redrawing it on existing canvas in the evt_timer function (instead of recreating the whole canvas, which gave a blink-y appearance if you know what I mean)
import wx
import numpy as np
import matplotlib.pyplot as plt
from scipy.optimize import leastsq
import wx.lib.plot as plot
import time
import os
wildcard = "Text File (*.txt)|*.txt|"\
"Picture (*.png)|*.png|"\
"All files (*.*)|*.*"#This wildcard shows the options for file endings in the "SAVE" tab - see OnSave(self,event)
wildcard2 = "Picture (*.png)|*.png|"\
"Text File (*.txt)|*.txt|"\
"All files (*.*)|*.*"
class PlotCanvas(plot.PlotCanvas):
def __init__(self,parent,id,size,accepted):
"""
This randomly generates sine data (with noise) and plots it to a panel.
Incorporated as a separate class instead of instatiating it as a plot.PlotCanvas object
to overcome an issue of the size of the plot in the panel.
"""
plot.PlotCanvas.__init__(self,parent,id,style=wx.BORDER_SUNKEN,size = size)
N = 100 # number of data points
self.t = np.linspace(0, 4*np.pi, N)
f = 1.15247 # Optional!! Advised not to use
self.data = 3.0*np.sin(f*self.t+0.001) + 0.5 + np.random.randn(N) # create artificial data with noise
guess_mean = np.mean(self.data)
guess_phase = 0
guess_freq = 1
guess_amp = 1
optimize_func = lambda x: x[0]*np.sin(x[1]*self.t+x[2]) + x[3] - self.data
est_amp, est_freq, est_phase, est_mean = leastsq(optimize_func, [guess_amp, guess_freq, guess_phase, guess_mean])[0]
fine_t = np.arange(0,max(self.t),0.1)
data_fit=est_amp*np.sin(est_freq*fine_t+est_phase)+est_mean
multiplier = 1
dataset1 = [(x,[d for d in self.data][[td for td in self.t].index(x)])for x in [td for td in self.t]]
fitdata1 = [(x,[df for df in data_fit][[tf for tf in fine_t].index(x)]) for x in [tf for tf in fine_t]]
dataset =[(x,y*multiplier) for (x,y) in dataset1]
fitdata = [(x,y*multiplier) for (x,y) in fitdata1]
self.data = dataset
self.data2 = fitdata
line = plot.PolyLine(self.data,legend = 'random',colour = 'light blue', width =2)
line2 = plot.PolyLine(self.data2,legend = 'sineline',colour ='black',width =2)
a = []
if "D" in accepted:
a.append(line)
if "S" in accepted:
a.append(line2)
if "G" in accepted:
pass
if "L" in accepted:
pass
gc = plot.PlotGraphics(a,'Line Graph','X','Y')
xmin = self.t[0]-0.01*(self.t[-1]-self.t[0])
xmax = self.t[-1]+0.01*(self.t[-1]-self.t[0])
self.Draw(gc,xAxis=(xmin,xmax),yAxis=(min([x[1] for x in dataset])-0.01*(max([x[1] for x in dataset])-min([x[1] for x in dataset])),
max([x[1] for x in dataset])+0.01*(max([x[1] for x in dataset])-min([x[1] for x in dataset]))))
#self.showLegend = True
#self.enableZoom = True
def Dialog(self, parent, message, c):# Will be used to notify the user of errors/processes
if c == "W":
caption = "Warning!"
dlg = wx.MessageDialog(parent, message, caption, wx.OK | wx.ICON_WARNING)
elif c == "I":
caption = "Information"
dlg = wx.MessageDialog(parent, message, caption, wx.OK | wx.ICON_INFORMATION)
dlg.ShowModal()
dlg.Destroy()#Destroys dialog on close
class Frame(wx.Frame):
"""
This is the main class. In it, we declare the separate panels, canvas, menubar, buttons and sizers.
"""
def __init__(self,parent,id,title):
wx.Frame.__init__(self, parent, id, title, wx.DefaultPosition)
self.CurrentDirectory = os.getcwd()
self.timer=wx.Timer(self)#Instantiating the timer
self.count=0
self.Bind(wx.EVT_TIMER,self.evt_timer)#Binding it to itself so that it is always triggered
self.Bind(wx.EVT_PAINT,self.paint)
menubar = wx.MenuBar()
fileMenu = wx.Menu() #Creating the Menubar at the top
#Creating 3 menus: fileMenu,fit,and help
save = wx.Menu()
z = wx.MenuItem(save,wx.ID_ANY,'Save Raw Data\tCtrl+D')
self.Bind(wx.EVT_MENU,self.OnSave,z)
save.Append(z)
z= wx.MenuItem(save,wx.ID_ANY,'Save Image\tCtrl+I')
self.Bind(wx.EVT_MENU,self.OnSaveImage,z)
save.Append(z)
fileMenu.AppendSubMenu(save,'&Save')
fileMenu.AppendSeparator()
z = wx.MenuItem(fileMenu, wx.ID_EXIT, '&Quit\tCtrl+W')
self.Bind(wx.EVT_MENU, self.OnQuit, z)
fileMenu.Append(z)
fit = wx.Menu()#Making a check menu
self.gaussian = fit.Append(wx.ID_ANY,'Gaussian',kind = wx.ITEM_CHECK)
#self.Bind(wx.EVT_MENU,self.ToggleGaussian,self.gaussian)
fit.Check(self.gaussian.GetId(),False)
self.sine = fit.Append(wx.ID_ANY,'Sine',kind = wx.ITEM_CHECK)
#self.Bind(wx.EVT_MENU,self.ToggleSine,self.sine)
fit.Check(self.sine.GetId(),False)
self.linear = fit.Append(wx.ID_ANY,'Linear',kind=wx.ITEM_CHECK)
#self.Bind(wx.EVT_MENU,self.ToggleLinear,self.linear)
fit.Check(self.linear.GetId(),False)
help = wx.Menu()
z = wx.MenuItem(help,wx.ID_ANY,'&About\tCtrl+H')
self.Bind(wx.EVT_MENU,self.OnHelp,z)
help.Append(z)
menubar.Append(fileMenu, '&File')
menubar.Append(fit, '&Fit')
menubar.Append(help, '&Help')#adding menus to menubar
self.SetMenuBar(menubar)#formatting the frame with menubar
self.sp = wx.SplitterWindow(self)#Splitting the window into 2 panels
self.p1 = wx.Panel(self.sp,style = wx.SUNKEN_BORDER)#For buttons and user events
self.p2 = wx.Panel(self.sp,style = wx.SUNKEN_BORDER)#For display of the plot
self.sp.SplitVertically(self.p1,self.p2,300)
sizer = wx.GridBagSizer(3, 3)#Versatile sizer for layout of first panel self.p1
bitmappath = self.CurrentDirectory + "\\BITMAPS"
bmp = wx.Bitmap(bitmappath+"\\SAVE.BMP",wx.BITMAP_TYPE_BMP)
self.saveBtn = wx.BitmapButton(self.p1,wx.ID_ANY,bitmap = bmp,size =(bmp.GetWidth()+10,bmp.GetHeight()+10))
self.Bind(wx.EVT_BUTTON,self.OnSave,self.saveBtn)
sizer.Add(self.saveBtn, (0, 0), wx.DefaultSpan, wx.ALL,5)
bmp = wx.Bitmap(bitmappath +"\\START.BMP",wx.BITMAP_TYPE_BMP)
self.startBtn = wx.BitmapButton(self.p1,-1,bitmap = bmp,size =(bmp.GetWidth()+10,bmp.GetHeight()+10))# A button that starts and stops the plotting
self.startBtn.startval = "START"
self.Bind(wx.EVT_BUTTON,self.paint,self.startBtn)
sizer.Add(self.startBtn, (0, 1), wx.DefaultSpan,wx.ALL,5)
sizer1 = wx.BoxSizer(wx.VERTICAL)
W,L = self.p2.GetSize()
self.p2.canvas = PlotCanvas(self.p2,wx.ID_ANY,(W,L),["D"])
sizer1.Add(self.p2.canvas,1,wx.ALL,0,0)
self.p2.SetSizerAndFit(sizer1)
self.p1.SetSizerAndFit(sizer)
self.p2.SetSizerAndFit(sizer1)
self.p2.SetSize(W,L)
self.Maximize(True)
self.Centre()
self.Show()
############### event methods ###########
def paint(self,event):
"""
Updates the canvas based on the value of the startbtn(not the image). Bound to self.timer.
"""
bitmappath = self.CurrentDirectory + "\\BITMAPS"
if self.startBtn.startval == "START":
self.timer.Start(1)# increase the value for more time
bmp = wx.Bitmap(bitmappath + "\\STOP.BMP",wx.BITMAP_TYPE_BMP)
self.startBtn.SetBitmap(bmp)
self.startBtn.startval = "STOP"
elif self.startBtn.startval == "STOP":
self.timer.Stop()
bmp = wx.Bitmap(bitmappath+ "\\START.BMP",wx.BITMAP_TYPE_BMP)
self.startBtn.SetBitmap(bmp)
self.startBtn.startval = "START"
def evt_timer(self,event):
self.count +=1
if self.count== 10:# By increasing count (or the number in self.timer.Start()) you can increase the interval between updates
#self.p2.canvas.Clear()
sizer1 = wx.BoxSizer(wx.VERTICAL)
W,L = self.p2.GetSize()
a = ["D"]
if self.sine.IsChecked():
a.append("S")
elif self.linear.IsChecked():
a.append("L")
elif self.gaussian.IsChecked():
a.append("G")
self.p2.canvas = PlotCanvas(self.p2,wx.ID_ANY,(W,L),a)
sizer1.Add(self.p2.canvas,1,wx.ALL,0,0)
self.p2.SetSizerAndFit(sizer1)
self.p2.SetSize(self.p2.GetBestSize())
self.count=0 # reset the count
def Dialog(self, parent, message, c):# Will be used to notify the user of errors/processes
if c == "W":
caption = "Warning!"
dlg = wx.MessageDialog(parent, message, caption, wx.OK | wx.ICON_WARNING)
elif c == "I":
caption = "Information"
dlg = wx.MessageDialog(parent, message, caption, wx.OK | wx.ICON_INFORMATION)
dlg.ShowModal()
dlg.Destroy()#Destroys dialog on close
def OnSave(self,event):#Triggered by menubar and button
try:
rawdata = self.p2.canvas.data
raw_X =[x[0] for x in rawdata]
raw_Y =[x[1] for x in rawdata]
dlg = wx.FileDialog(#Code for this from http://www.blog.pythonlibrary.org
self, message="Save file as ...",
defaultDir=self.CurrentDirectory,
defaultFile=str(time.ctime()), wildcard=wildcard, style=wx.FD_SAVE
)
if dlg.ShowModal() == wx.ID_OK:
path = dlg.GetPath()
dlg.Destroy()
f = open(path+".txt","w+")
for i in range(len(raw_X)):
f.write(str(raw_X[i])+"\t"+str(raw_Y[i])+"\n")
f.close()
self.Dialog(None,"File successfully saved","I")
except UnboundLocalError:#Catch error when user closes save window without selecting any directory or filename
pass
def OnSaveImage(self,event):
try:
rawdata = self.p2.canvas.data
raw_X = [x[0] for x in rawdata]
raw_Y = [x[1] for x in rawdata]
dlg = wx.FileDialog(
self, message="Save file as ...",
defaultDir=self.CurrentDirectory,
defaultFile=str(time.ctime()), wildcard=wildcard2, style=wx.FD_SAVE
)
if dlg.ShowModal() == wx.ID_OK:
path = dlg.GetPath()
dlg.Destroy()
fig1 = plt.figure()
plt.plot(raw_X,raw_Y)
plt.title("Raw Data")
fig1.savefig(path+".png")
self.Dialog(None,"File successfully saved","I")
except UnboundLocalError:
pass
def OnMultiply(self,e):
try:
factor = self.x.GetValue()
factor = float(factor)
self.IntegrationTime = factor
except ValueError as e:
self.Dialog(None,str(e),"W")
def OnQuit(self, e):
self.Close()
def OnHelp(self,e):
self.Dialog(None,"N/A","I")
def ToggleSine(self,e):
pass
def ToggleLinear(self,e):
self.Dialog(None,"Not added yet","W")
def ToggleGaussian(self,e):
self.Dialog(None,"Not added yet","W")
if __name__ =="__main__":
app=wx.App()
Frame(None,-1,"N/A")
app.MainLoop()

How to display an icon in the systray reflecting NumLk state

My computer doesn't have any way of letting me know if my NumLk is on or off, so I am trying to add an icon in my systray that will changed depending on the state of my NumLk. This .py will always be running when my computer is on.
So far I was able to mix 3 codes and I am able to display the icon in the systray but it doesn't get updated when the state of NumLk change. Actually if I press NumLk twice, I still get the same icon (the on one) and I get this error:
QCoreApplication::exec: The event loop is already running
File "\systray_icon_NumLk_on_off.py", line 21, in on_key_press
main(on)
File "\systray_icon_NumLk_on_off.py", line 46, in main
sys.exit(app.exec_())
SystemExit: -1
My code may not be the best way to do it, so any alternative is welcome! Here is what I came up so far:
#####get the state of NumLk key
from win32api import GetKeyState
from win32con import VK_NUMLOCK
#how to use: print(GetKeyState(VK_NUMLOCK))
#source: http://stackoverflow.com/questions/21160100/python-3-x-getting-the-state-of-caps-lock-num-lock-scroll-lock-on-windows
#####Detect if NumLk is pressed
import pyglet
from pyglet.window import key
window = pyglet.window.Window()
#source: http://stackoverflow.com/questions/28324372/detecting-a-numlock-capslock-scrlock-keypress-keyup-in-python
on=r'on.png'
off=r'off.png'
#window.event
def on_key_press(symbol, modifiers):
if symbol == key.NUMLOCK:
if GetKeyState(VK_NUMLOCK):
#print(GetKeyState(VK_NUMLOCK))#should be 0 and 1 but
main(on)
else:
main(off)
#window.event
def on_draw():
window.clear()
### display icon in systray
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
#source: http://stackoverflow.com/questions/893984/pyqt-show-menu-in-a-system-tray-application - add answer PyQt5
class SystemTrayIcon(QtWidgets.QSystemTrayIcon):
def __init__(self, icon, parent=None):
QtWidgets.QSystemTrayIcon.__init__(self, icon, parent)
menu = QtWidgets.QMenu(parent)
exitAction = menu.addAction("Exit")
self.setContextMenu(menu)
def main(image):
app = QtWidgets.QApplication(sys.argv)
w = QtWidgets.QWidget()
trayIcon = SystemTrayIcon(QtGui.QIcon(image), w)
trayIcon.show()
sys.exit(app.exec_())
if __name__ == '__main__':
pyglet.app.run()
The reason for QCoreApplication::exec: The event loop is already running is actually because you're trying to start app.run() twice. Qt will notice there's already an instance running and throw this exception. When instead, what you want to do is just swap the icon in the already running instance.
Your main problem here is actually the mix of libraries to solve one task if you ask me.
Rather two tasks, but using Qt5 for the graphical part is fine tho.
The way you use Pyglet is wrong from the get go.
Pyglet is intended to be a highly powerful and effective graphics library where you build a graphics engine ontop of it. For instance if you're making a game or a video-player or something.
The way you use win32api is also wrong because you're using it in a graphical window that only checks the value when a key is pressed inside that window.
Now, if you move your win32api code into a Thread (a QtThread to be precise) you can check the state no matter if you pressed your key inside your graphical window or not.
import sys
import win32api
import win32con
from PyQt5 import QtCore, QtGui, QtWidgets
from threading import Thread, enumerate
from time import sleep
class SystemTrayIcon(QtWidgets.QSystemTrayIcon):
def __init__(self, icon, parent=None):
QtWidgets.QSystemTrayIcon.__init__(self, icon, parent)
menu = QtWidgets.QMenu(parent)
exitAction = menu.addAction("Exit")
exitAction.setShortcut('Ctrl+Q')
exitAction.setStatusTip('Exit application')
exitAction.triggered.connect(QtWidgets.qApp.quit)
self.setContextMenu(menu)
class KeyCheck(QtCore.QThread):
def __init__(self, mainWindow):
QtCore.QThread.__init__(self)
self.mainWindow = mainWindow
def run(self):
main = None
for t in enumerate():
if t.name == 'MainThread':
main = t
break
while main and main.isAlive():
x = win32api.GetAsyncKeyState(win32con.VK_NUMLOCK)
## Now, GetAsyncKeyState returns three values,
## 0 == No change since last time
## -3000 / 1 == State changed
##
## Either you use the positive and negative values to figure out which state you're at.
## Or you just swap it, but if you just swap it you need to get the startup-state correct.
if x == 1:
self.mainWindow.swap()
elif x < 0:
self.mainWindow.swap()
sleep(0.25)
class GUI():
def __init__(self):
self.app = QtWidgets.QApplication(sys.argv)
self.state = True
w = QtWidgets.QWidget()
self.modes = {
True : SystemTrayIcon(QtGui.QIcon('on.png'), w),
False : SystemTrayIcon(QtGui.QIcon('off.png'), w)
}
self.refresh()
keyChecker = KeyCheck(self)
keyChecker.start()
sys.exit(self.app.exec_())
def swap(self, state=None):
if state is not None:
self.state = state
else:
if self.state:
self.state = False
else:
self.state = True
self.refresh()
def refresh(self):
for mode in self.modes:
if self.state == mode:
self.modes[mode].show()
else:
self.modes[mode].hide()
GUI()
Note that I don't do Qt programming often (every 4 years or so).
So this code is buggy at it's best. You have to press Ctrl+C + Press "Exit" in your menu for this to stop.
I honestly don't want to put more time and effort in learning how to manage threads in Qt or how to exit the application properly, it's not my area of expertis. But this will give you a crude working example of how you can swap the icon in the lower corner instead of trying to re-instanciate the main() loop that you did.

'on_activated' event listener and project switching

on_activated is supposed to be called "when a view gains input focus" (ST3 API docs). When I switch to a different project/workspace with cmd+control+p in ST3, on_activated gets called for all views in the working space/project. So it might be called 10 times or more. I find that behavior unexpected and would like to just get one call for the currently visible view. So why not just once for the view that gains input focus with the project switch? Any workarounds?
Thanks!
EDIT
Here is the code.
import sublime_plugin
class TestPlugin(sublime_plugin.EventListener):
count = 0
def on_activated(self, view):
self.count += 1
print('"on_activated" event fired %ith time!' % self.count)
Check whether the currently activated view is the one you want to.
import sublime_plugin
class TestPlugin(sublime_plugin.EventListener):
count = 0
def on_activated(self, view):
if self.view.window().active_view().id() == view.id():
print('"on_activated" successfully fired!')
else:
self.count += 1
print('"on_activated": %i unsuccessful fired.' % self.count)

wxPython: binding a dynamically created spinner to an event

I used the code below to create several spinners with a for loop.
Now, I can't figure out how to bind an event so that I know which spinner is being modified so that I can put the spinner value into the right variable.
If I can figure out which spinner is calling the handler I could map it to the correct variable.
Any thoughts? Is this even possible?
Thanks in advance!
import wx
class spinnerFrame(wx.Frame):
def __init__(self, parent, id):
wx.Frame.__init__(self, parent,id, "Spinner Frame", size = (300,200))
#constants
spnr_sz = (50,-1)
names = ('name1','name2','name3','name4','name5','name6')
sizer = wx.GridBagSizer(5, 5)
# TEXT FONT EXAMPLE
# m_text = wx.StaticText(panel, -1, "Hello World!")
# m_text.SetFont(wx.Font(14, wx.SWISS, wx.NORMAL, wx.BOLD))
# m_text.SetSize(m_text.GetBestSize())
#temp
sizer = wx.GridBagSizer(5, 5)
row = 0
for n in names:
row += 1
my_label = wx.StaticText(self, -1, n)
spinner = wx.SpinCtrl(self, -1, size = spnr_sz, min = 0, initial = 10 )
self.Bind(wx.EVT_SPINCTRL, self.OnCompute, spinner)
sizer.Add(my_label, (row,0))
sizer.Add(spinner, (row,1))
sizer.AddGrowableRow(7)
sizer.AddGrowableCol(4)
self.SetSizerAndFit(sizer)
self.Centre()
def OnCompute(self,event):
# a = spinner.GetValue()
# ????
if __name__=='__main__':
app = wx.App(True) # was False
frame = wx.Frame(None)
frame = spinnerFrame (parent=None, id = -1)
frame.Show()
app.MainLoop()
Yes, this is possible. The easiest way I know is to associate your unique name with each call to Bind, either by giving the spinner a name or using partial functions (or lambdas, but lambdas can end up being messy), and then check for the name in the handler. Examples of how to use the name are given in this previous SO question.

Resources