Cách tốt nhất để cấu trúc một ứng dụng tkinter?


136

Sau đây là cấu trúc tổng thể của chương trình tkinter python điển hình của tôi.

def funA():
    def funA1():
        def funA12():
            # stuff

    def funA2():
        # stuff

def funB():
    def funB1():
        # stuff

    def funB2():
        # stuff

def funC():
    def funC1():
        # stuff

    def funC2():
        # stuff


root = tk.Tk()

button1 = tk.Button(root, command=funA)
button1.pack()
button2 = tk.Button(root, command=funB)
button2.pack()
button3 = tk.Button(root, command=funC)
button3.pack()

funA funBfunCsẽ hiển thị một Toplevelcửa sổ khác với các widget khi người dùng nhấp vào nút 1, 2, 3.

Tôi tự hỏi nếu đây là cách đúng để viết một chương trình tkinter python? Chắc chắn, nó sẽ hoạt động ngay cả khi tôi viết theo cách này, nhưng nó có phải là cách tốt nhất? Nghe có vẻ ngu ngốc nhưng khi tôi nhìn thấy mã người khác viết, mã của họ không bị rối với hàng loạt hàm và chủ yếu là họ có các lớp.

Có bất kỳ cấu trúc cụ thể mà chúng ta nên làm theo như thực hành tốt? Làm thế nào tôi nên lập kế hoạch trước khi bắt đầu viết một chương trình python?

Tôi biết không có thứ gì là thực hành tốt nhất trong lập trình và tôi cũng không yêu cầu nó. Tôi chỉ muốn một số lời khuyên và giải thích để giữ cho tôi đi đúng hướng khi tôi đang học Python một mình.


2
Dưới đây là một hướng dẫn tuyệt vời về thiết kế GUI tkinter, với một vài ví dụ - python-textbok.readthedocs.org/en/latest/ nam Đây là một ví dụ khác với mẫu thiết kế MVC - sukhbinder.wordpress.com/2014/12/ 25 /
Lọ

12
Câu hỏi này có thể rộng, nhưng nó hữu ích và h là một câu trả lời tương đối phổ biến (liên quan đến hầu hết tất cả các câu trả lời [tkinter] khác). Tôi đang đề cử mở lại, vì tôi thấy việc mở nó sẽ hữu ích hơn là đóng cửa.
Bryan Oakley

Câu trả lời:


271

Tôi ủng hộ một cách tiếp cận hướng đối tượng. Đây là mẫu mà tôi bắt đầu với:

# Use Tkinter for python 2, tkinter for python 3
import tkinter as tk

class MainApplication(tk.Frame):
    def __init__(self, parent, *args, **kwargs):
        tk.Frame.__init__(self, parent, *args, **kwargs)
        self.parent = parent

        <create the rest of your GUI here>

if __name__ == "__main__":
    root = tk.Tk()
    MainApplication(root).pack(side="top", fill="both", expand=True)
    root.mainloop()

Những điều quan trọng cần chú ý là:

  • Tôi không sử dụng nhập ký tự đại diện. Tôi nhập gói dưới dạng "tk", yêu cầu tôi phải thêm tiền tố vào tất cả các lệnh tk.. Điều này ngăn ngừa ô nhiễm không gian tên toàn cầu, cộng với nó làm cho mã hoàn toàn rõ ràng khi bạn đang sử dụng các lớp Tkinter, các lớp ttk hoặc một số của riêng bạn.

  • Ứng dụng chính là một lớp học . Điều này cung cấp cho bạn một không gian tên riêng cho tất cả các cuộc gọi lại và các chức năng riêng tư của bạn và nói chung chỉ giúp việc tổ chức mã của bạn dễ dàng hơn. Trong một kiểu thủ tục, bạn phải viết mã từ trên xuống, xác định các hàm trước khi sử dụng chúng, v.v. Với phương pháp này, bạn không thực sự tạo ra cửa sổ chính cho đến bước cuối cùng. Tôi thích kế thừa từ tk.Framechỉ vì tôi thường bắt đầu bằng cách tạo khung, nhưng nó không có nghĩa là cần thiết.

Nếu ứng dụng của bạn có thêm các cửa sổ toplevel, tôi khuyên bạn nên tạo cho mỗi lớp một lớp riêng biệt, kế thừa từ đó tk.Toplevel. Điều này cung cấp cho bạn tất cả các lợi thế tương tự được đề cập ở trên - các cửa sổ là nguyên tử, chúng có không gian tên riêng và mã được tổ chức tốt. Thêm vào đó, nó giúp bạn dễ dàng đặt từng cái vào mô-đun của riêng mình một khi mã bắt đầu lớn.

