Python: Cách xây dựng một ứng dụng GUI Python với wxPython

Các khóa học qua video:
Python SQL Server PHP C# Lập trình C Java HTML5-CSS3-JavaScript
Học trên YouTube <76K/tháng. Đăng ký Hội viên
Viết nhanh hơn - Học tốt hơn
Giải phóng thời gian, khai phóng năng lực
Cách xây dựng một ứng dụng GUI Python với wxPython

Mục lục bài viết:


Có nhiều bộ công cụ giao diện người dùng đồ họa (GUI) mà bạn có thể sử dụng với ngôn ngữ lập trình Python, trong đó có ba công ty lớn là Tkinter , wxPython và PyQt. Mỗi bộ công cụ này đều hoạt động được với Windows, macOS và Linux, với PyQt có thêm khả năng hoạt động trên thiết bị di động.

Giao diện người dùng đồ họa là một ứng dụng có các nút, cửa sổ và nhiều tiện ích con khác mà người dùng có thể sử dụng để tương tác với ứng dụng của bạn. Một ví dụ điển hình là trình duyệt web. Nó có các nút, tab và cửa sổ chính, nơi tải tất cả nội dung.

Trong bài viết này, bạn sẽ học cách xây dựng giao diện người dùng đồ họa với Python bằng bộ công cụ wxPython GUI.

Dưới đây là các chủ đề được đề cập:

  • Bắt đầu với wxPython
  • Định nghĩa về GUI
  • Tạo ứng dụng Skeleton
  • Tạo một ứng dụng làm việc

Ta hãy bắt đầu nào!

Bắt đầu với wxPython

Bộ công cụ wxPython GUI là một trình bao bọc Python xung quanh một thư viện C ++ được gọi là wxWidgets. Lần phát hành đầu tiên của wxPython là vào năm 1998, vì vậy wxPython đã tồn tại khá lâu. Điểm khác biệt chính của wxPython so với các bộ công cụ khác như PyQt hoặc Tkinter , là wxPython sử dụng các widget thực tế trên nền tảng gốc bất cứ khi nào có thể. Điều này làm cho các ứng dụng wxPython trông giống với hệ điều hành mà nó đang chạy.

PyQt và Tkinter đều tự vẽ các widget của mình, đó là lý do tại sao chúng không phải lúc nào cũng khớp với các widget gốc, mặc dù PyQt rất gần.

Điều này không có nghĩa là wxPython không hỗ trợ các widget tùy chỉnh. Trên thực tế, bộ công cụ wxPython có nhiều widget tùy chỉnh đi kèm với nó, cùng với hàng chục widget cốt lõi. Trang tải xuống wxPython có một phần gọi là Tệp bổ sung đáng để xem.

Tại đây, có một bản tải xuống gói wxPython Demo. Đây là một ứng dụng nhỏ xinh thể hiện phần lớn các widget có trong wxPython. Bản trình diễn cho phép nhà phát triển xem mã trong một tab và chạy nó trong tab thứ hai. Bạn thậm chí có thể chỉnh sửa và chạy lại mã trong bản demo để xem những thay đổi của bạn ảnh hưởng đến ứng dụng như thế nào.

Cài đặt wxPython

Bạn sẽ sử dụng wxPython cho bài viết này, đó là wxPython 4, còn được gọi là bản phát hành Phoenix. Phiên bản wxPython 3 và wxPython 2 chỉ được xây dựng cho Python 2. Khi Robin Dunn, người bảo trì chính của wxPython, tạo ra bản phát hành wxPython 4, anh ấy không dùng nhiều bí danh nữa và xóa rất nhiều mã để làm cho wxPython trở nên Pythonic hơn và dễ bảo trì hơn.

Bạn sẽ muốn tham khảo các liên kết sau nếu bạn đang chuyển từ phiên bản cũ hơn của wxPython sang wxPython 4 (Phoenix):

Gói wxPython 4 tương thích với cả Python 2.7 và Python 3.

Bây giờ bạn có thể sử dụng pip để cài đặt wxPython 4, điều này không thể thực hiện được trong các phiên bản cũ của wxPython. Bạn có thể làm như sau để cài đặt nó trên máy của bạn:

$ pip install wxpython

Lưu ý: Trên Mac OS X, bạn sẽ cần cài đặt một trình biên dịch chẳng hạn như XCode để quá trình cài đặt hoàn tất thành công. Linux cũng có thể yêu cầu bạn cài đặt một số phụ thuộc trước khi trình cài đặt pip hoạt động bình thường.

Ví dụ: tôi cần cài đặt freeglut3-dev , libgstreamer-plugins-base0.10-dev và libwebkitgtk-3.0-dev trên Xubuntu để cài đặt nó.

May mắn thay, các thông báo lỗi pip hiển thị hữu ích trong việc tìm ra những gì còn thiếu và bạn có thể sử dụng phần điều kiện tiên quyết trên trang wxPython Github để giúp bạn tìm thấy thông tin cần thiết nếu bạn muốn cài đặt wxPython trên Linux.

Có một số wheel Python có sẵn cho các phiên bản Linux phổ biến nhất mà bạn có thể tìm thấy trong phần Extras Linux với cả phiên bản GTK2 và GTK3. Để cài đặt một trong những wheel này, bạn sẽ sử dụng lệnh sau:

$ pip install -U -f https://extras.wxpython.org/wxPython4/extras/linux/gtk3/ubuntu-18.04/ wxPython

Đảm bảo rằng bạn đã sửa đổi lệnh trên để phù hợp với phiên bản Linux của mình.

Định nghĩa về GUI

Như đã đề cập trong phần giới thiệu, giao diện người dùng đồ họa (GUI) là một giao diện được vẽ trên màn hình để người dùng tương tác.

