1

First off I'm relatively new to python and very new to PyQt. I'm taking my first steps into object oriented programming and I have been looking at online tutorials, but I'm stuck on a multiple inheritance issue.

I have class called CreateTable() whose purpose is to make a QTableWidget in my app window and when a row is clicked it opens a context menu and you can then choose the option to graph the selected row. Where the graph option is connected to another class Plotter().

My problem is that CreateTable needs to inherit both the QTableWidget from PyQt and my class MainWindow because I need to reference the layout variable in order to embed my graph into my application window.

My code for attempted inheritance is as follows and heavily borrows from here:How does Python's super() work with multiple inheritance?

class QTable(QTableWidget):
  def __init__(self):
    super(QTable, self).__init__()
class PassMain(MainWindow):
  def __init__(self):
    super(PassMain, self).__init__()
class PassMainTable(PassMain, QTable):
  def __init__(self):
    super(PassMainTable, self).__init__()

The main problem is when I try to place the graph inside my MainWindow layout.

self.vboxRight.addWidget(self.Graph) 

Here is my code for creating the table and calling plotter

class CreateTable(PassMainTable): #QTableWidget
def __init__(self, Data, row, col, colHeaders, rowHeaders): #Index, ColumnHeaders
    super(CreateTable, self).__init__()


    self.setSelectionBehavior(self.SelectRows)

    print("Start initialization")
    self.ColHeader = colHeaders
    self.setRowCount(row)
    self.setColumnCount(col)
    self.data = Data
    self.setHorizontalHeaderLabels(colHeaders)

    print("Right before for loop")

    n = len(Data)
    m = len(colHeaders)

    for i in range(n):
        DataValues = self.data.iloc[i,:]
        print("values are {}".format(DataValues))
        #m = len(values)
        ConvertedVals = pd.to_numeric(DataValues)

        ValList = DataValues.values.tolist()
        print(ValList)

        for j in range(0,m):
            self.item = QTableWidgetItem(str(round(ValList[j],5)))
            #print("{}, {}".format(i, j))
            self.setItem(i,j, self.item)

def contextMenuEvent(self, event):

    menu = QMenu(self)
    graphAction = menu.addAction("Graph")
    compareAction = menu.addAction("Compare")
    scatterAction = menu.addAction("Plot types")
    aboutAction = menu.addAction("about")
    quitAction = menu.addAction("quit")
    printAction = menu.addAction("Print Row")
    action = menu.exec_(self.mapToGlobal(event.pos()))
    if action == quitAction:
        qApp.quit()
    elif action == printAction:
        self.selected = self.selectedItems()
        n = len(self.selected)
        print("n is {}".format(n))
        for i in range(n):
            self.selected[i] = str(self.selected[i].text())
        for i in range(n):
            self.selected[i] = float(self.selected[i])
        print(self.selected)
    elif action == graphAction:
        self.selected = self.selectedItems()
        n = len(self.selected)
        for i in range(n):
            self.selected[i] = str(self.selected[i].text())
        for i in range(n):
            self.selected[i] = float(self.selected[i])
        print("right before plotter called")

        self.Graph = Plotter(self.selected, self.ColHeader)

        self.vboxRight.addWidget(self.Graph)
    else:
        print("u clicked something other than quit")

To make matters worse PyQt is catching all my errors as exceptions so all I get for errors is "Process finished with exit code 1"

if you need further reference to my full code I have provided a link here: https://github.com/Silvuurleaf/Data-Analysis-and-Visualization-GUI/blob/master/Plotter3.1.py

Thank you I appreciate any help y'all can give me.

eyllanesc
  • 235,170
  • 19
  • 170
  • 241
