Bài 17: QListWidget – Part 1– Basic Widgets – PyQt6

Bài học QComboBox các bạn đã biết cách hiển thị dữ liệu dạng danh sách rồi, tuy nhiên mặc định widget này chỉ cho lựa chọn một item, và không hiển thị toàn bộ danh sách ra nên đôi khi nó cũng bất tiện. QListWidget sẽ giúp giải quyết vấn đề này, nó là một widget hiển thị toàn bộ danh sách dữ liệu để quan sát và lựa chọn, trong trường hợp dữ liệu nhiều có thể xuất hiện thêm thanh Scroll để xem thêm dữ liệu.

Một số thuộc tính, phương thức và signal thường dùng của QListWidget:

Thuộc tính/ phương thức/ signalChức năng
QListWidgetClass để tạo đối tượng QListWidget
QListWidgetItemClass dùng để tạo các item cho QListWidget
addItem(QListWidgetItem)Hàm thêm một item vào QListWidget
addItems(iterable)Hàm thêm nhiều items vào QListWidget
insertItem(row, QListWidgetItem)Hàm chèn một item vào QListWidget ở vị trí thứ row
item(row)Hàm trả về QListWidgetItem ở vị trí thứ row
takeItem(row)Hàm xóa một item ở dòng thứ row ra khỏi QListWidget
currentRow()Hàm trả về vị trí đang chọn trong QListWidget
clear()Hàm xóa toàn bộ item trong QListWidget
itemClickedsignal lắng nghe người dùng nhấn chuột vào Item trên QListWidget
itemDoubleClickedsignal lắng nghe người dùng nhấn double chuột vào Item trên QListWidget
itemSelectionChangedsignal lắng nghe người dùng thay đổi lựa chọn Item trên QListWidget

Các bạn theo dõi Tui minh họa cách sử dụng QListWidget cũng như xử lý các sự kiện của Widget này như hình dưới đây:

Chương trình gồm 8 chức năng chính:

  1. Chức năng hiển thị danh sách mặc định ban đầu lên QListWidget, bao gồm các minh hoạt về Icon, màu nền, màu chữ…
  2. Chức năng thêm mới một QListWidgetItem
  3. Chức năng Cập nhật một Item
  4. Chức năng Chèn một Item vào một vị trí bất kỳ trong QListWidget
  5. Chức năng Xóa một Item ra khỏi QListWidget
  6. Chức năng xóa toàn bộ Item trong QListWidget
  7. Chức năng lựa chọn một Item bất kỳ trên QListWidget thì dữ liệu sẽ hiển thị lên Title của cửa sổ
  8. Chức năng khi Double click vào một Item bất kỳ trong QListWidget thì sẽ hiển thị màn hình cập nhật dữ liệu cho Item này.

Các bạn có thể viết code ra giao diện hoặc sử dụng Qt Designer để thiết kế. Nói chung cách nào đi nữa thì nó cũng là sử dụng thư viện PyQt6. Trước khi làm chi tiết các chức năng trong bài minh họa ở trên, thì các bạn cần phải nắm rõ và thực hiện được các bước cơ bản được giải thích dưới đây:

Bước 1: Tạo đối tượng QListWidget

self.list_widget = QListWidget(self)

Bước 2: Gọi các hàm addItem, addItems để thêm item vào QListWidget

self.list_widget.addItems(["Learn Python","Machine Learning","Deep Learning"])
self.list_widget.addItem("Smart Contract")

Ở trên là các mã lệnh để đưa danh sách dữ liệu vào QListWidget thông qua hàm addItems, và đưa một item vào QListWidget thông qua hàm addItem. Các dữ liệu trong trường hợp này là chuỗi.

Ta cũng có thể tạo đối tượng QListWidgetItem để đưa từng Item vào, tuy nhiên trường hợp này ta có thể cấu hình Icon, màu nền, màu chữ… cho từng Item:

item=QListWidgetItem()
item.setText("Metaverse")
item.setIcon(QIcon("images/ic_metaverse.png"))
item.setForeground(Qt.GlobalColor.red)
item.setBackground(Qt.GlobalColor.yellow)
self.list_widget.addItem(item)

