Sự kiện bàn phím
Các chương trình MQL có thể nhận thông điệp về các lần nhấn phím từ thiết bị đầu cuối bằng cách xử lý các sự kiện CHARTEVENT_KEYDOWN trong hàm OnChartEvent
.
Điều quan trọng cần lưu ý là các sự kiện chỉ được tạo ra trong biểu đồ đang hoạt động, và chỉ khi nó có tiêu điểm nhập liệu.
Trong Windows, tiêu điểm là sự lựa chọn logic và trực quan của một cửa sổ cụ thể mà người dùng đang tương tác. Theo quy tắc, tiêu điểm được di chuyển bằng cách nhấp chuột hoặc các phím tắt đặc biệt (Tab
, Ctrl+Tab
), khiến cửa sổ được chọn nổi bật. Ví dụ, con trỏ văn bản sẽ xuất hiện trong trường nhập liệu, dòng hiện tại sẽ được tô màu khác trong danh sách, v.v.
Các hiệu ứng hình ảnh tương tự cũng được nhận thấy trong thiết bị đầu cuối, đặc biệt khi một trong các cửa sổ Market Watch
, Data Window
, hoặc nhật ký Expert nhận được tiêu điểm. Tuy nhiên, tình hình hơi khác với các cửa sổ biểu đồ. Không phải lúc nào cũng có thể phân biệt bằng dấu hiệu bên ngoài liệu biểu đồ hiển thị ở phía trước có tiêu điểm nhập liệu hay không. Đảm bảo rằng bạn có thể chuyển tiêu điểm, như đã đề cập, bằng cách nhấp vào biểu đồ cần thiết (trên biểu đồ, không phải trên tiêu đề cửa sổ hoặc khung của nó) hoặc sử dụng phím nóng:
- Alt+W hiển thị cửa sổ với danh sách các biểu đồ, nơi bạn có thể chọn một biểu đồ.
- Ctrl+F6 chuyển sang biểu đồ tiếp theo (trong danh sách cửa sổ, thứ tự thường tương ứng với thứ tự các tab).
- Ctrl+Shift+F6 chuyển sang biểu đồ trước đó.
Danh sách đầy đủ các phím nóng của MetaTrader 5 có thể được tìm thấy trong tài liệu. Vui lòng chú ý rằng một số tổ hợp không tuân theo khuyến nghị chung của Microsoft (ví dụ, F10 mở cửa sổ báo giá, nhưng không kích hoạt menu chính).
Các tham số của sự kiện CHARTEVENT_KEYDOWN chứa thông tin sau:
lparam
— mã của phím được nhấndparam
— số lần nhấn phím được tạo ra trong thời gian giữ phímsparam
— một mặt nạ bit mô tả trạng thái của các phím bàn phím, được chuyển đổi thành chuỗi
Bits | Mô tả |
---|---|
0—7 | Mã quét phím (phụ thuộc vào phần cứng, OEM) |
8 | Thuộc tính phím bàn phím mở rộng |
9—12 | Dành cho mục đích dịch vụ Windows (không sử dụng) |
13 | Trạng thái phím Alt (1 - nhấn, 0 - thả), không khả dụng (xem bên dưới) |
14 | Trạng thái phím trước đó (1 - nhấn, 0 - thả) |
15 | Trạng thái phím thay đổi (1 nếu thả, 0 nếu nhấn) |
Trạng thái của phím Alt
thực tế không khả dụng, vì nó bị thiết bị đầu cuối chặn và bit này luôn là 0. Bit 15 cũng luôn bằng 0 do ngữ cảnh kích hoạt của sự kiện này: chỉ các lần nhấn phím được truyền đến chương trình MQL, không phải các lần thả phím.
Thuộc tính của bàn phím mở rộng (bit 8) được thiết lập, ví dụ, cho các phím của khối số (trên laptop thường được kích hoạt bằng Fn
), các phím như NumLock
, ScrollLock
, Ctrl
bên phải (trái ngược với Ctrl
bên trái chính), v.v. Đọc thêm về điều này trong tài liệu Windows.
Lần đầu tiên bất kỳ phím không thuộc hệ thống nào được nhấn, bit 14 sẽ là 0. Nếu bạn giữ phím nhấn, các lần lặp lại tự động được tạo ra của sự kiện sẽ có 1 trong bit này.
Cấu trúc sau sẽ giúp đảm bảo rằng mô tả của các bit là chính xác.
struct KeyState
{
uchar scancode;
bool extended;
bool altPressed;
bool previousState;
bool transitionState;
KeyState() { }
KeyState(const ushort keymask)
{
this = keymask; // sử dụng toán tử overload=
}
void operator=(const ushort keymask)
{
scancode = (uchar)(0xFF & keymask);
extended = 0x100 & keymask;
altPressed = 0x2000 & keymask;
previousState = 0x4000 & keymask;
transitionState = 0x8000 & keymask;
}
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Trong chương trình MQL, nó có thể được sử dụng như sau.
void OnChartEvent(const int id,
const long &lparam,
const double &dparam,
const string &sparam)
{
if(id == CHARTEVENT_KEYDOWN)
{
PrintFormat("%lld %lld %4llX", lparam, (ulong)dparam, (ushort)sparam);
KeyState state[1];
state[0] =(ushort)sparam;
ArrayPrint(state);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
Để tiện lợi cho mục đích thực tế, việc trích xuất các thuộc tính bit từ mặt nạ phím bằng macro sẽ thuận tiện hơn.
#define KEY_SCANCODE(SPARAM) ((uchar)(((ushort)SPARAM) & 0xFF))
#define KEY_EXTENDED(SPARAM) ((bool)(((ushort)SPARAM) & 0x100))
#define KEY_PREVIOUS(SPARAM) ((bool)(((ushort)SPARAM) & 0x4000))
2
3
Bạn có thể chạy chỉ báo EventAll.mq5
từ phần Thuộc tính biểu đồ liên quan đến sự kiện trên biểu đồ và xem giá trị tham số nào sẽ được hiển thị trong nhật ký khi nhấn một số phím nhất định.
Điều quan trọng cần lưu ý là mã trong lparam
là một trong những mã phím bàn phím ảo. Danh sách của chúng có thể được xem trong tệp MQL5/Include/VirtualKeys.mqh
, đi kèm với MetaTrader 5. Ví dụ, dưới đây là một số trong số đó:
#define VK_SPACE 0x20
#define VK_PRIOR 0x21
#define VK_NEXT 0x22
#define VK_END 0x23
#define VK_HOME 0x24
#define VK_LEFT 0x25
#define VK_UP 0x26
#define VK_RIGHT 0x27
#define VK_DOWN 0x28
...
#define VK_INSERT 0x2D
#define VK_DELETE 0x2E
...
// VK_0 - VK_9 mã ASCII của các ký tự '0' - '9' (0x30 - 0x39)
// VK_A - VK_Z mã ASCII của các ký tự 'A' - 'Z' (0x41 - 0x5A)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Các mã được gọi là ảo vì các phím tương ứng có thể nằm ở các vị trí khác nhau trên các bàn phím khác nhau, hoặc thậm chí được thực hiện thông qua việc nhấn đồng thời các phím phụ (như Fn
trên laptop). Ngoài ra, tính ảo có một khía cạnh khác: cùng một phím có thể tạo ra các ký tự hoặc hành động điều khiển khác nhau. Ví dụ, cùng một phím có thể biểu thị các chữ cái khác nhau trong các bố cục ngôn ngữ khác nhau. Ngoài ra, mỗi phím chữ cái có thể tạo ra chữ cái in hoa hoặc in thường, tùy thuộc vào chế độ của CapsLock
và trạng thái của phím Shift
.
Về vấn đề này, để lấy một ký tự từ mã phím ảo, API MQL5 có hàm đặc biệt TranslateKey
.
short TranslateKey(int key)
Hàm trả về một ký tự Unicode dựa trên mã phím ảo được truyền vào, dựa trên ngôn ngữ nhập hiện tại và trạng thái của các phím điều khiển.
Trong trường hợp xảy ra lỗi, giá trị -1 sẽ được trả về. Lỗi có thể xảy ra nếu mã không khớp với ký tự chính xác, ví dụ, khi cố gắng lấy ký tự cho phím Shift
.
Hãy nhớ rằng ngoài mã của phím được nhấn nhận được, chương trình MQL còn có thể Kiểm tra trạng thái bàn phím về các phím điều khiển và chế độ. Nhân tiện, các hằng số dạng TERMINAL_KEYSTATE_XXX, được truyền dưới dạng tham số cho hàm TerminalInfoInteger
, dựa trên nguyên tắc 1000 + mã phím ảo. Ví dụ, TERMINAL_KEYSTATE_UP là 1038 vì VK_UP là 38 (0x26).
Khi lập kế hoạch các thuật toán phản ứng với các lần nhấn phím, hãy nhớ rằng thiết bị đầu cuối có thể chặn nhiều tổ hợp phím, vì chúng được dành sẵn để thực hiện một số hành động nhất định (liên kết đến tài liệu đã được cung cấp ở trên). Đặc biệt, nhấn phím cách mở một trường để điều hướng nhanh dọc theo trục thời gian. API MQL5 cho phép kiểm soát một phần quá trình xử lý bàn phím tích hợp này và vô hiệu hóa nó nếu cần thiết. Xem phần về Kiểm soát chuột và bàn phím.
Chỉ báo đơn giản không có bộ đệm EventTranslateKey.mq5
đóng vai trò là một minh họa cho hàm này. Trong trình xử lý OnChartEvent
của nó cho các sự kiện CHARTEVENT_KEYDOWN, TranslateKey
được gọi để lấy một ký tự Unicode
hợp lệ. Nếu thành công, ký tự được thêm vào chuỗi thông điệp hiển thị trong bình luận biểu đồ. Khi nhấn Enter
, một dòng mới được chèn vào văn bản, và khi nhấn Backspace
, ký tự cuối cùng bị xóa khỏi cuối chuỗi.
#include <VirtualKeys.mqh>
string message = "";
void OnChartEvent(const int id,
const long &lparam, const double &dparam, const string &sparam)
{
if(id == CHARTEVENT_KEYDOWN)
{
if(lparam == VK_RETURN)
{
message += "\n";
}
else if(lparam == VK_BACK)
{
StringSetLength(message, StringLen(message) - 1);
}
else
{
ResetLastError();
const ushort c = TranslateKey((int)lparam);
if(_LastError == 0)
{
message += ShortToString(c);
}
}
Comment(message);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
Bạn có thể thử nhập các ký tự ở các trường hợp khác nhau và các ngôn ngữ khác nhau.
Hãy cẩn thận. Hàm trả về giá trị short
có dấu, chủ yếu để có thể trả về mã lỗi -1. Tuy nhiên, kiểu của một ký tự "rộng" hai byte được coi là một số nguyên không dấu, ushort
. Nếu biến nhận được khai báo là ushort
, việc kiểm tra bằng -1 (ví dụ, c!=-1
) sẽ đưa ra cảnh báo trình biên dịch "không khớp dấu" (yêu cầu ép kiểu rõ ràng), trong khi cách khác (c >= 0
) nói chung là sai, vì nó luôn bằng true
.
Để có thể chèn khoảng trắng giữa các từ trong thông điệp, điều hướng nhanh được kích hoạt bởi phím cách được tắt trước trong trình xử lý OnInit
.
void OnInit()
{
ChartSetInteger(0, CHART_QUICK_NAVIGATION, false);
}
2
3
4
Như một ví dụ đầy đủ về việc sử dụng các sự kiện bàn phím, hãy xem xét nhiệm vụ ứng dụng sau. Người dùng thiết bị đầu cuối biết rằng tỷ lệ của cửa sổ biểu đồ chính có thể được thay đổi tương tác mà không cần mở hộp thoại cài đặt bằng chuột: chỉ cần nhấn nút chuột trong tỷ lệ giá và, không thả nó, di chuyển lên/xuống. Thật không may, phương pháp này không hoạt động trong các cửa sổ phụ.
Các cửa sổ phụ luôn tự động điều chỉnh tỷ lệ để phù hợp với tất cả nội dung, và để thay đổi tỷ lệ, bạn phải mở một hộp thoại và nhập giá trị thủ công. Đôi khi nhu cầu này phát sinh nếu các chỉ báo trong cửa sổ phụ hiển thị "ngoại lệ" — các giá trị đơn lẻ quá lớn làm cản trở việc phân tích phần còn lại của dữ liệu kích thước bình thường (trung bình). Ngoài ra, đôi khi mong muốn chỉ đơn giản là phóng to hình ảnh để xử lý các chi tiết nhỏ hơn.
Để giải quyết vấn đề này và cho phép người dùng điều chỉnh tỷ lệ của cửa sổ phụ bằng các lần nhấn phím, chúng ta đã triển khai chỉ báo SubScalermq5
. Nó không có bộ đệm và không hiển thị bất cứ thứ gì.
SubScaler
phải là chỉ báo đầu tiên trong cửa sổ phụ, hoặc nói chính xác hơn, nó phải được thêm vào cửa sổ phụ trước khi chỉ báo làm việc mà bạn quan tâm được thêm vào đó, tỷ lệ của chỉ báo này bạn muốn kiểm soát. Để làm cho SubScaler
trở thành chỉ báo đầu tiên, nó nên được đặt trên biểu đồ (trong cửa sổ chính) và do đó tạo ra một cửa sổ phụ mới, nơi bạn có thể thêm chỉ báo phụ sau đó.
Trong hộp thoại cài đặt chỉ báo làm việc, điều quan trọng là bật tùy chọn Inherit scale
(trên tab Scale
).
Khi cả hai chỉ báo đang chạy trong một cửa sổ phụ, bạn có thể sử dụng các phím mũi tên Up/Down
để phóng to/thu nhỏ. Nếu phím Shift
được nhấn, phạm vi giá trị hiện tại hiển thị trên trục dọc được dịch chuyển lên hoặc xuống.
Phóng to có nghĩa là phóng to chi tiết ("thu phóng máy ảnh"), để một số dữ liệu có thể nằm ngoài cửa sổ. Thu nhỏ có nghĩa là bức tranh tổng thể trở nên nhỏ hơn ("thu phóng máy ảnh ra").
Các tham số đầu vào được đặt là:
- Giới hạn tối đa ban đầu — giới hạn trên của dữ liệu trong lần đặt ban đầu trên biểu đồ, mặc định là +1000.
- Giới hạn tối thiểu ban đầu — giới hạn dưới của dữ liệu trong lần đặt ban đầu trên biểu đồ, mặc định là -1000.
- Hệ số tỷ lệ — bước mà tỷ lệ sẽ thay đổi khi nhấn phím, giá trị trong khoảng [0.01 ... 0.5], mặc định là 0.1.
Chúng ta buộc phải yêu cầu người dùng cung cấp tối thiểu và tối đa vì SubScaler
không thể biết trước phạm vi giá trị làm việc của một chỉ báo bên thứ ba bất kỳ, sẽ được thêm vào cửa sổ phụ tiếp theo.
Khi biểu đồ được khôi phục sau khi bắt đầu một phiên thiết bị đầu cuối mới hoặc khi một mẫu tpl được tải, SubScaler
sẽ lấy tỷ lệ của trạng thái trước đó (đã lưu).
Bây giờ hãy xem xét việc triển khai của SubScaler
.
Các cài đặt trên được đặt trong các biến đầu vào tương ứng:
input double FixedMaximum = 1000; // Giới hạn tối đa ban đầu
input double FixedMinimum = -1000; // Giới hạn tối thiểu ban đầu
input double _ScaleFactor = 0.1; // Hệ số tỷ lệ [0.01 ... 0.5]
input bool Disabled = false;
2
3
4
Ngoài ra, biến Disabled
cho phép tạm thời vô hiệu hóa phản hồi bàn phím cho một phiên bản cụ thể của chỉ báo để thiết lập nhiều tỷ lệ khác nhau trong các cửa sổ phụ khác nhau (từng cái một).
Vì các biến đầu vào là chỉ đọc trong MQL5, chúng ta buộc phải khai báo một biến nữa ScaleFactor
để điều chỉnh giá trị nhập vào trong phạm vi cho phép [0.01 ... 0.5].
double ScaleFactor;
Số của cửa sổ phụ hiện tại (w
) và số lượng chỉ báo trong đó (n
) được lưu trữ trong các biến toàn cục: tất cả đều được điền vào trong trình xử lý OnInit
.
int w = -1, n = -1;
void OnInit()
{
ScaleFactor = _ScaleFactor;
if(ScaleFactor < 0.01 || ScaleFactor > 0.5)
{
PrintFormat("ScaleFactor %f được điều chỉnh về giá trị mặc định 0.1,"
" phạm vi hợp lệ là [0.01, 0.5]", ScaleFactor);
ScaleFactor = 0.1;
}
w = ChartWindowFind();
n = ChartIndicatorsTotal(0, w);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
Trong hàm OnChartEvent
, chúng ta xử lý hai loại sự kiện: thay đổi biểu đồ và sự kiện bàn phím. Sự kiện CHARTEVENT_CHART_CHANGE là cần thiết để theo dõi việc thêm chỉ báo tiếp theo vào cửa sổ phụ (chỉ báo làm việc cần được điều chỉnh tỷ lệ). Đồng thời, chúng ta yêu cầu phạm vi giá trị hiện tại của cửa sổ phụ (CHART_PRICE_MIN, CHART_PRICE_MAX) và xác định xem nó có bị suy giảm hay không, tức là khi cả tối đa và tối thiểu đều bằng không. Trong trường hợp này, cần áp dụng các giới hạn ban đầu được chỉ định trong tham số đầu vào (FixedMinimum
, FixedMaximum
).
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
switch(id)
{
case CHARTEVENT_CHART_CHANGE:
if(ChartIndicatorsTotal(0, w) > n)
{
n = ChartIndicatorsTotal(0, w);
const double min = ChartGetDouble(0, CHART_PRICE_MIN, w);
const double max = ChartGetDouble(0, CHART_PRICE_MAX, w);
PrintFormat("Thay đổi: %f %f %d", min, max, n);
if(min == 0 && max == 0)
{
IndicatorSetDouble(INDICATOR_MINIMUM, FixedMinimum);
IndicatorSetDouble(INDICATOR_MAXIMUM, FixedMaximum);
}
}
break;
...
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Khi nhận được sự kiện nhấn phím, hàm chính Scale
được gọi, hàm này không chỉ nhận lparam
mà còn nhận trạng thái của phím Shift
thu được bằng cách tham chiếu đến TerminalInfoInteger(TERMINAL_KEYSTATE_SHIFT)
.
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
switch(id)
{
case CHARTEVENT_KEYDOWN:
if(!Disabled)
Scale(lparam, TerminalInfoInteger(TERMINAL_KEYSTATE_SHIFT));
break;
...
}
}
2
3
4
5
6
7
8
9
10
11
Bên trong hàm Scale
, điều đầu tiên chúng ta làm là lấy phạm vi giá trị hiện tại vào các biến min
và max
.
void Scale(const long cmd, const int shift)
{
const double min = ChartGetDouble(0, CHART_PRICE_MIN, w);
const double max = ChartGetDouble(0, CHART_PRICE_MAX, w);
...
2
3
4
5
Sau đó, tùy thuộc vào việc phím Shift
hiện đang được nhấn hay không, việc phóng to hoặc dịch chuyển được thực hiện, tức là dịch chuyển phạm vi giá trị hiển thị lên hoặc xuống. Trong cả hai trường hợp, sự sửa đổi được thực hiện với một bước (hệ số) đã cho ScaleFactor
, liên quan đến các giới hạn min
và max
, và chúng được gán cho các thuộc tính chỉ báo INDICATOR_MINIMUM và INDICATOR_MAXIMUM, tương ứng. Do chỉ báo phụ có cài đặt Inherit scale, nó cũng trở thành cài đặt làm việc cho nó.
if((shift & 0x10000000) == 0) // Shift không được nhấn - thay đổi tỷ lệ
{
if(cmd == VK_UP) // phóng to (zoom in)
{
IndicatorSetDouble(INDICATOR_MINIMUM, min / (1.0 + ScaleFactor));
IndicatorSetDouble(INDICATOR_MAXIMUM, max / (1.0 + ScaleFactor));
ChartRedraw();
}
else if(cmd == VK_DOWN) // thu nhỏ (zoom out)
{
IndicatorSetDouble(INDICATOR_MINIMUM, min * (1.0 + ScaleFactor));
IndicatorSetDouble(INDICATOR_MAXIMUM, max * (1.0 + ScaleFactor));
ChartRedraw();
}
}
else // Shift được nhấn - dịch chuyển phạm vi
{
if(cmd == VK_UP) // dịch chuyển biểu đồ lên
{
const double d = (max - min) * ScaleFactor;
IndicatorSetDouble(INDICATOR_MINIMUM, min - d);
IndicatorSetDouble(INDICATOR_MAXIMUM, max - d);
ChartRedraw();
}
else if(cmd == VK_DOWN) // dịch chuyển biểu đồ xuống
{
const double d = (max - min) * ScaleFactor;
IndicatorSetDouble(INDICATOR_MINIMUM, min + d);
IndicatorSetDouble(INDICATOR_MAXIMUM, max + d);
ChartRedraw();
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
Đối với bất kỳ thay đổi nào, ChartRedraw
được gọi để cập nhật biểu đồ.
Hãy xem cách SubScaler
hoạt động với chỉ báo khối lượng tiêu chuẩn (bất kỳ chỉ báo nào khác, bao gồm cả tùy chỉnh, cũng được kiểm soát theo cách tương tự).
Tỷ lệ khác nhau được đặt bởi các chỉ báo SubScaler trong hai cửa sổ phụ
Ở đây trong hai cửa sổ phụ, hai phiên bản của SubScaler
áp dụng các tỷ lệ dọc khác nhau cho khối lượng.