emit dataChanged signal PyQt5 - pyqt5

I am not able to emit dataChanged signal from my model. I am under python 3.5.2, PyQt5.9.1.
I tried at least 4 different syntaxes, none of this works for me: different veiws of this model are only updated when I click on them...
#pyqtSlot()
def setData(self, index: QModelIndex, Any, role=None):
if role == QtCore.Qt.EditRole:
row = index.row()
color = QtGui.QColor(Any)
if color.isValid():
self._datas[row] = color
# self.dataChanged.emit(index,index) # doesn't work because PyQt5 changed signature
# self.dataChanged.emit(index, index, []) # doesn't update other views of the same model
# self.dataChanged.emit(index,index,[QtCore.Qt.EditRole,]) # neither
# self.data_changed.emit(index,index) # class method is 'data_changed = pyqtSignal(QModelIndex,QModelIndex)', doesn't work
return True
return False
This question How to emit dataChanged in PyQt5 is marked as solved, however, I am not able to reproduce
EDIT:
A Verifiable exemple, with several views of the same model. I am expecting all views to be updated, whenever I change the color
EDIT_2 solved... Just a typo... This exemple works as expected
from PyQt5 import QtGui, QtCore
from PyQt5 import QtWidgets
from PyQt5.QtWidgets import QApplication
from PyQt5.QtCore import QModelIndex, pyqtSignal,QAbstractListModel, pyqtSlot
import sys
class listModel(QAbstractListModel):
def __init__(self, colors=None):
super(QAbstractListModel, self).__init__()
self._datas = colors
def data(self, index: QModelIndex, role=None):
row = index.row()
value = self._datas[row]
if role == QtCore.Qt.DisplayRole:
return value.name()
elif role == QtCore.Qt.DecorationRole:
pixmap = QtGui.QPixmap(12,12)
pixmap.fill(value)
icon = QtGui.QPixmap(pixmap)
return icon
elif role == QtCore.Qt.ToolTipRole:
return "Hex code: " + self._datas[row].name()
def rowCount(self, parent=None, *args, **kwargs):
return len(self._datas)
def headerData(self, p_int, Qt_Orientation, role=None):
if role == QtCore.Qt.DisplayRole:
if Qt_Orientation == QtCore.Qt.Horizontal:
return "Palette"
else:
return "Color {a}".format(a=p_int)
def flags(self, QModelIndex: QModelIndex):
# check state editable or not?
return QtCore.Qt.ItemIsEditable | QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled
#pyqtSlot()
def setData(self, QModelIndex, Any, role=None):
if role == QtCore.Qt.EditRole:
row = QModelIndex.row()
color = QtGui.QColor(Any)
if color.isValid():
self._datas[row] = color
self.dataChanged.emit(QModelIndex, QModelIndex, [])
return True
return False
if __name__ == '__main__':
app = QApplication(sys.argv)
red = QtGui.QColor(255,0,0)
green = QtGui.QColor(0, 255, 0)
blue = QtGui.QColor(0, 0, 255)
colors = [red,green,blue]
model = listModel(colors)
listView = QtWidgets.QListView()
listView.setModel(model)
listView.setWindowTitle('list')
listView.show()
treeV = QtWidgets.QTreeView()
treeV.setModel(model)
treeV.setWindowTitle('tree')
treeV.show()
tableV = QtWidgets.QTableView()
tableV.setModel(model)
tableV.setWindowTitle('table')
tableV.show()
sys.exit(app.exec_())

self.dataChanged.emit(index, index, [QtCore.Qt.EditRole]) is correct. There must be a bug elsewhere in your code.

Related

PyQt - not showing instance of FigureCanvasQTAgg on QtWidget of TabPane

