< Lập trình tân binh | 3.13. Kiến trúc MVC và các widget phức hợp

3.13. Kiến trúc MVC và các widget phức hợp

Chúng ta sẽ bước vào một trong những bài học hấp dẫn nhất nhưng đồng thời cũng thuộc dạng khó nhất trong chương giáo trình về Qt này.

Trong bài học này, chúng ta sẽ làm việc với 3 widget phức hợp :

  • QListView: danh sách đơn các thành phần
  • QTreeView: danh sách thành phần nhiều lớp, dạng cây
  • QTableView : danh sách thành phần dạng bảng

Sẽ hơi khó hiểu cách sử dụng của các widget này nếu không có 1 số kiến thức lý thuyết cần thiết. Thật ra chính phần lý thuyết này là lý do khiến bài học này trở thành bài học hấp dẫn nhất trong chương này. Chúng ta sẽ tìm hiểu về kiến trúc MVC, là 1 kiểu kiến trúc phần mềm rất mạnh mẽ, cho phép tạo ra các chương trình vô cùng linh hoạt.

Kiến trúc MVC

Trước khi trình bày với các bạn về 3 loại widget kể trên, như đã nói trong phần mở đầu, tôi cần phải giới thiệu cho các bạn về kiến trúc MVC.

? Kiến trúc MVC là răng ? Dùng làm chi ? Liên quan quái gì với việc tạo ra giao diện người dùng ?

MVC là viết tắt của Model-View-Controler, nghĩa là Mô hình – Khung nhìn – Bộ điều khiển.

Hiển nhiên là các khái niệm này vẫn còn mờ mịt không kém gì lúc trước.

Lập trình viên chúng ta gọi MVC là 1 mẫu thiết kế (design pattern), 1 dạng kỹ thuật lập trình. Nói chính xác hơn là 1 cách thức lập trình và tổ chức mã nguồn. Chúng ta không bắt buộc phải lập trình theo cách thức này, nhưng nếu làm theo, chúng ta sẽ nhận được đoạn mã nguồn sáng sủa, dễ hiểu và linh hoạt hơn.

Trong kiến trúc MVC, chương trình được chia thành 3 phần riêng biệt.

  • Mô hình : là phần mã phụ trách về thông tin dữ liệu. Mô hình có thể là danh sách người dùng với tên, tuổi, vv…
  • Khung hình : là phần phụ trách việc hiển thị. Nó hiển thị thông tin được lưu trong mô hình. Ví dụ như danh sách người dùng ở trên có thể được hiển thị trong 1 bảng biểu. Vậy bảng biểu đó sẽ là khung hình.
  • Bộ điều khiển : đây là bộ phận đầu não của chương trình, thực hiện kết nối các xử lý. Ví dụ khi chúng ta chọn 1 người dùng trong bảng và ấn nút xóa, bộ điều khiển sẽ xử lý yêu cầu mô hình xóa người dùng đó.

Ban đầu sẽ khá là khó hiểu nhưng dần dần, các bạn sẽ nhận ra được lợi ích của việc tách biệt mã nguồn thành 3 bộ phận như trên. Không cần lo lắng khi các bạn chưa thể hiểu ngay cách thức các bộ phận giao tiếp với nhau.

Hãy bắt đầu với những hình ảnh minh họa dễ hiểu về các bộ phận này.

Trong hình minh họa trên, chúng ta dễ dàng nhận ra được 3 thành phần :

  • Mô hình là phần lưu trữ dữ liệu. Những dữ liệu này thường được đọc ra từ nội dung các tệp hoặc cơ sở dữ liệu.
  • Khung nhìn là bộ phận sẽ hiển thị dữ liệu của mô hình, trong trường hợp này của chúng ta là các widget. Nếu mô hình được cập nhật bởi 1 dữ liệu mới, vậy thì khung nhìn sẽ tự động được cập nhật theo để hiển thị dữ liệu đó.
  • Bộ điều khiển là phần chứa đựng nhiều giải thuật nhất. Đây là bộ não của chương trình. Vậy nên nếu cần thực hiện các xử lý trong chương trình, vậy thì chúng sẽ được thực hiện trong phần này.
Kiến trúc rút gọn mô hình – khung nhìn của Qt

Thật ra thì Qt không thật sự sử dụng kiến trúc MVC mà là 1 phiên bản rút gọn của nó : kiến trúc mô hình – khung nhìn.

