How to Enable Button if there is Item in ComboBox in pyqt5? - python

Hi, in this image i want to enable start button when USB name appear in combo-Box and if the name does not appear the button should disabled automatically, Here is My Code.
def x(self):
if (self.comboBox_3.currentIndex() == -1):
self.pushButton_5.setEnabled(False)
else:
self.pushButton_5.setEnabled(True)

You must use the currentIndexChanged signal to evaluate that logic:
# __init__ method
# ...
self.comboBox_3.currentIndexChanged[int].connect(self.on_currentIndexChanged)
def on_currentIndexChanged(self, index):
self.pushButton_5.setEnabled(index != -1)

Related

PySide6 hidePopup function explanation please

I'm currently building a desktop application using some checkable comboboxes.
I've used the code snippet we can find easily on the web about how customizing comboboxes to make the items checkable.
It 's been working fine but I wanted each combobox list not to collapse once any item was selected.
That's where the virtual function hidePopup(self) comes in: I only had to define it within the checkable combobox class, without any code!!, to prevent the popup list from hiding... I don't understand why since there is absolutely no code but "pass" in my functions!
You'll find below the script from the checkable combobox class.
class CheckableComboBox(QtWidgets.QComboBox):
def __init__(self):
super().__init__()
self.liste_artisans = []
self.view().pressed.connect(self.handle_item_pressed)
self.setModel(QStandardItemModel(self))
def handle_item_pressed(self, index):
# getting which item is pressed
item = self.model().itemFromIndex(index)
# make it check if unchecked and vice-versa
if item.checkState() == Qt.Checked:
item.setCheckState(Qt.Unchecked)
else:
item.setCheckState(Qt.Checked)
self.checked_items_list()
def checked_items_list(self):
for i in range(self.count()):
text_label = self.model().item(i, 0).text()
if self.item_check_status(i) and text_label not in self.liste_artisans:
self.liste_artisans.append(text_label)
elif not self.item_check_status(i) and text_label in self.liste_artisans:
self.liste_artisans.remove(text_label)
def hidePopup(self):
pass
# method called by checked_items_list
def item_check_status(self, index):
# getting item at index
item = self.model().item(index, 0)
# return true if checked else false
return item.checkState() == Qt.Checked

Checkbox with persistent editor

