I'm trying to port this IronPython WPF example to CPython with python.NET.
I'm able to get the window running after fixing some known issues (adding __namespace__ to the ViewModel class, using Single Thread Apartment and updating the syntax of pyevent.py to python3), but bindings are ignored: I can write in the textbox, but no events gets triggered, and the button doesn't fire the OnClick method.
Here's the full code:
import clr
clr.AddReference(r'wpf\PresentationFramework')
clr.AddReference('PresentationCore')
from System.Windows.Markup import XamlReader
from System.Windows import Application, Window
from System.ComponentModel import INotifyPropertyChanged, PropertyChangedEventArgs
import pyevent
from System.Threading import Thread, ThreadStart, ApartmentState
XAML_str = """<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Sync Paramanager" Height="180" Width="400">
<StackPanel x:Name="DataPanel" Orientation="Horizontal">
<Label Content="Size"/>
<Label Content="{Binding size}"/>
<TextBox x:Name="tbSize" Text="{Binding size, UpdateSourceTrigger=PropertyChanged}" />
<Button x:Name="Button" Content="Set Initial Value"></Button>
</StackPanel>
</Window>"""
class notify_property(property):
def __init__(self, getter):
def newgetter(slf):
#return None when the property does not exist yet
try:
return getter(slf)
except AttributeError:
return None
super().__init__(newgetter)
def setter(self, setter):
def newsetter(slf, newvalue):
# do not change value if the new value is the same
# trigger PropertyChanged event when value changes
oldvalue = self.fget(slf)
if oldvalue != newvalue:
setter(slf, newvalue)
slf.OnPropertyChanged(setter.__name__)
return property(
fget=self.fget,
fset=newsetter,
fdel=self.fdel,
doc=self.__doc__)
class NotifyPropertyChangedBase(INotifyPropertyChanged):
__namespace__ = "NotifyPropertyChangedBase"
PropertyChanged = None
def __init__(self):
self.PropertyChanged, self._propertyChangedCaller = pyevent.make_event()
def add_PropertyChanged(self, value):
self.PropertyChanged += value
def remove_PropertyChanged(self, value):
self.PropertyChanged -= value
def OnPropertyChanged(self, propertyName):
if self.PropertyChanged is not None:
self._propertyChangedCaller(self, PropertyChangedEventArgs(propertyName))
class ViewModel(NotifyPropertyChangedBase):
__namespace__ = "WpfViewModel"
def __init__(self):
super(ViewModel, self).__init__()
# must be string to two-way binding work correctly
self.size = '10'
#notify_property
def size(self):
return self._size
#size.setter
def size(self, value):
self._size = value
print(f'Size changed to {self.size}')
class TestWPF(object):
def __init__(self):
self._vm = ViewModel()
self.root = XamlReader.Parse(XAML_str)
self.DataPanel.DataContext = self._vm
self.Button.Click += self.OnClick
def OnClick(self, sender, event):
# must be string to two-way binding work correctly
self._vm.size = '10'
def __getattr__(self, name):
# provides easy access to XAML elements (e.g. self.Button)
return self.root.FindName(name)
def main():
tw = TestWPF()
app = Application()
app.Run(tw.root)
if __name__ == '__main__':
thread = Thread(ThreadStart(main))
thread.SetApartmentState(ApartmentState.STA)
thread.Start()
thread.Join()
It seems that assigning the ModelView to DataPanel's DataContext doesn't trigger any binding registration, but I have no clue on how to fix that.
Am I missing something obvious?
Unfortunately pythonnet does not support defining binding in the xaml file.
Updated Question
I think my original quandary might be a result of the structure of my PyQt app. The way I've approached creating a GUI is to divide the larger widget into smaller pieces, each given their own class until the parts are simple enough. Because of this, I end up with a ton of nesting, as a large widget holds instances of smaller widgets, and those hold their own even smaller widgets. It makes it hard to navigate data around the app.
How should a PyQt app be structured so that it is simple to understand in code and yet has a structure containing very little nesting? I haven't found many examples of this around, so I'm sort of stuck. The code example in my original question shows a pretty good example of the structure I'm currently using, which has a large amount of nesting.
Info on program
The GUI is used to create a set of parameters for running a test. The options in each setting should correspond to a binary number, and all of the binary numbers indicated by each set of options are collected, formed into a single sequence of binary numbers, and passed on. Changes to settings do not have to be carried over between sessions, as each new session will most likely correspond to a new test (and thus a new set of choices for settings).
The basic flow of the app should be that upon opening it, all available settings (about 20 total) are set to their default values. A user can go through and change whatever settings they would like, and once they're done they can press a "Generate" button to gather all of the binary numbers corresponding to the settings and create the command. It would be very helpful to have a live preview of individual bits that updates as settings are changed, which is why updates must be immediate.
Some settings are dependent on other; for instance, Setting A has 4 options, and if option 3 is selected, Setting B should be made visible, otherwise it is invisible.
Original Question
I'm definitely a beginner to PyQt, so I don't quite know if I've worded my question correctly, but here goes. I've got a GUI wherein I'm attempting to take a bunch of different settings, keep track of what number was selected from each setting, and then pass the number up to an object that keeps track of all of the numbers from all of the settings. The trouble is that I don't know the best way to get all the individual settings values up my tree of classes, so to speak. Here's the structure of my GUI so far:
Bottom: individual custom QWidgets, each responsible for a single setting. Each has a signal that fires whenever the value it returns changes.
Middle: a QWidget containing ~7-10 individual settings each. These collect settings into related groups.
Top: a QTabWidget that places each instance of a setting group into an individual tab. This widget also contains an object that should ideally collect all of the settings from individual groups into it.
My question is how do I get the values from the bottom layer signals to the top layer widget? My only idea is to connect all of the signals from those small setting widgets to a signal in the middle layer, and connect the middle layer signal to something in the top layer. This sort of chaining seems crazy, though.
I'm running PyQt5 and Python 3.7.
Here's some stripped down code which hopefully shows what I want to do.
class TabWindow(QTabWidget):
def __init__(self):
super().__init__()
self.tabs = [SettingsGroup1, SettingsGroup2, SettingsGroup3]
self.setting_storage = { # dictionary is where I'd like to store all settings values
# 'setting name': setting value
}
for tab in self.tabs:
self.addTab(tab, 'Example')
class SettingsGroup(QWidget):
def __init__(self):
super().__init__()
# not shown: layout created for widget
self.settings = []
def add_to_group(self, new_setting):
self.settings.append(new_setting)
# not shown: add setting to the layout
class SettingsGroup1(SettingsGroup):
def __init__(self):
super().__init__()
self.add_to_group([Setting1, Setting2, Setting3])
class SettingsGroup2(SettingsGroup):...
class SettingsGroup3(SettingsGroup):...
class Setting(QWidget):
val_signal = pyqtSignal([int], name='valChanged')
def __init__(self, name):
self.val = None
self.name = name
def set_val(self, new_val):
self.val = new_val
self.val_signal.emit(self.val) # <-- the signal I want to pass up
class Setting1(Setting):
def __init__(self, name):
super().__init__(name)
# not shown: create custom setting layout/interface
class Setting2(Setting):...
class Setting3(Setting):...
I use a lot of inheritance (SettingsGroup -> SettingsGroup1, 2, 3) because each subclass will have its own functions and internal dependencies that are unique to it. For each Setting subclass, for instance, there is a different user interface.
Thanks for any help provided!
EDIT: The question has been updated in the meantime, I've added a solution that's more specific at the bottom of this answer.
I feel like this question is slightly "opinion based", but since I've had my share of similar situations I'd like to propose my suggestions. In these situations it's important to understand that there's not one good way to do things, but many ways to do it wrong.
Original answer
An idea could be to create a common signal interface for every "level", which will get that signal and send it back to its parent by adding its own name to keep track of the setting "path"; the topmost widget will then evaluate the changes accordingly.
In this example every tab "group" has its own valueChanged signal, which includes the group name, setting name and value; the source signal is fired from the "source" (a spinbox, in this case), then it follows its parents which, in turn "add" their name in turn.
Keep in mind that you can also just use a generalized pyqtSignal(object) for every parent and connect it with widget.valueChanged.connect(self.valueChanged), and then track its group and setting by walking by self.sender() parents backwards.
As a final notice, if you are using these values for application settings, remember that Qt already provides the QSettings API, which can be used as a common and OS-transparent interface for every configuration you need to set (and remember between sessions) in your application. I implemented it in the example, but I suggest you to read its documentation to better understand how it works.
import sys
from PyQt5 import QtCore, QtWidgets
class SettingWidget(QtWidgets.QWidget):
valueChanged = QtCore.pyqtSignal(int)
def __init__(self, name):
super().__init__()
self.settings = QtCore.QSettings()
self.val = 0
self.name = name
layout = QtWidgets.QVBoxLayout()
self.setLayout(layout)
layout.addWidget(QtWidgets.QLabel(self.name))
self.spinBox = QtWidgets.QSpinBox()
layout.addWidget(self.spinBox)
self.spinBox.valueChanged.connect(self.set_val)
def set_val(self, new_val):
if self.val != new_val:
self.val = new_val
self.valueChanged.emit(self.val)
# enter a setting group, ensuring that same name settings won't
# be mismatched; this allows a single sub level setting only
self.settings.beginGroup(self.parent().name)
self.settings.setValue(self.name, new_val)
# leave the setting group. THIS IS IMPORTANT!!!
self.settings.endGroup()
class SettingWidget1(SettingWidget):
def __init__(self):
super().__init__('Setting1')
class SettingWidget2(SettingWidget):
def __init__(self):
super().__init__('Setting2')
class SettingWidget3(SettingWidget):
def __init__(self):
super().__init__('Setting3')
class SettingsGroup(QtWidgets.QWidget):
# create two signal signatures, the first sends the full "path",
# while the last will just send the value
valueChanged = QtCore.pyqtSignal([str, str, int], [int])
def __init__(self, name):
super().__init__()
self.name = name
layout = QtWidgets.QHBoxLayout()
self.setLayout(layout)
def add_to_group(self, new_setting):
widget = new_setting()
# emit both signal signatures
widget.valueChanged.connect(
lambda value, name=widget.name: self.valueChanged.emit(
self.name, name, value))
widget.valueChanged.connect(self.valueChanged[int])
self.layout().addWidget(widget)
class SettingsGroup1(SettingsGroup):
def __init__(self):
super().__init__('Group1')
self.add_to_group(SettingWidget1)
self.add_to_group(SettingWidget2)
class SettingsGroup2(SettingsGroup):
def __init__(self):
super().__init__('Group2')
self.add_to_group(SettingWidget3)
class TabWidget(QtWidgets.QTabWidget):
def __init__(self):
QtWidgets.QTabWidget.__init__(self)
self.settings = QtCore.QSettings()
self.tabs = [SettingsGroup1, SettingsGroup2]
self.settingsDict = {}
for tab in self.tabs:
widget = tab()
self.addTab(widget, widget.__class__.__name__)
widget.valueChanged[str, str, int].connect(self.valueChangedFullPath)
widget.valueChanged[int].connect(self.valueChangedOnly)
def valueChangedFullPath(self, group, setting, value):
# update the settings dict; if the group key doesn't exist, create it
try:
self.settingsDict[group][setting] = value
except:
self.settingsDict[group] = {setting: value}
settingsData = [group, setting, value]
print('Full path result: {}'.format(settingsData))
# Apply setting from here, instead of using the SettingWidget
# settings.setValue() option; this allows a single sub level only
# self.applySetting(data)
def valueChangedOnly(self, value):
parent = sender = self.sender()
# sender() returns the last signal sender, so we need to track down its
# source; keep in mind that this is *not* a suggested approach, as
# tracking the source might result in recursion if the sender's sender
# is not one of its children; this system also has issues if you're
# using a Qt.DirectConnection from a thread different from the one that
# emitted it
while parent.sender() in sender.children():
parent = sender.sender()
widgetPath = []
while parent not in self.children():
widgetPath.insert(0, parent)
parent = parent.parent()
settingsData = [w.name for w in widgetPath] + [value]
print('Single value result: {}'.format(settingsData))
# similar to valueChangedFullPath(), but with this implementation more
# nested "levels" can be used instead
# self.applySetting(settingsData)
def applySetting(self, settingsData):
# walk up to the next to last of settingsData levels, assuming they are
# all parent group section names
for count, group in enumerate(settingsData[:-2], 1):
self.settings.beginGroup(group)
# set the setting name settingsData[-2] to its value settingsData[-1]
self.settings.setValue(*settingsData[-2:])
for g in range(count):
self.settings.endGroup()
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
# set both Organization and Application name to make settings persistent
app.setOrganizationName('StackOverflow')
app.setApplicationName('Example')
w = TabWidget()
w.show()
sys.exit(app.exec_())
Alternate solution, based on updated answer
Since the answer has become more specific in its update, I'm adding another suggestion.
As far as we can understand now, you don't need that level of "nested" classes, but more specifically designed code that can be reused according to your purposes. Also, since you're using binary based data, it makes things a bit (pun intended) easier, as long as you know how bit operation works (which I assume you do) and the setting "widgets" don't require specific GUI customization.
In this example I created just one "setting" class and one "group" class, and their instancies are created only according to their names and default values.
import sys
from PyQt5 import QtCore, QtWidgets
defaultValues = '0010101', '1001010', '000111'
# set bit lengths for each setting; be careful in ensuring that each
# setting group has the full default value bit length!
groups = [
['Group 1', [1, 3, 2, 1]],
['Group 2', [1, 2, 2, 1, 1]],
['Group 1', [2, 1, 2, 1]],
]
class BinaryWidget(QtWidgets.QFrame):
changed = QtCore.pyqtSignal()
def __init__(self, name, index, defaults='0'):
QtWidgets.QFrame.__init__(self)
self.setFrameShape(self.StyledPanel|self.Sunken)
layout = QtWidgets.QGridLayout()
self.setLayout(layout)
self.index = index
self.defaults = defaults
self.buttons = []
# use the "defaults" length to create buttons
for i in range(len(defaults)):
value = int(defaults[i], 2) & 1
# I used QToolButtons as they're usually smaller than QPushButtons
btn = QtWidgets.QToolButton()
btn.setText(str(value))
layout.addWidget(btn, 1, i)
btn.setCheckable(True)
btn.setChecked(value)
btn.toggled.connect(self.changed)
# show the binary value on change, just for conveniency
btn.toggled.connect(lambda v, btn=btn: btn.setText(str(int(v))))
self.buttons.append(btn)
layout.addWidget(QtWidgets.QLabel(name), 0, 0, 1, layout.columnCount())
def value(self):
# return the correct value of all widget's buttons; they're reversed
# because of how bit shifting works
v = 0
for i, btn in enumerate(reversed(self.buttons)):
v += btn.isChecked() << i
# bit shift again, according to the actual "setting" bit index
return v << self.index
def resetValues(self):
oldValue = self.value()
self.blockSignals(True)
for i, value in enumerate(self.defaults):
self.buttons[i].setChecked(int(self.defaults[i], 2) & 1)
self.blockSignals(False)
newValue = self.value()
# emit the changed signal only once, and only if values actually changed
if oldValue != newValue:
self.changed.emit()
class Group(QtWidgets.QWidget):
changed = QtCore.pyqtSignal()
def __init__(self, name, defaults=None, lenghts=None):
QtWidgets.QWidget.__init__(self)
layout = QtWidgets.QHBoxLayout()
self.setLayout(layout)
self.name = name
self.bitLength = 0
self.widgets = []
if defaults is not None:
self.addOptions(defaults, lenghts)
def value(self):
v = 0
for widget in self.widgets:
v += widget.value()
return v
def addOption(self, name, index, default='0'):
widget = BinaryWidget(name, index, default)
self.layout().addWidget(widget)
self.widgets.append(widget)
widget.changed.connect(self.changed)
self.bitLength += len(default)
def addOptions(self, defaults, lenghts = None):
if lenghts is None:
lenghts = [1] * len(defaults)
# reverse bit order for per-setting indexing
defaultsIndex = 0
bitIndex = len(defaults)
for i, l in enumerate(lenghts):
self.addOption(
'Setting {}'.format(i + 1),
bitIndex - l,
defaults[defaultsIndex:defaultsIndex + l])
bitIndex -= l
defaultsIndex += l
def resetValues(self):
for widget in self.widgets:
widget.resetValues()
class Tester(QtWidgets.QWidget):
def __init__(self):
QtWidgets.QWidget.__init__(self)
layout = QtWidgets.QGridLayout()
self.setLayout(layout)
self.tabWidget = QtWidgets.QTabWidget()
layout.addWidget(self.tabWidget)
resultLayout = QtWidgets.QHBoxLayout()
layout.addLayout(resultLayout, layout.rowCount(), 0, 1, layout.columnCount())
self.tabs = []
self.labels = []
for (group, lenghts), defaults in zip(groups, defaultValues):
tab = Group(group, defaults, lenghts)
self.tabWidget.addTab(tab, group)
tab.changed.connect(self.updateResults)
self.tabs.append(tab)
tabLabel = QtWidgets.QLabel()
self.labels.append(tabLabel)
resultLayout.addWidget(tabLabel)
self.resetButton = QtWidgets.QPushButton('Reset values')
layout.addWidget(self.resetButton)
self.resetButton.clicked.connect(lambda: [tab.resetValues() for tab in self.tabs])
self.updateResults()
def values(self):
return [tab.value() for tab in self.tabs]
def updateResults(self):
for value, tab, label in zip(self.values(), self.tabs, self.labels):
label.setText('''
{0}: <span style="font-family:monospace;">{1} <b>{1:0{2}b}</b></span>
'''.format(tab.name, value, tab.bitLength))
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
w = Tester()
w.show()
sys.exit(app.exec_())
I am designing a program composed of a 3D viewer and a table with Python 3.4 and PySide bindings.
I have created a TableView with this class:
from PySide import QtGui
from PySide.QtCore import Qt
class MyTableView(QtGui.QWidget):
def __init__(self, parent=None):
super(MyTableView, self).__init__()
self.parent = parent
self.title = "Results"
self.initUI()
def initUI(self):
self.grid = QtGui.QGridLayout(self)
self.table = QtGui.QTableView()
self.grid.addWidget(self.table, 0, 0)
# Configure table
self.table.verticalHeader().setVisible(False)
self.table.horizontalHeader().setDefaultAlignment(Qt.AlignLeft)
self.table.setSortingEnabled(True)
self.table.setAlternatingRowColors(True)
self.table.setShowGrid(False)
self.table.setEditTriggers(QtGui.QAbstractItemView.NoEditTriggers)
self.table.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows)
and the model with this other class:
class MyModel(QStandardItemModel):
def __init__(self, path, *args, **kwargs):
super(MyModel, self).__init__()
self.path = path
self.parse()
def parse(self):
with open(self.path) as f:
self.mydata = yaml.load(f)
self.setColumnCount(len(self.mydata['headers']) + 1)
self.setHorizontalHeaderLabels(
['ID'] + self.mydata['headers'])
row = 0
for ind, val in self.mydata['rows'].items():
col = 0
self.insertRow(row)
self.setItem(row, col, QStandardItem(ind))
for v in val:
col += 1
self.setItem(row, col, QStandardItem(str(v)))
row += 1
which are then tied together in this controller:
from PySide.QtCore import Qt
class MyController(object):
def __init__(self, model, view):
self.model = model
self.tableview = view.table
self.fill_table()
self.connect_signals()
def fill_table(self):
self.tableview.setModel(self.model)
self.tableview.sortByColumn(0, Qt.AscendingOrder)
def connect_signals(self):
selectionModel = self.tableview.selectionModel()
selectionModel.selectionChanged.connect(self.selection_changed)
def selection_changed(self, selected, deselected):
print("Selection changed.")
Then, the program is executed through this script:
def main():
app = QtGui.QApplication(sys.argv)
MyController(MyModel(sys.argv[1]), MyView())
sys.exit(app.exec_())
if __name__ == '__main__':
main()
(Note that I haven't posted the main window class, but you get the idea)
The table gets rendered OK, but I am not able to connect the selectionChanged signal to the handler (which should udpate the viewer, but for testing purposes it's only a print statement).
What am I doing wrong? Thanks!
[EDIT]
I have discovered that it works if I use a lambda function to call the handler method. Can someone explain why?!
selectionModel.selectionChanged.connect(lambda: self.selection_changed(selectionModel.selectedRows()))
I tried to implement what you wrote and it worked - so I can't be 100% sure of why you are having trouble. But I suspect it is because of what I had to fix to get it to work at all: I had to sort out some problems you have with garbage collection.
In the example code you give, you create a MyController, a MyModel and a MyView. But they will all then be garbage collected (in CPython) since you don't keep a reference to them. If you add a reference to MyController
my_controller = MyController(MyModel(sys.argv[1]), MyView())
you are almost there, but I think the MyTableView might also then be garbage collected since the controller only keeps a reference to the QTableVIew not the MyTableView.
Presumably using the lanbda function changes the references you are preserving - it preserves the controller and the selection model - and that may be why it is working in that case.
Generally it's a good idea to use the Qt parenting mechanism. If you simply parented all these objects on the main window (or their natural parent widget) that would have prevented most of these problems.
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 trivial or elegant way to differentiate between many same-type signal sources in PySide/PyQt?
I am learning PySide. I have written simple application, which multiplies two numbers from two different QLineEdit() objects. Result is displayed in third QLineEdit.
Multiplier and multiplicand QLineEdit.textChanged() signals are connected to one method (TxtChanged). In this method i have to differentiate between signal sources. After some trials I figured out some workaround based upon placeholder text (4 lines below "is there another way?" comment in my code)
code:
import sys
from PySide import QtGui, QtCore
class myGUI(QtGui.QWidget):
def __init__(self, *args, **kwargs):
QtGui.QWidget.__init__(self, *args, **kwargs)
self.multiplier = 0
self.multiplicand = 0
self.myGUIInit()
def myGUIInit(self):
# input forms
a1_label = QtGui.QLabel("a1")
a1_edit = QtGui.QLineEdit()
a1_edit.setPlaceholderText("a1")
a2_label = QtGui.QLabel("a2")
a2_edit = QtGui.QLineEdit()
a2_edit.setPlaceholderText("a2")
# output form
a1a2_label = QtGui.QLabel("a1*a2")
self.a1a2_edit = QtGui.QLineEdit()
self.a1a2_edit.setReadOnly(True)
# forms events
a1_edit.textChanged.connect(self.TxtChanged)
a2_edit.textChanged.connect(self.TxtChanged)
# grid
grid = QtGui.QGridLayout()
grid.setSpacing(10)
grid.addWidget(a1_label,1,0)
grid.addWidget(a1_edit,1,1)
grid.addWidget(a2_label,2,0)
grid.addWidget(a2_edit,2,1)
grid.addWidget(a1a2_label,3,0)
grid.addWidget(self.a1a2_edit,3,1)
self.setLayout(grid)
self.setGeometry(100,100,200,200)
self.setWindowTitle("a*b")
self.show()
def TxtChanged(self,text):
sender = self.sender()
sender_text = sender.text()
if sender_text == '': sender_text = '0'
# is there another way?
if sender.placeholderText() == 'a1':
self.multiplicand = sender_text
else:
self.multiplier = sender_text
product = int(self.multiplier) * int(self.multiplicand)
print(self.multiplier,self.multiplicand,product)
self.a1a2_edit.setText(str(product))
def main():
app = QtGui.QApplication(sys.argv)
mainWindow = myGUI()
sys.exit(app.exec_())
main()
best regards,
ostrzysz
You can use the functools.partial function - and therefore connect your signals to straight to your method/function but rather to a python object which will automatically call your function with some extra data you pass it:
from functools import partial
...
....
a1_edit.textChanged.connect(partial(self.TxtChanged, a1_edit))
a2_edit.textChanged.connect(partial(self.TxtChanged, a2_edit))
...
def TxtChanged(self,sender, text):
# and here you have the "sender" parameter as it was filled in the call to "partial"
...
partials is part of the stdlib, and is very readable, but one can always use lambda instead of partial for the same effect -
a1_edit.textChanged.connect(lambda text: self.TxtChanged(a1_edit, text))
In this way the object yielded by the lambda expression will be a temporary function that will use the values for "self" and "a1_edit" from the current local variables (at the time the button is clicked), and the variable named "text" will be supplied by Pyside's callback.
One thing that bugs me most in your code is that you are using placeholderText to differentiate. QObjects has another property called objectName that is more suitable for your task. And, you don't need to use sender.text() to get the text of QLineEdit. textChanged already sends it, so you will have it in your text parameter.
Also, using a dictionary instead of two separate variables (multiplier and multiplicand) will simplify your code further.
Here is the changed code:
class myGUI(QtGui.QWidget):
def __init__(self, *args, **kwargs):
QtGui.QWidget.__init__(self, *args, **kwargs)
self.data = {"multiplier": 0,
"multiplicand": 0}
self.myGUIInit()
def myGUIInit(self):
a1_label = QtGui.QLabel("a1")
a1_edit = QtGui.QLineEdit()
a1_edit.setObjectName("multiplicand")
a2_label = QtGui.QLabel("a2")
a2_edit = QtGui.QLineEdit()
a2_edit.setObjectName("multiplier")
# skipped the rest because same
def TxtChanged(self, text):
sender = self.sender()
# casting to int while assigning seems logical.
self.data[sender.objectName()] = int(text)
product = self.data["multiplier"] * self.data["multiplicand"]
print(self.data["multiplier"], self.data["multiplicand"], product)
self.a1a2_edit.setText(str(product))
Although #jsbueno and #Avaris answered your direct question about signal sources, I wouldn't relay on this sources in your concrete case. You can make instance members a1_edit and a2_edit:
...
self.a1_edit = QtGui.QLineEdit()
...
self.a2_edit = QtGui.QLineEdit()
...
It will simplify your TxtChanged function:
def TxtChanged(self,text):
try:
multiplier = int(self.a1_edit.text())
multiplicand = int(self.a2_edit.text())
except ValueError:
self.a1a2_edit.setText('Enter two numbers')
return
product = multiplier * multiplicand
print(multiplier, multiplicand, product)
self.a1a2_edit.setText(str(product))
Also, instead of handling ValueError exception, you can use QIntValidator for input controls:
self.int_validator = QtGui.QIntValidator()
self.a1_edit.setValidator(self.int_validator)
self.a2_edit.setValidator(self.int_validator)