? Thế bộ điều khiển của chúng ta bị vứt vào sọt rác rồi à undecided Chẳng lẽ chương trình của chúng ta sẽ không biết suy nghĩ ?

Không cần quá lo lắng. Trong Qt, bộ điều khiển đã được tích hợp sẵn trong khung nhìn rồi. Chính nhờ vậy nên dữ liệu vẫn luôn luôn được tách biệt khỏi phần hiển thị đồng thời Qt cũng tránh được việc quản lý cấu trúc phức tập của MVC.

Ở đây, chúng ta sẽ tạm rời xa lý thuyết của MVC và chú ý hơn đến áp dụng thực tế của nguyên lý này trong Qt. Chúng ta sẽ cùng xem cách Qt tôn trọng những yếu tố cơ bản của MVC dù không cần chia mã ra quá nhỏ, việc mà đôi khi trở nên quá dài dòng và lặp đi lặp lại.

Vậy nên chúng ta sẽ chỉ nói về mô hình và khung nhìn.

Chúng được Qt quản lý như thế nào ?

Câu trả lời vẫn như mọi khi : « sử dụng các lớp ».

Các lớp quản lý mô hình

Có rất nhiều loại mô hình khác nhau bởi vì đương nhiên là các dữ liệu thì thường có những khác biệt.

Chúng ta có 2 lựa chọn :

  • Tự tạo các lớp riêng quản lý mô hình. Chúng ta sẽ phải tạo 1 lớp kế thừa từ QAbstractItemModel. Đây là giải pháp rất linh hoạt nhưng cũng vô cùng phức tạp. Vậy nên chúng ta sẽ không nhắc đến nó ở đây.
  • Sử dụng các lớp có sẵn tạo ra bởi Qt :
    • QStringListModel: danh sách chuỗi ký tự kiểu QString, rất cơ bản, hữu dụng trong những trường hợp đơn giản nhất.
    • QStandardItemModel: danh sách được sắp xếp dạng cây với mỗi mắt nối đều có thể chứa các danh sách nhỏ hơn. Loại mô hình này phức tạp hơn loại trước nhiều do nó hỗ trợ quản lý phân tầng thành phần. Cùng với QStringListModel, đây cũng là 1 trong các loai hay được dùng nhất.
    • QDirModel: danh sách thư mục và tệp trên máy tính bạn. Nó sẽ tự động phân tích hệ thống trong đĩa cứng của các bạn và trả về kết quả là 1 danh sách các tệp dưới dạng mô hình có thế dùng ngay.
    • QSqlQueryModel, QSqlTableModelQSqlRelationnalTableModel làm việc với các dữ liệu trong cơ sở dữ liệu. Chúng ta có thể dùng chúng để kết nối tới cơ sở dữ liệu thông qua các lớp này (sẽ khá hấp dẫn với những người hay làm việc với MySQL hay Oracle). Tôi sẽ không giới thiệu ở đây vì có thể đi quá xa chủ đề của chúng ta định nói.

Tất cả các lớp có sẵn vừa kể trên đều kế thừa QAbstractItemModel. Nếu không có lớp nào trong đây đáp ứng đủ nhu cầu sử dụng của bạn, vậy sẽ cần chúng ta tự viết 1 lớp kế thừa QAbstractItemModel.

Các lớp quản lý khung hình

Để hiển thị các dữ liệu trong mô hình, chúng ta cần đến khung nhìn. Với Qt, khung nhìn chính là widget bởi lẽ nó chính là 1 khung hiển thị trong cửa sổ.

Không phải tất cả các widget của Qt đều sử dụng kiến trúc mô hình – khung nhìn do không kiến trúc này không mang đến nhiều hiệu quả rõ rệt trong những trường hợp quá đơn giản như các widget chúng ta từng thảo luận qua lúc trước.

Trong Qt, chúng ta có thể tính được 3 widget khá thích hợp làm khung nhìn.

  • QListView : danh sách đơn
  • QTreeView : danh sách dạng cây, mỗi thành viên của danh sách bản thân chúng có thể là danh sách có nhiều thành viên con.
  • QTableView : danh sách dạng bảng biểu.

Đây chính là những widget mà tôi đã nói là phức hợp trong bài này. Nhưng trước khi có thể sử dụng chúng để hiển thị dữ liệu, trước hết chúng ta cần có 1 mô hình đã.

Liên kết 1 mô hình với 1 khung nhìn

Khi chúng ta sử dụng cấu trúc mô hình – khung nhìn trong Qt, nó luôn diễn ra theo 3 bước.

  1. Tạo mô hình
  2. Tạo khung nhìn
  3. Liên kết mô hình với khung nhìn