I'm continuing project described more in that question: PyQt - can't read Excel file
Basically my code looks like this right now:
# This is a sample Python script.
# Press Shift+F10 to execute it or replace it with your code.
# Press Double Shift to search everywhere for classes, files, tool windows, actions, and settings.
import csv
import sys
import numpy as np
from PyQt6 import QtWidgets
from PyQt6.QtWidgets import QDialog, QApplication, QFileDialog, QTableWidget, QTableWidgetItem, QTabWidget, QWidget
from PySide6.QtCore import Slot, SIGNAL
from PyQt6.uic import loadUi
import pandas as pd
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg
from matplotlib.figure import Figure
class MplCanvas(FigureCanvasQTAgg):
def __init__(self, parent=None, width=12, height=5, dpi=100):
fig = Figure(figsize=(width, height), dpi=100)
self.axes = fig.add_subplot(111)
super(MplCanvas, self).__init__(fig)
class MainWindow(QDialog):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent=parent)
self.initUI()
def initUI(self):
loadUi('gui.ui', self)
self.btnShow.setEnabled(False)
self.btnLoad.setEnabled(False)
self.btnBrowse.clicked.connect(self.browseFiles)
self.btnLoad.clicked.connect(self.loadExcelData)
self.btnClean.clicked.connect(self.cleanData)
self.btnShow.clicked.connect(self.showGraphs)
#Slot()
def browseFiles(self):
fname = QFileDialog.getOpenFileName(self, 'Open a file', 'C:\\', "Excel (*.xls *.xlsx)")
self.filename.setText(fname[0])
self.btnLoad.setEnabled(True)
#Slot()
def loadExcelData(self):
column_names = ["Action", "TimeOfFailure", "ReverseRankR", "S(i)", "Cdf", "Ppf", "LogTime"]
df = pd.read_excel(self.filename.text(), "Sheet1", names=column_names)
if df.size == 0:
return
self.tableExcelData.setRowCount(df.shape[0])
self.tableExcelData.setColumnCount(df.shape[1])
self.tableExcelData.setHorizontalHeaderLabels(df.columns)
for row in df.iterrows():
values = row[1]
for col_index, value in enumerate(values):
tableItem = QTableWidgetItem(str(value))
self.tableExcelData.setItem(row[0], col_index, tableItem)
self.btnLoad.setEnabled(False)
self.btnShow.setEnabled(True)
#Slot()
def cleanData(self):
self.btnLoad.setEnabled(True)
self.btnShow.setEnabled(False)
self.tableExcelData.setRowCount(0)
self.tableExcelData.setColumnCount(0)
#Slot()
def showGraphs(self):
timeOfDays = []
cdf = []
ppf = []
logTime = []
for row in range(self.tableExcelData.rowCount()):
isFailure = False
for column in range(self.tableExcelData.columnCount()):
value = self.tableExcelData.item(row, column)
if(column == 0 and str(value.text()) == 'F'):
isFailure = True
if isFailure == True:
if(column == 1): #TimeOfDays
value = int(value.text())
timeOfDays.append(value)
elif(column == 4): #CDF
value = float(value.text())
cdf.append(value)
elif(column == 5):
value = float(value.text())
ppf.append(value)
elif(column == 6):
value = float(value.text())
logTime.append(value)
print(timeOfDays)
print(cdf)
print(ppf)
print(logTime)
#fig = Figure(figsize=(12,5), dpi=100)
#firstSubplot = fig.add_subplot(111)
#firstSubplot.scatter(timeOfDays, ppf, '*')
#firstSubplot.plot(timeOfDays, ppf)
#fig.show()
#plt.plot(timeOfDays, ppf)
#plt.show()
try:
canvasFig = MplCanvas()
canvasFig.axes.scatter(timeOfDays, ppf, s=5, color='red')
canvasFig.axes.plot(timeOfDays, ppf)
canvasFig.draw()
self.tabFirstGraph.setCentralWidget(canvasFig)
except Exception as e:
print('Error: ' + str(e))
#canvas = FigureCanvasTkAgg(fig, master=self)
#canvas.get_tk_widget().pack()
#canvas.draw()
# Press the green button in the gutter to run the script.
if __name__ == '__main__':
app = QApplication(sys.argv)
mainWindow = MainWindow()
mainWidget = QtWidgets.QStackedWidget()
mainWidget.addWidget(mainWindow)
mainWidget.show()
sys.exit(app.exec())
# See PyCharm help at https://www.jetbrains.com/help/pycharm/
I'm trying to generate two graphs (now it's code for only creation of one):
try:
canvasFig = MplCanvas()
canvasFig.axes.scatter(timeOfDays, ppf, s=5, color='red')
canvasFig.axes.plot(timeOfDays, ppf)
canvasFig.draw()
self.tabFirstGraph.setCentralWidget(canvasFig) #
except Exception as e:
print('Error: ' + str(e))
I tried to create another TabPane ("tabFirstGraph" as name of this object) and set canvas figure object to fill this QWidget instance. But I'm getting constantly this error:
Error: 'QWidget' object has no attribute 'setCentralWidget'
I assumed already that problem is with line above (QWidget, QTableWidget don't have this method). But how can I show my canvas figure graph on "First Graph" Tab Pane?
Thanks in advance for your all answers. :)

