I'm trying to add a readout of the cursor position in a pqytplot plotwidget in PyQt5. I found this code which does what I want, but in a stand-alone window all within one program file:
import pyqtgraph as pg
from pyqtgraph.Qt import QtGui, QtCore
#generate layout
app = QtGui.QApplication([])
win = pg.GraphicsWindow()
label = pg.LabelItem(justify='right')
win.addItem(label)
p1 = win.addPlot(row=1, col=0)
data1 = [n**2 for n in range(100)]
p1.plot(data1, pen="r")
#cross hair
vLine = pg.InfiniteLine(angle=90, movable=False)
hLine = pg.InfiniteLine(angle=0, movable=False)
p1.addItem(vLine, ignoreBounds=True)
p1.addItem(hLine, ignoreBounds=True)
def mouseMoved(evt):
pos = evt[0] ## using signal proxy turns original arguments into a tuple
if p1.sceneBoundingRect().contains(pos):
mousePoint = p1.vb.mapSceneToView(pos)
index = int(mousePoint.x())
if index > 0 and index < len(data1):
label.setText("<span style='font-size: 12pt'>x=%0.1f, <span style='color: red'>y1=%0.1f</span>" % (mousePoint.x(), data1[index]))
vLine.setPos(mousePoint.x())
hLine.setPos(mousePoint.y())
proxy = pg.SignalProxy(p1.scene().sigMouseMoved, rateLimit=60, slot=mouseMoved)
## Start Qt event loop unless running in interactive mode or using pyside.
if __name__ == '__main__':
import sys
if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
QtGui.QApplication.instance().exec_()
The problem I'm running in to is figuring out how to implement something like this with my GUI - where I will have to pass reference to the plotwidget to the mouseMoved function. In the example above, the mousemoved function has access to hline, vline and p1, but in my code it won't - I need to be able to pass those through. But I have no idea how to do that.
I've tried to replicate this issue with the smallest amount of code possible. First here's a simple UI file for the GUI, called CursorLayout.ui
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1167</width>
<height>443</height>
</rect>
</property>
<property name="windowTitle">
<string>MainWindow</string>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0">
<layout class="QVBoxLayout" name="verticalLayout_6">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_16">
<property name="sizeConstraint">
<enum>QLayout::SetFixedSize</enum>
</property>
<item>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<widget class="QPushButton" name="startbutton">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Plot</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_5">
<item>
<widget class="PlotWidget" name="plotWidget" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>300</height>
</size>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3"/>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_17">
<property name="spacing">
<number>0</number>
</property>
<item>
<widget class="QPushButton" name="exitbutton">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Exit</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</widget>
</widget>
<customwidgets>
<customwidget>
<class>PlotWidget</class>
<extends>QWidget</extends>
<header location="global">pyqtgraph</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>
The main program is this:
from PyQt5 import uic
from PyQt5.QtWidgets import QApplication, QMainWindow
from initGUI import connecttolayout, setinitialview
class UI(QMainWindow):
def __init__(self):
super(UI, self).__init__()
uic.loadUi("CursorLayout.ui", self) #load GUI layout file created with QtDesigner
connecttolayout(self) # connect code to elements in UI file
setinitialview(self) # set initial view (button/label visibility, default values, etc)
self.show()
def clickedstartButton(self): #action if start button clicked
self.plotWidget.clear()
plotx = range(100)
ploty = [number**2 for number in plotx]
thisline = self.plotWidget.plot(plotx, ploty, pen='r')
QApplication.processEvents()
def clickedexitButton(self):
self.close()
app=QApplication([])
UIWindow=UI()
app.exec()
with file containing code to set up the gui, initGUI.py (not necessarily how you would do this, but this is to mimic the file structure of my larger program):
from PyQt5.QtWidgets import QPushButton
import pyqtgraph as pg
def connecttolayout(self): #connect GUI elements to elements in UI file
self.startButton = self.findChild(QPushButton, "startbutton")
self.exitButton = self.findChild(QPushButton, "exitbutton")
self.startButton.clicked.connect(self.clickedstartButton)
self.exitButton.clicked.connect(self.clickedexitButton)
def mouseMoved(evt):
pos = evt[0] ## using signal proxy turns original arguments into a tuple
if self.plotWidget.sceneBoundingRect().contains(pos):
mousePoint = self.plotWidget.vb.mapSceneToView(pos)
index = int(mousePoint.x())
#if index > 0 and index < len(data1):
if index > 0 and index < self.MFmax:
self.cursorlabel.setText("<span style='font-size: 12pt'>x=%0.1f, <span style='color: red'>y=%0.1f</span>" % (
mousePoint.x(), mousePoint.y()))
self.vLine.setPos(mousePoint.x())
self.hLine.setPos(mousePoint.y())
def setinitialview(self): #set initial view to pvst view and clear plot window
#set plot initial configuration
self.plotWidget.setBackground('w')
self.plotWidget.setLabels(left=('Pressure', 'Torr'))
self.plotWidget.setLabel('left',color='black',size=30)
self.plotWidget.setLabels(bottom=('Time', 's'))
self.plotWidget.setLabel('bottom',color='black',size=30)
self.plotWidget.clear()
# cross hair
self.vLine = pg.InfiniteLine(angle=90, movable=False)
self.hLine = pg.InfiniteLine(angle=0, movable=False)
self.plotWidget.addItem(self.vLine, ignoreBounds=True)
self.plotWidget.addItem(self.hLine, ignoreBounds=True)
self.cursorlabel = pg.LabelItem(justify='right')
proxy = pg.SignalProxy(self.plotWidget.scene().sigMouseMoved, rateLimit=60, slot=mouseMoved)
I'm actually surprised my attempt doesn't cause an error - pressing the plot button does create a plot, but it definitely doesn't create the cursor in the graph in the GUI.
How do I get the necessary info passed to the mouseMoved function?
There are a few little errors that will make your program fail:
The mouseMoved() function has to be inside your widget class because it needs the evt argument, which is generated in the widget.
The self.MFmax variable/constant is not created anywhere
In this line:
mousePoint = self.plotWidget.vb.mapSceneToView(pos)
The PlotWidget object doesn't have the vb attribute. It is a PlotItem's attribute, then you should change that line to this:
mousePoint = self.plotWidget.plotItem.vb.mapSceneToView(pos)
Pyqtgraph recommends here to use TextItem instead of LabelItem, to display text inside a scaled view, because of its scaling size.
Now, with that said and reorganizing your code to be more legible, here is my solution to your code (you only need the UI file and this script):
import sys
import pyqtgraph as pg
from pyqtgraph.Qt import QtGui, uic
ui_file = uic.loadUiType("CursorLayout.ui")[0]
class UI(QtGui.QMainWindow, ui_file):
def __init__(self):
## Inherit the QMainWindow and ui_file classes
QtGui.QMainWindow.__init__(self)
ui_file.__init__(self)
self.setupUi(self)
## Create aditional widgets
self.plot_item = self.plotWidget.plot()
self.vLine = pg.InfiniteLine(angle=90, movable=False)
self.hLine = pg.InfiniteLine(angle=0, movable=False)
self.cursorlabel = pg.TextItem(anchor=(-1,10))
## Build the rest of the GUI
self.format_plot()
## data
self.plotx = range(100)
self.ploty = [number**2 for number in self.plotx]
## Connect signals to actions
self.startbutton.clicked.connect(self.clickedstartButton)
self.exitbutton.clicked.connect(self.clickedexitButton)
self.plotWidget.scene().sigMouseMoved.connect(self.mouseMoved)
## OVERWRITE the mouseMoved action:
def mouseMoved(self, evt):
pos = evt
if self.plotWidget.sceneBoundingRect().contains(pos):
mousePoint = self.plotWidget.plotItem.vb.mapSceneToView(pos)
index = int(mousePoint.x())
if index > 0 and index < len(self.plotx):
# if index > 0 and index < self.MFmax:
self.cursorlabel.setHtml(
"<span style='font-size: 12pt'>x={:0.1f}, \
<span style='color: red'>y={:0.1f}</span>".format(
mousePoint.x(), mousePoint.y()))
self.vLine.setPos(mousePoint.x())
self.hLine.setPos(mousePoint.y())
def clickedstartButton(self): #action if start button clicked
self.plot_item.setData(self.plotx, self.ploty, pen='r')
self.plotWidget.addItem(self.cursorlabel)
def clickedexitButton(self):
self.close()
def format_plot(self):
self.plotWidget.setBackground('w')
self.plotWidget.setLabels(left=('Pressure', 'Torr'))
self.plotWidget.setLabel('left',color='black',size=30)
self.plotWidget.setLabels(bottom=('Time', 's'))
self.plotWidget.setLabel('bottom',color='black',size=30)
self.plotWidget.addItem(self.vLine, ignoreBounds=True)
self.plotWidget.addItem(self.hLine, ignoreBounds=True)
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
window = UI()
window.show()
sys.exit(app.exec_())
The code above will make the "crosshair" (the hline and vline) to follow your mouse and displaying the coordinates of that position, like this:
If you want the "crosshair" to track the points in the curve based on the x-axis position of your cursor, you can change the mouseMoved() function to this:
def mouseMoved(self, evt):
pos = evt
if self.plotWidget.sceneBoundingRect().contains(pos):
mousePoint = self.plotWidget.plotItem.vb.mapSceneToView(pos)
mx = np.array([abs(i-mousePoint.x()) for i in self.plotx])
index = mx.argmin()
if index >= 0 and index < len(self.plotx):
self.cursorlabel.setHtml(
"<span style='font-size: 12pt'>x={:0.1f}, \
<span style='color: red'>y={:0.1f}</span>".format(
self.plotx[index], self.ploty[index])
)
self.vLine.setPos(self.plotx[index])
self.hLine.setPos(self.ploty[index])
And this will be the result:
Performance of mx calculation in mouseMoved can be largly improved in order to get a faster response of the cursor:
def mouseMoved(self, evt):
pos = evt
if self.plotWidget.sceneBoundingRect().contains(pos):
mousePoint = self.plotWidget.plotItem.vb.mapSceneToView(pos)
mx = abs(np.ones(len(self.plotx))*mousePoint.x() - self.plotx)
index = mx.argmin()
if index >= 0 and index < len(self.plotx):
self.cursorlabel.setHtml(
"<span style='font-size: 12pt'>x={:0.1f}, \
<span style='color: red'>y={:0.1f}</span>".format(
self.plotx[index], self.ploty[index])
)
self.vLine.setPos(self.plotx[index])
self.hLine.setPos(self.ploty[index])
I want to create a custom output formatter in BS4 that will rearrange the order of attributes of tags in an XML in a specific way this is not alphabetical order.
For instance, I want to output the following tag:
<word form="συ" head="2610" id="2357" lemma="συ" postag="p-s----n-" relation="ExD_AP"/>
as:
<word id="2357" head="2610" postag="p-s----n-" form="συ" lemma="συ" relation="ExD_AP"/>
BS4's documentation offers a clue as to where to begin. They give the following example:
from bs4.formatter import HTMLFormatter
class UnsortedAttributes(HTMLFormatter):
def attributes(self, tag):
for k, v in tag.attrs.items():
if k == 'm':
continue
yield k, v
print(attr_soup.p.encode(formatter=UnsortedAttributes()))
This will make a custom HTML output formatter that will leave attributes in the order they were input and also ignore certain tags, but I don't know how to alter this so that it will output in whatever order I would like. Can anyone help me out?
How about this?
from simplified_scrapy.simplified_doc import SimplifiedDoc
html ='''
<word form="συ" head="2610" id="2357" lemma="συ" postag="p-s----n-" relation="ExD_AP"/>
'''
def toString(ele):
order = ['id','head','postag','from','lemma','relation']
result = '<'+ele.tag
for p in order:
result+=' {}="{}"'.format(p,ele[p])
return result+'/>'
doc = SimplifiedDoc(html)
ele = doc.word
print (toString(ele))
Result:
<word id="2357" head="2610" postag="p-s----n-" from="None" lemma="συ" relation="ExD_AP"/>
Strictly speaking, I have an answer to my own question, but it's going to take more work to actually implement it in a way I'd like. Here's how to do it.
Make a sub-class of the XMLFormatter (or HTMLFormatter if you're working with HTML), name it what you want. I chose "SortAttributes." Write the function "attributes" so that it will return a list of tuples: [(attribute1, value1), (attribute2, value2), etc.] in the order you want. Mine may look verbose, but I do it this way because I work with very inconsistent XML.
from bs4 import BeautifulSoup
from bs4.formatter import XMLFormatter
class SortAttributes(XMLFormatter):
def attributes(self, tag):
"""Reorder a tag's attributes however you want."""
attrib_order = ['id', 'head', 'postag', 'relation', 'form', 'lemma']
new_order = []
for element in attrib_order:
if element in tag.attrs:
new_order.append((element, tag[element]))
for pair in tag.attrs.items():
if pair not in new_order:
new_order.append(pair)
return new_order
xml_string = '''
<word form="συ" head="2610" id="2357" lemma="συ" postag="p-s----n-" relation="ExD_AP"/>
'''
soup = BeautifulSoup(xml_string, 'xml')
print(soup.encode(formatter=SortAttributes()))
This will output what I want:
<word id="2357" head="2610" postag="p-s----n-" relation="ExD_AP" form="συ" lemma="συ"/>
Conveniently, I can do this for an entire document with the same encode method. But if I write that to a file as a string, then all the tags are just strung together end to end. A sample would be like such:
<sentence id="783"><word id="2357" head="2610" postag="p-s----n-" relation="ExD_AP" form="συ" lemma="συ"/><word id="2358" head="2610" postag="p-s----n-" relation="ExD_AP" form="συ" lemma="συ"/><word id="2359" head="2610" postag="p-s----n-" relation="ExD_AP" form="συ" lemma="συ"/></sentence>
Instead of something I'd prefer:
<sentence id="783">
<word id="2357" head="2610" postag="p-s----n-" relation="ExD_AP" form="συ" lemma="συ"/>
<word id="2358" head="2610" postag="p-s----n-" relation="ExD_AP" form="συ" lemma="συ"/>
<word id="2359" head="2610" postag="p-s----n-" relation="ExD_AP" form="συ" lemma="συ"/>
</sentence>
To fix that, I can't just .prettify it because prettify rearranges the attributes back to alphabetical order. I'll have to go into more details with the XMLFormatter subclass instead. I hope someone finds this helpful in the future!
I am developing a UI for a desktop app. I've seen that "Menus" are deprecated and hence decided to use the Gtk.Application class for the first time ever.
In the following link https://python-gtk-3-tutorial.readthedocs.io/en/latest/application.html#example
they provide the following code:
import sys
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import GLib, Gio, Gtk
# This would typically be its own file
MENU_XML="""
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<menu id="app-menu">
<section>
<attribute name="label" translatable="yes">Change label</attribute>
<item>
<attribute name="action">win.change_label</attribute>
<attribute name="target">String 1</attribute>
<attribute name="label" translatable="yes">String 1</attribute>
</item>
<item>
<attribute name="action">win.change_label</attribute>
<attribute name="target">String 2</attribute>
<attribute name="label" translatable="yes">String 2</attribute>
</item>
<item>
<attribute name="action">win.change_label</attribute>
<attribute name="target">String 3</attribute>
<attribute name="label" translatable="yes">String 3</attribute>
</item>
</section>
<section>
<item>
<attribute name="action">win.maximize</attribute>
<attribute name="label" translatable="yes">Maximize</attribute>
</item>
</section>
<section>
<item>
<attribute name="action">app.about</attribute>
<attribute name="label" translatable="yes">_About</attribute>
</item>
<item>
<attribute name="action">app.quit</attribute>
<attribute name="label" translatable="yes">_Quit</attribute>
<attribute name="accel"><Primary>q</attribute>
</item>
</section>
</menu>
</interface>
"""
class AppWindow(Gtk.ApplicationWindow):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# This will be in the windows group and have the "win" prefix
max_action = Gio.SimpleAction.new_stateful("maximize", None,
GLib.Variant.new_boolean(False))
max_action.connect("change-state", self.on_maximize_toggle)
self.add_action(max_action)
# Keep it in sync with the actual state
self.connect("notify::is-maximized",
lambda obj, pspec: max_action.set_state(
GLib.Variant.new_boolean(obj.props.is_maximized)))
lbl_variant = GLib.Variant.new_string("String 1")
lbl_action = Gio.SimpleAction.new_stateful("change_label", lbl_variant.get_type(),
lbl_variant)
lbl_action.connect("change-state", self.on_change_label_state)
self.add_action(lbl_action)
self.label = Gtk.Label(label=lbl_variant.get_string(),
margin=30)
self.add(self.label)
self.label.show()
def on_change_label_state(self, action, value):
action.set_state(value)
self.label.set_text(value.get_string())
def on_maximize_toggle(self, action, value):
action.set_state(value)
if value.get_boolean():
self.maximize()
else:
self.unmaximize()
class Application(Gtk.Application):
def __init__(self, *args, **kwargs):
super().__init__(*args, application_id="org.example.myapp",
flags=Gio.ApplicationFlags.HANDLES_COMMAND_LINE,
**kwargs)
self.window = None
self.add_main_option("test", ord("t"), GLib.OptionFlags.NONE,
GLib.OptionArg.NONE, "Command line test", None)
def do_startup(self):
Gtk.Application.do_startup(self)
action = Gio.SimpleAction.new("about", None)
action.connect("activate", self.on_about)
self.add_action(action)
action = Gio.SimpleAction.new("quit", None)
action.connect("activate", self.on_quit)
self.add_action(action)
builder = Gtk.Builder.new_from_string(MENU_XML, -1)
self.set_app_menu(builder.get_object("app-menu"))
def do_activate(self):
# We only allow a single window and raise any existing ones
if not self.window:
# Windows are associated with the application
# when the last one is closed the application shuts down
self.window = AppWindow(application=self, title="Main Window")
self.window.present()
def do_command_line(self, command_line):
options = command_line.get_options_dict()
# convert GVariantDict -> GVariant -> dict
options = options.end().unpack()
if "test" in options:
# This is printed on the main instance
print("Test argument recieved: %s" % options["test"])
self.activate()
return 0
def on_about(self, action, param):
about_dialog = Gtk.AboutDialog(transient_for=self.window, modal=True)
about_dialog.present()
def on_quit(self, action, param):
self.quit()
if __name__ == "__main__":
app = Application()
app.run(sys.argv)
Where they show the following image as supossed output:
Yet I'm running it and I'm getting this window displayed:
I'm just bashing "python3 application.py"
I guess I'd like to know if either I'm dumb or the code isn't what should be anymore.
It must be a problem with your distro and/or window manager, because in Linux Mint 19 I get this window:
I am trying to create a settings window in tkinter python, I am trying to create same callback function for all comboboxes, but I can't figure how to identify which combobox is the caller. example code:
def Open_settings_Form():
def callbackFunc(event):
print(event.widget.current())
global SETTINGS
Settings_Form = Toplevel(Main_Form)
Settings_Form.title("LOG Settings")
Settings_Form.geometry('900x300')
labelTop = Label(Settings_Form, text = "Log Parsing Order")
labelTop.grid(column=0, row=0)
combo0 = ttk.Combobox(Settings_Form, state="readonly", values=Log_Fields)
combo0.bind("<<ComboboxSelected>>", callbackFunc)
combo0.current(SETTINGS['LOG_Order'][0])
combo0.grid(column=0, row=1)
combo1 = ttk.Combobox(Settings_Form, state="readonly", values=Log_Fields)
combo1.bind("<<ComboboxSelected>>", callbackFunc)
combo1.current(SETTINGS['LOG_Order'][1])
combo1.grid(column=1, row=1)
combo2 = ttk.Combobox(Settings_Form, state="readonly", values=Log_Fields)
combo2.bind("<<ComboboxSelected>>", callbackFunc)
combo2.current(SETTINGS['LOG_Order'][2])
combo2.grid(column=2, row=1)
combo3 = ttk.Combobox(Settings_Form, state="readonly", values=Log_Fields)
combo3.bind("<<ComboboxSelected>>", callbackFunc)
combo3.current(SETTINGS['LOG_Order'][3])
combo3.grid(column=3, row=1)
In my callbackFunc I can get the selected item of combobox, but not able to find which combobox fire the event. Is there any other bind method than ComboboxSelected which I am using ?
Following are the methods / Params for event.widget object:
['_Misc__winfo_getint', '_Misc__winfo_parseitem', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_bind', '_configure', '_displayof', '_do', '_getboolean', '_getconfigure', '_getconfigure1', '_getdoubles', '_getints', '_grid_configure', '_gridconvvalue', '_last_child_ids', '_name', '_nametowidget', '_noarg_', '_options', '_register', '_report_exception', '_root', '_setup', '_subst_format', '_subst_format_str', '_substitute', '_tclCommands', '_w', '_windowingsystem', 'after', 'after_cancel', 'after_idle', 'anchor', 'bbox', 'bell', 'bind', 'bind_all', 'bind_class', 'bindtags', 'cget', 'children', 'clipboard_append', 'clipboard_clear', 'clipboard_get', 'columnconfigure', 'config', 'configure', 'current', 'delete', 'deletecommand', 'destroy', 'event_add', 'event_delete', 'event_generate', 'event_info', 'focus', 'focus_displayof', 'focus_force', 'focus_get', 'focus_lastfor', 'focus_set', 'forget', 'get', 'getboolean', 'getdouble', 'getint', 'getvar', 'grab_current', 'grab_release', 'grab_set', 'grab_set_global', 'grab_status', 'grid', 'grid_anchor', 'grid_bbox', 'grid_columnconfigure', 'grid_configure', 'grid_forget', 'grid_info', 'grid_location', 'grid_propagate', 'grid_remove', 'grid_rowconfigure', 'grid_size', 'grid_slaves', 'icursor', 'identify', 'image_names', 'image_types', 'index', 'info', 'insert', 'instate', 'keys', 'lift', 'location', 'lower', 'mainloop', 'master', 'nametowidget', 'option_add', 'option_clear', 'option_get', 'option_readfile', 'pack', 'pack_configure', 'pack_forget', 'pack_info', 'pack_propagate', 'pack_slaves', 'place', 'place_configure', 'place_forget', 'place_info', 'place_slaves', 'propagate', 'quit', 'register', 'rowconfigure', 'scan_dragto', 'scan_mark', 'select_adjust', 'select_clear', 'select_from', 'select_present', 'select_range', 'select_to', 'selection_adjust', 'selection_clear', 'selection_from', 'selection_get', 'selection_handle', 'selection_own', 'selection_own_get', 'selection_present', 'selection_range', 'selection_to', 'send', 'set', 'setvar', 'size', 'slaves', 'state', 'tk', 'tk_bisque', 'tk_focusFollowsMouse', 'tk_focusNext', 'tk_focusPrev', 'tk_setPalette', 'tk_strictMotif', 'tkraise', 'unbind', 'unbind_all', 'unbind_class', 'update', 'update_idletasks', 'validate', 'wait_variable', 'wait_visibility', 'wait_window', 'waitvar', 'widgetName', 'winfo_atom', 'winfo_atomname', 'winfo_cells', 'winfo_children', 'winfo_class', 'winfo_colormapfull', 'winfo_containing', 'winfo_depth', 'winfo_exists', 'winfo_fpixels', 'winfo_geometry', 'winfo_height', 'winfo_id', 'winfo_interps', 'winfo_ismapped', 'winfo_manager', 'winfo_name', 'winfo_parent', 'winfo_pathname', 'winfo_pixels', 'winfo_pointerx', 'winfo_pointerxy', 'winfo_pointery', 'winfo_reqheight', 'winfo_reqwidth', 'winfo_rgb', 'winfo_rootx', 'winfo_rooty', 'winfo_screen', 'winfo_screencells', 'winfo_screendepth', 'winfo_screenheight', 'winfo_screenmmheight', 'winfo_screenmmwidth', 'winfo_screenvisual', 'winfo_screenwidth', 'winfo_server', 'winfo_toplevel', 'winfo_viewable', 'winfo_visual', 'winfo_visualid', 'winfo_visualsavailable', 'winfo_vrootheight', 'winfo_vrootwidth', 'winfo_vrootx', 'winfo_vrooty', 'winfo_width', 'winfo_x', 'winfo_y', 'xview', 'xview_moveto', 'xview_scroll']
Thanks a lot
You can pass a name to your combobox widget and retrieve it through event.widget callback.
import tkinter as tk
from tkinter import ttk
root = tk.Tk()
def details(event):
print (event.widget)
combo1 = ttk.Combobox(root,name="box1")
combo1["values"] = ("A","B","C")
combo1.pack()
combo1.bind("<<ComboboxSelected>>",details)
combo2 = ttk.Combobox(root,name="box2")
combo2["values"] = ("D","E","F")
combo2.pack()
combo2.bind("<<ComboboxSelected>>",details)
root.mainloop()