So I'm trying to add the "text" associated with a checked checkbox to a list as soon as they're checked, and I'm doing this:
class Interface(QtGui.QMainWindow):
def __init__(self):
super(Interface, self).__init__()
self.initUI()
self.shops=[]
def initUI(self):
widthPx = 500
heightPx = 500
self.setGeometry(100,100,widthPx,heightPx)
#menus
fileMenu = menuBar.addMenu("&File")
helpMenu = menuBar.addMenu("&Help")
#labels
shopList = _getShops()
for i, shop in enumerate(shopList):
cb = QtGui.QCheckBox(shop, self)
cb.move(20, 15*(i)+50)
cb.toggle()
cb.stateChanged.connect(self.addShop)
self.setWindowTitle("Absolute")
self.show()
def addShop(self, state):
if state == QtCore.Qt.Checked:
#I want to add the checkbox's text
self.shops.append('IT WORKS')
else:
self.shops.remove('IT WORKS')
But instead of adding "IT WORKS" I want to add the text associated with the checkbox that was just selected.
I usually pass additionnal parameters in my signals/slots using partial
Functools doc
You can use it to pass your checkbox text.
First, import partial:
from functools import partial
Then, change your connect() method and pass your checkbox text:
cb.stateChanged.connect( partial( self.addShop, shop) )
To finish, update your addShop() method:
def addShop(self, shop, state):
if state == Qt.Checked:
self.shops.append(shop)
else:
try:
self.shops.remove(shop)
except:
print ""
Notes:
I've added a try/except at the end because your checkboxes are checked by default. When you uncheck them, it tries to remove an unknow item from your self.shops list.
With this method, this is not the current checkbox text which is send to your method. It it the first text that was used to initialize your checkboxes. If, during the execution of your script, you modify the checkbox text, it will not be updated in your addShop method.
Update:
In fact, you can pass your checkbox in the partial:
cb.stateChanged.connect( partial( self.addShop, cb) )
and retrieve it this way:
def addShop(self, shop, state):
if state == Qt.Checked:
self.shops.append(shop.text())
else:
try:
self.shops.remove(shop.text())
except:
print ""
Related
I am writing an application in PySide2 and I have developed a class inheriting from Qdialog to display a list with checkboxes:
The code of the class:
class ListDialog(QDialog):
def __init__(self, items, all_checked = False, parent=None):
super(ListDialog, self).__init__(parent=parent)
self.setWindowTitle(title)
form = QFormLayout(self)
self.listView = QListView(self)
self.listView.setSelectionMode(QTableView.NoSelection)
form.addRow(self.listView)
self.model = QStandardItemModel(self.listView)
for item in items:
# create an item with a caption
standardItem = QStandardItem(item)
standardItem.setCheckable(True)
standardItem.setEditable(False)
if all_checked:
standardItem.setCheckState(Qt.Checked)
self.model.appendRow(standardItem)
self.listView.setModel(self.model)
The result (plus some extra code):
As it is, you can check multiple checkboxes, but I need to make it a single selection.
Note the line:
self.listView.setSelectionMode(QTableView.NoSelection)
At first, I thought setSelectionMode was responsible for this behaviour but this controls only the highlighting on the items of the list and not its checkboxes. Therefore I set it to NoSelection for not highlighting the text part, the checkboxes are working!
Is there an easy way to set the selection mode to single? Or should I overload the signal that controls the box checking to unselect all the boxes and then select the one I clicked?
An easy way to do that it to use a proxy model which will handle the single selection and the signal QStandardItemModel::itemChanged to know when user clicks on an item.
For example:
class SingleCheckProxyModel(QIdentityProxyModel):
def __init__(self, model, parent=None):
super().__init__(parent)
model.itemChanged.connect(self.checkSingleCheck)
self.setSourceModel(model)
self.currentItemChecked = None
def checkSingleCheck(self, item):
if self.currentItemChecked:
self.currentItemChecked.setCheckState(Qt.Unchecked)
if item.checkState(): # Allows the user to uncheck then check the same item
self.currentItemChecked = item
else:
self.currentItemChecked = None
class ListDialog(QDialog):
def __init__(self, items, all_checked = False, parent=None):
super(ListDialog, self).__init__(parent=parent)
self.setWindowTitle("kjnve")
form = QFormLayout(self)
self.listView = QListView(self)
self.listView.setSelectionMode(QTableView.NoSelection)
form.addRow(self.listView)
self.model = QStandardItemModel(self.listView)
for item in items:
# create an item with a caption
standardItem = QStandardItem(item)
standardItem.setCheckable(True)
standardItem.setEditable(False)
if all_checked:
standardItem.setCheckState(Qt.Checked)
self.model.appendRow(standardItem)
self.listView.setModel(SingleCheckProxyModel(self.model)) # Use proxy
The checkSingleCheck method will be called when the user clicks on an item. But, if you want to be able to edit the items, you have to adapt this function.
I'm writing this code to import textures. I pull them into a drop down menu and I'm trying to make another button that will give me the ability to remove the menuItems from that optionmenu. The clearList function at the bottom is the part that I'm having a lot of trouble with. That function probably doesn't make any sense right now because I have no idea how I'm going to accomplish this. Here's my code:
import maya.cmds as cmds
import os.path
import os
class TextureImport():
def __init__(self):
if cmds.window(TextureImport, q=True, ex=True):
cmds.deleteUI(TextureImport)
GUI=cmds.window(title="Texture Import Tool", widthHeight=(250,200), s=True, tlb=True)
cmds.columnLayout(adj=True)
cmds.button(label="Select Directory", command=self.selectDir)
cmds.optionMenu('optionMenu', label="File List")
cmds.button(label="Clear List", command=self.clearList)
cmds.flowLayout()
cmds.text('Select your object!', h=25)
cmds.button(label="Apply Texture")
cmds.setParent('..')
cmds.showWindow()
def selectDir(self, *args):
basicFilter = "Image Files (*.jpg *.jpeg *.tga *.png *.tiff *.bmp *.psd)"
myDir = cmds.fileDialog2 (fileFilter=basicFilter, dialogStyle=2, fm=3)
myFiles = os.listdir(myDir[0])
print myFiles
for items in myFiles:
fileEndings = ('.psd','.PSD','.jpg','JPG','.jpeg','.JPEG','.tga','.TGA','.png','.PNG','.tiff','.TIFF','.bmp','.BMP')
if items.endswith(fileEndings):
cmds.menuItem(items)
else:
cmds.warning(items + 'This is not a valid image type, you fool.')
def clearList(self, *args):
cmds.optionMenu('optionMenu', q=True)
if optionMenu != 0:
deleteUI (optionsMenu, menuItem=True)
TextureImport()
To delete the menu items, you have to call deleteUI on the items:
def clearList(self, *args):
menuItems = cmds.optionMenu('optionMenu', q=True, itemListLong=True) # itemListLong returns the children
if menuItems:
cmds.deleteUI(menuItems)
You're not storing the option menu to a variable:
def clearList(self, *args):
cmds.optionMenu('optionMenu', q=True)
if optionMenu != 0:
deleteUI (optionsMenu, menuItem=True)
Should be
def clearList(self, *args):
menu = cmds.optionMenu('optionMenu', q=True) # stored result
if menu: # took out unnecessary comparison
cmds.deleteUI(menu, menuItem=True) # added "cmds." for consistency of namespace
I am creating a small app in python using PySide. I read lines from a text file and display each line in a separate QLineEdit Widget. Each "entry" has 2 line edits and 2 QPushButtons. For every line I add those widgets. My problem is that I set a signal-slot for the QPushButtons, but when all the "entries" are generated, only the last entries QPushButtons connects to the slot. May someone please help me.
Here is my code
class ItemLogger(QtGui.QMainWindow, Ui.Ui_MainWindow):
def __init__(self, parent = None):
super(ItemLogger, self).__init__(parent)
self.setupUi(self)
self.parseBossItem()
self.comboBox.currentIndexChanged.connect(self.parseBossItem)
self.increase.clicked.connect(self.add_subtract)
def add_subtract(self):
initial = 1
print "kajskasdjflsdkjflk"
def addRow(self, item):
self.frame = QtGui.QFrame()
self.layout = QtGui.QHBoxLayout()
self.itemName = QtGui.QLineEdit(item)
self.itemName.setReadOnly(True)
self.itemCount = QtGui.QLineEdit()
self.itemCount.setText("0")
self.itemCount.setMaximumWidth(40)
self.decrease = QtGui.QPushButton("-")
self.increase = QtGui.QPushButton("+")
self.layout.addWidget(self.itemName)
self.layout.addWidget(self.itemCount)
self.layout.addWidget(self.decrease)
self.layout.addWidget(self.increase)
self.frame.setLayout(self.layout)
self.verticalLayout_3.addWidget(self.frame)
def parseBossItem(self):
if self.comboBox.currentText() == "Item_1":
item_list = open("BossItems/Random_Item")
for line in item_list.readlines():
self.addRow(line)
if self.comboBox.currentText() == "Item_2":
item_list = open("BossItems/Random_Item_2")
for line in item_list.readlines():
self.addRow(line)
This is because you only connected the last entry.
Here is what you are actually doing:
You add row for item 1, and assign button widgets to self.decrease, self.increase.
You add row for item 2, replacing values of self.decrease, self.increase by newly created widgets.
You connect self.increase, which is now the last added widget.
If you don't need access to you widgets after their creation, you should consider using local variables (e.g. without self) and connecting the signal inside the addRow function.
If you need to keep track of widget references, then you could add them to an array:
# Somewhere in __init__ or in parseBossItem
self.increase = []
# in addRow
self.increase.append(QtGui.QPushButton("+"))
self.layout.addWidget(self.increase[-1])
self.increase[-1].clicked.connect(self.add_subtract)
# and so on...
To use the same slot form different senders, you need to identify who sent the signal. You could do something like this:
def onIncrease(self):
button = self.sender()
if isinstance(button, QtGui.QPushButton):
buttonName = button.text()
if buttonName == 'name of button 1':
self.itemCount[0].setText(str(int(self.itemCount[0])+1))
elif buttonName == 'name of button 2':
...
Off course, this is assuming you put each QLineEdit in the array self.itemCount.
Since all your buttons have the same name, we need to use another approach.
# in addRow
self.increase.clicked.connect(lambda: self.onIncrease(itemCount))
def onIncrease(self, edit):
edit.setText(str(int(edit.text()+1))
I want to get selected text automatically in python, using gtk module.
There is a code to get selected text from anywhere:
#!/usr/bin/env python
import pygtk
pygtk.require('2.0')
import gtk
class GetSelectionExample:
# Signal handler invoked when user clicks on the
# "Get String Target" button
def get_stringtarget(self, widget):
# And request the "STRING" target for the primary selection
ret = widget.selection_convert("PRIMARY", "STRING")
return
# Signal handler called when the selections owner returns the data
def selection_received(self, widget, selection_data, data):
# Make sure we got the data in the expected form
if str(selection_data.type) == "STRING":
# Print out the string we received
print "STRING TARGET: %s" % selection_data.get_text()
elif str(selection_data.type) == "ATOM":
# Print out the target list we received
targets = selection_data.get_targets()
for target in targets:
name = str(target)
if name != None:
print "%s" % name
else:
print "(bad target)"
else:
print "Selection was not returned as \"STRING\" or \"ATOM\"!"
return False
def __init__(self):
# Create the toplevel window
window = gtk.Window(gtk.WINDOW_TOPLEVEL)
window.set_title("Get Selection")
window.set_border_width(10)
window.connect("destroy", lambda w: gtk.main_quit())
vbox = gtk.VBox(False, 0)
window.add(vbox)
vbox.show()
# Create a button the user can click to get the string target
button = gtk.Button("Get String Target")
eventbox = gtk.EventBox()
eventbox.add(button)
button.connect_object("clicked", self.get_stringtarget, eventbox)
eventbox.connect("selection_received", self.selection_received)
vbox.pack_start(eventbox)
eventbox.show()
button.show()
window.show()
def main():
gtk.main()
return 0
if __name__ == "__main__":
GetSelectionExample()
main()
But i don't want to this exactly.
i dont want to click a button. I want to see the selected text only, not after a button clicking. When i start the program ; it must show me the selected text automatically (without clicking any button!).
I want to this exactly :
#!/usr/bin/env python
import pygtk
pygtk.require('2.0')
import gtk
class MyApp (object):
def __init__(self):
self.window=gtk.Window(gtk.WINDOW_TOPLEVEL)
self.window.connect("delete_event", gtk.main_quit )
self.entry = gtk.Entry()
try:
self.s_text=gtk.SelectionData.get_text
# i expect that : the selected text (from anywhere)
# but it returns me :
#<method 'get_text' of 'gtk.SelectionData' objects>
except:
self.s_text="it must be selected text"
self.entry.set_text("Selected Text is : %s" % self.s_text )
self.window.add(self.entry)
self.window.show_all()
def main(self):
gtk.main()
app=MyApp()
app.main()
This program must show me the selected text in the entry box automatically.
Only this. But i can't do!
i expect " gtk.SelectionData.get_text " will show me the selected text but it returns "<method 'get_text' of 'gtk.SelectionData' objects>" .
And also i tried self.s_text=gtk.SelectionData.get_text()
But it returns me :
self.s_text=gtk.SelectionData.get_text()
TypeError: descriptor 'get_text' of 'gtk.SelectionData' object needs an argument
How can i do this? And also i am a beginner python programmer; if u can write the code ; it will be very good for me :)
thanks a lot !!
self.s_text=gtk.SelectionData.get_text
Method get_text is not called yet! You are assigning self.s_text to the method(function) object itself. Which is converted to string in "Selected Text is : %s" % self.s_text
You should changed it to:
self.s_text=gtk.SelectionData.get_text()
EDIT: But since you need a SelectionData object to pass to this method, the whole idea is wrong. I combined you two codes as something working:
#!/usr/bin/env python
import pygtk
pygtk.require('2.0')
import gtk
class MyApp (object):
def __init__(self):
self.window=gtk.Window(gtk.WINDOW_TOPLEVEL)
self.window.connect("delete_event", gtk.main_quit )
self.entry = gtk.Entry()
self.window.selection_convert("PRIMARY", "STRING")
self.window.connect("selection_received", self.selection_received)
self.window.add(self.entry)
self.window.show_all()
# Signal handler called when the selections owner returns the data
def selection_received(self, widget, selection_data, data):
print 'selection_data.type=%r'%selection_data.type
# Make sure we got the data in the expected form
if str(selection_data.type) == "STRING":
self.entry.set_text("Selected Text is : %s" % selection_data.get_text())
elif str(selection_data.type) == "ATOM":
# Print out the target list we received
targets = selection_data.get_targets()
for target in targets:
name = str(target)
if name != None:
self.entry.set_text("%s" % name)
else:
self.entry.set_text("(bad target)")
else:
self.entry.set_text("Selection was not returned as \"STRING\" or \"ATOM\"!")
return False
def main(self):
gtk.main()
app=MyApp()
app.main()
Is there a way to add undo and redo capabilities in Tkinter Entry widgets or must I use single line Text widgets for this type of functionality?
If the latter, are there any tips I should follow when configuring a Text widget to act as an Entry widget?
Some features that might need tweaking include trapping the Return KeyPress, converting tab keypresses into a request to change focus, and removing newlines from text being pasted from the clipboard.
Check the Tkinter Custom Entry. I have added Cut, Copy, Paste context menu, and undo redo functions.
# -*- coding: utf-8 -*-
from tkinter import *
class CEntry(Entry):
def __init__(self, parent, *args, **kwargs):
Entry.__init__(self, parent, *args, **kwargs)
self.changes = [""]
self.steps = int()
self.context_menu = Menu(self, tearoff=0)
self.context_menu.add_command(label="Cut")
self.context_menu.add_command(label="Copy")
self.context_menu.add_command(label="Paste")
self.bind("<Button-3>", self.popup)
self.bind("<Control-z>", self.undo)
self.bind("<Control-y>", self.redo)
self.bind("<Key>", self.add_changes)
def popup(self, event):
self.context_menu.post(event.x_root, event.y_root)
self.context_menu.entryconfigure("Cut", command=lambda: self.event_generate("<<Cut>>"))
self.context_menu.entryconfigure("Copy", command=lambda: self.event_generate("<<Copy>>"))
self.context_menu.entryconfigure("Paste", command=lambda: self.event_generate("<<Paste>>"))
def undo(self, event=None):
if self.steps != 0:
self.steps -= 1
self.delete(0, END)
self.insert(END, self.changes[self.steps])
def redo(self, event=None):
if self.steps < len(self.changes):
self.delete(0, END)
self.insert(END, self.changes[self.steps])
self.steps += 1
def add_changes(self, event=None):
if self.get() != self.changes[-1]:
self.changes.append(self.get())
self.steps += 1
Disclaimer: these are just thoughts that come into my mind on how to implement it.
class History(object):
def __init__(self):
self.l = ['']
self.i = 0
def next(self):
if self.i == len(self.l):
return None
self.i += 1
return self.l[self.i]
def prev(self):
if self.i == 0:
return None
self.i -= 1
return self.l[self.i]
def add(self, s):
del self.l[self.i+1:]
self.l.append(s)
self.i += 1
def current(self):
return self.l[self.i]
Run a thread that every X seconds (0.5?) save the state of the entry:
history = History()
...
history.add(stringval.get())
You can also set up events that save the Entry's status too, such as the pressure of Return.
prev = history.prev()
if prev is not None:
stringvar.set(prev)
or
next = history.next()
if next is not None:
stringvar.set(next)
Beware to set locks as needed.
Update on using this method for Undo/Redo:
I am creating a GUI with lot of frames and each contains at least ten or more 'entry' widgets.
I used the History class and created one history object for each entry field that I had. I was able to store all entry widgets values in a list as done here.
I am using 'trace' method attached to each entry widget which will call 'add' function of History class and store each changes. In this way, I was able to do it without running any thread separately.
But the biggest drawback of doing this is, we cannot do multiple undos/redos with this method.
Issue:
When I trace each and every change of the entry widget and add that to the list, it also 'traces' the change that happens when we 'undo/redo' which means we cannot go more one step back. once u do a undo, it is a change that will be traced and hence the 'undo' value will be added to the list at the end. Hence this is not the right method.
Solution:
Perfect way to do this is by creating two stacks for each entry widget. One for 'Undo' and one for 'redo'. When ever there is a change in entry, push that value into the undo stack. When user presses undo, pop the last stored value from the undo stack and importantly push this one to the 'redo stack'. hence, when the user presses redo, pop the last value from redo stack.
Based on Evgeny's answer with a custom Entry, but added a tkinter StringVar with a trace to the widget to more accurately track when changes are made to its contents (not just when any Key is pressed, which seemed to add empty Undo/Redo items to the stack). Also added a max depth using a Python deque.
If we're changing the contents of the Entry via code rather than keyboard input, we can temporarily disable the trace (e.g. see in the undo method below).
Code:
class CEntry(tk.Entry):
def __init__(self, master, **kw):
super().__init__(master=master, **kw)
self._undo_stack = deque(maxlen=100)
self._redo_stack = deque(maxlen=100)
self.bind("<Control-z>", self.undo)
self.bind("<Control-y>", self.redo)
# traces whenever the Entry's contents are changed
self.tkvar = tk.StringVar()
self.config(textvariable=self.tkvar)
self.trace_id = self.tkvar.trace("w", self.on_changes)
self.reset_undo_stacks()
# USE THESE TO TURN TRACE OFF THEN BACK ON AGAIN
# self.tkvar.trace_vdelete("w", self.trace_id)
# self.trace_id = self.tkvar.trace("w", self.on_changes)
def undo(self, event=None): # noqa
if len(self._undo_stack) <= 1:
return
content = self._undo_stack.pop()
self._redo_stack.append(content)
content = self._undo_stack[-1]
self.tkvar.trace_vdelete("w", self.trace_id)
self.delete(0, tk.END)
self.insert(0, content)
self.trace_id = self.tkvar.trace("w", self.on_changes)
def redo(self, event=None): # noqa
if not self._redo_stack:
return
content = self._redo_stack.pop()
self._undo_stack.append(content)
self.tkvar.trace_vdelete("w", self.trace_id)
self.delete(0, tk.END)
self.insert(0, content)
self.trace_id = self.tkvar.trace("w", self.on_changes)
def on_changes(self, a=None, b=None, c=None): # noqa
self._undo_stack.append(self.tkvar.get())
self._redo_stack.clear()
def reset_undo_stacks(self):
self._undo_stack.clear()
self._redo_stack.clear()
self._undo_stack.append(self.tkvar.get())