2
\$\begingroup\$

I'm writing software which allows a user to view data in a number of different formats, and they can switch between formats at any time. I'm wondering if there's a better way to do this than switching between subclasses, and if not, if there's a better way to write this than I have.

import tkinter as tk from tkinter import ttk class Display(ttk.Frame): def __init__(self, master=None): ttk.Frame.__init__(self, master, relief='sunken', padding='20') self.widgets = [ ttk.Label(self, text="Data Value #1"), ttk.Label(self, text="Data Value #2"), ttk.Label(self, text="Data Value #3"), ttk.Label(self, text="Data Value #4"), ttk.Label(self, text="Data Value #5"), ttk.Label(self, text="Data Value #6"), ttk.Label(self, text="Data Value #7"), ttk.Label(self, text="Data Value #8"), ttk.Label(self, text="Data Value #9"), ttk.Label(self, text="Data Value #10"), ] def show(self): for i in self.widgets: i.pack() class Display1(Display): def show(self): for i, widget in enumerate(self.widgets): if i % 2 == 0: widget.pack() class Display2(Display): def show(self): for i, widget in enumerate(self.widgets): if i % 2 == 1: widget.pack() class Display3(Display): def show(self): for i, widget in enumerate(self.widgets): if i > 4: widget.pack() class Display4(Display): def show(self): for i, widget in enumerate(self.widgets): if i < 5: widget.pack() class Display5(Display): def show(self): for i, widget in enumerate(self.widgets): self.widgets[-i].pack() class Window(tk.Tk): def __init__(self): tk.Tk.__init__(self) # # # Initialize buttons to select how to view the data. self.selection = tk.StringVar(self, value="Option 1") self.display_options = [ ttk.Radiobutton(self, text="Option 1", variable=self.selection, value="Option 1", command=self.change_display), ttk.Radiobutton(self, text="Option 2", variable=self.selection, value="Option 2", command=self.change_display), ttk.Radiobutton(self, text="Option 3", variable=self.selection, value="Option 3", command=self.change_display), ttk.Radiobutton(self, text="Option 4", variable=self.selection, value="Option 4", command=self.change_display), ttk.Radiobutton(self, text="Option 5", variable=self.selection, value="Option 5", command=self.change_display), ] for i, button in enumerate(self.display_options): button.grid(row=i, column=1, padx=20) # # # Initialize the frame holding the data self.data_frame = Display(self) self.data_frame.grid(row=0, column=0, rowspan=10) self.data_frame.show() def change_display(self): for i in self.data_frame.pack_slaves(): i.pack_forget() self.data_frame.grid_forget() match self.selection.get(): case "Option 1": self.data_frame = Display1(self) case "Option 2": self.data_frame = Display2(self) case "Option 3": self.data_frame = Display3(self) case "Option 4": self.data_frame = Display4(self) case "Option 5": self.data_frame = Display5(self) case _: self.data_frame = Display(self) self.data_frame.show() self.data_frame.grid(row=0, column=0, rowspan=10) if __name__ == '__main__': win = Window() win.mainloop() 

The program initializes a window with a selection of formats to view the data, and shows the data in some default format. Each subclass of Display only overrides the show() method.

This isn't the entire program I'm working on, but the important part is that the subclasses of Display don't actually initialize themselves, and only override certain parent methods. Is there a better way to change the way the data is displayed than this?

\$\endgroup\$

1 Answer 1

3
\$\begingroup\$

Display and Window should both follow has-a rather than is-a for better loose coupling; that is, they should keep the widgets to themselves and only selectively expose functionality as needed.

Subclassing is not justified in this case.

Your conditional packing loops (1) should not use conditions at all, and (2) should simply iterate over a range of indices instead.

You had the right(ish) idea to use a Var, but it shouldn't be a StringVar - it should be an IntVar, since that's usable as an index.

You need to make use of loops to fill your label and radio lists.

Rather than manually checking every single option, just pull the value of the IntVar and use that as an index into a tuple of ranges.

Rather than command, prefer to trace the variable itself.

After these changes you will not need to save your display_options and the parent Tk as members of your class.

Suggested

import tkinter as tk from tkinter import ttk from typing import Sequence displays = ( range(1, 10, 2), range(2, 11, 2), range(6, 11, 1), range(1, 6, 1), range(10, 0, -1), ) class Display: def __init__(self, parent: tk.Tk) -> None: self.frame = ttk.Frame(parent, relief='sunken', padding='20') self.frame.grid(row=0, column=0, rowspan=10) self.labels = [ ttk.Label(self.frame, text=f'Data Value #{i}') for i in range(1, 11) ] def show(self, indices: Sequence[int]) -> None: for label in self.frame.pack_slaves(): label.pack_forget() for i in indices: self.labels[i - 1].pack() class Window: def __init__(self): parent = tk.Tk() self.mainloop = parent.mainloop self.selection = tk.IntVar(parent) self.selection.trace_add('write', self.change_display) for i in range(5): button = ttk.Radiobutton( parent, text=f"Option {i + 1}", variable=self.selection, value=i, ) button.grid(row=i, column=1, padx=20) self.data_frame = Display(parent) self.selection.set(0) def change_display(self, name: str, index: str, mode: str) -> None: display = displays[self.selection.get()] self.data_frame.show(display) if __name__ == '__main__': win = Window() win.mainloop() 

Screenshot

An alternative method involves calls to grid/gridremove instead of pack:

import tkinter as tk from tkinter import ttk from typing import Sequence DISPLAYS = ( range( 1, 10, 2), range( 2, 11, 2), range( 6, 11, 1), range( 1, 6, 1), range(10, 0, -1), ) class Display: def __init__(self, parent: tk.Tk) -> None: self.frame = ttk.Frame(parent, relief='sunken', padding='20') self.frame.grid(row=0, column=0, rowspan=5) self.labels = [ ttk.Label(self.frame, text=f'Data Value #{i}') for i in range(1, 11) ] def show(self, indices: Sequence[int]) -> None: for y, i in enumerate(indices): self.labels[i - 1].grid(row=y, column=0) for i in set(range(1, 11)) - set(indices): self.labels[i - 1].grid_remove() class Window: def __init__(self) -> None: parent = tk.Tk() self.mainloop = parent.mainloop self.selection = tk.IntVar(parent) self.selection.trace_add('write', self.change_display) for i in range(5): button = ttk.Radiobutton( parent, text=f"Option {i + 1}", variable=self.selection, value=i, ) button.grid(row=i, column=1, padx=20) self.data_frame = Display(parent) self.selection.set(0) def change_display(self, name: str, index: str, mode: str) -> None: display = DISPLAYS[self.selection.get()] self.data_frame.show(display) if __name__ == '__main__': Window().mainloop() 
\$\endgroup\$
0

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.