Python PyQt5 QTableView Change selected row Backgroundcolor

I have a QTableView with QAbstractTableModel. I want to change row backgroundcolor when I click one cell. I know at least two methods that can change row backgroundcolor when clicking one cell. One is use delegate, and another is use setData method in QAbstractTable. But I have got none of them,,,oops. Here I tried using setData method in QAbstractTable to just change the selected cell backgroundcolor, but failed! Could you pls help me to correct my code in order to change a whole row color not just a cell. Anyway, changing cell color is not even ok! Much thanks! Code below
import sys
import typing
import numpy as np
import pandas as pd
from PyQt5 import QtCore
from PyQt5.QtWidgets import QApplication, QMainWindow, \
QWidget, QTableView, QVBoxLayout
from PyQt5.QtCore import QAbstractTableModel, Qt, QModelIndex
class MyTableModel(QAbstractTableModel):
def __init__(self, data:pd.DataFrame):
super().__init__()
self._data = data
def data(self, index: QModelIndex, role: int = ...) -> typing.Any:
if role==Qt.DisplayRole:
value = str(self._data.iloc[index.row()][index.column()])
return value
def setData(self, index: QModelIndex, value: typing.Any, role: int = ...) -> bool:
if not index.isValid():
return False
else:
if role==Qt.BackgroundColorRole:
self.dataChanged.emit(index, index, [role])
return True
def rowCount(self, parent: QModelIndex = ...) -> int:
return self._data.shape[0]
def columnCount(self, parent: QModelIndex = ...) -> int:
return self._data.shape[1]
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.myTable = QTableView()
df = self.get_DataFrame_Data()
self.model = MyTableModel(df)
self.myTable.setModel(self.model)
self.myTable.clicked.connect(self.change_row_bgcolor)
hlayout = QVBoxLayout()
hlayout.addWidget(self.myTable)
dummy_widget = QWidget()
dummy_widget.setLayout(hlayout)
self.setCentralWidget(dummy_widget)
self.setFixedSize(600, 600)
def get_DataFrame_Data(self):
ndarray = np.random.randint(10, 50, (7, 3))
df = pd.DataFrame(data=ndarray, columns=['col1','col2','col3'])
return df
def change_row_bgcolor(self, index):
self.model.setData(index,Qt.red,Qt.BackgroundColorRole)
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec_()
Solved! Two ways to changed QTableView Row background color when user mouse clicking.
Use QStyledItemDelegate.
Subclass QStyledItemDelegate. You should set a class property (etc tableview's currentindex) which can be reset value from outside the class, by this, the delegate's default loop will compare the tableview's currentindex.Code:
class TableDelegate(QStyledItemDelegate):
select_index = None
def paint(self, painter: QtGui.QPainter, option: 'QStyleOptionViewItem', index: QtCore.QModelIndex) -> None:
# option.state
row = index.row()
column = index.column()
select_row = self.select_index.row()
# self.initStyleOption(option,index)
if row == select_row:
# option.font.setItalic(True)
option.font.setStyle(QFont.StyleOblique)
bg = QColor(135, 206, 255)
painter.fillRect(option.rect, bg)
# painter.eraseRect(option.rect)
QStyledItemDelegate.paint(self, painter, option, index)
Use QAbstractTableModel.Also, you should set a class property, than the method data()'s default loop will compare with the class property(tableview's current index). and set backgroud color.Code:
class MyTableModel(QAbstractTableModel):
def __init__(self, data:pd.DataFrame):
super().__init__()
self._data = data
self.color_enabled = False
self.color_back = Qt.magenta
self.target_row = -1
def data(self, index: QModelIndex, role: int) -> typing.Any:
if role==Qt.DisplayRole:
# print(index.row())
value = str(self._data.iloc[index.row()][index.column()])
return value
if role == Qt.BackgroundRole and index.row()==self.target_row \
and self.color_enabled==True:
return QBrush(self.color_back)
And,,! there is another special problem that should emphasized here. When user click one cell, there is a default backgroud which I see in my computer is blue. If you want to whole row background color is same when clicking, you should do this after creating a QTableView:
self.myTable.setStyleSheet("QTableView::item:selected{"
"background:rgb(135, 206, 255)}")
This means,you set the selected cell bgcolor by QSS, and then, either when you use QAbstractTableModel' data() method or pain() method in QStyledItemDelege, you should set the same color. Then everything is ok!