Bước 3: Để chèn item vào QListWidget ta dùng hàm insertItem. Hàm này có 2 đối số, đối số 1 là vị trí muốn chèn, đối số 2 là giá trị muốn chèn. Ví dụ dưới đây sẽ chèn item vào vị trí đằng sau dòng mà người dùng đang chọn:

item="Value or QListWidgetItem"
current_row = self.list_widget.currentRow()
self.list_widget.insertItem(current_row+1, item)

Bước 4: Để chỉnh sửa Item trong QListWidget ta làm như sau:

updatedItem=self.list_widget.item(0)
updatedItem.setText("New value for item at row 0")

Bước 5: Để xóa item đang chọn ra khỏi QListWidget ta gọi lệnh takeItem

current_row = self.list_widget.currentRow()
if current_row >= 0:
    current_item = self.list_widget.takeItem(current_row)
    del current_item

Bước 6: Để xóa toàn bộ item ra khỏi QListWidget ta gọi lệnh clear():

self.list_widget.clear()

Dưới đây là mã lệnh minh họa đầy đủ các chức năng của QListWidget:

import sys

from PyQt6.QtCore import Qt
from PyQt6.QtWidgets import QInputDialog, QApplication, QWidget, QGridLayout, QListWidget, QPushButton, QLabel, \
    QListWidgetItem
from PyQt6.QtGui import QIcon

class MainWindow(QWidget):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        self.setWindowTitle('QListWidget Demo')
        self.setWindowIcon(QIcon('images/ic_logo.jpg'))
        self.setGeometry(100, 100, 400, 100)

        layout = QGridLayout(self)
        self.setLayout(layout)

        self.list_widget = QListWidget(self)
        #Create a QListWidgetItem and set the attributes
        newItem = QListWidgetItem()
        newItem.setText("Metaverse")
        newItem.setIcon(QIcon('images/ic_metaverse.png'))
        newItem.setForeground(Qt.GlobalColor.red)
        newItem.setBackground(Qt.GlobalColor.yellow)
        #add new QListWidgetItem
        self.list_widget.addItem(newItem)

        #add a text for a new QlistWidgetItem
        self.list_widget.addItem("Smart Contract")
        #get item at row 1 and set icon for this Item
        self.list_widget.item(1).setIcon(QIcon('images/ic_smartcontract.png'))
        #add an array for QListWidget
        self.list_widget.addItems(["Learn Python", "Machine Learning", "Deep Learning"])

        layout.addWidget(self.list_widget, 0, 0, 5, 1)
        # create buttons and do signals and slots for QPushButton action:
        add_button = QPushButton('Add New Item')
        add_button.clicked.connect(self.addItem)

        update_button = QPushButton('Update Item')
        update_button.clicked.connect(self.updateItem)

        insert_button = QPushButton('Insert New Item')
        insert_button.clicked.connect(self.insertItem)

        remove_button = QPushButton('Remove Selected Item')
        remove_button.clicked.connect(self.removeItem)

        clear_button = QPushButton('Clear All')
        clear_button.clicked.connect(self.clearAll)

        layout.addWidget(add_button, 0, 1)
        layout.addWidget(update_button, 1, 1)
        layout.addWidget(insert_button, 2, 1)
        layout.addWidget(remove_button, 3, 1)
        layout.addWidget(clear_button, 4, 1)
        #mouse and key signal for QListWidget
        self.list_widget.itemClicked.connect(self.processItemClicked)
        self.list_widget.itemDoubleClicked.connect(self.processItemDoubleClicked)
        #Signal listener user selected the item
        self.list_widget.itemSelectionChanged.connect(self.processItemSelectionChanged)
        # show the window
        self.show()
    #slot to show text of selected item into the title of Window
    def processItemSelectionChanged(self):
        current_row = self.list_widget.currentRow()
        item = self.list_widget.item(current_row)
        self.setWindowTitle(item.text())
    #slot show the update ui:
    def processItemDoubleClicked(self):
        self.updateItem()
    #slot for clicking the item
    def processItemClicked(self):
        current_row = self.list_widget.currentRow()
        data = self.list_widget.item(current_row)
        print("itemClicked=",data.text())
    #slot to show Adding new Item for QListWidget, using QInputDialog
    def addItem(self):
        text, ok = QInputDialog.getText(self, 'Add a New Data', 'New Data:')
        if ok and text:
            self.list_widget.addItem(text)

    # slot to show Updating selected Item for QListWidget, using QInputDialog
    def updateItem(self):
        current_row = self.list_widget.currentRow()
        if current_row >= 0:
            item = self.list_widget.item(current_row)
            text, ok = QInputDialog.getText(self, 'Update Data', 'New Data:',text=item.text())
            if ok and text:
                item.setText(text)

    # slot to show Inserting a new Item for QListWidget, using QInputDialog
    def insertItem(self):
        text, ok = QInputDialog.getText(self, 'Insert a New Data', 'New Data:')
        if ok and text:
            current_row = self.list_widget.currentRow()
            self.list_widget.insertItem(current_row+1, text)
    #slot to remove selected item
    def removeItem(self):
        current_row = self.list_widget.currentRow()
        if current_row >= 0:
            current_item = self.list_widget.takeItem(current_row)
            del current_item
    #slot to remove all item from QListWidget
    def clearAll(self):
        self.list_widget.clear()