Markus T.
  • 113
  • 1
  • 13
  • I do not understand why you need to inherit from QTableWidget and QMainWindow at a time, this is not appropriate, both inherit from QWidget. – eyllanesc Jul 21 '17 at 16:55
  • Adding a widget is not the same as inheriting – eyllanesc Jul 21 '17 at 17:01
  • You could provide the .csv file. Another thing, the menu should be able to run anywhere in the window, and if so, I think the QAction graph should be disabled when the menu is run outside the QTableWidget. – eyllanesc Jul 21 '17 at 17:12
  • I'm not trying to inherit from QMainWindow. I made a separate class called MainWindow where I define the layout vboxRight, which is where I want to embed my graph. – Markus T. Jul 21 '17 at 17:24
  • Additionally I want the context menu only to be available when my data table is clicked on that's why I define the context menu method inside the class CreateTable. – Markus T. Jul 21 '17 at 17:25
  • Then the createtable class should not inherit from QMainWindow. You could provide me the .csv file to be able to test it since it seems to have a special format – eyllanesc Jul 21 '17 at 17:28
  • Is there a way I could email it to you? Not really sure how to upload csv files to stackoverflow. – Markus T. Jul 21 '17 at 17:36
  • my e-mail is e.yllanescucho@gmail.com – eyllanesc Jul 21 '17 at 17:37
  • Do you want to add several plot or just one plot ?, currently your code adds a new table, do you want that to happen? – eyllanesc Jul 21 '17 at 17:40
  • okay I sent the csv file – Markus T. Jul 21 '17 at 17:41
  • Yes the plan is to be able to add multiple tables and plots to the same window. Which I might change if I deem the window to be too cluttered. – Markus T. Jul 21 '17 at 17:42
  • Okay I understand, do you want every time you select a row to update the plot, or generate a new plot? – eyllanesc Jul 21 '17 at 17:45
  • I plan to be able to do both. The goal is to make boxplots in the same graphing window. So if compare is clicked in the contextmenu it will plot on the same graphwindow, but there will also be another option to create separate graphs if need be. – Markus T. Jul 21 '17 at 17:54
  • So, in short, you want each table to have a single plot, am I correct? – eyllanesc Jul 21 '17 at 17:55
  • not necessarily, but for now yes. The long term goal is to be able to do the first and last example provided here: https://matplotlib.org/examples/pylab_examples/boxplot_demo.html – Markus T. Jul 21 '17 at 18:00
  • And be able to plot them as two separate graphs on the screen at the same time. – Markus T. Jul 21 '17 at 18:01
  • try with my answer – eyllanesc Jul 21 '17 at 18:19

1 Answers1

1

In order to share data it is not necessary to inherit from the widget, just use signals, this is the natural way in PyQt to share data asynchronously. In your case for example we will create a signal called dataSignal, according to what is observed of your code you want to use the variables self.selected, self.ColHeader, the first one is of type list, and the second numpy.ndarray so with that We will build the signal:

class CreateTable(QTableWidget): #QTableWidget
    dataSignal = pyqtSignal(list, np.ndarray)
    def __init__(self, Data, row, col, colHeaders, rowHeaders): #Index, ColumnHeaders
        super(CreateTable, self).__init__()


        self.setSelectionBehavior(self.SelectRows)

        print("Start initialization")
        self.ColHeader = colHeaders
        self.setRowCount(row)
        self.setColumnCount(col)
        self.data = Data
        self.setHorizontalHeaderLabels(colHeaders)

        print("Right before for loop")

        n = len(Data)
        m = len(colHeaders)

        for i in range(n):
            DataValues = self.data.iloc[i,:]
            print("values are {}".format(DataValues))
            #m = len(values)
            ConvertedVals = pd.to_numeric(DataValues)

            ValList = DataValues.values.tolist()
            print(ValList)

            for j in range(0,m):
                self.item = QTableWidgetItem(str(round(ValList[j],5)))
                #print("{}, {}".format(i, j))
                self.setItem(i,j, self.item)

    def contextMenuEvent(self, event):

        menu = QMenu(self)
        graphAction = menu.addAction("Graph")
        compareAction = menu.addAction("Compare")
        scatterAction = menu.addAction("Plot types")
        aboutAction = menu.addAction("about")
        quitAction = menu.addAction("quit")
        printAction = menu.addAction("Print Row")
        action = menu.exec_(self.mapToGlobal(event.pos()))
        if action == quitAction:
            qApp.quit()
        elif action == printAction:
            self.selected = self.selectedItems()
            n = len(self.selected)
            print("n is {}".format(n))
            for i in range(n):
                self.selected[i] = str(self.selected[i].text())
            for i in range(n):
                self.selected[i] = float(self.selected[i])
            print(self.selected)
        elif action == graphAction:
            self.selected = self.selectedItems()
            n = len(self.selected)
            for i in range(n):
                self.selected[i] = str(self.selected[i].text())
            for i in range(n):
                self.selected[i] = float(self.selected[i])
            print("right before plotter called")

            print(type(self.selected), type(self.ColHeader))
            self.dataSignal.emit(self.selected, self.ColHeader)

        else:
            print("u clicked something other than quit")

Then in the MainWindow class we create a slot, in this create the Plotter object and add it to the layout.

def dataPlotter(self, x_data,y_data):
    self.Graph = Plotter(x_data, y_data)
    self.vboxRightBottom.addWidget(self.Graph)

To do this we connect the signal when we create the CreateTable object:

self.Table = CreateTable(self.BaseStats, row, col, colHeaders, rowHeaders)
self.Table.dataSignal.connect(self.dataPlotter)
self.vboxRightBottom.addWidget(self.Table)

The complete code is here.

eyllanesc
  • 235,170
  • 19
  • 170
  • 241
  • Yeah that works perfectly!! I have to tweak the layout of the page and maybe use your idea of updating my current plot because it drops the graph underneath my data table, but otherwise its great thanks! – Markus T. Jul 21 '17 at 18:38