Giao diện người dùng có một số thành phần phổ biến:

  • Cửa sổ chính
  • Menu
  • Thanh công cụ
  • Nút
  • Text Entry
  • Label

Tất cả các mục này được gọi chung là widget. Có nhiều widget phổ biến khác và nhiều widget tùy chỉnh mà wxPython hỗ trợ. Một nhà phát triển sẽ lấy các widget và sắp xếp chúng một cách hợp lý trên một cửa sổ để người dùng tương tác.

Vòng lặp sự kiện

Giao diện người dùng đồ họa hoạt động bằng cách đợi người dùng thực hiện điều gì đó. Một cái gì đó được gọi là một sự kiện. Sự kiện xảy ra khi người dùng nhập nội dung nào đó trong khi ứng dụng của bạn đang được lấy nét hoặc khi người dùng sử dụng chuột để nhấn nút hoặc tiện ích con khác.

Bên dưới vỏ bọc, bộ công cụ GUI đang chạy một vòng lặp vô hạn được gọi là vòng lặp sự kiện. Vòng lặp sự kiện chỉ đợi các sự kiện xảy ra và sau đó hành động trên các sự kiện đó theo những gì nhà phát triển đã mã hóa ứng dụng để thực hiện. Khi ứng dụng không nắm bắt một sự kiện, nó sẽ bỏ qua một cách hiệu quả rằng nó thậm chí đã xảy ra.

Khi bạn đang lập trình một giao diện người dùng đồ họa, bạn cần lưu ý rằng bạn sẽ cần kết nối từng tiện ích con với các trình xử lý sự kiện để ứng dụng của bạn sẽ làm được điều gì đó.

Có một lưu ý đặc biệt mà bạn cần ghi nhớ khi làm việc với các vòng lặp sự kiện: chúng có thể bị chặn. Khi bạn chặn một vòng lặp sự kiện, GUI sẽ không phản hồi và có vẻ như đóng băng đối với người dùng.

Bất kỳ quá trình nào bạn khởi chạy trong GUI sẽ mất hơn một phần tư giây có thể sẽ được khởi chạy dưới dạng một chuỗi hoặc quy trình riêng biệt. Điều này sẽ ngăn GUI của bạn không bị đóng băng và mang đến cho người dùng trải nghiệm tốt hơn.

Framework wxPython có các phương thức an toàn cho luồng đặc biệt mà bạn có thể sử dụng để liên lạc lại với ứng dụng của mình để cho ứng dụng biết rằng luồng đã hoàn tất hoặc để cập nhật cho nó.

Hãy tạo một ứng dụng khung để chứng minh cách các sự kiện hoạt động.

Tạo ứng dụng khung (Skeleton)

Khung ứng dụng trong ngữ cảnh GUI là giao diện người dùng với các tiện ích không có bất kỳ trình xử lý sự kiện nào. Những điều này rất hữu ích cho việc tạo mẫu. Về cơ bản, bạn chỉ cần tạo GUI và trình bày nó cho các bên liên quan của bạn để đăng nhập trước khi dành nhiều thời gian cho logic phụ trợ.

Hãy bắt đầu bằng cách tạo một ứng dụng Hello Worl với wxPython:

import wx

app = wx.App()
frame = wx.Frame(parent=None, title='Hello World')
frame.Show()
app.MainLoop()

Lưu ý: Người dùng Mac có thể nhận được thông báo sau: Chương trình này cần quyền truy cập vào màn hình. Vui lòng chạy với phiên bản Framework của python và chỉ khi bạn đã đăng nhập trên màn hình chính của máy Mac. Nếu bạn thấy thông báo này và bạn không chạy trong virtualenv, thì bạn cần chạy ứng dụng của mình bằng pythonw thay vì python . Nếu bạn đang chạy wxPython từ bên trong virtualenv, hãy xem wiki wxPython để biết giải pháp.

Trong ví dụ này, bạn có hai phần: wx.App và wx.Framewx.App là đối tượng ứng dụng của wxPython và được yêu cầu để chạy GUI của bạn. wx.App bắt đầu một cái gì đó gọi là .MainLoop(). Đây là vòng lặp sự kiện mà bạn đã học trong phần trước.

Phần còn lại là wx.Frame, sẽ tạo ra một cửa sổ để người dùng tương tác. Trong trường hợp này, bạn đã nói với wxPython rằng frame không có cha và tiêu đề của nó là Hello World. Đây là giao diện khi bạn chạy mã:

Xin chào thế giới trong wxPython

Lưu ý: Ứng dụng sẽ trông khác khi bạn chạy trên Mac hoặc Windows.

Theo mặc định, a wx.Frame sẽ bao gồm các nút thu nhỏ, phóng to và thoát ở trên cùng. Mặc dù vậy, thông thường bạn sẽ không tạo ứng dụng theo cách này. Hầu hết mã wxPython sẽ yêu cầu bạn phân lớp wx.Frame và các tiện ích con khác để bạn có thể có được toàn bộ sức mạnh của bộ công cụ.

Hãy dành một chút thời gian và viết lại mã của bạn dưới dạng một lớp:

import wx

class MyFrame(wx.Frame):    
    def __init__(self):
        super().__init__(parent=None, title='Hello World')
        self.Show()

if __name__ == '__main__':
    app = wx.App()
    frame = MyFrame()
    app.MainLoop()

Bạn có thể sử dụng mã này làm mẫu cho ứng dụng của mình. Tuy nhiên, ứng dụng này không làm được nhiều việc, vì vậy chúng ta hãy dành một chút thời gian để tìm hiểu một chút về một số widget khác mà bạn có thể thêm vào.

Các widget