if __name__ == '__main__':
    app = QApplication(sys.argv)
    window = MainWindow()
    sys.exit(app.exec())

Đoạn lệnh từ 20 tới 27 là tạo một đối tượng QListWidgetItem và thiết lập các thuộc tính như text, icon, màu nền, màu chữ:

#Create a QListWidgetItem and set the attributes
newItem = QListWidgetItem()
newItem.setText("Metaverse")
newItem.setIcon(QIcon('images/ic_metaverse.png'))
newItem.setForeground(Qt.GlobalColor.red)
newItem.setBackground(Qt.GlobalColor.yellow)
#add new QListWidgetItem
self.list_widget.addItem(newItem)

Đoạn lệnh từ 29 tới 32 là thêm một Item vào QListWidget, đồng thời sau khi thêm chương trình sẽ truy suất item có rowindex là 1 để chỉnh sửa bằng cách thay đổi icon:

#add a text for a new QlistWidgetItem
self.list_widget.addItem("Smart Contract")
#get item at row 1 and set icon for this Item
self.list_widget.item(1).setIcon(QIcon('images/ic_smartcontract.png'))

Đoạn lệnh 33 tới 34 để thêm danh sách các item dạng text vào QListWidget:

#add an array for QListWidget
self.list_widget.addItems(["Learn Python", "Machine Learning", "Deep Learning"])

Những mã lệnh từ dòng 20 tới 34 sẽ giúp ta hiển thị danh sách dữ liệu vào QListWidget như hình dưới đây:

Chức năng “Add New Item”:

add_button.clicked.connect(self.addItem)
#slot to show Adding new Item for QListWidget, using QInputDialog
def addItem(self):
    text, ok = QInputDialog.getText(self, 'Add a New Data', 'New Data:')
    if ok and text:
        self.list_widget.addItem(text)

Khi nhấn vào nút “Add New Item”, chương trình sẽ mở cửa sổ nhập liệu bằng QInputDialog như hình dưới đây:

Trong ô nhập liệu “New Data” nhập liệu rồi nhấn nút OK, kết quả sẽ thấy “PyQt6 & Qt Designer” được đưa vào cuối danh sách:

Chức năng “Update Item”:

update_button.clicked.connect(self.updateItem)
# slot to show Updating selected Item for QListWidget, using QInputDialog
def updateItem(self):
    current_row = self.list_widget.currentRow()
    if current_row >= 0:
        item = self.list_widget.item(current_row)
        text, ok = QInputDialog.getText(self, 'Update Data', 'New Data:',text=item.text())
        if ok and text:
            item.setText(text)