Bước cuối cùng là bước chính và quan trọng nhất bởi đây là nơi tạo nên cầu nối giữa 2 bộ phận này. Nếu chúng ta không cung cấp cho khung nhìn bất cứ mô hình nào thì nó sẽ không biết là phải hiển thị gì và dẫn đến là chẳng có gì được hiển thị cả.

Kết nối được thực hiện nhờ phương thức setModel() của khung nhìn.

! Bộ điều khiển không được thể hiện trong hình do Qt sử dụng kiến trúc đơn giản hóa mô hình – khung nhìn và tự quản lý phần điều khiển giúp chúng ta.

Và đấy là cách trong thực tế chúng ta kết nối mô hình và khung nhìn. Lợi ích của kiến trúc này là nó rất linh hoạt. Chúng ta có thể có 2 mô hình cùng kết nối đến khung nhìn và hiển thị cái nào thì tùy thuộc vào yêu cầu đưa ra. Lợi ích nhất là khi mô hình thay đổi, ngay lập tức chúng ta nhận thấy thông qua thay đổi tức thì của phần hiển thị.

Nhiều mô hình hay nhiều khung nhìn

Chúng ta có thể tìm hiểu sâu hơn nữa. Hãy tưởng tượng chúng ta có nhiều mô hình hoặc nhiều khung nhìn.

Nhiều mô hình với 1 khung nhìn

Hãy tưởng tượng chúng ta có 2 mô hình : danh sách người dùng và danh sách thủ đô các nước. Khung hình của chúng ta sẽ liên kết với 2 mô hình này.

Đến 1 lúc chúng ta sẽ cần đưa ra lựa chọn do mỗi lúc, khung nhìn chỉ có thể hiển thị được 1 mô hình duy nhất. Ưu điểm ở đây là chúng ta có thể thay đổi mô hình cần hiển thị ngay trong quá trình thực thi chương trình chỉ bằng cách dùng phương thức setModel().

1 mô hình với nhiều khung nhìn

Trong trường hợp ngược lại, chúng ta sẽ có 1 mô hình liên kết với nhiều khung nhìn. Lần này, không gì ngăn cấm chúng ta hiển thị đồng thời mô hình trong cả 2 khung nhìn.

Chúng ta cũng có thể hiển thị cùng 1 mô hình nhưng theo 2 cách khác nhau, ví dụ 1 dưới dạng bảng và 1 dạng danh sách.

Bởi vì cùng 1 mô hình được liên kết cho nhiều khung nhìn khác nhau, khi mô hình thay đổi, tất cả các khung hình liên kết sẽ đồng thời thay đổi. Ví dụ nếu tôi thay đổi tuổi của người dùng trong mô hình thông qua bảng biểu, thông tin này cũng sẽ lập tức được thay đổi trong danh sách mà không cần thêm 1 dòng mã nào.

Sử dụng các mô hình đơn giản

Để bắt đầu nhẹ nhàng với kiến trúc đơn giản hóa của Qt, chúng ta sẽ bắt đầu với 1 loại mô hình được xây dựng đầy đủ : QDirModel.

Điểm đặc biệt của nó là nó vô cùng dễ sử dụng. Lớp này sẽ tự động phân tích ổ cứng và tự sinh ra mô hình phù hợp. Để tạo ra mô hình loại này, chúng ta cần 1 câu lệnh đơn giản.

QDirModel *moHinh = new QDirModel();

Vậy là chúng ta đã có mô hình tương ứng với ổ đĩa cứng của chúng ta. Hãy cùng liên kết nó với 1 khung nhìn.

? Cần sử dụng khung nhìn nào bây giờ ? Danh sách đơn, danh sách cây hay bảng biểu ? Các mô hình tương thích với tất cả các khung nhìn chứ ?

Đúng vậy, tất cả các khung nhìn đều có thể hiển thị bất cứ mô hình nào. Chúng luôn luôn tương thích với nhau.

Dù vậy, các bạn sẽ sớm nhận ra là 1 vài khung nhìn thích hợp với 1 số mô hình cụ thể hơn so với các loại khung nhìn khác. Lấy ví dụ mô hình kiểu QDirModel, khung nhìn thích hợp nhất không nghi ngờ gì chính là danh sách dạng cây QTreeView. Tuy nhiên, chúng ta vẫn sẽ thử nghiệm tất cả các loại khung hình để so sánh cách thức chúng vận hành.