Cuối cùng, bạn có thể muốn xem xét sử dụng các lớp cho mọi phần chính của giao diện của bạn. Ví dụ: nếu bạn đang tạo một ứng dụng bằng thanh công cụ, ngăn điều hướng, thanh trạng thái và khu vực chính, bạn có thể tạo từng ứng dụng trong số các lớp đó. Điều này làm cho mã chính của bạn khá nhỏ và dễ hiểu:

class Navbar(tk.Frame): ...
class Toolbar(tk.Frame): ...
class Statusbar(tk.Frame): ...
class Main(tk.Frame): ...

class MainApplication(tk.Frame):
    def __init__(self, parent, *args, **kwargs):
        tk.Frame.__init__(self, parent, *args, **kwargs)
        self.statusbar = Statusbar(self, ...)
        self.toolbar = Toolbar(self, ...)
        self.navbar = Navbar(self, ...)
        self.main = Main(self, ...)

        self.statusbar.pack(side="bottom", fill="x")
        self.toolbar.pack(side="top", fill="x")
        self.navbar.pack(side="left", fill="y")
        self.main.pack(side="right", fill="both", expand=True)

Vì tất cả các trường hợp đó có chung một cha mẹ, nên cha mẹ thực sự trở thành một phần "bộ điều khiển" của kiến ​​trúc mô hình-khung nhìn-bộ điều khiển. Vì vậy, ví dụ, cửa sổ chính có thể đặt một cái gì đó trên thanh trạng thái bằng cách gọi self.parent.statusbar.set("Hello, world"). Điều này cho phép bạn xác định một giao diện đơn giản giữa các thành phần, giúp duy trì khớp nối đến mức tối thiểu.


22
@Bryan Oakley bạn có biết bất kỳ mã mẫu tốt nào trên internet mà tôi có thể nghiên cứu cấu trúc của chúng không?
Chris Aung

2
Tôi thứ hai cách tiếp cận hướng đối tượng. Tuy nhiên, theo tôi, việc từ chối sử dụng tính kế thừa trên lớp mà gọi GUI là một ý tưởng hay. Nó cung cấp cho bạn sự linh hoạt hơn nếu cả hai đối tượng Tk và Frame là thuộc tính của một lớp không kế thừa từ bất cứ thứ gì. Bằng cách này, bạn có thể truy cập các đối tượng Tk và Frame dễ dàng hơn (và ít mơ hồ hơn) và phá hủy một thứ sẽ không phá hủy mọi thứ trong lớp của bạn nếu bạn không muốn nó. Tôi quên mất lý do chính xác tại sao điều này lại quan trọng trong một số chương trình, nhưng nó cho phép bạn làm nhiều việc hơn.
Brōtsyorfuzthrāx

1
Sẽ không chỉ đơn giản là có một lớp cung cấp cho bạn một không gian tên riêng tư? Tại sao phân lớp Khung cải thiện về điều đó?
gcb

3
@gcb: có, bất kỳ lớp nào sẽ cung cấp cho bạn một không gian tên riêng tư. Tại sao phân lớp một Khung? Tôi thường sẽ tạo một khung, vì vậy, nó là một lớp ít hơn để quản lý (lớp con của Khung, so với một lớp kế thừa từ đối tượng, với khung là thuộc tính). Tôi đã đọc lại câu trả lời một chút để làm cho nó rõ ràng hơn. Cảm ơn vì bạn đã phản hồi.
Bryan Oakley

2
@madtyn: không cần lưu tài liệu tham khảo parent, trừ khi bạn sẽ sử dụng nó sau này. Tôi đã không lưu nó bởi vì không có mã nào trong ví dụ của tôi yêu cầu nó phải được lưu.
Bryan Oakley

39

Việc đặt từng cửa sổ cấp cao nhất của bạn vào lớp riêng của nó sẽ giúp bạn sử dụng lại mã và tổ chức mã tốt hơn. Bất kỳ nút và phương thức liên quan nào có trong cửa sổ nên được xác định bên trong lớp này. Đây là một ví dụ (lấy từ đây ):

import tkinter as tk