Khi người sử dụng nhấn vào “Update Item”: Đầu tiên chương trình sẽ kiểm tra xem người sử dụng đang chọn Item nào trong QListWidget, sau khi kiểm tra có thấy item được lựa chọn (current_row>=0) thì lúc này chương trình sẽ hiển thị giá trị đang chọn này vào QInputDialog và yêu cầu người dùng nhập lại. Ví dụ như người dùng chọn “Machine Learning” rồi nhấn nút “Update Item”:

Nếu người dùng thay đổi giá trị khác và nhấn OK thì lúc này dữ liệu trên giao diện sẽ thay đổi. Ví dụ như người dùng thay “Machine Learning” thành “Basic Machine Learning”:

Tương tự chức năng “Insert New Item“, chương trình sẽ chèn dữ liệu ngay đằng sau dòng đang chọn:

insert_button.clicked.connect(self.insertItem)
# slot to show Inserting a new Item for QListWidget, using QInputDialog
def insertItem(self):
    text, ok = QInputDialog.getText(self, 'Insert a New Data', 'New Data:')
    if ok and text:
        current_row = self.list_widget.currentRow()
        self.list_widget.insertItem(current_row+1, text)

Trước tiên bạn chọn một dòng dữ liệu bất kỳ trong QListWidget, ví dụ như nhấn vào dòng “Deep Learning”, rồi chọn nút “Insert New Item”:

Nhập dữ liệu, ví dụ “Flask API” sau đó nhấn nút OK, chương trình sẽ thêm dòng dữ liệu “Flask API” ở ngay bên dưới “Deep Learning”:

Tiếp theo tới chức năng “Remove Selected Item” để xóa một dòng dữ liệu đang chọn ra khỏi QListWidget.

remove_button.clicked.connect(self.removeItem)
#slot to remove selected item
def removeItem(self):
    current_row = self.list_widget.currentRow()
    if current_row >= 0:
        current_item = self.list_widget.takeItem(current_row)
        del current_item

Mã lệnh ở trên sẽ xóa dòng dữ liệu đang chọn ra khỏi QListWidget. Tuy nhiên thông thường thao tác xóa ta nên hiển thị một cửa sổ xác nhận xem người dùng có thực sự muốn xóa hay không, để làm được điều này ta sử dụng QMessageBox như mã lệnh dưới đây:

#slot to remove selected item
def removeItem(self):
    current_row = self.list_widget.currentRow()
    if current_row >= 0:
        item=self.list_widget.item(current_row)
        msg=QMessageBox()
        msg.setText(f"Are you sure you want to remove {item.text()}?")
        msg.setWindowTitle("Removing Confirmation")
        msg.setIcon(QMessageBox.Icon.Question)
        buttons=QMessageBox.StandardButton.Yes|QMessageBox.StandardButton.No
        msg.setStandardButtons(buttons)
        result= msg.exec()
        if result==QMessageBox.StandardButton.Yes:
            current_item = self.list_widget.takeItem(current_row)
            del current_item

Sau khi bổ sung mã lệnh xác nhận xóa hay không thì chương trình sẽ xuất hiện cửa sổ như dưới đây:

Tiếp theo là chức năng “Clear All” để xóa toàn bộ item nằm bên trong QListWidget:

clear_button.clicked.connect(self.clearAll)
#slot to remove all item from QListWidget
def clearAll(self):
    self.list_widget.clear()

Chạy phần mềm lên, nếu bạn nhấnt nút “Clear All” thì toàn bộ dữ liệu trong QListWidget sẽ bị xóa toàn bộ. Tuy nhiên cũng tương tự như chức năng “Remove Selected Item” bạn nên bổ sung thêm cửa sổ xác nhận có muốn xóa toàn bộ hay không. Coding minh họa dưới này Tui làm theo cách mới để xác nhận:

#slot to remove all item from QListWidget
def clearAll(self):
    answer = QMessageBox.question(
        self,
        'Confirmation',
        'Do you want to clear all Data?',
        QMessageBox.StandardButton.Yes |
        QMessageBox.StandardButton.No
    )
    if answer == QMessageBox.StandardButton.Yes:
        self.list_widget.clear()