Bộ công cụ wxPython có hơn một trăm widget để bạn lựa chọn. Điều này cho phép bạn tạo các ứng dụng phong phú, nhưng cũng có thể gây khó khăn khi cố gắng tìm ra tiện ích con nào sẽ sử dụng. Đây là lý do tại sao wxPython Demo hữu ích, vì nó có bộ lọc tìm kiếm mà bạn có thể sử dụng để giúp bạn tìm thấy các tiện ích con có thể áp dụng cho dự án của bạn.

Hầu hết các ứng dụng GUI cho phép người dùng nhập một số văn bản và nhấn một nút. Hãy tiếp tục và thêm các widget đó:

import wx

class MyFrame(wx.Frame):    
    def __init__(self):
        super().__init__(parent=None, title='Hello World')
        panel = wx.Panel(self)

        self.text_ctrl = wx.TextCtrl(panel, pos=(5, 5))
        my_btn = wx.Button(panel, label='Press Me', pos=(5, 55))

        self.Show()

if __name__ == '__main__':
    app = wx.App()
    frame = MyFrame()
    app.MainLoop()

Khi bạn chạy mã này, ứng dụng của bạn sẽ giống như sau:

Hello World in wxPython with widget

Tiện ích đầu tiên bạn cần thêm có tên là wx.Panel. Tiện ích này không bắt buộc, nhưng được khuyến nghị. Trên Windows, bạn thực sự được yêu cầu sử dụng Panel để màu nền của khung có màu xám phù hợp. Tính năng duyệt tab bị tắt nếu không có Panel trên Windows.

Khi bạn thêm tiện ích panel vào một frame và panel là con duy nhất của frame, thì nó sẽ tự động mở rộng để lấp đầy khung với chính nó.

Bước tiếp theo là thêm một wx.TextCtrl vào panel. Đối số đầu tiên cho hầu hết tất cả các widget là widget nào sẽ được sử dụng. Trong trường hợp này, bạn muốn điều khiển văn bản và nút ở trên cùng của bảng điều khiển, vì vậy nó là cấp độ chính mà bạn chỉ định.

Bạn cũng cần cho wxPython biết vị trí đặt tiện ích, bạn có thể thực hiện bằng cách chuyển vào một vị trí thông qua tham số pos. Trong wxPython, vị trí gốc là (0,0) là góc trên bên trái của giá trị gốc. Vì vậy, đối với điều khiển văn bản, bạn nói với wxPython rằng bạn muốn đặt góc trên cùng bên trái của nó 5 pixel từ bên trái (x) và 5 pixel từ trên cùng (y).

Sau đó, bạn thêm nút của mình vào bảng điều khiển và gắn nhãn cho nó. Để ngăn các tiện ích con chồng lên nhau, bạn cần đặt tọa độ y thành 55 cho vị trí của nút.

Định vị tuyệt đối

Khi bạn cung cấp tọa độ chính xác cho vị trí tiện ích con của mình, kỹ thuật mà bạn đã sử dụng được gọi là định vị tuyệt đối. Hầu hết các bộ công cụ GUI đều cung cấp khả năng này, nhưng nó không thực sự được khuyến khích.

Khi ứng dụng của bạn trở nên phức tạp hơn, việc theo dõi tất cả các vị trí của widget sẽ trở nên khó khăn và nếu bạn phải di chuyển các widget xung quanh. Việc đặt lại tất cả các vị trí đó trở thành một cơn ác mộng.

May mắn thay, tất cả các bộ công cụ GUI hiện đại đều cung cấp giải pháp cho điều này, đó là những gì bạn sẽ tìm hiểu tiếp theo.

Sizers (Kích thước động)

Bộ công cụ wxPython bao gồm các sizers, được sử dụng để tạo bố cục động. Chúng quản lý vị trí của các widget cho bạn và sẽ điều chỉnh chúng khi bạn thay đổi kích thước cửa sổ ứng dụng. Các bộ công cụ GUI khác như PyQt chẳng hạn sẽ gọi sizers là bố cục.

Dưới đây là các loại sizers chính mà bạn sẽ thấy được sử dụng thường xuyên nhất:

  • wx.BoxSizer
  • wx.GridSizer
  • wx.FlexGridSizer

Hãy thêm một ví dụ wx.BoxSizer của bạn và xem liệu ta có thể làm cho nó hoạt động độc đáo hơn một chút hay không:

import wx

class MyFrame(wx.Frame):    
    def __init__(self):
        super().__init__(parent=None, title='Hello World')
        panel = wx.Panel(self)        
        my_sizer = wx.BoxSizer(wx.VERTICAL)        
        self.text_ctrl = wx.TextCtrl(panel)
        my_sizer.Add(self.text_ctrl, 0, wx.ALL | wx.EXPAND, 5)        
        my_btn = wx.Button(panel, label='Press Me')
        my_sizer.Add(my_btn, 0, wx.ALL | wx.CENTER, 5)        
        panel.SetSizer(my_sizer)        
        self.Show()

if __name__ == '__main__':
    app = wx.App()
    frame = MyFrame()
    app.MainLoop()

Tại đây, bạn tạo một thể hiện của wx.BoxSizer và truyền cho nó wx.VERTICAL, đây là hướng mà các widget được thêm vào bộ chỉnh sửa.

Trong trường hợp này, các widget sẽ được thêm theo chiều dọc, có nghĩa là chúng sẽ được thêm lần lượt từ trên xuống dưới. Bạn cũng có thể đặt định hướng của BoxSizer thành wx.HORIZONTAL. Khi bạn làm điều đó, các widget sẽ được thêm từ trái sang phải.

Để thêm một widget vào sizer, bạn sẽ sử dụng .Add(). Nó chấp nhận tối đa năm đối số:

  • window (là widget)
  • proportion
  • flag
  • border
  • userData