class Demo1:
    def __init__(self, master):
        self.master = master
        self.frame = tk.Frame(self.master)
        self.button1 = tk.Button(self.frame, text = 'New Window', width = 25, command = self.new_window)
        self.button1.pack()
        self.frame.pack()
    def new_window(self):
        self.newWindow = tk.Toplevel(self.master)
        self.app = Demo2(self.newWindow)

class Demo2:
    def __init__(self, master):
        self.master = master
        self.frame = tk.Frame(self.master)
        self.quitButton = tk.Button(self.frame, text = 'Quit', width = 25, command = self.close_windows)
        self.quitButton.pack()
        self.frame.pack()
    def close_windows(self):
        self.master.destroy()

def main(): 
    root = tk.Tk()
    app = Demo1(root)
    root.mainloop()

if __name__ == '__main__':
    main()

Cũng thấy:

Mong rằng sẽ giúp.


6

Đây không phải là một cấu trúc xấu; Nó sẽ chỉ làm việc tốt. Tuy nhiên, bạn phải có chức năng trong một chức năng để thực hiện các lệnh khi ai đó nhấp vào nút hoặc một cái gì đó

Vì vậy, những gì bạn có thể làm là viết các lớp cho những cái này sau đó có các phương thức trong lớp xử lý các lệnh cho các lần nhấp nút và như vậy.

Đây là một ví dụ:

import tkinter as tk

class Window1:
    def __init__(self, master):
        pass
        # Create labels, entries,buttons
    def button_click(self):
        pass
        # If button is clicked, run this method and open window 2


class Window2:
    def __init__(self, master):
        #create buttons,entries,etc

    def button_method(self):
        #run this when button click to close window
        self.master.destroy()

def main(): #run mianloop 
    root = tk.Tk()
    app = Window1(root)
    root.mainloop()

if __name__ == '__main__':
    main()

Thông thường các chương trình tk có nhiều cửa sổ là nhiều lớp lớn và trong __init__ tất cả các mục, nhãn, v.v ... được tạo và sau đó mỗi phương thức là xử lý các sự kiện nhấn nút

Thực sự không phải là một cách đúng đắn để làm điều đó, bất cứ điều gì phù hợp với bạn và hoàn thành công việc miễn là nó có thể đọc được và bạn có thể dễ dàng giải thích nó bởi vì nếu bạn không thể dễ dàng giải thích chương trình của mình, có lẽ có cách tốt hơn để làm điều đó .

Hãy xem Thinking in Tkinter .


3
"Suy nghĩ trong Tkinter" ủng hộ nhập khẩu toàn cầu, mà tôi nghĩ là lời khuyên rất tồi.
Bryan Oakley

1
Đó là sự thật tôi không khuyên bạn nên sử dụng toàn cầu chỉ là một số cấu trúc methos lớp chính mà bạn đúng :)
Nối tiếp

2

OOP nên là cách tiếp cận và framenên là một biến lớp thay vì biến thể hiện .

from Tkinter import *
class App:
  def __init__(self, master):
    frame = Frame(master)
    frame.pack()
    self.button = Button(frame, 
                         text="QUIT", fg="red",
                         command=frame.quit)
    self.button.pack(side=LEFT)
    self.slogan = Button(frame,
                         text="Hello",
                         command=self.write_slogan)
    self.slogan.pack(side=LEFT)
  def write_slogan(self):
    print "Tkinter is easy to use!"

root = Tk()
app = App(root)
root.mainloop()

nhập mô tả hình ảnh ở đây

Tham khảo: http://www.python-cference.eu/tkinter_buttons.php


2
Bạn chỉ có thể sử dụng TKintertrên Python 2. Tôi khuyên bạn nên sử dụng tkintercho Python 3. Tôi cũng sẽ đặt ba dòng mã cuối cùng dưới một main()hàm và gọi nó ở cuối chương trình. Tôi chắc chắn sẽ tránh sử dụng from module_name import *vì nó gây ô nhiễm không gian tên toàn cầu và có thể làm giảm khả năng đọc.
Zac

1
Làm thế nào bạn có thể cho biết sự khác biệt giữa button1 = tk.Button(root, command=funA)button1 = ttk.Button(root, command=funA)nếu tkintermô-đun mở rộng cũng đang được nhập khẩu? Với *cú pháp, cả hai dòng mã sẽ xuất hiện button1 = Button(root, command=funA). Tôi không khuyên bạn nên sử dụng cú pháp đó.
Zac

0