Hiển thị mô hình với QTreeView
#include "CuaSoChinh.h"
CuaSoChinh::CuaSoChinh() {
    QVBoxLayout *lop = new QVBoxLayout();
    QDirModel *moHinh = new QDirModel();

    QTreeView *khungNhin = new QTreeView();
    khungNhin->setModel(moHinh);
    lop->addWidget(khungNhin);
    setLayout(lop);
}

Chúng ta tạo ra mô hình, rồi đến khung hiển thị và yêu cầu nó hiển thị mô hình được tạo ra lúc trước.

Kết quả là chúng ta sẽ có :

1 khung hiển thị dạng cây sẽ hiển thị mô hình đĩa cứng của chúng ta. Mỗi thành viên của danh sách bản thân đều có thể chứa 1 đối tượng danh sách dạng cây khác. Hãy thử duyệt nội dụng của khung hiển thị này.

Hiển thị mô hình với QListView

Chúng ta sẽ thực hiện xử lý tương tự, nhưng lần này là với QListView. Mô hình vẫn giữ nguyên, chỉ có cách hiển thị là thay đổi.

#include "CuaSoChinh.h"
CuaSoChinh::CuaSoChinh() {
    QVBoxLayout *lop = new QVBoxLayout();
    QDirModel *moHinh = new QDirModel();

    QListView *khungNhin = new QListView();
    khungNhin->setModel(moHinh);
    lop->addWidget(khungNhin);
    setLayout(lop);
}

Kiểu hiển thị này chỉ hiển thị các đối tượng theo từng tầng. Đấy chính là lý do mà chúng ta chỉ thấy được danh sách các phân vùng trong ổ cứng mà không có nội dung của của chúng.

Khung hiển thị đơn giản là hiển thị theo khả năng của nó nên chúng ta chỉ thấy được các thành phần thuộc tầng thứ nhất. Đây chính là ví dụ rõ nét minh họa cho việc trong 1 số trường hợp, 1 kiểu hiển thị sẽ thích hợp hơn so với các kiểu hiển thị khác.

Chúng ta có thể thay đổi gốc của mô hình được sử dụng khi hiển thị theo cách làm của đoạn mã dưới đây.

khungNhin->setRootIndex(moHinh->index("C:"));
Hiển thị mô hình với QTableView

Bảng biểu cũng không cho phép hiển thị nhiều tầng (chỉ có danh sách dạng cây cho phép chúng ta làm thế). Tuy nhiên nó lại có thể hiển thị nhiều cột.

#include "CuaSoChinh.h"
CuaSoChinh::CuaSoChinh() {
    QVBoxLayout *lop = new QVBoxLayout();
    QDirModel *moHinh = new QDirModel();

    QTableView *khungNhin = new QTableView();
    khungNhin->setModel(moHinh);
    lop->addWidget(khungNhin);
    setLayout(lop);
}

Tương tự như trước, chúng ta cũng có thể sử dụng setRootIndex() để thay đổi gốc hiển thị của mô hình.

Sử dụng các mô hình tùy chỉnh

Mô hình QDirModel mà chúng ta vừa sử dụng bên trên khá là dễ thao tác. Nó tự động phân tích ổ cứng của chúng ta và tạo ra nội dung mà không cần thêm bất cứ cài đặt hay tùy chỉnh gì.

Đây là 1 sự lựa chọn tốt để bắt đầu khám phá về về các khung hình thông qua việc hiển thị mô hình đơn giản. Tuy nhiên, đáng buồn là trong đa số trường hợp còn lại, chúng ta muốn hiển thị dữ liệu riêng, mô hình riêng của chúng ta. Trong phần này, chúng ta sẽ thảo luận về yêu cầu đó thông qua 2 loại mô hình mới.

  • QStringListModel : danh sách đơn chứa các văn bản, chỉ hiển thị được 1 tầng.
  • QStandardItemModel : danh sách phức hợp với nhiều tầng khác nhau, phù hợp cho đa số nhu cầu trong các trường hợp.

Với những trường hợp đơn giản, chúng ta sẽ sử dụng QStringListModel nhưng chúng ta cũng sẽ tìm hiểu cả về QStandardItemModel vì  những tính năng linh hoạt mà nó cung cấp.

QStringListModel : danh sách chuỗi ký tự QString

Mô hình này cũng khá đơn giản. Nó cho phép chúng ta quản lý danh sách với mỗi thành viên là 1 chuỗi ký tự, ví dụ như danh sách tên các nước chẳng hạn.