Đối số window là tiện ích con sẽ được thêm vào trong khi proportion dùng để thiết lập khoảng cách tương đối so với các tiện ích con khác trong bộ chỉnh sửa tiện ích cụ thể này sẽ chiếm. Theo mặc định là 0, điều này sẽ yêu cầu wxPython đặt widget ở tỷ lệ mặc định của nó.

Đối số thứ ba là flag. Ta có thể truyền nhiều flag (cờ) nếu bạn muốn bằng cách sử dụng ký tự xổ đứng:|. Bộ công cụ wxPython sử dụng |để thêm flag bằng cách sử dụng một loạt các bit OR.

Trong ví dụ này, bạn thêm control Text với các cờ wx.ALL và wx.EXPAND. Cờ wx.ALL sẽ nói với wxPython rằng bạn muốn thêm một đường viền trên tất cả các mặt của các widget, trong khi wx.EXPAND làm cho các widget mở rộng càng nhiều càng tốt trong sizer.

Cuối cùng, bạn có tham số border, điều này cho wxPython biết bạn muốn có bao nhiêu pixel đường viền xung quanh tiện ích. Tham số userData chỉ được sử dụng khi bạn muốn làm điều gì đó phức tạp với kích thước của các widget và nó ít được dùng trong thực tế.

Thêm nút vào bộ sizer theo các bước chính xác. Tuy nhiên, để làm cho mọi thứ thú vị hơn một chút, ta sẽ thay cờ wx.EXPAND thành wx.CENTER để căn giữa trên màn hình.

Kết quả sẽ trông giống như sau:

Xin chào thế giới trong wxPython với Sizers

Nếu bạn muốn tìm hiểu thêm về sizers, tài liệu wxPython có một trang rất hay về chủ đề này.

Thêm sự kiện

Mặc dù ứng dụng của bạn trông thú vị hơn về mặt trực quan, nhưng nó vẫn không thực sự làm được gì. Ví dụ, nếu bạn nhấn nút, không có gì thực sự xảy ra.

Hãy giao cho nút một công việc như sau:

import wx

class MyFrame(wx.Frame):    
    def __init__(self):
        super().__init__(parent=None, title='Hello World')
        panel = wx.Panel(self)        
        my_sizer = wx.BoxSizer(wx.VERTICAL)        
        self.text_ctrl = wx.TextCtrl(panel)
        my_sizer.Add(self.text_ctrl, 0, wx.ALL | wx.EXPAND, 5)        
        my_btn = wx.Button(panel, label='Press Me')
        my_btn.Bind(wx.EVT_BUTTON, self.on_press)
        my_sizer.Add(my_btn, 0, wx.ALL | wx.CENTER, 5)        
        panel.SetSizer(my_sizer)        
        self.Show()

    def on_press(self, event):
        value = self.text_ctrl.GetValue()
        if not value:
            print("You didn't enter anything!")
        else:
            print(f'You typed: "{value}"')

if __name__ == '__main__':
    app = wx.App()
    frame = MyFrame()
    app.MainLoop()

Các widget trong wxPython cho phép bạn đính kèm các ràng buộc sự kiện vào chúng để chúng có thể đáp ứng các loại sự kiện nhất định.

Lưu ý: Khối mã trên sử dụng f-string. Bạn có thể đọc tất cả về chúng trong f-Strings của Python 3: Cú pháp định dạng chuỗi được cải thiện.

Bạn muốn nút thực hiện điều gì đó khi người dùng nhấn vào nút đó. Bạn có thể thực hiện điều này bằng cách gọi phương thức .Bind() của nút. .Bind() sẽ lấy sự kiện bạn muốn liên kết, sau đó trình xử lý sẽ gọi khi sự kiện xảy ra, một nguồn tùy chọn và một vài id tùy chọn.

Trong ví dụ này, bạn liên kết đối tượng nút của mình với sự kiện wx.EVT_BUTTON và yêu cầu nó gọi on_press() khi sự kiện đó được kích hoạt.

Một sự kiện được "kích hoạt" khi người dùng thực hiện sự kiện mà bạn đã liên kết. Trong trường hợp này, sự kiện mà bạn thiết lập là sự kiện nhấn nút wx.EVT_BUTTON.

.on_press() chấp nhận đối số thứ hai mà ở ví dụ trên tên là event. Đây là theo quy ước, bạn hoàn toàn có thể gọi nó là một cái gì đó khác nếu bạn muốn. Tuy nhiên, tham số sự kiện ở đây đề cập đến thực tế là khi phương thức này được gọi, đối số thứ hai của nó phải là một đối tượng sự kiện của một số loại.

Bên trong .on_press(), bạn có thể lấy nội dung của control text bằng cách gọi phương thức GetValue() của nó. Sau đó, bạn in một chuỗi để stdout tùy thuộc vào nội dung của điều khiển văn bản là gì.

Bây giờ bạn đã có những kiến ​​thức cơ bản, hãy cùng tìm hiểu cách tạo một ứng dụng hữu ích!

Tạo một ứng dụng công việc

Bước đầu tiên khi tạo ra thứ gì đó mới là tìm ra thứ bạn muốn tạo. Trong trường hợp này, tôi có quyền tự do đưa ra quyết định đó cho bạn. Bạn sẽ học cách tạo trình chỉnh sửa thẻ MP3! Bước tiếp theo khi tạo một cái gì đó mới là tìm ra những gói nào có thể giúp bạn hoàn thành nhiệm vụ của mình.

Nếu bạn thực hiện tìm kiếm trên Google Python mp3 tagging, bạn sẽ thấy bạn có một số tùy chọn:

  • mp3-tagger
  • eyeD3
  • mutagen

Tôi đã thử một vài trong số này và quyết định rằng eyeD3 có một API đẹp mà bạn có thể sử dụng mà không bị sa lầy với đặc tả ID3 của MP3. Bạn có thể cài đặt eyeD3 bằng cách sử dụng pip, như sau:

$ pip install eyed3

Khi cài đặt gói này trên macOS, bạn có thể cần cài đặt libmagic bằng cách sử dụng brew. Người dùng Windows và Linux sẽ không gặp bất kỳ sự cố nào khi cài đặt eyeD3.

Thiết kế giao diện người dùng

Khi nói đến thiết kế một giao diện, luôn luôn tốt đẹp nếu bạn chỉ cần phác thảo ra cách bạn nghĩ giao diện người dùng sẽ trông như thế nào.

Bạn cần có thể thực hiện những việc sau:

  • Mở một hoặc nhiều tệp MP3
  • Hiển thị các thẻ MP3 hiện tại
  • Chỉnh sửa thẻ MP3

Hầu hết các giao diện người dùng sử dụng menu hoặc nút để mở tệp hoặc thư mục. Bạn có thể sử dụng menu File cho việc này. Vì bạn có thể muốn xem các thẻ cho nhiều tệp MP3, bạn sẽ cần tìm một tiện ích có thể thực hiện điều này một cách dễ dàng.

Dạng hiển thị là bảng với các cột và hàng sẽ là lý tưởng vì khi đó bạn có thể có các cột được gắn nhãn cho các thẻ MP3. Bộ công cụ wxPython có một số tiện ích con sẽ hoạt động cho việc này, với hai tiện ích hàng đầu như sau:

  • wx.grid.Grid
  • wx.ListCtrl

Bạn nên sử dụng wx.ListCtrl trong trường hợp này vì tiện ích con Grid quá mức cần thiết và thành thật mà nói, nó cũng phức tạp hơn một chút. Cuối cùng, bạn cần một nút để sử dụng để chỉnh sửa thẻ của MP3 đã chọn.

Bây giờ bạn biết mình muốn gì, bạn có thể vẽ nó lên:

Trình chỉnh sửa MP3 trong wxPython

Hình minh họa ở trên cho chúng ta một ý tưởng về cách ứng dụng sẽ trông như thế nào. Bây giờ bạn đã biết mình muốn làm gì, đã đến lúc viết mã!

Tạo giao diện người dùng

Có nhiều cách tiếp cận khác nhau khi viết một ứng dụng mới. Ví dụ, bạn có cần tuân theo mẫu thiết kế Model-View-Controller không? Làm thế nào để bạn chia các lớp? Mỗi lớp một file? Có rất nhiều câu hỏi như vậy và khi bạn có nhiều kinh nghiệm hơn với thiết kế GUI, bạn sẽ biết mình muốn trả lời chúng như thế nào.

Trong trường hợp của bạn, bạn thực sự chỉ cần hai lớp:

  • Một lớp wx.Panel
  • Một lớp wx.Frame

Bạn cũng có thể tranh luận về việc tạo mô-đun loại bộ điều khiển, nhưng đối với những thứ như thế này, bạn thực sự không cần nó. Một trường hợp cũng có thể được thực hiện để đặt mỗi lớp vào mô-đun riêng của nó, nhưng để giữ cho nó nhỏ gọn, bạn sẽ tạo một tệp Python duy nhất cho tất cả mã của mình.

Hãy bắt đầu với các import và lớp panel:

import eyed3
import glob
import wx

class Mp3Panel(wx.Panel):    
    def __init__(self, parent):
        super().__init__(parent)
        main_sizer = wx.BoxSizer(wx.VERTICAL)
        self.row_obj_dict = {}

        self.list_ctrl = wx.ListCtrl(
            self, size=(-1, 100), 
            style=wx.LC_REPORT | wx.BORDER_SUNKEN
        )
        self.list_ctrl.InsertColumn(0, 'Artist', width=140)
        self.list_ctrl.InsertColumn(1, 'Album', width=140)
        self.list_ctrl.InsertColumn(2, 'Title', width=200)
        main_sizer.Add(self.list_ctrl, 0, wx.ALL | wx.EXPAND, 5)        
        edit_button = wx.Button(self, label='Edit')
        edit_button.Bind(wx.EVT_BUTTON, self.on_edit)
        main_sizer.Add(edit_button, 0, wx.ALL | wx.CENTER, 5)        
        self.SetSizer(main_sizer)

    def on_edit(self, event):
        print('in on_edit')

    def update_mp3_listing(self, folder_path):
        print(folder_path)

Ở đây, bạn import các gói eyed3glob của Python và gói wx cho giao diện người dùng của mình. Tiếp theo, bạn phân lớp wx.Panel và tạo giao diện người dùng của mình. Bạn cần một từ điển để lưu trữ dữ liệu về các tệp MP3 của mình, bạn đặt tên cho nó là row_obj_dict.

Sau đó, bạn tạo một wx.ListCtrl và đặt nó ở chế độ báo cáo ( wx.LC_REPORT) với đường viền chìm (wx.BORDER_SUNKEN). Kiểm soát danh sách có thể có một số dạng khác tùy thuộc vào cờ kiểu mà bạn truyền vào, nhưng cờ báo cáo là phổ biến nhất.

Để làm cho ListCtrl có header chính xác, bạn sẽ cần phải gọi .InsertColumn() cho mỗi tiêu đề cột. Sau đó, bạn cung cấp chỉ mục của cột, nhãn của cột và chiều rộng của cột sẽ được tính bằng pixel.

Bước cuối cùng là thêm nút Edit của bạn, một trình xử lý sự kiện và một phương thức. Bạn có thể tạo ràng buộc cho sự kiện và để trống phương thức mà nó gọi lúc này.

Bây giờ bạn nên viết mã cho frame:

class Mp3Frame(wx.Frame):    
    def __init__(self):
        super().__init__(parent=None,
                         title='Mp3 Tag Editor')
        self.panel = Mp3Panel(self)
        self.Show()

