1. Tải bản cài đặt AutoIT mới nhất

    Chào Khách. Nếu bạn mới tham gia và chưa cài đặt AutoIT.
    Vui lòng vào topic trên để tải bản AutoIT mới nhất nhé
    Dismiss Notice
  2. Quy định và nội quy

    Chào Khách. Vui lòng đọc kỹ nội quy và quy định của diễn đàn
    Để tránh bị ban một cách đáng tiếc nhé!
    Dismiss Notice
  3. Hướng dẫn chèn mã AutoIT trong diễn đàn

    Chào Khách. Vui lòng xem qua bài viết này
    Để biết cách chèn mã AutoIT trong diễn đàn bạn nhé :)
    Dismiss Notice

Hướng dẫn Tạo GUI hotkey với callback window

Thảo luận trong 'Hướng dẫn - Bài tập căn bản' bắt đầu bởi wuuyi123, 23/3/19.

  1. wuuyi123

    wuuyi123 Thành viên
    • 18/23

    Tham gia ngày:
    18/6/16
    Bài viết:
    54
    Đã được thích:
    98
    # About
    Trong post này, mình sẽ hướng dẫn chi tiết cách tạo hotkey bằng cách sử dụng callback window của GUI.

    Một vài ưu điểm của phương pháp này:
    - Chỉ thực thi khi GUI đang active
    - Không chiếm key như hàm HotKeySet
    - Không gặp lỗi khi sử dụng GUI on event với hàm GUISetAccelerators (cụ thể là khi dùng Opt("GUIOnEventMode", 1) thì message case trong GUI loop sẽ không thể sử dụng do hàm GUIGetMsg không có tác dụng, trừ khi dùng GUICtrlSetOnEvent).
    - Sử dụng callback nên chấp cả khi đang dính loop vẫn chạy (trừ Sleep)

    # Start code
    Đầu tiên là tạo một GUI đơn giản:

    Mã (AutoIt):

    GUICreate('Test')
    GUISetState()

    while (true)
        if (GUIGetMsg() == -3) then exit
    wend
     
    Để add một hàm vào callback của GUI, ta sử dụng hàm GUIRegisterMsg()
    - Tham số msgID là message window, chỉ cần search Google là có ngay, ta chỉ cần quan tâm đến hai message là WM_KEYUP (0x0101) và WM_KEYDOWN (0x0100).
    - Tham số thứ hai là tên một function, khi nào message kích hoạt thì hàm đó sẽ được gọi.

    Reg hàm vào callback message WM_KEYDOWN (thường thì các hotkey sẽ kích hoạt khi phím vừa ấn phím xuống, nên mình chọn keydown)

    Mã (AutoIt):

    GUIRegisterMsg(0x0100, '_OnKey')

    func _OnKey($hwnd, $msg, $wp, $lp)
        ; todo
    endfunc
     
    Hàm _OnKey phải theo cấu trúc như trên với 4 tham số
    - $hwnd là handle của GUI
    - $msg là số message gửi đến khi hàm này được gọi
    - $wp, $lp có thể hiểu là các tham số callback được biểu thị dưới dạng số nguyên

    $wp của message này sẽ là mã của phím (Virtual-Key code, tham khảo tại đây).
    Đối với các phím chữ cái thì mã này bằng với mã ASCII của kí tự đó khi viết in hoa.
    Chẳng hạn: Asc('A') == 0x41, Asc('H') == 0x48...

    Như vậy muốn khi nhấn phím A để thực thi một hàm gì đó thì chỉ cần match $wp và một keycode là xong.

    Mã (AutoIt):

    func _OnKey($hwnd, $msg, $wp, $lp)
        switch ($wp)
            case 0x41 ; key A
                MsgBox(0, '', 'A is pressed')
            case 0x42 ; key B
                ConsoleWrite('B is pressed' & @crlf)
        endswitch
    endfunc
     
    Thử nhấn phím AB xem nào. Nhưng khi nhấn giữ phím B thì hàm cứ liên tục gọi khá là khó chịu, sau đây là cách để giữ mà phím chỉ thực thi một lần duy nhất cho đến khi nhả phím ra.

    Reg tiếp hàm _OnKey vào message keyup và xóa code trong hàm đó, sửa lại như sau:

    Mã (AutoIt):

    ...
    GUIRegisterMsg(0x0100, '_OnKey') ; keydown
    GUIRegisterMsg(0x0101, '_OnKey') ; keyup

    func _OnKey($hwnd, $msg, $wp, $lp)
        local static $last = -1

        switch ($msg)
            case 0x0101

            case 0x0100

        endswicth
    endfunc
     
    $last là biến static nhằm mục đích ghi lại phím vừa nhấn xuống, gán là -1 vì các keycode đều là số nguyên dương.
    switch-case để phân hai message keyup - keydown.

    Trong case 0x0101 chỉ cần set $last trở về giá trị -1, tức là khi nhả phím ra thì sẽ reset lại key.

    Mã (AutoIt):

    ...
            case 0x0101
                $last = -1
     
    Tiếp đến case 0x0100 thì check, nếu $last khác với $wp (keycode) thì mới thực thi và gán $last là keycode, tức là phím chưa từng press sẽ đánh dấu là đã press, liên hệ với keyup thì khi nhả phím sẽ reset phím đó.

    Mã (AutoIt):

    ...
            case 0x0100
                if ($last <> $wp) then
                    $last = $wp
                    ; do something
                endif
     
    Vậy là xong, xóa dòng `; do something` và thay bằng code trước vào.

    Mã (AutoIt):

    ...
                    $last = $wp
                    ; do something...
                    switch ($wp)
                        case 0x41 ; key A
                            MsgBox(0, '', 'A is pressed')
                        case 0x42 ; key B
                            ConsoleWrite('B is pressed' & @crlf)
                    endswitch
    ...
     
    Thử nhấn giữ phím B xem nào, chỉ có một dòng được in ra console phải không?

    Tiếp theo là catch các phím chức năng (4 phím thường sử dụng là Alt, Ctrl, ShiftWin (phím lá cờ ấy)).
    Thêm một dòng sau vào hàm _OnKey (dưới phần khai báo $last lúc nãy). Ở đây mình chỉ hướng dẫn hai phím CtrlShift thôi nhé.

    Mã (AutoIt):

    ...
        local static $last = -1
        local static $_ctrl = 0, $_shift = 0
    ...
     
    Mục đích của dòng này là lưu lại phím chức năng đã nhấn hay chưa.

    Mã (AutoIt):

    ...
            case 0x101
                $last = -1
                switch ($wp)
                    case 0x11, 0xA2, 0xA3
                        $_ctrl = 0
                    case 0x10, 0xA0, 0xA1
                        $_shift = 0
                endswitch

            case 0x100
                switch ($wp)
                    case 0x11, 0xA2, 0xA3
                        $_ctrl = 1
                    case 0x10, 0xA0, 0xA1
                        $_shift = 1
                endswitch
    ...
     
    Khi keydown thì lưu lại phím chức năng đang được nhấn giữ, khi keyup sẽ reset nó.
    Ví dụ hokey Ctrl + A, khi nhấn hotkey này ta sẽ nhấn phím Ctrl trước, giữ nó và nhấn tiếp phím A thì $_ctrl chắc chắn sẽ có giá trị là 1 và khi nhấn A thì $wp sẽ bằng 0x41, match hai điều kiện này với nhau là xong.

    Để tiện cho việc test thì mình tạo thêm một hàm sau:

    Mã (AutoIt):

    func _OnPressed($key, $isCtrl, $isShift)
        switch ($key)
            case 0x41 ; key A
                if ($isCtrl) then MsgBox(0, '', 'Ctrl + A is pressed')
            case 0x42 ; key B
                if (isShift) then MsgBox(0, '', 'Shift + B is pressed')
        endswitch
    endfunc
     
    Thêm ref (set gọi) hàm này ở phần case keydown lúc nãy.

    Mã (AutoIt):

    ...
            case 0x100
                switch ($wp)
                    case 0x11, 0xA2, 0xA3
                        $_ctrl = 1
                    case 0x10, 0xA0, 0xA1
                        $_shift = 1
                    case else
                        if ($last <> $wp) then
                            $last = $wp
                            _OnPressed($last, $_ctrl, $_shift)
                        endif
                        $_ctrl = 0
                        $_shift = 0
                endswitch
    ...
     
    Vậy là xong, nhấn Ctrl + AShift + B xem!

    # Challenge
    • Với các phím chức năng còn lại như AltWin thì ta có các case vk code tương ứng như sau: Alt là (0x12, 0xA4, 0xA5) và Win là (0x5B, 0x5C). Thử thêm vào các biến static $_alt, $_win và hai tham số $isAlt, $isWin cho hàm _OnPressed xem.
    • Nên nhớ một điều là phải buông phím chức năng ra rồi nhấn lại mới có hiệu lực, bạn có cách nào giải quyết vấn đề này không? Tức là nhấn Ctrl + A, thì chỉ cần buông phím A ra và nhấn tiếp phím B (phím Ctrl vẫn còn giữ) thì sẽ được Ctrl + B.
    • Trong AutoIt có 2 loại GUI, một là tạo trực tiếp từ hàm GUICreate và hai là tạo từ WinAPI, mình chỉ hướng dẫn GUI au3 thôi, nếu bạn đọc kỹ sẽ bài này kết hợp với các bài về callback & gui winapi trước của mình thì sẽ làm được cho cả hai.
    Full code here:

    Mã (AutoIt):

    GUICreate('Test')
    GUISetState()

    GUIRegisterMsg(0x0100, '_OnKey')
    GUIRegisterMsg(0x0101, '_OnKey')

    while (true)
        if (GUIGetMsg() == -3) then exit
    wend

    func _OnKey($hwnd, $msg, $wp, $lp)
        local static $last = -1
        local static $_ctrl = 0, $_shift = 0

        switch ($msg)
            case 0x101
                $last = -1
                switch ($wp)
                    case 0x11, 0xA2, 0xA3
                        $_ctrl = 0
                    case 0x10, 0xA0, 0xA1
                        $_shift = 0
                endswitch

            case 0x100
                switch ($wp)
                    case 0x11, 0xA2, 0xA3
                        $_ctrl = 1
                    case 0x10, 0xA0, 0xA1
                        $_shift = 1
                    case else
                        if ($last <> $wp) then
                            $last = $wp
                            _OnPressed($last, $_ctrl, $_shift)
                        endif
                        $_ctrl = 0
                        $_shift = 0
                endswitch
        endswitch
    endfunc

    func _OnPressed($key, $isCtrl, $isShift)
        switch ($key)
            case 0x41 ; key A
                if ($isCtrl) then MsgBox(0, '', 'Ctrl + A is pressed')
            case 0x42 ; key B
                if ($isShift) then MsgBox(0, '', 'Shift + B is pressed')
            case 0x43 ; key C
                MsgBox(0, '', 'C is pressed')
        endswitch
    endfunc
     
     
    Chỉnh sửa cuối: 24/3/19
  2. yutijang

    yutijang Thành viên năng động
    • 28/34

    Tham gia ngày:
    1/7/18
    Bài viết:
    110
    Đã được thích:
    61
    Cho mình hỏi thăm,
    Bạn có nhắc tới "- Không gặp lỗi khi sử dụng GUI on event với hàm GUISetAccelerators"
    Bạn có thể nói cho mình (mọi người) biết lỗi đó cụ thể là lỗi gì không?

    Xin cám ơn!
     
  3. wuuyi123

    wuuyi123 Thành viên
    • 18/23

    Tham gia ngày:
    18/6/16
    Bài viết:
    54
    Đã được thích:
    98
    'GUI on event' tức là gui không sử dụng vòng lặp để catch message, khi sử dụng Opt('GUIOnEventMode', 1) thì sẽ bật chức năng này lên. Còn hàm GUISetAccelerators sẽ kích hoặt case message trong vòng lặp của GUI nên khi 'GUI on event' sẽ không có tác dụng.

    Test thử code sau, copy và run thử, nhấn Ctrl + A sẽ thấy hộp thoại xuất hiện.
    Sau đó xóa chỗ chấm phẩy (uncomment) và run sẽ thấy sự khác biệt.

    Mã (AutoIt):

    $hGUI = GUICreate('Test')
    $btn = GUICtrlCreateButton('Test', 20, 20, 80, 30)

    local $aAccelKeys[1][2] = [["^a", $btn]]
    GUISetAccelerators($aAccelKeys)

    GUISetState()

    ;Opt('GUIOnEventMode', 1)

    ; fix for on exit
    ;GUISetOnEvent(-3, _exit)
    ;func _exit()
    ;    exit
    ;endfunc

    while (true)
        switch (GUIGetMsg())
            case -3
                exit
            case $btn
                MsgBox(0, '', 'Test button is clicked!')
        endswitch
    wend
     
     
    yutijang thích bài này.
  4. yutijang

    yutijang Thành viên năng động
    • 28/34

    Tham gia ngày:
    1/7/18
    Bài viết:
    110
    Đã được thích:
    61
    Cám ơn bạn đã trả lời!

    Mình biết 'GUI on event' là gì, cái mình hỏi là: "- Không gặp lỗi khi sử dụng GUI on event với hàm GUISetAccelerators" thì lỗi đó là lỗi gì?

    Và bạn có trả lời là "sẽ không có tác dụng" khi 'GUI on event'.
    Nhưng mình vẫn dùng được GUISetAccelerators() với GUIOnEventMode mà.

    Mình kết hợp với GUICtrlCreateDummy(), GUICtrlSetOnEvent() và @GUI_CtrlId vẫn có thể sử dụng GUISetAccelerators() với GUIOnEventMode
     
  5. wuuyi123

    wuuyi123 Thành viên
    • 18/23

    Tham gia ngày:
    18/6/16
    Bài viết:
    54
    Đã được thích:
    98
    Haizz, iêm nói lỗi ở trên có lẽ do nhầm với cái project CEF năm ngoái. Còn phần test lúc nãy không có đề cập đến GUICtrlSetOnEvent. Hàm này càng không liên quan gì với những gì iêm nói.

    Khi dùng Opt GUIOnEventMode thì hàm GUIGetMsg() sẽ không lấy được message, suy ra switch-case để match button click là không thể. Còn GUICtrlSetOnEvent() sẽ sử dụng callback để catch khi nào button click sẽ gọi hàm.
     
    yutijang thích bài này.
  6. yutijang

    yutijang Thành viên năng động
    • 28/34

    Tham gia ngày:
    1/7/18
    Bài viết:
    110
    Đã được thích:
    61
    Mình chỉ thắc mắc gạch đầu dòng thứ 3 trong phần ưu điểm mà bạn viết ở bài hướng dẫn thôi. Chứ không có ý gì.

    Vậy thì có thể dùng kết hợp bất cứ gì miễn đạt mục đích là được :).

    Cám ơn bạn đã chia sẻ, cám ơn tinh thần chia sẻ của bạn.

    Hướng dẫn rất hay và hữu ích, mình sẽ đổi qua dùng cách của bạn vì nó đơn giản hơn cách mình đang dùng.
     
  7. Tungtata

    Tungtata Tà tà mà sống ~ Thành viên BQT Administrator
    • 93/113

    Tham gia ngày:
    25/8/15
    Bài viết:
    289
    Đã được thích:
    924
    Nơi ở:
    Hà Nội
    cảm ơn chia sẻ của bạn, rất hay
    Tuy nhiên như bạn bên trên có nói mình vẫn sử dụng GUISetAccelerators trong GUIOnEvent đc
     
  8. wuuyi123

    wuuyi123 Thành viên
    • 18/23

    Tham gia ngày:
    18/6/16
    Bài viết:
    54
    Đã được thích:
    98
    Right, nhưng chỉ sử dụng được khi kèm với GuiCtrlSetOnEvent thôi, iêm đã đính chính lại dòng đó.
     
  9. #Rainy# Hoàng Vũ IT

    #Rainy# Hoàng Vũ IT Hoàng Vũ IT Moderator
    • 43/45

    Tham gia ngày:
    21/11/16
    Bài viết:
    404
    Đã được thích:
    122
    Có 1 vài lỗi nhẹ như.
    1. Khi ấn Ctrl+a xong active GUI lên ấn Ctrl+A sẽ k có tác dụng(1), ấn thêm lần nữa có tác dụng(2)
    Lỗi trên tức là lần (1) sẽ chạy tác nhiệm nhả phím nên lần 2 bấm mới có tác dụng.
    2. Sao khi ấn Ctrl+A vẫn k thể tắt GUI chính đi đc nhỉ, theo đúng nguyên lí là phải được chứ ta.
    K biết là lỗi hay do máy
     
  10. wuuyi123

    wuuyi123 Thành viên
    • 18/23

    Tham gia ngày:
    18/6/16
    Bài viết:
    54
    Đã được thích:
    98
    1. Cái này mình đã nói trong phần Challenge rồi đấy, có rất nhiều cách để fix chỗ này nên mình mới cho vào đấy, gợi ý đơn giản nhất là sử dụng _IsPressed() để catch phím chức năng.

    2. Có thể là bạn sử dụng code ở trên, tức là Ctrl+A sẽ tạo ra hộp thoại MsgBox, nếu chưa đóng nó thì không thoát được chương trình.
     
  11. wuuyi123

    wuuyi123 Thành viên
    • 18/23

    Tham gia ngày:
    18/6/16
    Bài viết:
    54
    Đã được thích:
    98
    Mình xin hướng dẫn thêm 1 cách khá hay để solve Challenge này.
    Đọc kỹ trên MSDN sẽ thấy khi lấy bit (từ $wp - wParam) trong khoảng dưới 16 (tức là convert nó về số nguyên 16bit (short) thì sẽ check được key có đang repeat hay không). Sau đó hợp số đó với KF_REPEAT (0x4000) sẽ được kết quả logic (là 0 nếu press lần đầu tiên).

    Sử dụng hàm _HiWord() để convert về 16bit. Ngoài ra còn có thể dùng dllstruct để viết lại hàm này trên AutoIt theo bản gốc.

    Mã (AutoIt):

    func _HiWord ($p)
        return bitShift(bitAND($p, 0xffff0000), 16)
    endfunc
     
    Mã (AutoIt):

    func _OnKey($hwnd, $msg, $wp, $lp)
        local static $_ctrl = 0, $_shift = 0

        switch ($msg)
            case 0x101
                switch ($wp)
                    case 0x11, 0xA2, 0xA3
                        $_ctrl = 0
                    case 0x10, 0xA0, 0xA1
                        $_shift = 0
                endswitch

            case 0x100
                switch ($wp)
                    case 0x11, 0xA2, 0xA3
                        $_ctrl = 1
                    case 0x10, 0xA0, 0xA1
                        $_shift = 1
                endswitch

                if (BitAND(_HiWord($lp), 0x4000) == 0) then
                    _OnPressed($wp, $_ctrl, $_shift)
                endif

        endswitch
    endfunc
     
     
    Chỉnh sửa cuối: 25/3/19
    Huân Hoàng and Tungtata like this.

Chia sẻ trang này

Đang tải...