Khi chạy phần mềm lên, nhấn nút “Clear All” ta có màn hình xác nhận:

Chức năng tiếp theo trong bài này đó là lắng nghe người sử dụng đang chọn dòng nào, dòng nào được chọn thì dữ liệu sẽ được hiển thị lên thanh tiêu đề của cửa sổ. Ở đây các bạn có thể dùng các signal itemClicked hoặc itemSelectionChanged cho QListWidget. Điểm khác nhau của itemClicked với với ItemSelectionChanged là itemClicked chỉ lắng nghe lúc người dùng nhấn chuột vào từng item. Còn itemSelectionChanged có thể lắng nghe kể cả người sử dụng dùng chuột hay dùng phím mũi tên lên xuống:

self.list_widget.itemClicked.connect(self.processItemClicked)
self.list_widget.itemSelectionChanged.connect(self.processItemSelectionChanged)
#slot to show text of selected item into the title of Window
def processItemSelectionChanged(self):
    current_row = self.list_widget.currentRow()
    item = self.list_widget.item(current_row)
    self.setWindowTitle(item.text())
#slot for clicking the item
def processItemClicked(self):
    current_row = self.list_widget.currentRow()
    data = self.list_widget.item(current_row)
    print("itemClicked=",data.text())

Cuối cùng đó là chức năng số 8, chức năng khi người dùng Double click (nhấn chuột 2 lần liên tục vào QListWidget) thì sẽ thực hiện chức năng cập nhật dữ liệu (nó chính là gọi “Update Item”):

self.list_widget.itemDoubleClicked.connect(self.processItemDoubleClicked)
#slot show the update ui:
def processItemDoubleClicked(self):
    self.updateItem()

Dưới đây là mã lệnh đầy đủ của “QListWidgetDemo.py” bao gồm bổ sung các cửa sổ xác nhận:

import sys

from PyQt6.QtCore import Qt
from PyQt6.QtWidgets import QInputDialog, QApplication, QWidget, QGridLayout, QListWidget, QPushButton, QLabel, \
    QListWidgetItem, QMessageBox
from PyQt6.QtGui import QIcon