Việt Nam

Pháp

Đức

Mỹ

Nhật

Để tạo ra mô hình dạng này, chúng ta thực hiện 2 bước :

  • Tạo ra đối tượng QStringList chứa danh sách các chuỗi.
  • Tạo ra đối tượng QStringListModel và truyền cho nó tham số là đối tượng QStringList vừa được tạo ra bên trên để khởi tạo.

QStringList ghi đè toán tử << để cho phép chúng ta có thể dễ dàng thêm các chuỗi ký tự thành viên vào trong danh sách.

1 đoạn mã ví dụ thì lúc nào cũng rõ ràng hơn đúng không.

#include "CuaSoChinh.h"
CuaSoChinh::CuaSoChinh(){
    QVBoxLayout *lop = new QVBoxLayout();
    QStringList danhSachTenNuoc;
    danhSachTenNuoc << " Việt Nam" << "Pháp" << "Đức" << "Mỹ" << "Nhật";
    QStringListModel *moHinh = new QStringListModel(danhSachTenNuoc);

    QListView *khungNhin = new QListView();
    khungNhin->setModel(moHinh);
    lop->addWidget(khungNhin);
    setLayout(lop);
}

Khung hiển thị của chúng ta bây giờ sẽ hiển thị danh sách các nước mà chúng ta vừa thêm vào mô hình.

Chắc hẳn các bạn đều đồng ý rằng phép ghi đè toán tử << khá là hữu dụng. Tuy nhiên nếu đấy không phải là gu của các bạn, chúng ta luôn có giải pháp khác là sử dụng phương thức append().

danhSachTenNuoc.append("Anh");

Nếu trong quá trình chương trình được thực thi mà một nước được thêm vào mô hình, xóa hoặc thay đổi trong mô hình thì khung hiển thị (dạng danh sách) sẽ tự động thực hiện những thay đổi hiển thị tương ứng. Chúng ta sẽ thực hành thao tác trên trong phần tới của bài học này.

QStandardItemModel : danh sách nhiều tầng và nhiều cột

Mô hình loại này hoàn chỉnh hơn nhiều và cũng vì lẽ đó, phức tạp hơn nhiều so với cái trước đó. Nó cho phép tạo ra đủ loại mô hình khác nhau mà chúng ta có thể nghĩ ra được.

Để hình dung dễ hơn các loại mô hình mà chúng ta có thể thiết kế với QStandardItemModel, hãy quan sát sơ đồ mà tài liệu Qt cung cấp cho chúng ta.

  • List Model : là mô hình đơn giản với 1 cột duy nhất và không có thành phần con. Đây chính là mô hình được sử dụng bới QStringList nhưng đương nhiên QStandardItemModel cũng có thể làm được điều tương tự.
  • Table Model : Các thành phần được sắp xếp thành bảng nhiều dòng và cột. Mô hình loại này tương thích tốt với QTableView.
  • Tree Model : bản thân các thành phần có thể chứa trong nó các thành phần con, được phân thành nhiều tầng. Trong sơ đồ trên thì chúng ta không thấy thể hiện ra nhưng chúng ta hoàn toàn có thể có nhiều cột trong 1 mắt thành phần của mô hình dạng cây này. Nó vô cùng tương thích với khung hiển thị QTreeView.
Quản lý nhiều dòng và cột

Để xây dụng mô hình QStandardItemList, chúng ta cần đưa ra tham số số dòng và số cột chúng ta muốn quản lý. Các dòng và cột phát sinh luôn có thể được thêm vào sau, khi chúng ta cần dùng đến.

#include "CuaSoChinh.h"
CuaSoChinh::CuaSoChinh(){
    QVBoxLayout *lop = new QVBoxLayout();
    QStandardItemModel *moHinh = new QStandardItemModel(5, 3);

    QTableView * khungNhin = new QTableView();
    khungNhin->setModel(moHinh);
    lop->addWidget(khungNhin);
    setLayout(lop);
}

! Nếu chúng ta chỉ yêu cầu 1 cột thì sẽ tạo ra mô hình giống hệt kiểu List Model.

Trong mô hình ở đây, chúng ta đã tạo ra 5 hàng và 3 cột. Các mắt lưới đều còn trống nhưng cấu trúc bảng thì đã được xây dựng xong.

! Chúng ta luôn có thể sử dụng phương thức khởi tạo mặc định không tham số nếu chúng ta không có sẵn kích thước của bảng. Chúng ta sẽ cần sử dụng đến các phương thức appendRow()appendColumn() khi muốn thêm dòng hay cột mới.

