Urwid: make cursor invisible - python

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.

Related

How to get functional combobox in QTableView

As the title suggests I'm looking to get a combobox in a QTableView.
I've looked at several other questions that deal with comboboxes in tableviews, but they mostly concern comboboxes as editors and that is not what I'm looking for.
I would like the combobox to be visible at all times and get its data from the underlying model. It doesn't have to set data in the model.
I tried to adapt this example of a progress bar delegate: How to include a column of progress bars within a QTableView?
Leading to this code:
class ComboDelegate(QStyledItemDelegate):
def paint(self, painter, option, index):
combo = QStyleOptionComboBox()
combo.rect = option.rect
combo.currentText = 'hallo' # just for testing
combo.editable = True
combo.frame = False
QApplication.style().drawComplexControl(QStyle.CC_ComboBox, combo, painter)
But this gives me a greyed out, non functional combobox.
How do you get a functional combobox there?
As the name suggests, the paint() function only draws a combobox, it does not create one.
If you want a persistent widget set for an item, and that widget doesn't need to update the model, then you should use setIndexWidget().
A basic implementation on a static model could be like this:
class SomeWidget(QWidget):
def __init__(self):
# ...
self.table.setModel(someModel)
for row in range(someModel.rowCount()):
combo = QComboBox(editable=True)
combo.addItem('hallo')
self.table.setIndexWidget(someModel.index(row, 2), combo)
If the model can change at runtime (and is possibly empty at startup), then you need to connect to the rowsInserted signal:
class SomeWidget(QWidget):
def __init__(self):
# ...
self.updateCombos()
someModel.rowsInserted.connect(self.updateCombos)
def updateCombos(self):
for row in range(self.table.model().rowCount()):
index = someModel.index(row, 2)
if self.table.indexWidget(index):
continue
combo = QComboBox(editable=True)
combo.addItem('hallo')
self.table.setIndexWidget(index, combo)
Then you can access any combo based on the index row:
def getComboValue(self, row):
index = self.table.model().index(row, 2)
widget = self.table.indexWidget(index)
if isinstance(widget, QComboBox):
return widget.currentText()
Remember: whenever you're studying the documentation, you must also review the documentation of all inherited classes. In this case you should not only read the docs about QTableView, but also the whole inheritance tree: QTableView > QAbstractItemView > QAbstractScrollArea > QFrame > QWidget > QObject and QPaintDevice.

As soon as python class receives QMessageBox.Information as argument, resizing by stylesheet won't work