if __name__ == '__main__':
    app = wx.App(False)
    frame = Mp3Frame()
    app.MainLoop()

Lớp này đơn giản hơn nhiều so với lớp đầu tiên ở chỗ tất cả những gì bạn cần làm là đặt tiêu đề của frame và khởi tạo lớp panel Mp3Panel. Khi bạn đã hoàn tất, giao diện người dùng của bạn sẽ trông như thế này:

wxPython MP3 Tag Editor

Giao diện người dùng trông gần như đúng, nhưng bạn không có menu File. Điều này khiến bạn không thể thêm MP3 vào ứng dụng và chỉnh sửa thẻ của chúng!

Hãy khắc phục điều đó ngay bây giờ.

Làm cho ứng dụng hoạt động

Bước đầu tiên để làm cho ứng dụng của bạn hoạt động là cập nhật ứng dụng để ứng dụng có menu File vì sau đó bạn có thể thêm tệp MP3 vào tác phẩm của mình. Các menu hầu như luôn được thêm vào lớp wx.Frame, do đó, đây là lớp bạn cần sửa đổi.

Lưu ý: Một số ứng dụng đã không còn có menu trong ứng dụng của chúng. Một trong số đó là Microsoft Office khi họ thêm thanh Ribbon. Bộ công cụ wxPython có một tiện ích tùy chỉnh mà bạn có thể sử dụng để tạo các ribbon là wx.lib.agw.ribbon.

Một loại ứng dụng khác cũng đã bỏ menu là các trình duyệt web, chẳng hạn như Google Chrome và Mozilla Firefox. Hiện thời chúng chỉ sử dụng các thanh công cụ.

Hãy tìm hiểu cách thêm thanh menu vào ứng dụng của chúng ta:

class Mp3Frame(wx.Frame):

    def __init__(self):
        wx.Frame.__init__(self, parent=None, 
                          title='Mp3 Tag Editor')
        self.panel = Mp3Panel(self)
        self.create_menu()
        self.Show()

    def create_menu(self):
        menu_bar = wx.MenuBar()
        file_menu = wx.Menu()
        open_folder_menu_item = file_menu.Append(
            wx.ID_ANY, 'Open Folder', 
            'Open a folder with MP3s'
        )
        menu_bar.Append(file_menu, '&File')
        self.Bind(
            event=wx.EVT_MENU, 
            handler=self.on_open_folder,
            source=open_folder_menu_item,
        )
        self.SetMenuBar(menu_bar)

    def on_open_folder(self, event):
        title = "Choose a directory:"
        dlg = wx.DirDialog(self, title, 
                           style=wx.DD_DEFAULT_STYLE)
        if dlg.ShowModal() == wx.ID_OK:
            self.panel.update_mp3_listing(dlg.GetPath())
        dlg.Destroy()

Trong đoạn code trên, bạn thêm một lời gọi đến .create_menu() bên trong phương thức khởi tạo của lớp. Sau đó, trong .create_menu(), bạn sẽ tạo một đối tượng của wx.MenuBar và một đối tượng của wx.Menu.

Để thêm một mục menu vào menu, bạn gọi đối tượng menu .Append() và truyền cho nó như sau:

  • Một số nhận dạng duy nhất (id)
  • Nhãn cho mục menu mới
  • Một chuỗi trợ giúp

Tiếp theo, bạn cần thêm menu vào menubar, vì vậy bạn sẽ cần gọi .Append() của menubar. Nó lấy đối tượng menu và nhãn cho menu. Nhãn này hơi kỳ lạ ở chỗ bạn đã gọi &File thay vì gọi File. Dấu & yêu cầu wxPython tạo phím tắt Alt+F để mở menu File bằng bàn phím của bạn.

Lưu ý: Nếu bạn muốn thêm phím tắt vào ứng dụng của mình, thì bạn sẽ muốn sử dụng một phiên bản của wx.AcceleratorTable để tạo chúng. Bạn có thể đọc thêm về Bảng Accerator trong tài liệu wxPython.

Để tạo liên kết sự kiện, bạn sẽ cần gọi self.Bind(), điều này sẽ liên kết frame với wx.EVT_MENU. Khi bạn sử dụng self.Bind() cho một sự kiện menu, bạn không chỉ cần bảo cho wxPython biết cách sử dụng handler như thế nào, mà còn phải chỉ cho nó source phải liên kết tới.

Cuối cùng, bạn phải gọi frame .SetMenuBar() và truyền cho nó đối tượng menubar để hiển thị cho người dùng.

Bây giờ bạn đã thêm menu vào frame của mình, hãy xem qua trình xử lý sự kiện của mục menu, được tái tạo lại bên dưới đây:

def on_open_folder(self, event):
    title = "Choose a directory:"
    dlg = wx.DirDialog(self, title, style=wx.DD_DEFAULT_STYLE)
    if dlg.ShowModal() == wx.ID_OK:
        self.panel.update_mp3_listing(dlg.GetPath())
    dlg.Destroy()

Trong đoạn code trên, vì bạn muốn người dùng chọn một thư mục chứa MP3, nên bạn sẽ cần sử dụng wx.DirDialogwx.DirDialog chỉ cho phép người dùng mở thư mục.

Bạn có thể đặt tiêu đề của hộp thoại và các cờ kiểu khác nhau. Để hiển thị hộp thoại, bạn sẽ cần phải gọi .ShowModal(). Điều này sẽ làm cho hộp thoại hiển thị theo phương thức, có nghĩa là người dùng sẽ không thể tương tác với ứng dụng chính của bạn trong khi hộp thoại được hiển thị.

Nếu người dùng nhấn nút OK của hộp thoại, bạn có thể nhận được lựa chọn đường dẫn của người dùng thông qua hộp thoại .GetPath(). Bạn sẽ muốn truyền đường dẫn đó đến lớp panel của mình, bạn có thể thực hiện ở đây bằng cách gọi .update_mp3_listing() của panel.

