Search code examples
pythontkintersubclass

How to call a class method from another class method function?


I'm making a quiz app that involves reading data from a JSON file and storing the user's name and password into an SQL database.

I made 2 classes: one class runs the GUI and the other class handles the quiz logic. How can I call the quiz class into the GUI class from a click of a Tkinter button?

Here is the code below:

import tkinter as tk
from tkinter import tix 
from tkinter import ttk
import json
import tkinter.messagebox
import sqlite3

# MAIN GUI DESIGN:

class Main:

    def __init__(self):

        self.root = tix.Tk()
        self.root.title("Quiz App")
        self.root.geometry("850x600+500+200")
        self.root.resizable(False,False)

    # FUNCTIONS AND METHODS:

    def frame2to3(self): # THIS IS WHERE I NEED HELP CALLING THE CLASS INTO THIS METHOD

        self.frame2.pack_forget()
        self.frame3.pack(fill="both",expand=True)

    def clearfunc1(self):

        self.entry1.delete(0, 'end')
        self.entry2.delete(0, 'end')

    def clearfunc2(self):

        self.entry3.delete(0, 'end')
        self.entry4.delete(0, 'end')

    def close(self):

        if tkinter.messagebox.askyesno("Quiz App","Are you sure you want to exit?") > 0:
            self.root.destroy()
            return

m = Main()



# QUIZ LOGIC:

class Quiz(Main): # THE CLASS I NEED TO CALL 

    def __init__(self):

        self.q_no =0

        self.display_question()

        self.opt_selected = IntVar()

        self.opts = self.radio_buttons()

        self.display_options()
        self.buttons()

        self.data_size = len(question)

        self.correct =0

    def display_result(self):

        wrong_count = self.data_size - self.correct
        correct  = f"Correct: {self.correct}"
        wrong = f"Wrong: {wrong_count}"

        score = int(self.correct/self.data_size*100)
        result = f"Score: {score}%"

        self.show_result = tk.Label(m.frame3(), text=f"{result}\n{correct}\n{Wrong}")

    def check_ans(self,q_no):

        if self.opt_selected.get() == answer[q_no]:

            return True

    def next_btn(self):

        if self.check_ans(self.q_no):

            self.correct += 1
            self.q_no += 1

        if self.q_no == self.data_size:

            self.display_result()

            self.root.destroy()
        else:

            self.display_question()
            self.display_options()

    def buttons(self):

        self.next_btn = tk.Button(m.frame3(), text = "next" , command=self.next_btn)
        self.next_btn.place(x=780,y=550)

        self.exitButton3 = tk.Button(m.frame3(),text="Exit", command = self.close)
        self.exitButton3.place(x=250,y=140)

    def display_options(self):

        val = 0

        self.opt_selected.set(0)

        for option in options[self.q_no]:
            self.opts[val]['text'] = option
            val += 1

    def display_question(self):

        ques_no = tk.Label(m.frame3(), text=question[self.q_no])
        ques_no.place(x=30,y=90)

    def radio_buttons(self):

        q_list =[]

        y_pos = 150

        while len(q_list) < 4:

            radio_btn = tk.Radiobutton(m.frame3(), text = "", variable=self.opt_selected,value = len(q_list)+1)
            q_list.append(radio_btn)

            radio_btn.place(x=100,y=y_pos)

            y_pos += 30

        return q_list

with open('data1.json') as f:
    data1 = json.load(f)

question = (data1['question'])
options = (data1['options'])
answer = (data1[ 'answer'])

q = Quiz()

Solution

  • Beside removing the strange pattern of subclassing Main, as already mentioned by khelwood, you might want to add a parameter quiz to the Main class __init__. You can then store a reference to the Quiz instance as an attribute of the Main instance. The instantiation it self would be in this order:

    quiz = Quiz()
    main = Main(quiz=quiz)
    

    and the __init__ method:

    class Main:
        def __init__(self, quiz):
            self._quiz = quiz
            self.root = tix.Tk()
            ...
    

    Update: But as the Quiz instance is required during Main instantiation, we might switch the order (and the parameters):

    main = Main()
    quiz = Quiz(main=main)
    main.quiz = quiz
    

    and

    class Main:
        def __init__(self):
            self.root = tix.Tk()
            ...
    

    and

    class Quiz:
        def __init__(self, main):
            self._main = main
            ....