Tổ chức ứng dụng của bạn bằng cách sử dụng lớp giúp bạn và những người khác làm việc với bạn dễ dàng gỡ lỗi và cải thiện ứng dụng một cách dễ dàng.

Bạn có thể dễ dàng sắp xếp ứng dụng của mình như thế này:

class hello(Tk):
    def __init__(self):
        super(hello, self).__init__()
        self.btn = Button(text = "Click me", command=close)
        self.btn.pack()
    def close():
        self.destroy()

app = hello()
app.mainloop()

-2

Có lẽ cách tốt nhất để tìm hiểu cách cấu trúc chương trình của bạn là đọc mã của người khác, đặc biệt nếu đó là một chương trình lớn mà nhiều người đã đóng góp. Sau khi xem mã của nhiều dự án, bạn sẽ có được một ý tưởng về phong cách đồng thuận nên là gì.

Python, như một ngôn ngữ, đặc biệt ở chỗ có một số hướng dẫn mạnh mẽ về cách bạn nên định dạng mã của mình. Đầu tiên là cái gọi là "Zen of Python":

  • Đẹp thì tốt hơn xấu.
  • Rõ ràng là tốt hơn so với ngầm.
  • Đơn giản là tốt hơn phức tạp.
  • Phức tạp tốt hơn phức tạp.
  • Bằng phẳng là tốt hơn so với lồng nhau.
  • Thưa thì tốt hơn dày đặc.
  • Tính dễ đọc.
  • Trường hợp đặc biệt không đủ đặc biệt để phá vỡ các quy tắc.
  • Mặc dù thực tế đánh bại sự tinh khiết.
  • Lỗi không bao giờ nên âm thầm vượt qua.
  • Trừ khi im lặng rõ ràng.
  • Trước sự mơ hồ, hãy từ chối sự cám dỗ để đoán.
  • Nên có một-- và tốt nhất là chỉ có một cách rõ ràng để làm điều đó.
  • Mặc dù cách đó ban đầu có thể không rõ ràng trừ khi bạn là người Hà Lan.
  • Bây giờ tốt hơn bao giờ hết.
  • Mặc dù không bao giờ thường tốt hơn đúng bây giờ.
  • Nếu việc thực hiện khó giải thích, đó là một ý tưởng tồi.
  • Nếu việc thực hiện dễ giải thích, nó có thể là một ý tưởng tốt.
  • Không gian tên là một ý tưởng tuyệt vời - hãy làm nhiều hơn nữa!

Ở cấp độ thực tế hơn, có PEP8 , hướng dẫn phong cách cho Python.

Với những người trong tâm trí, tôi sẽ nói rằng kiểu mã của bạn không thực sự phù hợp, đặc biệt là các hàm lồng nhau. Tìm cách làm phẳng chúng ra, bằng cách sử dụng các lớp hoặc chuyển chúng thành các mô-đun riêng biệt. Điều này sẽ làm cho cấu trúc chương trình của bạn dễ hiểu hơn nhiều.


12
-1 để sử dụng Zen của Python. Mặc dù đó là tất cả những lời khuyên tốt, nhưng nó không trực tiếp giải quyết câu hỏi đã được hỏi. Lấy đoạn cuối cùng ra và câu trả lời này có thể áp dụng cho hầu hết mọi câu hỏi về trăn trên trang web này. Đó là lời khuyên tốt, tích cực, nhưng nó không trả lời câu hỏi.
Bryan Oakley

1
@BryanOakley Tôi không đồng ý với bạn về điều đó. Có, Zen của Python rất rộng và có thể được sử dụng để giải quyết nhiều câu hỏi. Ông đã đề cập trong đoạn cuối để chọn các lớp hoặc đặt các hàm trong các mô-đun riêng biệt. Ông cũng đề cập đến PEP8, một hướng dẫn phong cách cho Python, với các tài liệu tham khảo về nó. Mặc dù không phải là một câu trả lời trực tiếp, tôi nghĩ rằng câu trả lời này là đáng tin cậy trong thực tế rằng nó đề cập đến nhiều tuyến đường khác nhau có thể được thực hiện. Đó chỉ là ý kiến ​​của tôi
Zac

1
Tôi đến đây để tìm câu trả lời cho câu hỏi cụ thể này. Ngay cả đối với một câu hỏi mở, tôi không thể làm gì với câu trả lời này. -1'd từ tôi là tốt.
jonathan