QAbstractTableModel not updating on editing table cell in QTableView

Im using PyQt5 to develop an MVC application using sqlite DB.
I have a custom model inheriting from QAbstractTableModel. For the view im using in QTableView.
In the custom model i have added function setdata() and flags() needed to make the table cells in the view editable.
But when i edit the cells in the QtableView ,they do not persist in the sqlite DB.
When creating the view i use setModel() function to link the view and model.
What i want is a view with an editable table which on edit persists in the view and updates the sqlite database as well.
eg If i edit first row for result1 column from 1.23 to 1.234,althought the View now shows 1.234, the sqlite DB shows 1.23 instead of 1.234 :-(
#musicamante Below is my code so far
from PyQt5 import QtCore, QtGui,QtWidgets
from PyQt5.QtWidgets import (
QApplication,
QMainWindow,
)
import sys, sqlite3
import pandas as pd
from PyQt5.QtCore import Qt
class TestUI():
def __init__(self):
self.db = sqlite3.connect("test.db")
data = pd.read_sql_query("SELECT * FROM Reports",self.db) # where Reports is the table name, which i have already created beforehand
self.model = TableModel(data)
def createView(self, title):
view = QtWidgets.QTableView(self.centralwidget)
view.setGeometry(QtCore.QRect(60, 130, 541, 301))
view.resizeColumnsToContents()
view.setObjectName("view")
view.setModel(self.model)
view.setWindowTitle(title)
return view
def setupUi(self, MainWindow):
MainWindow.resize(800, 480)
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.createView("TableView")
MainWindow.setCentralWidget(self.centralwidget)
class TableModel(QtCore.QAbstractTableModel):
def __init__(self, data):
super(TableModel, self).__init__()
self._data = data
self.initializeModel()
def initializeModel(self):
self.setHeaderData(2, QtCore.Qt.Horizontal, "serialNo")
self.setHeaderData(3, QtCore.Qt.Horizontal, "timestamp")
self.setHeaderData(4, QtCore.Qt.Horizontal, "result1")
def data(self, index, role):
if index.isValid():
if role == Qt.DisplayRole or role == Qt.EditRole:
value = self._data.iloc[index.row(), index.column()]
return str(value)
if role == Qt.ForegroundRole or role == Qt.EditRole:
value = self._data.iloc[index.row(), index.column()]
if (
(isinstance(value, int) or isinstance(value, float))
and value > 0
):
return QtGui.QColor('red')
def rowCount(self, index):
return self._data.shape[0]
def columnCount(self, index):
return self._data.shape[1]
def headerData(self, section, orientation, role):
# section is the index of the column/row.
if role == Qt.DisplayRole:
if orientation == Qt.Horizontal:
return str(self._data.columns[section])
if orientation == Qt.Vertical:
return str(self._data.index[section])
def setData(self, index, value, role):
if role == Qt.EditRole:
self._data.iloc[index.row(), index.column()] = value
return True
def flags(self, index):
return QtCore.Qt.ItemIsEditable | QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable
if __name__ == "__main__":
app = QApplication(sys.argv)
MainWindow = QMainWindow()
ui = TestUI()
ui.setupUi(MainWindow)
MainWindow.show()
sys.exit(app.exec_())

Releasing pan function in NavigationToolbar2QT

I want to cancel pan function from other button. So far, my understanding is that when I want to pan&zoom image, I will click 'Pan' button. If I would like to do other function, e.g. 'Mark' function (in my case), I have to click 'Pan' Button again, then click whatever button I want to do.
I have searched for solving this and found something like 'release_pan', 'button_release_event', but I don't understand how to implement them correctly.
To be clear, I want to cancel pan function from 'Mark' button, and here is my code.
import sys
import time
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.widgets import Cursor
from matplotlib.backends.qt_compat import QtCore, QtWidgets
if QtCore.qVersion() >= "5.":
from matplotlib.backends.backend_qt5agg import (
FigureCanvas, NavigationToolbar2QT as NavigationToolbar)
else:
from matplotlib.backends.backend_qt4agg import (
FigureCanvas, NavigationToolbar2QT as NavigationToolbar)
from matplotlib.figure import Figure
class ApplicationWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.coor = [0,0] #temporary user selection
self.cid = None
self._main = QtWidgets.QWidget()
self.setCentralWidget(self._main)
layout = QtWidgets.QVBoxLayout(self._main)
butt = QtWidgets.QHBoxLayout()
self.static_canvas = FigureCanvas(Figure(figsize=(5, 5), dpi=100))
self.addToolBar = NavigationToolbar(self.static_canvas, self)
self.addToolBar.hide()
self.home = QtWidgets.QPushButton('Home')
self.pan = QtWidgets.QPushButton('Pan')
self.mark = QtWidgets.QPushButton('Mark')
butt.addWidget(self.home)
butt.addWidget(self.pan)
butt.addWidget(self.mark)
layout.addLayout(butt)
layout.addWidget(self.static_canvas)
self._static_ax = self.static_canvas.figure.subplots()
self.tar = plt.imread(r'my_image.tif').copy()
self._static_ax.imshow(self.tar)
# Set cursor
self.cursor = Cursor(self._static_ax, horizOn=True, vertOn=True, useblit=True,
color = 'r', linewidth = 1)
#trigger zone
self.home.clicked.connect(self.Home)
self.pan.clicked.connect(self.Pan)
self.mark.clicked.connect(self.Mark)
def coor_onclick(self, event):
"""
This function will get coordination from click and plot it on canvas
"""
#check out-figure click
if event.xdata == None or event.ydata == None:
pass
else:
self.coor[0] = int(event.xdata)
self.coor[1] = int(event.ydata)
# print(self.coor)
#show line marking on canvas
tar = self.tar.copy()
#NOTE:: self.coor = [x,y] = [col, row]
# x = self.coor[0]
# y = self.coor[1]
#marking line
for r in range(tar.shape[1]):
for c in range(tar.shape[0]):
tar[self.coor[1], c] = [255, 0, 0]
tar[r, self.coor[0]] = [255, 0, 0]
#set final mark on canvas
self._static_ax.clear()
self._static_ax.imshow(tar)
self._static_ax.axis('off')
# Set cursor
self.cursor = Cursor(self._static_ax, horizOn=True, vertOn=True, useblit=True,
color = 'r', linewidth = 1)
self.static_canvas.draw()
def Home(self):
self.cid = self.static_canvas.mpl_connect('button_press_event', self.coor_onclick)
self.addToolBar.home()
def Pan(self):
if self.cid is None:
pass
else:
#disconnect to self.coor_onclick
self.static_canvas.mpl_disconnect(self.cid)
self.addToolBar.pan()
def Mark(self):
self.cid = self.static_canvas.mpl_connect('button_press_event', self.coor_onclick)
if __name__ == "__main__":
# Check whether there is already a running QApplication (e.g., if running
# from an IDE).
qapp = QtWidgets.QApplication.instance()
if not qapp:
qapp = QtWidgets.QApplication(sys.argv)
app = ApplicationWindow()
app.show()
app.activateWindow()
app.raise_()
qapp.exec_()
I have modified from matplotlib documentation.
Check the current mode of NavigationToolbar and if the mode is "PAN", set the mode off by calling pan() again (which will uncheck the action (check out the source code for more details.)).
FYI:
You can check the current mode of the NavigationToolbar by using NavigationToolbar.mode.name, currently there are two modes: "ZOOM" and "PAN".
In your code, change function Mark like this:
def Mark(self):
# if the current mode is Pan, set the mode off by unchecking it.
if self.nav_toolbar.mode.name == "PAN":
self.nav_toolbar.pan()
self.cid = self.static_canvas.mpl_connect(
'button_press_event', self.coor_onclick)

how to show the cursor coordinates in wxPython when using wx.SplitterWindow

I'm trying to do something similar to that shown in this link to draw a cursor and report the data coordinates in the status bar. However, my code is a little bit different since I need to use wx.SplitterWindow to separate buttons and plots. Basically, in the main frame module I create the status bar, but the plot is generated in a separated module. See below my current code:
from matplotlib.figure import Figure
from matplotlib.backends.backend_wxagg import \
FigureCanvasWxAgg as FigCanvas, \
NavigationToolbar2WxAgg as NavigationToolbar, \
wxc as wxc
import pylab
import wx
from numpy import arange, sin, pi
class data:
def __init__(self):
self.t = []
self.s = []
class Plot_Panel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent)
# create some sizers
mainSizer = wx.BoxSizer(wx.VERTICAL)
checkSizer = wx.BoxSizer(wx.HORIZONTAL)
# create figrue
self.fig = Figure()
self.canvas = FigCanvas(self, -1, self.fig)
self.axes = self.fig.add_subplot(111)
# create the widgets
self.toggleMarker = wx.CheckBox(self, label="Show Marker")
# layout the widgets
mainSizer.Add(self.canvas, 1, wx.EXPAND)
checkSizer.Add(self.toggleMarker, 0, wx.ALL, 5)
mainSizer.Add(checkSizer)
self.SetSizer(mainSizer)
def draw_plot(self, data):
# Clear the previous figure
self.fig.clear()
# Redraw figure
self.axes = self.fig.add_subplot(111)
# Define data to plot
self.plot_data= self.axes.plot(data.t, data.s, linewidth=3, color='y',)[0]
# Draw Cursor or not
if self.toggleMarker.IsChecked():
# Note that event is a MplEvent
self.canvas.mpl_connect('motion_notify_event', self.UpdateStatusBar)
self.canvas.Bind(wx.EVT_ENTER_WINDOW, self.ChangeCursor)
self.canvas.draw()
def ChangeCursor(self, event):
self.canvas.SetCursor(wxc.StockCursor(wx.CURSOR_BULLSEYE))
def UpdateStatusBar(self, event):
if event.inaxes:
x, y = event.xdata, event.ydata
# self.statusBar.SetStatusText(("x= "+str(Pos.x)+" y="+str(Pos.y)))
class Button_Panel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent)
# create the widgets
self.toggleStart = wx.Button(self, id=wx.ID_ANY, label="Plot data")
class ProportionalSplitter(wx.SplitterWindow):
def __init__(self,parent, id = -1, proportion=0.66, size = wx.DefaultSize, **kwargs):
wx.SplitterWindow.__init__(self,parent,id,wx.Point(0, 0),size, **kwargs)
self.SetMinimumPaneSize(50) #the minimum size of a pane.
self.proportion = proportion
if not 0 < self.proportion < 1:
raise ValueError, "proportion value for ProportionalSplitter must be between 0 and 1."
self.ResetSash()
self.Bind(wx.EVT_SIZE, self.OnReSize)
self.Bind(wx.EVT_SPLITTER_SASH_POS_CHANGED, self.OnSashChanged, id=id)
self.Bind(wx.EVT_PAINT, self.OnPaint)
self.firstpaint = True
def SplitHorizontally(self, win1, win2):
if self.GetParent() is None: return False
return wx.SplitterWindow.SplitHorizontally(self, win1, win2,
int(round(self.GetParent().GetSize().GetHeight() * self.proportion)))
def SplitVertically(self, win1, win2):
if self.GetParent() is None: return False
return wx.SplitterWindow.SplitVertically(self, win1, win2,
int(round(self.GetParent().GetSize().GetWidth() * self.proportion)))
def GetExpectedSashPosition(self):
if self.GetSplitMode() == wx.SPLIT_HORIZONTAL:
tot = max(self.GetMinimumPaneSize(),self.GetParent().GetClientSize().height)
else:
tot = max(self.GetMinimumPaneSize(),self.GetParent().GetClientSize().width)
return int(round(tot * self.proportion))
def ResetSash(self):
self.SetSashPosition(self.GetExpectedSashPosition())
def OnReSize(self, event):
"Window has been resized, so we need to adjust the sash based on self.proportion."
self.ResetSash()
event.Skip()
def OnSashChanged(self, event):
"We'll change self.proportion now based on where user dragged the sash."
pos = float(self.GetSashPosition())
if self.GetSplitMode() == wx.SPLIT_HORIZONTAL:
tot = max(self.GetMinimumPaneSize(),self.GetParent().GetClientSize().height)
else:
tot = max(self.GetMinimumPaneSize(),self.GetParent().GetClientSize().width)
self.proportion = pos / tot
event.Skip()
def OnPaint(self, event):
if self.firstpaint:
if self.GetSashPosition() != self.GetExpectedSashPosition():
self.ResetSash()
self.firstpaint = False
event.Skip()
class Main_Window(wx.Frame):
def __init__(self, parent, title):
wx.Frame.__init__(self, parent, title = title)
# Create a StatusBar at the bottom of the window
self.statusBar = wx.StatusBar(self, -1)
self.SetStatusBar(self.statusBar)
# Set plot panel
self.splitter = ProportionalSplitter(self,-1, 0.85)
self.ppanel = Plot_Panel(self.splitter)
self.ppanel.SetBackgroundColour('#ffffff')
# Set button panel
self.bpanel = Button_Panel(self.splitter)
# Set frame
self.splitter.SplitVertically(self.ppanel, self.bpanel)
self.Show(True)
self.Maximize(True)
# bind the widgets
self.ppanel.toggleMarker.Bind(wx.EVT_CHECKBOX, self.onToggleMarker)
self.bpanel.toggleStart.Bind(wx.EVT_BUTTON, self.onToggleStart)
# Set classes
self.data = data()
def onToggleMarker(self, event):
self.ppanel.draw_plot(self.data)
def onToggleStart(self, event):
self.data.t = arange(0.0, 1.0, 0.01)
self.data.s = sin(2*2*pi*self.data.t)
# plot data
self.ppanel.draw_plot(self.data)
def main():
app = wx.App(False)
frame = Main_Window(None, "GUI")
frame.Show()
app.MainLoop()
if __name__ == "__main__" :
main()
The plot is shown when the button "Plot data" is pressed. What I would like to do is to show the x and y position in the status bar when the checkbox "Show Marker" is checked (in a similar way as it is done in the code posted in the link), and stop when it is unchecked. But I'm not sure if it is possible to do it in my code due to having the definitions of the status bar and the plot in different modules. Any hint will be welcome.
What a joy to get a full, working example program with the question.
You just need to pass the base module as a parameter to self.ppanel
i.e.
self.ppanel = Plot_Panel(self.splitter, self)
then refer to that when updating the status bar, see below for references to base
from matplotlib.figure import Figure
from matplotlib.backends.backend_wxagg import \
FigureCanvasWxAgg as FigCanvas, \
NavigationToolbar2WxAgg as NavigationToolbar, \
wxc as wxc
import pylab
import wx
from numpy import arange, sin, pi
class data:
def __init__(self):
self.t = []
self.s = []
class Plot_Panel(wx.Panel):
def __init__(self, parent, base):
wx.Panel.__init__(self, parent)
self.base = base
# create some sizers
mainSizer = wx.BoxSizer(wx.VERTICAL)
checkSizer = wx.BoxSizer(wx.HORIZONTAL)
# create figrue
self.fig = Figure()
self.canvas = FigCanvas(self, -1, self.fig)
self.axes = self.fig.add_subplot(111)
# create the widgets
self.toggleMarker = wx.CheckBox(self, label="Show Marker")
# layout the widgets
mainSizer.Add(self.canvas, 1, wx.EXPAND)
checkSizer.Add(self.toggleMarker, 0, wx.ALL, 5)
mainSizer.Add(checkSizer)
self.SetSizer(mainSizer)
def draw_plot(self, data):
# Clear the previous figure
self.fig.clear()
# Redraw figure
self.axes = self.fig.add_subplot(111)
# Define data to plot
self.plot_data= self.axes.plot(data.t, data.s, linewidth=3, color='y',)[0]
# Draw Cursor or not
if self.toggleMarker.IsChecked():
# Note that event is a MplEvent
self.canvas.mpl_connect('motion_notify_event', self.UpdateStatusBar)
self.canvas.Bind(wx.EVT_ENTER_WINDOW, self.ChangeCursor)
self.canvas.draw()
def ChangeCursor(self, event):
self.canvas.SetCursor(wxc.StockCursor(wx.CURSOR_BULLSEYE))
def UpdateStatusBar(self, event):
if event.inaxes:
x, y = event.xdata, event.ydata
self.base.statusBar.SetStatusText(("x= "+str(x)+" y="+str(y)))
class Button_Panel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent)
# create the widgets
self.toggleStart = wx.Button(self, id=wx.ID_ANY, label="Plot data")
class ProportionalSplitter(wx.SplitterWindow):
def __init__(self,parent, id = -1, proportion=0.66, size = wx.DefaultSize, **kwargs):
wx.SplitterWindow.__init__(self,parent,id,wx.Point(0, 0),size, **kwargs)
self.SetMinimumPaneSize(50) #the minimum size of a pane.
self.proportion = proportion
if not 0 < self.proportion < 1:
raise ValueError, "proportion value for ProportionalSplitter must be between 0 and 1."
self.ResetSash()
self.Bind(wx.EVT_SIZE, self.OnReSize)
self.Bind(wx.EVT_SPLITTER_SASH_POS_CHANGED, self.OnSashChanged, id=id)
self.Bind(wx.EVT_PAINT, self.OnPaint)
self.firstpaint = True
def SplitHorizontally(self, win1, win2):
if self.GetParent() is None: return False
return wx.SplitterWindow.SplitHorizontally(self, win1, win2,
int(round(self.GetParent().GetSize().GetHeight() * self.proportion)))
def SplitVertically(self, win1, win2):
if self.GetParent() is None: return False
return wx.SplitterWindow.SplitVertically(self, win1, win2,
int(round(self.GetParent().GetSize().GetWidth() * self.proportion)))
def GetExpectedSashPosition(self):
if self.GetSplitMode() == wx.SPLIT_HORIZONTAL:
tot = max(self.GetMinimumPaneSize(),self.GetParent().GetClientSize().height)
else:
tot = max(self.GetMinimumPaneSize(),self.GetParent().GetClientSize().width)
return int(round(tot * self.proportion))
def ResetSash(self):
self.SetSashPosition(self.GetExpectedSashPosition())
def OnReSize(self, event):
"Window has been resized, so we need to adjust the sash based on self.proportion."
self.ResetSash()
event.Skip()
def OnSashChanged(self, event):
"We'll change self.proportion now based on where user dragged the sash."
pos = float(self.GetSashPosition())
if self.GetSplitMode() == wx.SPLIT_HORIZONTAL:
tot = max(self.GetMinimumPaneSize(),self.GetParent().GetClientSize().height)
else:
tot = max(self.GetMinimumPaneSize(),self.GetParent().GetClientSize().width)
self.proportion = pos / tot
event.Skip()
def OnPaint(self, event):
if self.firstpaint:
if self.GetSashPosition() != self.GetExpectedSashPosition():
self.ResetSash()
self.firstpaint = False
event.Skip()
class Main_Window(wx.Frame):
def __init__(self, parent, title):
wx.Frame.__init__(self, parent, title = title)
# Create a StatusBar at the bottom of the window
self.statusBar = wx.StatusBar(self, -1)
self.SetStatusBar(self.statusBar)
# Set plot panel
self.splitter = ProportionalSplitter(self,-1, 0.85)
self.ppanel = Plot_Panel(self.splitter, self)
self.ppanel.SetBackgroundColour('#ffffff')
# Set button panel
self.bpanel = Button_Panel(self.splitter)
# Set frame
self.splitter.SplitVertically(self.ppanel, self.bpanel)
self.Show(True)
self.Maximize(True)
# bind the widgets
self.ppanel.toggleMarker.Bind(wx.EVT_CHECKBOX, self.onToggleMarker)
self.bpanel.toggleStart.Bind(wx.EVT_BUTTON, self.onToggleStart)
# Set classes
self.data = data()
def onToggleMarker(self, event):
self.ppanel.draw_plot(self.data)
def onToggleStart(self, event):
self.data.t = arange(0.0, 1.0, 0.01)
self.data.s = sin(2*2*pi*self.data.t)
# plot data
self.ppanel.draw_plot(self.data)
def main():
app = wx.App(False)
frame = Main_Window(None, "GUI")
frame.Show()
app.MainLoop()
if __name__ == "__main__" :
main()