In python, I created a class “ScrollMessageBoxShowRC“ , which receives three arguments (see below):
result = ScrollMessageBoxShowRC(QMessageBox.Information, '', '')
result.exec_()
Originally, I instantiated the class with solely "None" as argument. As long as it received only "None", I could resize the class as follows: self.setStyleSheet("QScrollArea{min-width:410 px; min-height: 600px}"), see below:
class ScrollMessageBoxShowRC(QMessageBox):
def __init__(self, *args, **kwargs):
QMessageBox.__init__(self, *args, **kwargs)
self.setWindowTitle("Contacts to view or to delete.")
scroll = QScrollArea(self)
scroll.setWidgetResizable(True)
self.content = QWidget()
scroll.setWidget(self.content)
lay = QVBoxLayout(self.content)
lay.setStyleSheet("min-width: 100px;");
dlts = {}
self.x = {}
for rc in dbaccess.allRC():
dlt = QCheckBox('delete', self)
dlt.stateChanged.connect(partial(self.btnstateDel, dlt, dlts))
dlt.setObjectName(rc[9])
qb = QPushButton(rc[9], self)
qb.released.connect(partial(self.button_releasedRC, rc[9]))
lay.addWidget(qb)
lay.addWidget(dlt)
self.buttonClicked.connect(self.msgButtonClickDel)
self.layout().addWidget(scroll, 0, 0, 1, self.layout().columnCount())
self.setStyleSheet("QScrollArea{min-width:410 px; min-height: 600px}")
def btnstateDel(self, dlt, dlts):
dlts[dlt.objectName()] = False
if dlt.isChecked:
dlts[dlt.objectName()] = True
self.x = dlts
def msgButtonClickDel(self, i):
if i.text() == "OK":
dbaccess.deleteRCs(self.x)
def button_releasedRC(self, nameshow):
pass
Since I changed the arguments to QMessageBox.Information, '', '' the stylesheet setting the size of the Widget seems no longer to have been in vigour. I could not find out why this is the case. Could anybody give me a hint what I might have overlooked?
QMessageBox is a special type of QDialog that creates its own layout on execution. When created without arguments, it behaves almost like a basic QDialog, but as soon as elements are added (most importantly, the icon), the internal layout takes precedence and in certain cases is also completely deleted a recreated.
Since QMessageBox is generally intended as a convenience simple class, and it seems that the only actual specific feature used here is the icon, there's no need to use it at all.
A basic QDialog can be used instead, and the icon can be loaded using the style, just like QMessageBox does. Then you can add buttons using QDialogButtonBox.
class ScrollMessageBoxShowRC(QDialog):
def __init__(self, *args, **kwargs):
QDialog.__init__(self, *args, **kwargs)
layout = QGridLayout(self)
icon = self.style().standardIcon(
QStyle.SP_MessageBoxInformation, None, self)
iconSize = self.style().pixelMetric(
QStyle.PM_MessageBoxIconSize, None, self)
self.iconLabel = QLabel(pixmap=icon.pixmap(iconSize, iconSize))
layout.addWidget(self.iconLabel, 0, 0,
alignment=Qt.AlignLeft|Qt.AlignTop)
self.scroll = QScrollArea()
layout.addWidget(self.scroll, 0, 1)
# ...
self.buttonBox = QDialogButtonBox(QDialogButtonBox.Ok)
layout.addWidget(self.buttonBox,
layout.rowCount(), 0, 1, layout.columnCount())
self.buttonBox.accepted.connect(self.accept)
self.buttonBox.rejected.connect(self.reject)
def accept(self):
dbaccess.deleteRCs(self.x)
super().accept()
The icons of QMessageBox are listed in the StandardPixmap enum of QStyle.
Further important notes:
you should not use string matching for buttons, especially if the names are set by the library: "OK" can be a different string in other systems (for instance, on my computer the "k" is lowercase);
even if you're using the local dlts in partial, you're actually always updating self.x, which makes it pointless to have two dictionaries and also sending them to the connected function;
the released signal is emitted whenever a button is released, which can also happen when the user presses the mouse on the button and moves the mouse outside it while keeping the mouse pressed; use clicked instead;
isChecked is a function, you should use if dlt.isChecked():, or, better dlts[dlt.objectName()] = dlt.isChecked(), instead of setting it to False every time before actually checking;
stateChanged is used for three state check boxes that can be partially checked; if you only need a bool value, use toggled;
for basic signals that are directly (not queued, like in threads) connected to functions for which the behavior is known and specific for those signal senders, you can use self.sender() instead of partials; in this way, you can also directly use the signal arguments instead of querying the property; if you're not interested in the argument, ignore it in the function signature; always use sender() with care, though;
# ...
self.dlts = {}
for rc in dbaccess.allRC():
dlt = QCheckBox('delete', self)
dlt.toggled.connect(self.btnstateDel)
dlt.setObjectName(rc[9])
qb = QPushButton(rc[9], self)
qb.clicked.connect(self.button_releasedRC)
lay.addWidget(qb)
lay.addWidget(dlt)
def btnstateDel(self, checked):
self.dlts[self.sender().objectName()] = checked
def button_releasedRC(self):
nameshow = self.sender()
# ...

Dynamically add QTableView to dynamically created tab pages (QTabWidget)