Mỗi thành viên trong mô hình là 1 đối tượng QStandardItem. Để khai báo thành viên, chúng ta sẽ sử dụng phương thức setItem() của mô hình. Phương thức này nhận vào tọa độ muốn hiển thị và thành viên QStandardItem mà chúng ta muốn hiển thị. Chú ý, các trị số tọa độ bắt đầu từ 0!

#include "CuaSoChinh.h"
CuaSoChinh::CuaSoChinh(){
    QVBoxLayout *lop = new QVBoxLayout();
    QStandardItemModel *moHinh = new QStandardItemModel(5, 3);
    moHinh->setItem(3, 1, new QStandardItem("Tân binh !"));

    QTableView * khungNhin = new QTableView();
    khungNhin->setModel(moHinh);
    lop->addWidget(khungNhin);
    setLayout(lop);
}

Vậy là tôi đã tạo ra thành viên là chuỗi ký tự “Tân binh !” vào vị trí hàng 4 cột 2.

Và đấy là cách chúng ta thêm các thành viên vào mô hình.

Thêm thành viên con

Bây giờ chúng ta hãy thử thêm các thành viên con vào trong mô hình. Trong trường hợp này, để hiển thị tối ưu mô hình, chúng ta sẽ phải chuyển qua sử dụng khung nhìn QTreeView. Dưới đây là các bước tiến hành để thực hiện việc này :

  1. Tạo ra thành viên kiểu QStandardItem
  2. Thêm thành viên này vào mô hình nhờ phương thức appendRow()
  3. Thêm thành viên con vào thành viên vừa tạo ra cũng nhờ appendRow()
#include "CuaSoChinh.h"
CuaSoChinh::CuaSoChinh(){
    QVBoxLayout *lop = new QVBoxLayout();
    QStandardItemModel *moHinh = new QStandardItemModel();
    QStandardItem *thanhVien = new QStandardItem("Tân binh");

    moHinh->appendRow(item);
    thanhVien->appendRow(new QStandardItem("18 tuổi"));
    QTreeView *vue = new QTreeView;
    khungNhin->setModel(moHinh);
    lop->addWidget(khungNhin);
    setLayout(lop);
}

Kết quả là chúng ta đã tạo ra được 1 thành viên là “Tân binh” chứa thành viên con là “17 tuổi”.

Các bạn có thể tập luyện bằng cách tạo ra nhiều thành viên và thành viên con và thêm chúng dần dần vào mô hình. Nếu chúng ta tổ chức tốt thì sẽ không gặp nhiều khó khăn nhưng điều này đòi hỏi khá nhiều thời gian để thực tập.

! Để xóa phần đề mục khá vô dụng (trong trường hợp trên là “1”), chúng ta có thể sử dụng câu lệnh khungNhin->header()->hide();

Quản lý các lựa chọn

Chúng ta đã cùng khám phá làm cách nào để liên kết 1 mô hình với 1 khung nhìn cũng như đã học cách sử dụng nhiều loại mô hình khác nhau như QDirModel, QStringListModel hay QStandardItemModel.

Chỉ còn lại việc cuối cùng là cách làm sao chúng ta có thể xác định đối tượng được người dùng lựa chọn trong khung hiển thị.

Chúng ta sẽ đâm đầu vào phần tiếp theo của bài học này, khá phức tạp với vô vàn lớp liên kết qua lại với nhau.

Kiến trúc mô hình – khung nhìn, như đã nói, vô cùng linh hoạt nhưng đi kèm với đó đòi hỏi mức độ thao tác khá tinh tế với nhiều bước cần thực hiện trong các trình tự nhất định. Chính vì thế, để tránh hậu quả là bài học trở nên quá dài dòng và phức tạp, tôi quyết định sẽ hạn chế các ví dụ của phần này trong khuôn khổ việc lấy ra đối tượng được chọn từ QListView. Về phần áp dụng những kiến thức dưới đây lên các khung nhìn khác, tôi dành chúng cho các bạn. Đừng lo lắng quá, chúng không có quá nhiều khác biệt với nhau do nguyên lý hoạt động không hề thay đổi.

Chúng ta sẽ thêm vào nút “Hiển thị lựa chọn” vào cửa sổ để giúp cho việc minh họa trở nên dễ dàng.