Cuối cùng, bạn cần đóng hộp thoại. Để đóng một hộp thoại, phương pháp được khuyến nghị là .Destroy().

Hộp thoại có một phương thức .Close(), nhưng về cơ bản nó chỉ ẩn hộp thoại và nó sẽ không tự hủy khi bạn đóng ứng dụng của mình, điều này có thể dẫn đến các vấn đề kỳ lạ chẳng hạn như ứng dụng của bạn hiện đang tắt đúng cách. Sẽ đơn giản hơn khi gọi .Destroy() trên hộp thoại để ngăn vấn đề này.

Bây giờ chúng ta hãy cập nhật lớp Mp3Panel của bạn. Bạn có thể bắt đầu bằng cách cập nhật .update_mp3_listing():

def update_mp3_listing(self, folder_path):
    self.current_folder_path = folder_path
    self.list_ctrl.ClearAll()

    self.list_ctrl.InsertColumn(0, 'Artist', width=140)
    self.list_ctrl.InsertColumn(1, 'Album', width=140)
    self.list_ctrl.InsertColumn(2, 'Title', width=200)
    self.list_ctrl.InsertColumn(3, 'Year', width=200)

    mp3s = glob.glob(folder_path + '/*.mp3')
    mp3_objects = []
    index = 0
    for mp3 in mp3s:
        mp3_object = eyed3.load(mp3)
        self.list_ctrl.InsertItem(index, 
            mp3_object.tag.artist)
        self.list_ctrl.SetItem(index, 1, 
            mp3_object.tag.album)
        self.list_ctrl.SetItem(index, 2, 
            mp3_object.tag.title)
        mp3_objects.append(mp3_object)
        self.row_obj_dict[index] = mp3_object
        index += 1

Trong đoạn code trên, bạn đặt thư mục hiện tại thành thư mục được chỉ định và sau đó xóa kiểm soát danh sách. Điều này giúp kiểm soát danh sách luôn mới và chỉ hiển thị các tệp MP3 mà bạn hiện đang làm việc. Điều đó cũng có nghĩa là bạn cần phải chèn lại tất cả các cột một lần nữa.

Tiếp theo, bạn sẽ muốn lấy thư mục đã được chuyển vào và sử dụng mô-đun glob của Python để tìm kiếm các tệp MP3.

Sau đó, bạn có thể lặp lại các file MP3 và biến chúng thành các đối tượng eyed3. Bạn có thể làm điều này bằng cách gọi .load() của eyed3. Giả sử rằng các MP3 đã có các thẻ thích hợp, thì bạn có thể thêm nghệ sĩ, album và tiêu đề của MP3 vào điều khiển danh sách.

Điều thú vị là phương thức thêm một hàng mới vào đối tượng điều khiển danh sách bằng cách gọi .InsertItem() cho cột đầu tiên và SetItem() cho tất cả các cột tiếp theo.

Bước cuối cùng là lưu đối tượng MP3 của bạn vào từ điển Python của bạn, row_obj_dict.

Bây giờ bạn cần cập nhật trình xử lý sự kiện .on_edit() để có thể chỉnh sửa các thẻ của MP3:

def on_edit(self, event):
    selection = self.list_ctrl.GetFocusedItem()
    if selection >= 0:
        mp3 = self.row_obj_dict[selection]
        dlg = EditDialog(mp3)
        dlg.ShowModal()
        self.update_mp3_listing(self.current_folder_path)
        dlg.Destroy()

Điều đầu tiên bạn cần làm là lấy lựa chọn của người dùng bằng cách gọi điều khiển danh sách .GetFocusedItem().

Nếu người dùng chưa chọn bất kỳ thứ gì trong điều khiển danh sách, nó sẽ trả về -1. Giả sử rằng người dùng đã chọn một cái gì đó, bạn sẽ muốn trích xuất đối tượng MP3 từ từ điển của mình và mở hộp thoại trình chỉnh sửa thẻ MP3. Đây sẽ là một hộp thoại tùy chỉnh mà bạn sẽ sử dụng để chỉnh sửa thẻ nghệ sĩ, album và tiêu đề của tệp MP3.

Như thường lệ sẽ hiển thị hộp thoại theo cách thức thông thường (ShowModal). Khi hộp thoại đóng, hai dòng cuối cùng trong .on_edit() sẽ thực thi. Hai dòng này sẽ cập nhật điều khiển danh sách để nó hiển thị thông tin thẻ MP3 hiện tại mà người dùng vừa chỉnh sửa và hủy hộp thoại.

Tạo hộp thoại chỉnh sửa

Phần cuối cùng của chương trình là tạo một hộp thoại chỉnh sửa thẻ MP3. Để ngắn gọn, chúng ta sẽ bỏ qua việc phác thảo giao diện này vì nó là một chuỗi các hàng có chứa nhãn và điều khiển văn bản. Các điều khiển văn bản phải có thông tin thẻ hiện tại được điền sẵn bên trong chúng. Bạn có thể tạo nhãn cho các điều khiển văn bản bằng cách tạo các đối tượng của wx.StaticText.

Khi bạn cần tạo một hộp thoại tùy chỉnh thì ta sử dụng lớp wx.Dialog. Bạn có thể sử dụng nó để thiết kế trình chỉnh sửa:

class EditDialog(wx.Dialog):    
    def __init__(self, mp3):
        title = f'Editing "{mp3.tag.title}"'
        super().__init__(parent=None, title=title)        
        self.mp3 = mp3        
        self.main_sizer = wx.BoxSizer(wx.VERTICAL)        
        self.artist = wx.TextCtrl(
            self, value=self.mp3.tag.artist)
        self.add_widgets('Artist', self.artist)        
        self.album = wx.TextCtrl(
            self, value=self.mp3.tag.album)
        self.add_widgets('Album', self.album)        
        self.title = wx.TextCtrl(
            self, value=self.mp3.tag.title)
        self.add_widgets('Title', self.title)        
        btn_sizer = wx.BoxSizer()
        save_btn = wx.Button(self, label='Save')
        save_btn.Bind(wx.EVT_BUTTON, self.on_save)        
        btn_sizer.Add(save_btn, 0, wx.ALL, 5)
        btn_sizer.Add(wx.Button(
            self, id=wx.ID_CANCEL), 0, wx.ALL, 5)
        self.main_sizer.Add(btn_sizer, 0, wx.CENTER)        
        self.SetSizer(self.main_sizer)

Trong đoạn mã trên, bạn muốn bắt đầu bằng cách phân lớp wx.Dialog và đặt cho nó một tiêu đề tùy chỉnh dựa trên tiêu đề của MP3 mà bạn đang chỉnh sửa.

Tiếp theo, bạn có thể tạo bộ chỉnh âm bạn muốn sử dụng và các tiện ích. Để làm cho mọi thứ dễ dàng hơn, bạn có thể tạo một phương thức trợ giúp có tên .add_widgets() để thêm tiện ích con wx.StaticText dưới dạng hàng với các phiên bản điều khiển văn bản. Tiện ích duy nhất khác ở đây là nút Save.

Bây giờ ta viết phương thức add_widgets:

    def add_widgets(self, label_text, text_ctrl):
        row_sizer = wx.BoxSizer(wx.HORIZONTAL)
        label = wx.StaticText(self, label=label_text,
                              size=(50, -1))
        row_sizer.Add(label, 0, wx.ALL, 5)
        row_sizer.Add(text_ctrl, 1, wx.ALL | wx.EXPAND, 5)
        self.main_sizer.Add(row_sizer, 0, wx.EXPAND)

add_widgets() lấy văn bản của nhãn và đối tượng điều khiển văn bản. Sau đó, nó tạo ra một định hướng theo chiều ngang BoxSizer.

Tiếp theo, bạn sẽ tạo một đối tượng wx.StaticText sử dụng văn bản được truyền vào cho tham số nhãn của nó. Bạn cũng sẽ đặt kích thước của nó là rộng 50 pixel và chiều cao mặc định được đặt bằng -1. Vì bạn muốn có nhãn trước điều khiển văn bản, bạn sẽ thêm tiện ích StaticText vào BoxSizer của mình trước và sau đó thêm điều khiển văn bản.

Cuối cùng, bạn muốn thêm bộ chỉnh chiều ngang vào bộ chỉnh chiều dọc cấp cao nhất. Bằng cách lồng các sizers vào nhau, bạn có thể thiết kế các ứng dụng phức tạp.

Bây giờ bạn sẽ cần tạo trình xử lý sự kiện on_save() để có thể lưu các thay đổi của mình:

    def on_save(self, event):
        self.mp3.tag.artist = self.artist.GetValue()
        self.mp3.tag.album = self.album.GetValue()
        self.mp3.tag.title = self.title.GetValue()
        self.mp3.tag.save()
        self.Close()

Tại đây, bạn đặt các thẻ cho nội dung của các điều khiển văn bản và sau đó gọi .save() của đối tượng eyed3. Cuối cùng, bạn gọi hộp thoại .Close(). Lý do bạn gọi. Close() ở đây thay vì .Destroy() là vì bạn đã gọi .Destroy() trong .on_edit() rồi.

Bây giờ ứng dụng của bạn đã hoàn tất!

Phần kết luận

Bạn đã học được rất nhiều về wxPython trong bài viết này. Bạn đã làm quen với những điều cơ bản về tạo ứng dụng GUI bằng wxPython.

Như vậy bạn đã biết thêm về những điều sau:

  • Cách làm việc với một số widget của wxPython
  • Cách sự kiện hoạt động trong wxPython
  • Cách định vị tuyệt đối so với sizers
  • Cách tạo ứng dụng frame

Cuối cùng, bạn đã học cách làm cho một ứng dụng hoạt động, một trình chỉnh sửa thẻ MP3. Bạn có thể sử dụng những gì bạn đã học được trong bài viết này để tiếp tục cải tiến ứng dụng này hoặc có thể tự mình tạo ra một ứng dụng tuyệt vời.

Bộ công cụ wxPython GUI rất mạnh mẽ và đầy đủ các tiện ích con thú vị mà bạn có thể sử dụng để xây dựng các ứng dụng đa nền tảng. Bạn chỉ bị giới hạn bởi trí tưởng tượng của bạn.

Đọc thêm

Nếu bạn muốn tìm hiểu thêm về wxPython, bạn có thể xem một số liên kết sau:

Để biết thêm thông tin về những gì bạn có thể làm với Python, bạn có thể muốn xem Tôi có thể làm gì với Python? Nếu bạn muốn tìm hiểu thêm về Python super(), thì Sử dụng super () trong lập trình hướng đối tượng có thể phù hợp với bạn.

Bạn cũng có thể tải xuống mã cho ứng dụng chỉnh sửa thẻ MP3 mà bạn đã tạo trong bài viết này nếu bạn muốn nghiên cứu sâu hơn về nó.

» Tiếp: Các kiểu dữ liệu cơ bản trong Python
« Trước: Sử dụng PyInstaller để dễ dàng phân phối các ứng dụng Python
Các khóa học qua video:
Python SQL Server PHP C# Lập trình C Java HTML5-CSS3-JavaScript
Học trên YouTube <76K/tháng. Đăng ký Hội viên
Viết nhanh hơn - Học tốt hơn
Giải phóng thời gian, khai phóng năng lực
Copied !!!