Không có cách nào, câu hỏi là về cấu trúc một ứng dụng tkinter , không có gì về hướng dẫn kiểu dáng / mã hóa / zen. Dễ dàng như trích dẫn @Arbiter "Mặc dù không phải là câu trả lời trực tiếp", vì vậy, đó KHÔNG phải là câu trả lời. Điều này giống như "có thể có và có thể không", với zen được chuẩn bị trước.
m3nda

-7

Cá nhân tôi không sử dụng cách tiếp cận hướng đối tượng, chủ yếu là vì nó a) chỉ cản trở; b) bạn sẽ không bao giờ sử dụng lại như là một mô-đun.

nhưng một điều không được thảo luận ở đây, là bạn phải sử dụng luồng hoặc đa xử lý. Luôn luôn. nếu không ứng dụng của bạn sẽ là khủng khiếp.

chỉ cần làm một bài kiểm tra đơn giản: bắt đầu một cửa sổ, sau đó tìm nạp một số URL hoặc bất cứ thứ gì khác. thay đổi là UI của bạn sẽ không được cập nhật trong khi yêu cầu mạng đang diễn ra. Có nghĩa, cửa sổ ứng dụng của bạn sẽ bị hỏng. phụ thuộc vào hệ điều hành bạn đang sử dụng, nhưng hầu hết các lần, nó sẽ không vẽ lại, bất cứ thứ gì bạn kéo qua cửa sổ sẽ được dán trên nó, cho đến khi quá trình quay trở lại mainloop TK.


4
Những gì bạn nói đơn giản là không đúng sự thật. Tôi đã viết rất nhiều ứng dụng dựa trên tk, cả cá nhân và thương mại và hầu như không bao giờ phải sử dụng các chủ đề. Chủ đề có vị trí của chúng, nhưng đơn giản là không đúng khi bạn phải sử dụng chúng khi viết chương trình tkinter. Nếu bạn có các hàm runnng dài, bạn có thể cần các luồng hoặc đa xử lý, nhưng có rất nhiều, nhiều loại chương trình bạn có thể viết mà không cần các luồng.
Bryan Oakley

Tôi nghĩ rằng nếu bạn đánh giá lại câu trả lời của bạn rõ ràng hơn một chút về điều đó, nó sẽ là một câu trả lời tốt hơn. Nó cũng thực sự có ích khi có một ví dụ kinh điển về việc sử dụng các chủ đề với tkinter.
Bryan Oakley

không quan tâm đến việc là câu trả lời tốt nhất ở đây vì nó không đúng chủ đề. nhưng hãy nhớ rằng bắt đầu với luồng / bội là rất dễ dàng. nếu bạn phải thêm sau, đó là một trận chiến thua. và ngày nay, hoàn toàn không có ứng dụng nào không bao giờ nói chuyện với mạng. và ngay cả khi bạn bỏ qua và nghĩ rằng 'tôi chỉ có ít đĩa IO', ngày mai khách hàng của bạn quyết định tệp đó sẽ tồn tại trên NFS và bạn đang chờ IO mạng và ứng dụng của bạn dường như đã chết.
gcb

2
@ erm3nda: "mọi ứng dụng được kết nối với mạng hoặc thực hiện ghi IO sẽ nhanh hơn rất nhiều khi sử dụng các luồng hoặc quy trình con" - điều đó đơn giản là không đúng. Việc xâu chuỗi sẽ không nhất thiết làm cho chương trình của bạn nhanh hơn và trong một số trường hợp sẽ làm cho chương trình chậm hơn. Trong lập trình GUI, lý do chính để sử dụng các luồng là để có thể chạy một số mã có thể chặn GUI.
Bryan Oakley

2
@ erm3nda: không, tôi không nói chủ đề là không cần thiết chút nào . Chúng chắc chắn là cần thiết (tốt, chủ đề hoặc đa xử lý) cho rất nhiều thứ. Chỉ là có một lớp ứng dụng GUI rất lớn, nơi tkinter phù hợp nhưng đơn giản là các luồng không cần thiết. Và vâng, "trình cài đặt, notepad, và các công cụ dễ dàng khác" thuộc danh mục đó. Thế giới được tạo thành từ nhiều "công cụ dễ dàng" hơn là những thứ như word, excel, photoshop, v.v. Ngoài ra, hãy nhớ rằng bối cảnh ở đây là tkinter . Tkinter thường không được sử dụng cho các ứng dụng rất lớn, phức tạp.
Bryan Oakley
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.