class MainWindow(QWidget):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        self.setWindowTitle('QListWidget Demo')
        self.setWindowIcon(QIcon('images/ic_logo.jpg'))
        self.setGeometry(100, 100, 400, 100)

        layout = QGridLayout(self)
        self.setLayout(layout)

        self.list_widget = QListWidget(self)
        #Create a QListWidgetItem and set the attributes
        newItem = QListWidgetItem()
        newItem.setText("Metaverse")
        newItem.setIcon(QIcon('images/ic_metaverse.png'))
        newItem.setForeground(Qt.GlobalColor.red)
        newItem.setBackground(Qt.GlobalColor.yellow)
        newItem.setCheckState(Qt.CheckState.Checked)
        #add new QListWidgetItem
        self.list_widget.addItem(newItem)

        #add a text for a new QlistWidgetItem
        self.list_widget.addItem("Smart Contract")
        #get item at row 1 and set icon for this Item
        self.list_widget.item(1).setIcon(QIcon('images/ic_smartcontract.png'))
        #add an array for QListWidget
        self.list_widget.addItems(["Learn Python", "Machine Learning", "Deep Learning"])

        layout.addWidget(self.list_widget, 0, 0, 5, 1)
        # create buttons and do signals and slots for QPushButton action:
        add_button = QPushButton('Add New Item')
        add_button.clicked.connect(self.addItem)

        update_button = QPushButton('Update Item')
        update_button.clicked.connect(self.updateItem)

        insert_button = QPushButton('Insert New Item')
        insert_button.clicked.connect(self.insertItem)

        remove_button = QPushButton('Remove Selected Item')
        remove_button.clicked.connect(self.removeItem)

        clear_button = QPushButton('Clear All')
        clear_button.clicked.connect(self.clearAll)

        layout.addWidget(add_button, 0, 1)
        layout.addWidget(update_button, 1, 1)
        layout.addWidget(insert_button, 2, 1)
        layout.addWidget(remove_button, 3, 1)
        layout.addWidget(clear_button, 4, 1)
        #mouse and key signal for QListWidget
        self.list_widget.itemClicked.connect(self.processItemClicked)
        self.list_widget.itemDoubleClicked.connect(self.processItemDoubleClicked)
        #Signal listener user selected the item
        self.list_widget.itemSelectionChanged.connect(self.processItemSelectionChanged)
        # show the window
        self.show()
    #slot to show text of selected item into the title of Window
    def processItemSelectionChanged(self):
        current_row = self.list_widget.currentRow()
        item = self.list_widget.item(current_row)
        self.setWindowTitle(item.text())
    #slot show the update ui:
    def processItemDoubleClicked(self):
        self.updateItem()
    #slot for clicking the item
    def processItemClicked(self):
        current_row = self.list_widget.currentRow()
        data = self.list_widget.item(current_row)
        print("itemClicked=",data.text())
    #slot to show Adding new Item for QListWidget, using QInputDialog
    def addItem(self):
        text, ok = QInputDialog.getText(self, 'Add a New Data', 'New Data:')
        if ok and text:
            self.list_widget.addItem(text)

    # slot to show Updating selected Item for QListWidget, using QInputDialog
    def updateItem(self):
        current_row = self.list_widget.currentRow()
        if current_row >= 0:
            item = self.list_widget.item(current_row)
            text, ok = QInputDialog.getText(self, 'Update Data', 'New Data:',text=item.text())
            if ok and text:
                item.setText(text)

    # slot to show Inserting a new Item for QListWidget, using QInputDialog
    def insertItem(self):
        text, ok = QInputDialog.getText(self, 'Insert a New Data', 'New Data:')
        if ok and text:
            current_row = self.list_widget.currentRow()
            self.list_widget.insertItem(current_row+1, text)
    #slot to remove selected item
    def removeItem(self):
        current_row = self.list_widget.currentRow()
        if current_row >= 0:
            item=self.list_widget.item(current_row)
            msg=QMessageBox()
            msg.setText(f"Are you sure you want to remove {item.text()}?")
            msg.setWindowTitle("Removing Confirmation")
            msg.setIcon(QMessageBox.Icon.Question)
            buttons=QMessageBox.StandardButton.Yes|QMessageBox.StandardButton.No
            msg.setStandardButtons(buttons)
            result= msg.exec()
            if result==QMessageBox.StandardButton.Yes:
                current_item = self.list_widget.takeItem(current_row)
                del current_item
    #slot to remove all item from QListWidget
    def clearAll(self):
        answer = QMessageBox.question(
            self,
            'Confirmation',
            'Do you want to clear all Data?',
            QMessageBox.StandardButton.Yes |
            QMessageBox.StandardButton.No
        )
        if answer == QMessageBox.StandardButton.Yes:
            self.list_widget.clear()

if __name__ == '__main__':
    app = QApplication(sys.argv)
    window = MainWindow()
    sys.exit(app.exec())

Như vậy là tới đây Tui đã hướng dẫn xong QListWidget, các bạn đã nắm rất rõ các kiến thức liên quan tới widget này, biết được ý nghĩa chức năng của từng thuộc tính, phương thức và signal của QListWidget. Các bạn đã ứng dụng được các đặc tính này trong ví dụ minh họa với 8 chức năng chính. Từ đây các bạn có thể dễ dàng áp dụng QListWidget ở các dự án cụ thể đáp ứng nhu cầu lưu trữ và hiển thị dữ liệu khác nhau của khách hàng.

Mà lệnh đầy đủ và các hình ảnh sử dụng trong dự án này các bạn có thể tải tại đây:

https://www.mediafire.com/file/nuxagzk5uer9sva/LearnQListWidget.rar/file

Bài học tiếp theo Tui sẽ tiếp tục trình bày ứng dụng của QListWidget trong việc quản lý dữ liệu Employee. Các bạn chú ý theo dõi

Chúc các bạn thành công!

One thought on “Bài 17: QListWidget – Part 1– Basic Widgets – PyQt6”

Leave a Reply