I'm using a table to control the visibility and color of a plot. I would like a checkbox to toggle visibility and a drop-down to select the color. To this end, I have something like the following. It feels as through having a persistent editor prevents use of the checkbox.
The example is a bit contrived (in how the model/view are set up), but illustrates how the checkbox doesn't function while the editor is open.
How can I have a checkbox that can be used alongside a visible combobox? Is it better to use two columns?
import sys
from PyQt5 import QtWidgets, QtCore
class ComboDelegate(QtWidgets.QItemDelegate):
def __init__(self, parent):
super().__init__(parent=parent)
def createEditor(self, parent, option, index):
combo = QtWidgets.QComboBox(parent)
li = []
li.append("Red")
li.append("Green")
li.append("Blue")
li.append("Yellow")
li.append("Purple")
li.append("Orange")
combo.addItems(li)
combo.currentIndexChanged.connect(self.currentIndexChanged)
return combo
def setEditorData(self, editor, index):
editor.blockSignals(True)
data = index.model().data(index)
if data:
idx = int(data)
else:
idx = 0
editor.setCurrentIndex(0)
editor.blockSignals(False)
def setModelData(self, editor, model, index):
model.setData(index, editor.currentIndex())
#QtCore.pyqtSlot()
def currentIndexChanged(self):
self.commitData.emit(self.sender())
class PersistentEditorTableView(QtWidgets.QTableView):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
QtCore.pyqtSlot('QVariant', 'QVariant')
def data_changed(self, top_left, bottom_right):
for row in range(len(self.model().tableData)):
self.openPersistentEditor(self.model().index(row, 0))
class TableModel(QtCore.QAbstractTableModel):
def __init__(self, parent=None):
super(TableModel, self).__init__(parent)
self.tableData = [[1, 2, 3], [1, 2, 3], [1, 2, 3]]
self.checks = {}
def columnCount(self, *args):
return 3
def rowCount(self, *args):
return 3
def checkState(self, index):
if index in self.checks.keys():
return self.checks[index]
else:
return QtCore.Qt.Unchecked
def data(self, index, role=QtCore.Qt.DisplayRole):
row = index.row()
col = index.column()
if role == QtCore.Qt.DisplayRole:
return '{0}'.format(self.tableData[row][col])
elif role == QtCore.Qt.CheckStateRole and col == 0:
return self.checkState(QtCore.QPersistentModelIndex(index))
return None
def setData(self, index, value, role=QtCore.Qt.EditRole):
if not index.isValid():
return False
if role == QtCore.Qt.CheckStateRole:
self.checks[QtCore.QPersistentModelIndex(index)] = value
self.dataChanged.emit(index, index)
return True
return False
def flags(self, index):
fl = QtCore.QAbstractTableModel.flags(self, index)
if index.column() == 0:
fl |= QtCore.Qt.ItemIsEditable | QtCore.Qt.ItemIsUserCheckable
return fl
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
view = PersistentEditorTableView()
view.setItemDelegateForColumn(0, ComboDelegate(view))
model = TableModel()
view.setModel(model)
model.dataChanged.connect(view.data_changed)
model.layoutChanged.connect(view.data_changed)
index = model.createIndex(0, 0)
persistet_index = QtCore.QPersistentModelIndex(index)
model.checks[persistet_index] = QtCore.Qt.Checked
view.data_changed(index, index)
view.show()
sys.exit(app.exec_())
NOTE: After some rethinking and analysis of the Qt source code, I realized that my original answer, while valid, is a bit imprecise.
The problem relies on the fact that all events received on an index with an active editor are automatically sent to the editor, but since the editor could have a geometry that is smaller than the visual rect of the index, if a mouse event is sent outside the geometry that event is ignored and not processed by the view as it normally would without an editor.
UPDATE: In fact, the view does receive the event; the problem is that if an editor exists the event is automatically ignored furtherward (since it's assumed that the editor handled it) and nothing is sent to the editorEvent method of the delegate.
You can intercept "mouse edit" events as long as you implement the virtual edit(index, trigger, event) function (which is not the same as the edit(index) slot). Note that this means that if you override that function, you cannot call the default edit(index) anymore, unless you create a separate function that calls the default implementation (via super().edit(index)).
Consider that the following code works better if the delegate is actually a QStyledItemDelegate, instead of the simpler QItemDelegate class: the Qt dev team itself suggests to use the styled class instead of the basic one (which is intended for very basic or specific usage), as it's generally considered more consistent.
class PersistentEditorTableView(QtWidgets.QTableView):
# ...
def edit(self, index, trigger, event):
# if the edit involves an index change, there's no event
if (event and index.column() == 0 and
index.flags() & QtCore.Qt.ItemIsUserCheckable and
event.type() in (event.MouseButtonPress, event.MouseButtonDblClick) and
event.button() == QtCore.Qt.LeftButton):
opt = self.viewOptions()
opt.rect = self.visualRect(index)
opt.features |= opt.HasCheckIndicator
checkRect = self.style().subElementRect(
QtWidgets.QStyle.SE_ItemViewItemCheckIndicator,
opt, self)
if event.pos() in checkRect:
if index.data(QtCore.Qt.CheckStateRole):
state = QtCore.Qt.Unchecked
else:
state = QtCore.Qt.Checked
return self.model().setData(
index, state, QtCore.Qt.CheckStateRole)
return super().edit(index, trigger, event)
Obviously, you could do something similar by implementing the mousePressEvent on the view, but that could complicate things if you need some different implementation of the mouse press event also, and you should also consider the double click events. Implementing edit() is conceptually better, since it's more consistent with the purpose: clicking -> toggling.
There's only one last catch: keyboard events.
A persistent editor automatically grabs the keyboard focus, so you can't use any key (usually, the space bar) to toggle the state if the editor handles that event; since a combobox handles some "toggle" events to show its popup, those events won't be received by the view (the focus is on the combo, not on the view!) unless you ignore the event by properly implementing the eventFilter of the delegate for both keyboard events and focus changes.
Original answer
There are various possibilities to work around this:
as you proposed, use a separate column; this is not always possible or suggested, as the model structure could not allow it;
create an editor that also includes a QCheckBox; as long as there will always be an open editor, this is not an issue, but could create some level of inconsistency if editor could actually be open and destroyed;
add the combo to a container that has a fixed margin, so that mouse event not handled by the combo can be captured by the delegate event filter;
The third possibility is a bit more complex, but it ensures that both displaying and interaction are consistent with the normal behavior.
To achieve this, using a QStyledItemDelegate is suggested as it provides access to the style options.
class ComboDelegate(QtWidgets.QStyledItemDelegate):
# ...
def createEditor(self, parent, option, index):
option = QtWidgets.QStyleOptionViewItem(option)
self.initStyleOption(option, index)
style = option.widget.style()
textRect = style.subElementRect(
style.SE_ItemViewItemText, option, option.widget)
editor = QtWidgets.QWidget(parent)
editor.index = QtCore.QPersistentModelIndex(index)
layout = QtWidgets.QHBoxLayout(editor)
layout.setContentsMargins(textRect.left(), 0, 0, 0)
editor.combo = QtWidgets.QComboBox()
layout.addWidget(editor.combo)
editor.combo.addItems(
("Red", "Green", "Blue", "Yellow", "Purple", "Orange"))
editor.combo.currentIndexChanged.connect(self.currentIndexChanged)
return editor
def setEditorData(self, editor, index):
editor.combo.blockSignals(True)
data = index.model().data(index)
if data:
idx = int(data)
else:
idx = 0
editor.combo.setCurrentIndex(0)
editor.combo.blockSignals(False)
def eventFilter(self, editor, event):
if (event.type() in (event.MouseButtonPress, event.MouseButtonDblClick)
and event.button() == QtCore.Qt.LeftButton):
style = editor.style()
size = style.pixelMetric(style.PM_IndicatorWidth)
left = editor.layout().contentsMargins().left()
r = QtCore.QRect(
(left - size) / 2,
(editor.height() - size) / 2,
size, size)
if event.pos() in r:
model = editor.index.model()
index = QtCore.QModelIndex(editor.index)
if model.data(index, QtCore.Qt.CheckStateRole):
value = QtCore.Qt.Unchecked
else:
value = QtCore.Qt.Checked
model.setData(
index, value, QtCore.Qt.CheckStateRole)
return True
return super().eventFilter(editor, event)
def updateEditorGeometry(self, editor, opt, index):
# ensure that the editor fills the whole index rect
editor.setGeometry(opt.rect)
Unrelated, but still important:
Consider that the #pyqtSlot decorator is usually not required in normal situations like these. Also note that you missed the # for the data_changed decorator, and the signature is also invalid, since it's incompatible with the signals you've connected it to. A more correct slot decoration would have been the following:
#QtCore.pyqtSlot(QtCore.QModelIndex, QtCore.QModelIndex, 'QVector<int>')
#QtCore.pyqtSlot('QList<QPersistentModelIndex>', QtCore.QAbstractItemModel.LayoutChangeHint)
def data_changed(self, top_left, bottom_right):
# ...
With the first decoration for the dataChanged signal, and the second for layoutChanged. But, as said before, it's generally unnecessary to use slots as they usually are only required for better threading handling and sometimes provide slightly improved performance (which is not that important to this purpose).
Also note that if you want to ensure that there's always an open editor whenever the model changes its "layout", you should also connect to rowsInserted and columnsInserted signals, since those operations do not send the layout change signal.

Urwid: make cursor invisible

I'm using urwid, which is a Python "framework" for designing terminal user interfaces in ncurses. There's one thing though that I'm not able to do in urwid that was easy in curses - make the cursor invisible. As it is now, the cursor is visible when selecting buttons, and it just looks plain ugly. Is there a way to disable it?
I agree that the flashing cursor on an urwid.Button looks a bit lame, so I've come up with a solution to hide it. In urwid, the Button class is just a subclass of WidgetWrap containing a SelectableIcon and two Text widgets (the enclosing "<" and ">"). It's the SelectableIcon class that sets the cursor position to the first character of the label, by default. By subclassing SelectableIcon, modifying the cursor position and then wrapping it into an urwid.WidgetWrap subclass you can create your own custom button that can do all the tricks a built-in Button, or even more.
Here' what it looks like in my project.
import urwid
class ButtonLabel(urwid.SelectableIcon):
def __init__(self, text):
"""
Here's the trick:
we move the cursor out to the right of the label/text, so it doesn't show
"""
curs_pos = len(text) + 1
urwid.SelectableIcon.__init__(self, text, cursor_position=curs_pos)
Next, you can wrap a ButtonLabel object along with any other objects into a WidgetWrap subclass that will be your custom button class.
class FixedButton(urwid.WidgetWrap):
_selectable = True
signals = ["click"]
def __init__(self, label):
self.label = ButtonLabel(label)
# you could combine the ButtonLabel object with other widgets here
display_widget = self.label
urwid.WidgetWrap.__init__(self, urwid.AttrMap(display_widget, None, focus_map="button_reversed"))
def keypress(self, size, key):
"""
catch all the keys you want to handle here
and emit the click signal along with any data
"""
pass
def set_label(self, new_label):
# we can set the label at run time, if necessary
self.label.set_text(str(new_label))
def mouse_event(self, size, event, button, col, row, focus):
"""
handle any mouse events here
and emit the click signal along with any data
"""
pass
In this code, there is actually not much combination of widgets in the FixedButton WidgetWrap subclass, but you could add a "[" and "]" to the edges of the button, wrap it into a LineBox, etc. If all this is superfluous, you can just move the event handling functions into the ButtonLabel class, and make it emit a signal when it gets clicked.
To make the button reversed when the user moves on it, wrap it into AttrMap and set the focus_map to some palette entry ("button_reversed", in my case).
Building upon Drunken Master's answer, I've cleaned up the solution as much as possible.
The urwid.SelectableIcon is basically an urwid.Text field with this ugly blinking cursor.
So instead of overriding the urwid.SelectableIcon and packing it into an urwid.WidgetWrap, let's take an urwid.Text directly and make it selectable and react to button/mouse activation (inspired from urwid's simple menu tutorial):
import urwid
choices = u'Chapman Cleese Gilliam Idle Jones Palin'.split()
class ListEntry(urwid.Text):
_selectable = True
signals = ["click"]
def keypress(self, size, key):
"""
Send 'click' signal on 'activate' command.
"""
if self._command_map[key] != urwid.ACTIVATE:
return key
self._emit('click')
def mouse_event(self, size, event, button, x, y, focus):
"""
Send 'click' signal on button 1 press.
"""
if button != 1 or not urwid.util.is_mouse_press(event):
return False
self._emit('click')
return True
def menu(title, choices):
body = [urwid.Text(title), urwid.Divider()]
for c in choices:
button = ListEntry(c)
urwid.connect_signal(button, 'click', item_chosen, c)
body.append(urwid.AttrMap(button, None, focus_map='reversed'))
return urwid.ListBox(urwid.SimpleFocusListWalker(body))
def item_chosen(button, choice):
response = urwid.Text([u'You chose ', choice, u'\n'])
done = ListEntry(u'Ok')
urwid.connect_signal(done, 'click', exit_program)
main.original_widget = urwid.Filler(urwid.Pile([response,
urwid.AttrMap(done, None, focus_map='reversed')]))
def exit_program(button):
raise urwid.ExitMainLoop()
main = urwid.Padding(menu(u'Pythons', choices), left=2, right=2)
top = urwid.Overlay(main, urwid.SolidFill(u'\N{MEDIUM SHADE}'),
align='center', width=('relative', 60),
valign='middle', height=('relative', 60),
min_width=20, min_height=9)
urwid.MainLoop(top, palette=[('reversed', 'standout', '')]).run()
Works like a charm:
urwid uses the curs_set function, but does not expose it as a class method anywhere. Someone could modify urwid to allow using this method; otherwise there's no reliable method of doing this.
You might report it as an issue.
Along the lines of Drunken Master's answer, but with "minimally invasive surgery":
class ButtonLabel(urwid.SelectableIcon):
'''
use Drunken Master's trick to move the cursor out of view
'''
def set_text(self, label):
'''
set_text is invoked by Button.set_label
'''
self.__super.set_text(label)
self._cursor_position = len(label) + 1
class MyButton(urwid.Button):
'''
- override __init__ to use our ButtonLabel instead of urwid.SelectableIcon
- make button_left and button_right plain strings and variable width -
any string, including an empty string, can be set and displayed
- otherwise, we leave Button behaviour unchanged
'''
button_left = "["
button_right = "]"
def __init__(self, label, on_press=None, user_data=None):
self._label = ButtonLabel("")
cols = urwid.Columns([
('fixed', len(self.button_left), urwid.Text(self.button_left)),
self._label,
('fixed', len(self.button_right), urwid.Text(self.button_right))],
dividechars=1)
super(urwid.Button, self).__init__(cols)
if on_press:
urwid.connect_signal(self, 'click', on_press, user_data)
self.set_label(label)
Here, we only modify the button's appearance but otherwise leave its behaviour unchanged.