Khi chiếc nút được ấn, chúng ta sẽ làm bật ra 1 hộp thoại QMessageBox hiển thị thành viên được chọn trong danh sách.

Sẽ xuất hiện 2 trường hợp cần chúng ta xử lý :

  • Khi chỉ từng thành viên được chọn
  • Khi người dùng có thể chọn nhiều thành viên cùng lúc.
Lựa chọn đơn

Trước tiên, chúng ta sẽ cần đến liên kết giữa 1 tín hiệu và 1 slot để cho chương trình có thể xử lý được thao tác bấm nút.

Hãy thay đổi 1 chút trong tệp .h của cửa sổ để bắt đầu.

#ifndef CUASOCHINH_H
#define CUASOCHINH_H
#include <QtWidgets>

class CuaSoChinh : public QWidget {
    Q_OBJECT
    public:
        CuaSoChinh();

    private:
        QListView *khungNhin;
        QStringListModel *moHinh;
        QPushButton *nutBam;

    private slots:
        void hienThiLuaChon();
};
#endif

Tôi thêm vào xử lý Q_OBJECT cũng như biến 1 số thành phần của cửa sổ thành thuộc tính của nó để slot có thể truy cập đến chúng. Ngoài ra, slot hienThiLuaChon() được thêm vào là slot sẽ được kích hoạt khi nút được bấm.

Bây giờ, hãy đến với mã nguồn trong tệp .cpp, nơi chúng ta thiết lập liên kết cũng như định nghĩa xử lý của slot.

#include "CuaSoChinh.h"

CuaSoChinh::CuaSoChinh() {
    QVBoxLayout *lop = new QVBoxLayout;
    QStringList danhSachTenNuoc;
    danhSachTenNuoc << " Việt Nam" << "Pháp" << "Đức" << "Mỹ" << "Nhật";

    QStringListModel *moHinh = new QStringListModel(danhSachTenNuoc);
    QListView *khungNhin = new QListView();
    khungNhin->setModel(moHinh);
    nutBam = new QPushButton("Hiển thị lựa chọn");
    lop->addWidget(khungNhin);
    lop->addWidget(nutBam);
    setLayout(lop);

    connect(nutBam, SIGNAL(clicked()), this, SLOT(hienThiLuaChon()));
}

void CuaSoChinh::hienThiLuaChon() {
    QItemSelectionModel *luaChon = khungNhin->selectionModel();
    QModelIndex chiSoThanhVienDuocChon = luaChon->currentIndex();
    QVariant thanhVienDuocChon = luaChon->data(chiSoThanhVienDuocChon, Qt::DisplayRole);
    QMessageBox::information(this, "Đối tượng được chọn ", thanhVienDuocChon.toString());
}

Phân tích mã nguồn của slot, chúng ta thấy có những xử lý dưới đây.

  1. Chúng ta lấy ra đối tượng QItemSelectionModel chứa những thông tin về thành viên được chọn trong khung hiển thị. Chính khung hiển thị là nơi chứa thông tin về con trỏ trên đối tượng này, chúng ta có thể lấy được thông tin đó qua phương thức khungNhin->selectionModel().
  2. Chúng ta sử dụng phương thức currentIndex() để lấy thông tin về lựa chọn của người dùng. Phương thức này trả về kết quả là 1 chỉ sổ của đối tượng được chọn trong khung hiển thị.
  3. Bây giờ, khi chúng ta đã biết được chỉ số của đối tượng được chọn, chúng ta sẽ muốn lấy ra nội dung của nó. Chúng ta sử dụng phương thức data() của mô hình, truyền cho nó tham số là chỉ số vừa nhận được. Kết quả được lấy ra là đối tượng thuộc lớp QVariant, là lớp có thể chứa nhiều kiểu dữ liệu khác nhau như giá trị nguyên hay chuỗi ký tự.
  4. Hiển thị kết quả vừa nhận được trên đối tượng được chọn. Để lấy ra giá trị của chuỗi trong QVariant, chúng ta dùng tới phương thức toString().

Vậy là, chỉ cần nút được bấm thì chúng ta sẽ được thông báo là thành viên nào trong danh sách đang được chọn.

Lựa chọn phức

Mặc định thì người dùng chỉ có thể chọn 1 đối tượng duy nhất trong danh sách. Để thay đổi cài đặt mặc định này và cho phép người dùng chọn nhiều đối tượng 1 lúc, chúng ta cần thêm dòng lệnh sau vào đoạn mã.

khungNhin->setSelectionMode(QAbstractItemView::ExtendedSelection);