I am trying to have a series of QTableView created at runtime and added to newly created pages of a multipage QTabWidget.
All seems to go fine, but the QTableView don't show up.
The QTabWidget gets zeroed (reset to no pages) and refurbished (...) flawlessly (at least it looks like so) depending on the selection of a combobox (and the dictionaries therein related).
I am also using a delegate callback to include a column of checkboxes to the QTableView (thanks to https://stackoverflow.com/a/50314085/7710452), which works fine stand alone.
Here is the code.
Main Window
EDIT
as recommended by eyllanesc, here is the standalone module (jump to the end of the post for details on the part I think is problematic):
"""
qt5 template
"""
import os
import sys
from PyQt5 import QtWidgets as qtw
from PyQt5 import QtCore as qtc
from PyQt5 import QtGui as qtg
from PyQt5 import uic
from configparser import ConfigParser, ExtendedInterpolation
from lib.SearchControllers import findGuis, get_controller_dict, show_critical, show_exception
import resources.resources
from lib.CheckBoxesDelegate import CheckBoxDelegate
myForm_2, baseClass = uic.loadUiType('./forms/setup.ui')
class MainWindow(baseClass):
def __init__(self, config_obj: ConfigParser,
config_name: str,
proj_name: str,
*args,
**kwargs):
super().__init__(*args, **kwargs)
self.ui = myForm_2()
self.ui.setupUi(self)
# your code begins here
self.setWindowTitle(proj_name + " Setup")
self.ui.logo_lbl.setPixmap(qtg.QPixmap(':/logo_Small.png'))
self.config_obj = config_obj
self.config_name = config_name
self.proj_filename = proj_name
self.proj_config = ConfigParser(interpolation=ExtendedInterpolation())
self.proj_config.read(proj_name)
self.guis_dict = {}
self.components = {}
self.cdp_signals = {}
self.root_path = self.config_obj['active']['controllers']
self.tableViews = []
self.tabs = []
self.iniControllersBox()
self.setActSignals()
self.load_bulk()
self.set_signals_table()
self.update_CurController_lbl()
self.update_ControllersTab() # here is where the action gets hot
# your code ends here
self.show() # here crashes if I passed the new tab to the instance of
# QTabView. otherwise it shows empty tabs
#########################################################
def load_bulk(self):
# get the list of running components into a dictionary
for i in self.list_controllers:
i_path = os.path.join(self.root_path, i)
print(i)
self.components[i] = get_controller_dict(i_path,
self.config_obj,
'Application.xml',
'Subcomponents/Subcomponent',
'Name',
'src')
for j in self.components[i]:
print(j)
signals_key = (i , j)
tgt = os.path.join(self.root_path, self.components[i][j])
self.cdp_signals[signals_key] = get_controller_dict(i_path,
self.config_obj,
self.components[i][j],
'Signals/Signal',
'Name',
'Type',
'Routing')
def set_signals_table(self):
self.ui.MonitoredDevicesTable.setHorizontalHeaderItem(0, qtw.QTableWidgetItem('GUI caption'))
self.ui.MonitoredDevicesTable.setHorizontalHeaderItem(1, qtw.QTableWidgetItem('Monitored Signal'))
def setActSignals(self):
self.ui.controllersBox.currentIndexChanged.connect(self.update_guis_list)
self.ui.controllersBox.currentIndexChanged.connect(self.update_CurController_lbl)
self.ui.controllersBox.currentIndexChanged.connect(self.update_ControllersTab)
def update_ControllersTab(self):
self.ui.componentsTab.clear() # this is the QTabWidget
self.tabs = []
self.tableViews = []
curr_controller = self.ui.controllersBox.currentText()
for i in self.components[curr_controller]:
if len(self.cdp_signals[curr_controller, i]) == 0:
continue
self.tabs.append(qtw.QWidget())
tabs_index = len(self.tabs) - 1
header_labels = ['', 'Signal', 'Type', 'Routing', 'Input']
model = qtg.QStandardItemModel(len(self.cdp_signals[curr_controller, i]), 5)
model.setHorizontalHeaderLabels(header_labels)
# in the next line I try to create a new QTableView passing
# the last tab as parameter, in the attempt to embed the QTableView
# into the QWidget Tab
self.tableViews.append(qtw.QTableView(self.tabs[tabs_index]))
tbw_Index = len(self.tableViews) - 1
self.tableViews[tbw_Index].setModel(model)
delegate = CheckBoxDelegate(None)
self.tableViews[tbw_Index].setItemDelegateForColumn(0, delegate)
rowCount = 0
for row in self.cdp_signals[curr_controller, i]:
for col in range(len(self.cdp_signals[curr_controller, i][row])):
index = model.index(rowCount, col, qtc.QModelIndex())
model.setData(index, self.cdp_signals[curr_controller, i][row][col])
try:
self.ui.componentsTab.addTab(self.tabs[tabs_index], i) # no problems, some controllers ask up to
except Exception as ex:
print(ex)
def update_CurController_lbl(self):
self.ui.active_controller_lbl.setText(self.ui.controllersBox.currentText())
def iniControllersBox(self):
self.list_controllers = [os.path.basename(f.path) for f in os.scandir(self.root_path) if f.is_dir() and str(
f.path).upper().endswith('NC')]
self.ui.controllersBox.addItems(self.list_controllers)
for i in range(self.ui.controllersBox.count()):
self.ui.controllersBox.setCurrentIndex(i)
newKey = self.ui.controllersBox.currentText()
cur_cntrlr = os.path.join(self.config_obj['active']['controllers'], self.ui.controllersBox.currentText())
self.guis_dict[newKey] = findGuis(cur_cntrlr, self.config_obj)
self.ui.controllersBox.setCurrentIndex(0)
self.update_guis_list()
def update_guis_list(self, index=0):
self.ui.GuisListBox.clear()
self.ui.GuisListBox.addItems(self.guis_dict[self.ui.controllersBox.currentText()])
if __name__ == '__main__':
config = ConfigParser()
config.read('./config.ini')
app = qtw.QApplication([sys.argv])
w = MainWindow(config, './config.ini',
'./test_setup_1.proj')
sys.exit(app.exec_())
and here the external to add the checkboxes column:
class CheckBoxDelegate(QtWidgets.QItemDelegate):
"""
A delegate that places a fully functioning QCheckBox cell of the column to which it's applied.
"""
def __init__(self, parent):
QtWidgets.QItemDelegate.__init__(self, parent)
def createEditor(self, parent, option, index):
"""
Important, otherwise an editor is created if the user clicks in this cell.
"""
return None
def paint(self, painter, option, index):
"""
Paint a checkbox without the label.
"""
self.drawCheck(painter, option, option.rect, QtCore.Qt.Unchecked if int(index.data()) == 0 else QtCore.Qt.Checked)
def editorEvent(self, event, model, option, index):
'''
Change the data in the model and the state of the checkbox
if the user presses the left mousebutton and this cell is editable. Otherwise do nothing.
'''
if not int(index.flags() & QtCore.Qt.ItemIsEditable) > 0:
return False
if event.type() == QtCore.QEvent.MouseButtonRelease and event.button() == QtCore.Qt.LeftButton:
# Change the checkbox-state
self.setModelData(None, model, index)
return True
if event.type() == QtCore.QEvent.MouseButtonPress or event.type() == QtCore.QEvent.MouseMove:
return False
return False
def setModelData (self, editor, model, index):
'''
The user wanted to change the old state in the opposite.
'''
model.setData(index, 1 if int(index.data()) == 0 else 0, QtCore.Qt.EditRole)
The 1st picture shows the layout in QTDesigner, the 2nd the result (emtpy tabs) when avoiding the crashing.
the QTabWidget has no problems in zeroing, or scale up, back to as many tab as I need, it's just that I have no clue on how to show the QTabview. My approach was to try to embed the QTabView in the tabpage passing it as parameter to the line creating the new QTabView.
Since I am using rather convoluted dictionaries, calling an XML parser to fill them up, not to mention the config files, I know even this version of my script is hardly reproduceable/runnable.
If someone had the patience of focusing on the update_ControllersTab method though, and tell me what I am doing wrong handling the QWidgets, it'd be great.
Again the basic idea is to clear the QTabWidget any time the user selects a different controller (combo box on the left):
self.ui.componentsTab.clear() # this is the QTabWidget
self.tabs = [] # list to hold QTabView QWidgets (pages) throughout the scope
self.tableViews = [] # list to hold QTabView(s) thorughout the scope
count how many tabs (pages) and hence embedded TabViews I need with the new controllers selected.
and then for each tab needed:
create a new tab (page)
self.tabs.append(qtw.QWidget())
tabs_index = len(self.tabs) - 1
create a new QTabView using a model:
header_labels = ['', 'Signal', 'Type', 'Routing', 'Input']
model = qtg.QStandardItemModel(len(self.cdp_signals[curr_controller, i]), 5)
model.setHorizontalHeaderLabels(header_labels)
self.tableViews.append(qtw.QTableView(self.tabs[tabs_index]))
tbw_Index = len(self.tableViews) - 1
self.tableViews[tbw_Index].setModel(model)
populate the TableView with data, and then finally add the tab widget (with the suppposedly embedded QTableView to the QTabWidget (the i argument is a string from my dbases Names:
self.ui.componentsTab.addTab(self.tabs[tabs_index], i)
This method is called also by the __init__ to initialize and apparently all goes error free, until the last 'init' statement:
`self.show()`
at which point the app crashes with:
Process finished with exit code 1073741845
on the other hand, if here instead of trying to embed the QTableView:
self.tableViews.append(qtw.QTableView(self.tabs[tabs_index]))
I omit the parameter, that is:
self.tableViews.append(qtw.QTableView())
the app doesn't crash anymore, but of course no QtableViews are shown, only empty tabpages:
As stupid as this may sound the problem is in... the delegate class that creates the checkboxes in the first column (see https://stackoverflow.com/a/50314085/7710452)
I commented out those two lines:
delegate = CheckBoxDelegate(None)
self.tableViews[tbw_Index].setItemDelegateForColumn(0, delegate)
and... bingo!
the CheckBoxDelegate works fine in the example shown in the post (a single QTableView form). I also tinkered around adding columns and rows, and moving the checkbox column back and forth with no problems. In that standalone. But as soon as I add the class and set the delegate, i am back at square zero, app crashing with:
Process finished with exit code 1073741845
so I am left with this problem now. Thnx to whomever read this.
Problem solved, see comment to post above.

Python .NET WinForms - How to pass info from a textbox to a button click event

Before I get into my question, I am (self) learning how Python and the .NET CLR interact with each other. It has been a fun, yet, at times, a frustrating experience.
With that said, I am playing around on a .NET WinForm that should just simply pass data that is typed into a text box and display it via a message box. Learning how to do this should propel me into other means of passing data. This simple task seems to elude me and I can't seem to find any good documentation on how tyo accomplish this. Has anyone attempted this? If so, I am willing to learn so if someone could point me in the right direction or give me a hint as to what I've done wrong?
PS - I have done some coding in C#.NET and VB.NET so the passing of the variables seems like it should be enough but apparently it isn't.
import clr
clr.AddReference("System.Windows.Forms")
clr.AddReference("System.Drawing")
from System.Windows.Forms import *
from System.Drawing import *
class MyForm(Form):
def __init__(self):
# Setup the form
self.Text = "Test Form"
self.StartPosition = FormStartPosition.CenterScreen # https://learn.microsoft.com/en-us/dotnet/api/system.windows.forms.form.startposition?view=net-5.0
# Create label(s)
lbl = Label()
lbl.Parent = self
lbl.Location = Point(15,15) # From Left, From Top
lbl.Text = "Enter text below"
lbl.Size = Size(lbl.PreferredWidth, lbl.PreferredHeight)
# Create textbox(s)
txt = TextBox()
txt.Parent = self
txt.Location = Point(lbl.Left - 1, lbl.Bottom + 2) # From Left, From Top
# Create button(s)
btn = Button()
btn.Parent = self
btn.Location = Point(txt.Left - 1, txt.Bottom + 2) # From Left, From Top
btn.Text = "Click Me!"
btn.Click += self.buttonPressed
def buttonPressed(self, sender, args):
MessageBox.Show('This works.')
MessageBox.Show(txt.Text) # This does not
Application.EnableVisualStyles()
Application.SetCompatibleTextRenderingDefault(False)
form = MyForm()
Application.Run(form)
txt is a local variable in __init__, meaning that you can't access it from any other function. To fix it, make it an instance variable by attaching it to self (which refers to the instance itself):
self.txt = TextBox()
self.txt.Parent = self
self.txt.Location = Point(lbl.Left - 1, lbl.Bottom + 2) # From Left, From Top
and
def buttonPressed(self, sender, args):
MessageBox.Show('This works.')
MessageBox.Show(self.txt.Text) # Now this does too

wx.Radiobox.SetBackgroundColour("pink") won't change the color of RadioButton until mouse pass by

I've built a wx.Dialog, with some wx.Radiobox need to validate. If user doesn't make a choice, the validator wouldn't return True and popup a wx.MessageBox tells user that you should choose something. Meanwhile, the background color should have changed to pink.
The problem here is when using RadioBox.SetBackgroundColour("pink"), the Background color of RadioBox's label text changed to pink, which is perfectly normal, but the background color for RadioButton's label text won't change until mouse pass by.
I'm wondering why this and how to fix it. I've looked up in official docs and googled, but find nothing. Anyone have idea?
The platform is win8.1x64 and python 2.7.3 and wxpython 2.8.12.1. For using Psychopy standalone package, the version of both python and wxpython are quite old.
The Dialog as follow, a quite simple one :
class Dlg(wx.Dialog):
"""A simple dialogue box."""
def __init__(self):
global app
app = wx.PySimpleApp()
wx.Dialog.__init__(self, None,-1)
self.sizer = wx.FlexGridSizer(cols=1)
def show(self, cancelBTN=False):
"""Show a dialog with 2 RadioBox"""
# add two RadioBox
RadioBox1 = wx.RadioBox(self, -1, label="Wierd color", choices=["", "choice A", "choice B"], validator=RadioObjectValidator())
RadioBox2 = wx.RadioBox(self, -1, label="Wierd color", choices=["", "choice A", "choice B"], validator=RadioObjectValidator())
RadioBox1.ShowItem(0, show=False) # Hide the first choice
RadioBox2.ShowItem(0, show=False) # Hide the first choice
RadioBox1.GetValue = RadioBox1.GetStringSelection
RadioBox2.GetValue = RadioBox2.GetStringSelection
self.sizer.Add(RadioBox1, 1, wx.ALIGN_LEFT)
self.sizer.Add(RadioBox2, 1, wx.ALIGN_LEFT)
# add buttons for OK
self.sizer.Add(wx.Button(self, wx.ID_OK), 1, flag=wx.ALIGN_RIGHT)
self.SetSizerAndFit(self.sizer)
if self.ShowModal() == wx.ID_OK:
pass
# do something
self.Destroy()
And Validator as this:
class RadioObjectValidator(wx.PyValidator):
""" This validator is used to ensure that the user has entered something
into the text object editor dialog's text field.
"""
def __init__(self):
""" Standard constructor.
"""
wx.PyValidator.__init__(self)
def Clone(self):
""" Standard cloner.
Note that every validator must implement the Clone() method.
"""
return RadioObjectValidator()
def Validate(self, win):
""" Validate the contents of the given RadioBox control.
"""
RadioBox = self.GetWindow()
choice = RadioBox.GetValue()
if not choice:
wx.MessageBox(u'Choose something please', u'info', wx.OK | wx.ICON_EXCLAMATION)
RadioBox.SetBackgroundColour("pink")
RadioBox.SetFocus()
RadioBox.Refresh()
return False
else:
RadioBox.SetBackgroundColour(
wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
RadioBox.Refresh()
return True
def TransferToWindow(self):
""" Transfer data from validator to window.
The default implementation returns False, indicating that an error
occurred. We simply return True, as we don't do any data transfer.
"""
return True # Prevent wxDialog from complaining.
def TransferFromWindow(self):
""" Transfer data from window to validator.
The default implementation returns False, indicating that an error
occurred. We simply return True, as we don't do any data transfer.
"""
return True # Prevent wxDialog from complaining.
And simply run like this:
if __name__ == "__main__":
test = Dlg()
test.show()
Well, it's a bug in wxpython2.8.12.1, and has fixed in wxpython3.0.0.0, Thanks #GreenAsJade for reminding.

Categories

Resources