Nếu một lớp dẫn xuất từ QObject ghi đè phương thức eventFilter, nó sẽ trở thành bộ lọc sự kiện (Bộ lọc sự kiện). Phương thức này được khai báo như sau:
ảo
bool
eventFilter(QObject *đã xem, QEvent *
sự kiện
);
Tham số được theo dõi là đối tượng lắng nghe sự kiện, tức là người nhận sự kiện; tham số sự kiện tất nhiên là sự kiện cần được xử lý. Bộ lọc sự kiện (cũng có thể dịch là "bộ lọc") có thể chặn các sự kiện trước người nhận và sau khi xử lý, họ cũng có thể quyết định có chuyển tiếp sự kiện đến người nhận hay không. Nếu bạn không muốn chuyển tiếp sự kiện đến người nhận, hãy trả về true; nếu bạn vẫn muốn sự kiện tiếp tục lan truyền, hãy trả về false.
Công dụng hữu ích nhất của thứ này là: cửa sổ cấp cao nhất của bạn có K thành phần con (thường là lớp con của QWidget). Nếu thành phần đó không xác định tín hiệu bạn muốn sử dụng, thì nó chỉ có thể được giải quyết bằng cách xử lý các sự kiện, còn bạn thì không. có thể Nếu bạn không muốn dẫn xuất một lớp chỉ để xử lý một sự kiện (ví dụ: thành phần QLabel thực hiện điều gì đó khi chuột di chuột), bạn có thể sử dụng các bộ lọc sự kiện. Lớp cửa sổ cấp cao nhất ghi đè phương thức eventFilter để chặn các sự kiện (chẳng hạn như mouseMove) được gửi đến các thành phần phụ và xử lý chúng trực tiếp, điều này có thể tiết kiệm N hàng trăm dòng mã.
Lớp ghi đè phương thức eventFilter sẽ trở thành bộ lọc của sự kiện và lớp gọi phương thức installEventFilter để cài đặt bộ lọc sẽ là người nhận ban đầu của sự kiện. Lấy ví dụ về thành phần QLabel mà chúng tôi đã đưa ra ở trên. Giả sử rằng tên lớp của cửa sổ cấp cao nhất là DuckWindow. Sau đó, DuckWindow ghi đè phương thức eventFilter, là phương thức chặn sự kiện và thành phần QLabel là bộ thu ban đầu của sự kiện; sự kiện này, vì vậy hãy gọi installEventFilter. Phương thức này là vậy. Đó là QLabel::installEventFilter(DuckWindow).
Không biết bạn bè có hiểu được lời Lão Chu nói không. Đối tượng chịu trách nhiệm lọc các sự kiện sẽ ghi đè phương thức eventFilter; chỉ những đối tượng được lọc bởi những đối tượng khác mới gọi phương thức installEventFilter.
Hãy nói chuyện với các ví dụ. Các bài tập chúng ta sẽ làm dưới đây như sau:
Tôi đã định nghĩa một lớp có tên MyWindow, lớp này kế thừa lớp QWidget, làm cửa sổ cấp cao nhất. Sau đó, trong cửa sổ, tôi sử dụng bố cục QHBoxLayout để sắp xếp các thành phần con trong cửa sổ theo chiều ngang. Nhưng mỗi thành phần phụ có một màu khác nhau. Cách tiếp cận thông thường là viết một lớp thành phần tùy chỉnh, truyền đối tượng QColor từ hàm tạo hoặc thông qua hàm thành viên, sau đó ghi đè phương thức PaintEvent để vẽ. Chắc chắn không có vấn đề gì với cách tiếp cận này. Nhưng! Nếu tôi không muốn viết một lớp tùy chỉnh thì tôi phải xem xét các bộ lọc sự kiện. Lọc sự kiện PaintEvent và chỉ sử dụng một màu nhất định để vẽ nền cho thành phần phụ.
Tệp tiêu đề khai báo lớp MyWindow.
#ifndefMYWIN
#định nghĩa
MYWIN
#bao gồm
#bao gồm
#bao gồm
#bao gồm
#bao gồm
#bao gồm
lớp học
Cửa sổ của tôi:
công cộng
QWidget { Q_ĐỐI TƯỢNG
công cộng
: Cửa sổ của tôi(QWidget
* cha mẹ=
không có gì);
bool
sự kiệnFilter(QObject *obj, QEvent *
sự kiện
)
ghi đè
;
riêng tư
:
//
Viên riêng, dùng để trị mụn
vô hiệu
paintSomething(QPainter *p,
hằng số
QMàu sắc &màu sắc,
hằng số
QRect &
vẽ hình chữ nhật);
//
cách trình bày
Bố cục QHBox *
cách trình bày;
//
Ba thành phần con
Tiện ích QWidget *w1, *w2, *
w3; };
#kết thúc nếu
Hãy để tôi đề cập đến phương thức eventFilter ở đây. Có thể khai báo nó là công khai hoặc được bảo vệ. Lão Chu tuyên bố nó là công khai ở đây, điều này phù hợp với tuyên bố của lớp cơ sở.
PaintS Something là một phương pháp riêng tư, được tùy chỉnh để vẽ mọi thứ. Một số bạn có thể hỏi: PaintDevice của QPainter không thể lấy diện tích hình chữ nhật bằng kích thước của cài đặt bản vẽ (ở đây đề cập đến cửa sổ hoặc thành phần). Tại sao phải truyền QRect từ tham số? Vì trực tiếp này xuất phát từ tham số sự kiện của đối tượng QPaintEvent nên nó không nhất thiết đề cập đến toàn bộ khu vực hình chữ nhật của cửa sổ/thành phần. Nếu là vẽ lại một phần thì hình chữ nhật này có thể là một phần nhỏ của diện tích. Vì vậy, chúng ta vẽ bằng cách sử dụng diện tích hình chữ nhật mà sự kiện đã đi qua.
Bố cục cửa sổ sử dụng QHBoxLayout, đây là một phương pháp bố trí rất đơn giản. Các thành phần con được sắp xếp theo chiều ngang trên cửa sổ.
Đoạn mã sau triển khai hàm tạo và khởi tạo từng đối tượng.
Cửa sổ của tôi::Cửa sổ của tôi(QWidget *
cha mẹ): QWidget(cha mẹ) {
//
khởi tạo
bố trí =
mới
Bố cục QHBox;
cái này
->
setLayout(bố cục); w1
=
mới
Tiện ích Q
cái này
); w2
=
mới
Tiện ích Q
cái này
); w3
=
mới
Tiện ích Q
cái này
); cách trình bày
->
addWidget(w1); bố trí
->
addWidget(w2); bố trí
->
addWidget(w3);
//
Cài đặt bộ lọc sự kiện
w1->cài đặt bộ lọc sự kiện(
cái này
)
;
w2
->cài đặtEventFilter(
cái này
)
;
w3
->cài đặtEventFilter(
cái này
)
; }
Bộ lọc sự kiện sẽ chỉ có hiệu lực sau khi phương thức installEventFilter được gọi trên đối tượng bị chặn để liên kết bộ lọc. Ở đây, vì lớp MyWindow ghi đè phương thức eventFilter nên bộ lọc sẽ như thế này.
Sau đây là mã triển khai của phương thức eventFilter. Nó chỉ lọc sự kiện vẽ và chuyển phần còn lại cho lớp cơ sở để thực hiện.
bool
MyWindow::eventFilter(QObject *obj, QEvent *
sự kiện
) {
//
Nếu đó là một sự kiện sơn
//
Ở đây "VÀ" xác định xem người nhận sự kiện có nằm trong ba thành phần phụ đó hay không
//
Ngăn chặn các vật thể không mong muốn khác xuất hiện
//
Nhưng điều đó sẽ không xảy ra ở đây, vì chỉ những đối tượng được cài đặt bộ lọc mới chặn các sự kiện.
nếu như
(
sự kiện
->kiểu() ==
QEvent::Sơn
&& (obj==w1 || obj==w2 || obj==
w3)) { QPaintSự kiện
* pe = static_cast(
sự kiện
);
Tiện ích QWidget
* uiobj = static_cast
(đối tượng)
; QPainter họa sĩ;
//
Lưu ý ở đây rằng thiết bị vẽ không phải là cái này mà là đối tượng nhận các sự kiện vẽ.
//
Vì loại nó yêu cầu là QPaintDevcie* nên cần phải chuyển đổi loại
//
Loại biến uiobj được chuyển đổi là QWidget* và không có vấn đề gì khi truyền tham số.
họa sĩ.begin(uiobj);
nếu như
(w1 ==
uiobj) {
//
màu đỏ
paintSomething(&painter, QColor(
"
màu đỏ
"
), trên->
hình chữ nhật()); }
nếu như
(w2 ==
uiobj) {
//
màu cam
paintSomething(&painter, QColor(
"
quả cam
"
), trên->
hình chữ nhật()); }
nếu như
(w3 ==
uiobj) {
//
Màu tím
paintSomething(&painter, QColor(
"
màu tím
"
), trên->
rect()); } họa sĩ.end();
trở lại
ĐÚNG VẬY
;
}
trở lại
QWidget::eventFilter(đối tượng,
sự kiện
); }
Sau khi chặn và xử lý sự kiện Paint các bạn nhớ return true để sự kiện không truyền vào đối tượng đích (chúng ta xử lý cho nó nên không cần xử lý lại nữa, sự kiện Paint mặc định của lớp QWidget không làm gì cả).
Đoạn mã sau đây là phương thức PaintS Something. Tôi vừa vẽ một cái mụn khổng lồ...ồ không, nó có hình bầu dục.
vô hiệu
MyWindow::paintSomething(QPainter *p,
hằng số
QMàu sắc &màu sắc,
hằng số
QRect &
paintRect) {
//
Đặt cọ
p->
setBrush(QBrush(màu));
//
không có phác thảo
p->
setPen(Qt::NoPen);
//
Vẽ một hình elip
p->
drawEllipse(paintRect); }
Đặt NoPen trong setPen là loại bỏ đường viền khi vẽ hình tròn và đường viền sẽ được vẽ theo mặc định.
Cuối cùng, đã đến lúc đi vào chức năng chính.
số nguyên
chủ yếu(
số nguyên
đối số,
char
**
argv) { Ứng dụng QApplication(argc,argv); MyWindow wind;
//
tiêu đề cửa sổ
gió.setWindowTitle(
"
Làm một số công việc
"
);
//
Thay đổi kích thước cửa sổ
gió.thay đổi kích thước(
321
,
266
); gió.hiển thị();
trở lại
QApplication::exec(); }
Chạy ra nhìn xem, có ba cái mụn nằm ngang, đẹp làm sao.
。
Hãy lấy một ví dụ khác. Lần này chúng tôi chặn sự kiện đóng của cửa sổ. Khi cửa sổ sắp đóng, chúng tôi xuất ra một thông báo gỡ lỗi.
#ifndef bò
#định nghĩa
bò sữa
#bao gồm
<đối tượng="" q="">
#bao gồm
lớp học
Bộ lọc của tôi:
công cộng
Đối tượng Q {
được bảo vệ
:
bool
sự kiệnFilter(QObject *obj, QEvent *e)
ghi đè
; };
#kết thúc nếu
đối>
Lần này chúng tôi không xuất phát từ bất kỳ kiểu trực quan nào mà trực tiếp từ lớp QObject. Ở đây chúng ta chỉ viết lại phương thức eventFilter và không sử dụng tín hiệu cũng như cao nên không cần thêm macro Q_OBJECT. Nói cách khác, bộ lọc của chúng tôi được sử dụng độc lập và không nhằm mục đích thêm vào cây đối tượng của Qt.
Đây là mã thực hiện:
bool
MyFilter::eventFilter(QObject *obj, QEvent *
e) {
nếu như
(e->kiểu() ==
QEvent::Đóng) {
//
Chuyển đổi loại là bắt buộc ở đây
Cửa sổ QWidget* = qobject_cast
(đối tượng);
//
Xem thứ này có phải là cửa sổ không (có thể là điều khiển)
nếu như
(cửa sổ->cửa sổCờ() &
Qt::Cửa sổ) {
//
Lấy tiêu đề của cửa sổ này
Tiêu đề QString = cửa sổ->
windowTitle();
//
Thông tin gỡ lỗi đầu ra
qDebug() <<
"
Đóng cửa sổ:
"
<<
tiêu đề; } }
//
Sự kiện tiếp tục được chuyển giao
trở lại
SAI
; }
Tốt hơn hết là trả về false và tiếp tục chuyển sự kiện vào cửa sổ. Sau cùng, cửa sổ có thể phải thực hiện một số việc quan trọng khi đóng lại, chẳng hạn như lưu tệp đang mở. Nếu WindowFlags của QWidget chứa giá trị Window, nó cho biết đó là một cửa sổ.
Viết hàm chính ngay bên dưới.
số nguyên
chủ yếu(
số nguyên
đối số,
char
**
argv) { Ứng dụng QApplication(argc, argv); MyFilter
*lọc =
mới
Bộ lọc của tôi;
//
Hãy thử có ba cửa sổ
Tiện ích QWidget *win1 =
mới
QWidget; thắng1
->đặtTiêu đề cửa sổ(
"
đầu chó
"
); thắng1
->
installEventFilter(bộ lọc); win1
->
hiển thị(); QWidget
*thắng2 =
mới
QWidget; win2
->đặtTiêu đề cửa sổ(
"
đầu gà
"
); win2
->
installEventFilter(bộ lọc); win2
->
hiển thị(); QWidget
*thắng3 =
mới
QWidget; win3
->đặtTiêu đề cửa sổ(
"
đầu chuột
"
); win3
->
installEventFilter(bộ lọc); win3
->
trình diễn();
trở lại
QApplication::exec();
//
Không bắt buộc
delete
lọc; lọc
=
nullptr; }
bộ lọc là một loại con trỏ. Nó không được thêm vào cây đối tượng Qt và sẽ không được tự động xóa. Sử dụng xóa để giải quyết nó sau khi thực thi trả về. Một thói quen tốt khi dọn dẹp là đặt lại biến con trỏ về null sau del, để lỗi ít xảy ra hơn vào lần tiếp theo bạn tham chiếu biến. Miễn là if(! filter), bạn có thể phát hiện ra rằng nó trống.
Dù sao thì chương trình cũng đã thoát nên bạn có thể để nó rò rỉ ở đây cũng chẳng hại gì. Sau khi chương trình bị treo, không gian xử lý sẽ được hệ thống thu hồi.
Tất nhiên, cũng tốt khi sử dụng con trỏ "phạm vi" do Qt cung cấp đặc biệt, con trỏ này sẽ tự động XX khi vượt quá phạm vi.
số nguyên
chủ yếu(
số nguyên
đối số,
char
**
argv) { Ứng dụng QApplication(argc, argv);
Con trỏ QScoped
bộ lọc(
mới
Bộ lọc của tôi)
;
//
Hãy thử có ba cửa sổ
Tiện ích QWidget *win1 =
mới
QWidget; thắng1
->đặtTiêu đề cửa sổ(
"
đầu chó
"
); thắng1
->
cài đặtEventFilter(
bộ lọc.dữ liệu()
); thắng1
->
hiển thị(); QWidget
*thắng2 =
mới
QWidget; win2
->đặtTiêu đề cửa sổ(
"
đầu gà
"
); win2
->
cài đặtEventFilter(
bộ lọc.dữ liệu()
); win2
->
hiển thị(); QWidget
*thắng3 =
mới
QWidget; win3
->đặtTiêu đề cửa sổ(
"
đầu chuột
"
); win3
->
cài đặtEventFilter(
bộ lọc.dữ liệu()
); win3
->
trình diễn();
trở lại
QApplication::exec(); }
QScopedPointer đề cập đến đối tượng được đóng gói thông qua hàm tạo. Để truy cập đối tượng con trỏ được đóng gói, bạn có thể sử dụng thành viên dữ liệu.
Sau khi chạy sẽ xuất hiện 3 cửa sổ. Đóng từng cái một, thông tin gỡ lỗi sau sẽ được xuất ra:
。
-------------------------------------------------- -------------------------------------- 。
Cuối cùng Lão Chu lại nói đến chuyện khác.
Chúng tôi biết rằng Qt đã chính thức ra mắt Python cho Qt, được gọi là PySide. Vào tháng 5, Lao Chu gặp phải sự cố: PySide6 không thể tải tệp QML. Lỗi được báo cáo là tải dll không thành công và không tìm thấy mô-đun được chỉ định.
Không có phương pháp trực tuyến nào hoạt động. Trước hết, C++ có thể tải tệp QML bình thường khi số phiên bản Qt giống nhau (cả 6.5.1). Việc bạn đang tạo tệp tài nguyên hay truy cập tệp trực tiếp không quan trọng. Nhưng Python báo lỗi. Điều này ít nhất cho thấy rằng một số .dll không bị thiếu trên máy của tôi, nếu không thì mã C++ cũng sẽ báo lỗi.
Sau đó, Lão Chu thắc mắc liệu có vấn đề gì trong việc biên soạn Qt chính thức nên tôi đã thay thế thư viện liên kết động trong PySide6 bằng thư viện động Qt do tôi tự biên soạn. Nếu lỗi vẫn còn thì hãy loại bỏ những khác biệt trong quá trình biên dịch.
Sau đó, Lao Chu cho rằng có vấn đề với Python. Các thử nghiệm từ phiên bản 3.7 đến 3.10 cũng báo lỗi; môi trường ảo được xây dựng với các đường dẫn khác nhau cũng báo lỗi;
Tại thời điểm này, bạn có thể trực tiếp xác định rằng đó là sự cố với Python. Không có vấn đề gì với số phiên bản Python được cài đặt bởi cửa hàng Windows sẽ báo lỗi, nhưng Python được cài đặt bởi các cửa hàng không phải Windows sẽ bình thường.
Tuy nhiên tôi phải nói thêm một câu nữa: Nếu bạn muốn sử dụng Qt 666 thì nên sử dụng C++. Sử dụng Python chỉ phù hợp với người mới bắt đầu và giải trí. Vì Rust có thể gọi mã C/C++ nên bạn có thể thử sử dụng Rust. Rust không có bất kỳ tính năng quản lý bộ nhớ tự động nào. Nếu bạn muốn GC, chỉ cần sử dụng .NET. Trọng tâm của Rust là an toàn bộ nhớ. Nó có vẻ khá hấp dẫn và các quan chức rất tự hào về điều đó. Sau khi có sẵn (cảm giác giống như sử dụng Go), nó thực sự không dễ sử dụng như C++. C++ không thể mang quá nhiều hành trang lịch sử chỉ bằng cách khoe khoang. Tất nhiên, rò rỉ bộ nhớ C++ không đáng sợ như bạn nghĩ. Phát triển thói quen tốt Các đối tượng có phạm vi ngắn và lượng dữ liệu nhỏ có thể được phân bổ trực tiếp trên ngăn xếp. Nếu bạn cần chuyển các đối tượng trong các ngữ cảnh mã khác nhau hoặc nếu dữ liệu được phân bổ lớn, hãy sử dụng con trỏ. Kiên quyết loại bỏ các biến kiểu con trỏ khi chúng không còn cần thiết nữa và sau đó nhớ đặt biến thành nullptr. Về cơ bản, việc phát triển những thói quen tốt này không phải là vấn đề lớn.
Nói chung, nếu bạn đã quen với việc viết mã, bạn sẽ không quên xóa. Điều dễ bỏ sót là một số đối tượng nhất định sẽ được chia sẻ giữa các mã lớn và phức tạp, và một số đối tượng nhất định sẽ được tham chiếu ở nhiều nơi. Vì vậy, tôi cảm thấy chóng mặt khi viết mã và không nhớ phải hủy nó.
Đối với các đối tượng sẽ được tham chiếu nhiều lần, bạn có thể viết nhận xét để nhắc nhở bản thân hoặc người khác dọn dẹp hoặc thêm dấu trang. Sau khi viết mã, nếu nhìn vào danh sách dấu trang, bạn sẽ nhớ những đối tượng nào chưa bị hủy. Nếu mã được viết phức tạp sẽ dễ bị nhầm lẫn và các đối tượng được làm sạch thường sẽ được truy cập. Do đó, bạn cũng có thể tạo một câu lệnh if trước khi truy cập vào biến con trỏ, if (ptr). Trong biểu thức bool, nếu biến thuộc loại con trỏ trống, nó sẽ nhận được giá trị sai và nếu không trống, nó sẽ nhận được giá trị sai. ĐÚNG VẬY. Bằng cách này bạn có thể tránh được nhiều lỗi cấp thấp.
Ngay cả những ngôn ngữ không sử dụng con trỏ thường xuyên cũng có thể không gặp vấn đề. Trong C#, nếu bạn truy cập một biến null (VB is Nothing), bạn sẽ gặp lỗi cổ điển: "Tham chiếu đối tượng không được đặt thành phiên bản của đối tượng", đó là NullReferenceException. Miễn là bạn thấy điều này trong mã .NET, bạn sẽ biết rằng một đối tượng null chắc chắn đã được truy cập.
Trong C++, việc viết như thế này có thể khởi tạo lớp MyClass và chỉ phân bổ nó vào ngăn xếp.
Nhưng trong C#, giá trị ban đầu là null, nghĩa là nó không được khởi tạo và bạn phải sử dụng new để khởi tạo nó. Ồ, nhân tiện, tôi nghĩ ra điều gì đó, lớp dữ liệu cơ sở ẩn trong C# là Mảng, vì vậy nó là kiểu tham chiếu và giá trị ban đầu là null. Ngay cả khi các phần tử trong nhóm dữ liệu của bạn là kiểu giá trị, mảng đó. chính nó là một loại tham chiếu. Các đại biểu cũng là loại tham khảo. Một số sinh viên mới bắt đầu có thể nghĩ rằng đại biểu là loại giá trị.
Nếu các hàm C++ truyền giá trị bằng "tham chiếu", con trỏ và tham số tham chiếu thường được sử dụng, chẳng hạn như int *p, const int &a, const char *w (không thể thay đổi), v.v. Trong C#, nếu đó là tham chiếu type, chỉ cần khai báo trực tiếp. Ví dụ MyClass x, loại giá trị có thể sử dụng từ khóa ref int v.
Trong C#, int?, double?, v.v. có thể biến nó thành kiểu tham chiếu. Bạn có thể so sánh nó với int* trong C.
Cuối cùng, bài viết này về [VSCode và Qt6] sử dụng bộ lọc sự kiện để vận hành hàng loạt các thành phần con có ở đây. Nếu bạn muốn biết thêm về [VSCode và Qt6] bằng cách sử dụng bộ lọc sự kiện để vận hành hàng loạt các thành phần con, vui lòng Tìm kiếm các bài viết CFSDN hoặc tiếp tục duyệt. các bài viết liên quan tôi hy vọng bạn sẽ ủng hộ blog của tôi trong tương lai! .
Tôi là một lập trình viên xuất sắc, rất giỏi!