QAbstractItemView còn cung cấp những chế độ lựa chọn khác ngoài 2 chế độ lựa chọn đơn và lựa chọn phức trên. Để biết thêm chi tiết, xin mời các bạn ghé thăm trang tài liệu Qt của lớp này.

Với chế độ chọn mà chúng ta vừa thiết lập, chúng ta có thể chọn bất kỳ đối tượng nào. Người dùng có thể dùng phím Shift để chọn nhiều đối tượng liên tiếp hoặc Ctrl để chọn nhiều đối tượng cách nhau.

Lần này, để lấy ra danh sách các đối tượng được chọn sẽ phức tạp hơn 1 chút vì sẽ có đồng thời nhiều đối tượng. Chúng ta sẽ không thể sử dụng phương thức currentIndex() mà phải dùng tới selectedIndexes().

Dưới đây là đoạn mã mới của slot mà chúng ta sẽ cùng nhau phân tích.

void CuaSoChinh::hienThiLuaChon() {
    QItemSelectionModel *luaChon = khungNhin->selectionModel();
    QModelIndexList danhSachDuocChon = luaChon->selectedIndexes();
    QString cacDoiTuongDuocChon;

    for (int i = 0 ; i < danhSachDuocChon.size() ; i++) {
        QVariant doiTuongDuocChon= moHinh->data(danhSachDuocChon[i], Qt::DisplayRole);
        cacDoiTuongDuocChon += danhSachDuocChon.toString() + "<br />";
    }

    QMessageBox::information(this, "Các đối tượng được chọn", cacDoiTuongDuocChon);
}
  1. Trước tiên, không có gì khác, chúng ta vẫn lấy ra đối tượng chứa sự thông tin về lựa chọn của người dùng.
  2. Thay vì sử dụng currentIndex(), chúng ta dùng selectedIndexes() bởi lúc này, có thể có nhiều thành viên được chọn.
  3. Chúng ta tạo ra đối tượng QString trống và ghi dần vào đó danh sách các nước được chọn để sau đó hiển thị trong cửa sổ thông báo.
  4. Thực thi vòng lặp trên đối tượng danh sách mà chúng ta vừa lấy ra (đây là 1 đối tượng QList nhưng chúng ta có thể thao tác như với các mảng). Vậy là chúng ta sẽ lặp trên các thành phần của mảng và lấy ra giá trị tương ứng của chúng.
  5. Ghi dần từng giá trị vào chuỗi ký tự trống được tạo ra lúc trước.
  6. Kết thúc vòng lặp, chúng ta cho hiển thị chuỗi chứa danh sách các nước được chọn. Vậy là xong !

Ở đây, tôi hài lòng với việc lấy ra các giá trị và đơn giản là hiển thị chúng nhưng các bạn có thể làm được nhiều điều thú vị hơn nhiều với các giá trị đó. Về phần mình, tôi chỉ có thể chỉ ra cho các bạn nguyên lý vận hành cơ bản của cấu trúc mô hình – khung nhìn trong Qt còn khả năng vận dụng nó hiệu quả tới đâu nằm ở chính các bạn.

Bây giờ, khi chúng ta đã nắm được các nguyên lý, công việc của các bạn là trong từng trường hợp đưa ra lựa chọn xem cần sử dụng công cụ nào cho hợp lý.

Đừng ngại thực hành thêm với các khung nhìn dạng cây với dạng bảng biểu cũng như nghiên cứu tỉ mỉ chúng nếu cần thiết. Các bạn có thể dùng lại bài thực hành lúc trước của chúng ta và thêm vào 1 widget sử dụng kiến trúc MVC để rèn luyện thêm.

Bài học này có vẻ khá khó nhưng đôi lúc thêm vào các thử thách khiến mọi thứ trở nên thú vị hơn chứ hả sealed. Chúng ta đâu còn là những tay mơ nữa.

Thêm nữa, đừng bỏ quên tài liệu cung cấp bởi Qt, các bạn có thể tìm thấy mọi câu trả lời trong đó.

! Trong thực tế, các lớp khung hình đơn giản mà chúng ta vừa thảo luận (QListWidget, QTableWidget, QTreeWidget) không đòi hỏi chúng ta phải tự tạo ra các mô hình. Tuy nhiên cần hiểu việc này khiến chúng trở nên kém linh hoạt hơn. Hãy dùng chúng khi nào các bạn muốn tạo ra các chương trình đơn giản và không muốn léng phéng với kiến trúc MVC.