Get input from custom dialog with entry, ok, and cancel button and return - Python/GTK3

In Python with gobject, I am having immense issues getting input from the user.
Here is my code:
def get_network_pw(self, e):
def okClicked(self):
print(pwd.get_text())
return pwd.get_text()
pwDialog.destroy()
def cancelClicked(self):
print("nope!")
pwDialog.hide()
return None
#Getting the about dialog from UI.glade
pwDialog = self.builder.get_object("passwordDialog")
okBtn = self.builder.get_object("pwdOkBtn")
cancelBtn = self.builder.get_object("pwdCancelBtn")
pwd = self.builder.get_object("userEntry")
# Opening the about dialog.
#okBtn.connect("clicked", okClicked)
#cancelBtn.connect("clicked", cancelClicked)
pwDialog.run()
I am not sure where I am going wrong? It refuses to return the userEntry text. I also tried the code from Python/Gtk3 : How to add a Gtk.Entry to a Gtk.MessageDialog? and Simple, versatile and re-usable entry dialog (sometimes referred to as input dialog) in PyGTK to no avail.
EDIT
I have a dialog I made in glade. It contains a GtkTextBox (userEntry), an Ok button (pwdOkBtn) and a cancel button (pwdCancelBtn). When the user clicks OK, it theoretically should return what they entered in the text box (say, 1234). When they click cancel, it should return None. However, when they click Ok, it returns "", and when they click cancel, it returns "". I'm not sure where I am going wrong here.
Extra code tries:
I tried the following code as well:
def get_network_pw(self, e):
d = GetInputDialog(None, "Enter Password")
dialog = d.run()
if dialog is 1:
print("OK")
else:
print("Nope!")
d.hide()
GetInputDialog:
class GetInputDialog(Gtk.Dialog):
def __init__(self, parent, title):
Gtk.Dialog._init(self, title, parent)
self.response = "Cancel"
self.setupHeader()
self.setupUI()
def setupUI(self):
wdg = self.get_content_area() #explained bellow
self.txtSource = Gtk.Entry() #create a text entry
wdg.add(self.txtSource)
self.show_all() #show the dialog and all children
def setupHeader(self, title="Get User Input"):
hb = Gtk.HeaderBar()
hb.props.show_close_button = True
hb.props.title = title
self.set_titlebar(hb)
btnOk = Gtk.Button("OK")
btnOk.connect("clicked", self.btnOkClicked)
hb.pack_start(btnOk)
btnCancel = Gtk.Button("Cancel")
btnCancel.connect("clicked", self.btnCancelClicked)
hb.pack_start(btnCancel)
def btnOkClicked(self, e):
self.response = "Ok" #set the response var
dst = self.txtSource #get the entry with the url
txt = dst.get_text()
return 1
def btnCancelClicked(self, e):
self.response = "Cancel"
return -1
I think you're overcomplicating it. The run method returns the id of the button pressed in a dialog:
Don't use the .hide() and .destroy() methods in that way, those are for different situations. .destroy() destroys the widget, so you should not call it unless you know what you're doing.
Place the .hide() after the .run().
Capture the return value of the run(), and setup the buttons in the dialog to a different Response ID in Glade.
The relevant part of the code is:
def _btn_cb(self, widget, data=None):
"""
Button callback
"""
ret = self.dialog.run()
self.dialog.hide()
if ret == 0:
self.label.set_text(
self.entry.get_text()
)
The full code for this example is here:
https://gist.github.com/carlos-jenkins/c27bf6d5d76723a4b415
Extra: If you want to check a condition to accept the Ok button (don't know, for example that the entry is valid) execute the run() in a while loop, check if button is Cancel then break, else check the validity of the input, if valid do something and break, else continue:
def _btn_cb(self, widget, data=None):
"""
Button callback
"""
while True:
ret = self.dialog.run()
if ret == -1: # Cancel
break
try:
entry = int(self.entry.get_text())
self.label.set_text(str(entry))
break
except ValueError:
# Show in an error dialog or whatever
print('Input is not an integer!')
self.dialog.hide()

PyGTK: access buttons in gtk.MessageDialog?

I have a function that creates prompts using gtk.MessageDialog in PyGTK. How could I access the predefined buttons? Or would I need to manually construct a gtk.Dialog? I'd rather not, seeing as MessageDialog is a convenience function.
The function:
def gtkPrompt(self, name):
# Create new GTK dialog with all the fixings
prompt = gtk.MessageDialog(None, 0, gtk.MESSAGE_QUESTION, gtk.BUTTONS_OK_CANCEL, name)
# Set title of dialog
prompt.set_title("Prompt")
# Create and add entry box to dialog
entry = gtk.Entry()
prompt.vbox.add(entry)
# Show all widgets in prompt
prompt.show_all()
# Run dialog until user clicks OK or Cancel
if prompt.run() == gtk.RESPONSE_CANCEL:
# User cancelled dialog
rval = False
else:
# User clicked OK, grab text from entry box
rval = entry.get_text()
# Destory prompt
prompt.destroy()
# Give the good (or bad) news
return rval
You can use get_children() to get to the "OK" button:
def yesNoDialog(window, message, default=False):
dialog=gtk.MessageDialog(window, gtk.DIALOG_MODAL |
gtk.DIALOG_DESTROY_WITH_PARENT,
gtk.MESSAGE_QUESTION,
gtk.BUTTONS_YES_NO, message)
if default:
h_button_box=dialog.vbox.get_children()[1]
yes_button=h_button_box.get_children()[0]
yes_button.grab_default()
response=dialog.run()
dialog.destroy()
if response==gtk.RESPONSE_YES:
return True
else:
return False
Since 2.22 you can use get_widget_for_response() method. For example:
cancelButton = dialog.get_widget_for_response(response_id=gtk.RESPONSE_CANCEL)
gtk.MessageDialog is a subclass of gtk.Dialog. gtk.Dialog objects store their buttons in a gtk.HBox under the action_area attribute.
In code:
> prompt.action_area.get_children()
[<gtk.Button object at 0x18c0aa0 (GtkButton at 0x130e990)>, <gtk.Button object at 0x18c0af0 (GtkButton at 0x130e8d0)>]

Categories

Resources