Chương 4: Thực thi chương trình

Yukino Ichikawa

Thành viên
Như vậy chúng ta đã bước qua giai đoạn sơ cấp, kể từ chương này chúng ta sẽ bước vào giai đoạn trung cấp.
Nội dung của chương này bao gồm:
4.1 Điểm chú ý trong khi tham khảo Module đi kèm tập sách này
4.2 Tránh khởi động nhiều ứng dụng
4.3 Chờ để thực thi chương trình cho tới khi ứng dụng kết thúc
4.4 Gửi đi tin nhắn không thông qua hàng đợi tin nhắn
4.5 Lấy Class/Caption của Window
4.6 Ưu tiên hiển thị cho Window
4.7 Thực thi chương trình liên quan tới cửa sổ con (chưa rõ, sẽ đính chính lại)
4.8 Thao tác với DialogBox của File Property.
 

vanthanhVBA

Thành viên
4.1 Điểm chú ý trong khi tham khảo Module đi kèm tập sách này
Do đặc điểm là các bài viết trên diễn đàn, vì vậy chúng tôi sẽ không cung cấp ngay cho các bạn các file ví dụ mẫu. Thay vào đó, nói tới ví dụ nào thì diễn đàn sẽ cung cấp file ví dụ đó.

Ngoài ra, chúng ta có một quy ước riêng, các hàm API sẽ được khai báo ở Module0. Từ Module1 trở đi sẽ chỉ đơn thuần viết code VBA. Do đó nếu bạn chỉ xem từ Module1 trở đi thì sẽ không thấy các khai báo hàm API, xin hãy lưu ý điều này.
Ở bất cứ Module nào cũng có thể gọi các hàm API vì vậy chúng ta sẽ khai báo chúng dưới dạng Public.

Về xử lý lỗi: Có rất nhiều ví dụ mà chúng ta sẽ đề cập tới, nhắm tới mục đích giới thiệu hàm API là chính, do đó code sẽ viết dưới dạng đơn giản nhất có thể, về cơ bản sẽ không có các xử lý lỗi. Thay vào đó, khi thực thi hàm API sẽ hiển thị giá trị trả về cho biết việc thực thi thành công hay là không. Dựa vào giá trị trả về đó, nếu bạn muốn viết code cho phần xử lý lỗi, xin hãy tự phân chia thành các trường hợp.

Về Win64 với Win32: Nội dung của cuốn sách này sẽ tập trung vào code của Win64 API. Về code của Win 32 API sẽ có một bảng liệt kê mà chúng tôi sẽ cung cấp, bạn có thể tham khảo sau.
Ngoài ra, chúng tôi cũng đã thuyết minh về sự khác biệt giữa code của Win 64 API và Win 32 API, các bạn hãy ôn tập lại.

Win 32 API không có từ khóa PtrSafe khi khai báo trong câu lệnh Declare.
Kiểu giá trị trả về của hàm hay kiểu dữ liệu của đối số nếu là LongPtr thì trong Win 32 API sẽ chuyển thành Long.
 

tuhocvba

Administrator
Thành viên BQT
4.2 Tránh khởi động nhiều ứng dụng
Nếu Notepad chưa được mở thì khởi động mở Notepad

Trường hợp nếu chúng ta dùng hàm Shell để khởi động mở Notepad thì ta sẽ không phán đoán được là Notepad đã đang được mở hay chưa, như vậy mỗi lần thực thi chương trình thì lại có một cửa sổ Notepad mới mở ra.
Với Windows, khi khởi động một ứng dụng (application) nào đó thì nó sẽ tạo ra window cho application đó. Ta gọi là application window.
Bạn cần đăng nhập để thấy đính kèm

Tất cả các appication window thuộc về class.
Nói tóm lại nếu như có một hàm API dựa trên class này và trả về handle (hiểu tượng trưng tương tự như mã số chưng minh dân) của application thì dựa trên giá trị trả về này ta có thể viết lệnh điều kiện và phòng tránh được việc cùng một ứng dụng nhưng lại được mở ra quá nhiều cửa sổ. (Ví dụ: cửa sổ notepad1, cửa sổ notepad2,...).
Trong ví dụ dưới đây, chúng ta sẽ khởi động notepad nếu nó chưa được mở.
Trước hết hãy xem ví dụ WIN 64API
Module0:
'----------------------------------------------------------------------
'Dua ra class name hoac caption va nhan ve handle cua window
'
'Gia tri tra ve: Thanh cong = lay duoc handle window
'           That bai = NULL
'----------------------------------------------------------------------
Declare PtrSafe Function FindWindow Lib "user32" _
      Alias "FindWindowA" _
      (ByVal lpClassName As String, _
      ByVal lpWindowName As String) As LongPtr
Module1:
'**********************************
'Phong tranh mo nhieu cua so ung dung
'**********************************

Sub FindWindow_Sample()
    Dim strClassName As String  'Class name
    Dim rc As LongPtr
   
    Dim lngProcessID As Long    'Gia tri tra ve cua ham Shell
   
    'Chi dinh class name
    strClassName = "Notepad"
       
    'Lay handle cua Notepad window
    rc = FindWindow(strClassName, _
                    vbNullString)
                   
                   
    'Neu notepad dang duoc mo thi se khong khoi dong notepad
    If rc <> 0& Then
        MsgBox "Notepad dang duoc mo"
        Exit Sub
    End If
   
    'Khoi dong mo Notepad
    lngProcessID = Shell("Notepad.exe", vbNormalFocus)
   
    'Neu khong khoi dong duoc Notepad thi hay su dung doan code duoi day.
'Tuy vao tung OS (he dieu hanh may tinh) ma duong dan duoi day co the se khac,
'truong hop do ban can thay doi lai duong dan file exe trong code nay cho phu hop
    'lngProcessID = Shell("C:\Windows\Notepad.exe", vbNormalFocus)
End Sub
(Còn nữa)
 

Euler

Mod
Thành viên BQT
4.2 Tránh khởi động nhiều ứng dụng
Nếu Notepad chưa được mở thì khởi động mở Notepad

Sau đây ta sẽ viết code cho Win 32 API:
Những nơi khai báo là LongPtr thì chuyển thành Long. Từ khóa Ptrsafe nếu có thì bỏ đi.
Module0:
'----------------------------------------------------------------------
'Dua ra class name hoac caption va nhan ve handle cua window
'
'Gia tri tra ve: Thanh cong = lay duoc handle window
'           That bai = NULL
'----------------------------------------------------------------------
Declare Function FindWindow Lib "user32" _
      Alias "FindWindowA" _
      (ByVal lpClassName As String, _
      ByVal lpWindowName As String) As Long
Module1:
'**********************************
'Phong tranh mo nhieu cua so ung dung
'**********************************

Sub FindWindow_Sample()
    Dim strClassName As String  'Class name
    Dim rc As Long
 
    Dim lngProcessID As Long    'Gia tri tra ve cua ham Shell
 
    'Chi dinh class name
    strClassName = "Notepad"
     
    'Lay handle cua Notepad window
    rc = FindWindow(strClassName, _
                    vbNullString)
                 
                 
    'Neu notepad dang duoc mo thi se khong khoi dong notepad
    If rc <> 0& Then
        MsgBox "Notepad dang duoc mo"
        Exit Sub
    End If
 
    'Khoi dong mo Notepad
    lngProcessID = Shell("Notepad.exe", vbNormalFocus)
 
    'Neu khong khoi dong duoc Notepad thi hay su dung doan code duoi day.
'Tuy vao tung OS (he dieu hanh may tinh) ma duong dan duoi day co the se khac,
'truong hop do ban can thay doi lai duong dan file exe trong code nay cho phu hop
    'lngProcessID = Shell("C:\Windows\Notepad.exe", vbNormalFocus)
End Sub
Sau đây, chúng ta cùng điểm qua một số ứng dụng phổ biến trên Windows:
Application WindowsClass
NotePadNotePad
PaintMSPaintApp
WordPadWordPadClass
ExcelXLMAIN
WordOpusApp
Outlookrctrl_renwnd32
PowerPointPPTFrameClass
Ở đây tôi không ghi Acess, calculator, hay muốn khởi động userform trên Excel thì làm thế nào. Tôi sẽ cung cấp danh sách đầy đủ này sau.

Ghi chú:
Từ Excel 2002 trở đi, thông qua thuộc tính Hwnd có thể lấy được Handler của Application Windows.
Ở ví dụ trên chúng ta đã lấy handler của NotePad. Đối với Excel, đó là ứng dụng chúng ta đang chạy code, cho nên class của nó là XLMAIN. Vì vậy, ta có thể lấy được handler của Excel như sau:
Mã:
rc = FindWindow("XLMAIN", vbNullString)
Thực tế, các phiên bản Excel cũ hơn cho tới Excel 2000, người ta lấy handler bằng phương pháp này. Nhưng từ Excel 2002 trở đi, chúng ta có thể dùng thuộc tính Hwnd để lấy handler của Application.
Mã:
rc = Application.Hwnd
Đối với Visual Basic, từ xưa tới giờ vẫn có thể sử dụng thuộc tính Hwnd nhưng không thể nói la nó tốt hơn cách sử dụng hàm FindWindow.
Nếu sử dụng thuộc tính Hwnd, từ Excel 2000 trở về trước (cũ hơn), chương trình sẽ bị lỗi.
Đối với việc sử dụng hàm API trong VBA, không nên dùng thuộc tính Hwnd, nên sử dụng hàm API FindWindow.
File demo:
 

tuhocvba

Administrator
Thành viên BQT
4.3 Chờ để thực thi chương trình cho tới khi ứng dụng kết thúc
Chương trình tạm dừng cho tới khi NotePad kết thúc

Trong trường hợp chúng ta dùng hàm Shell để khởi động một Application (ví dụ NotePad), dù cho Application này chưa kết thúc thì chương trình VBA vẫn tiếp tục được thực thi. Điều đó có thể nói là việc thực thi như vậy là không đồng bộ, bởi vì như đã nói ở chương 2, Windows thực thi các chương trình không đồng thời cùng lúc.
Ở ví dụ tiếp theo, chúng ta suy nghĩ làm sao để chương trình VBA sẽ tạm dừng không thực thi các câu lệnh tiếp theo cho tới khi hàm Shell khởi động xong NotePad.
Win64-Module2:
'Ham tra ve handler cua process object
'gia tri tra ve:
'thanh cong = handler cua process
'that bai = NULL
Declare PtrSafe Function OpenProcess Lib "kernel32" _
    (ByVal dwDesiredAccess As Long, _
    ByVal InheriHandle As Long, _
    ByVal dwProcessId As Long) As LongPtr 'Win64: LongPtr

Public Const STANDARD_RIGHTS_REQUIRED = &HF0000
Public Const SYNCHRONIZE = &H100000
Public Const PROCESS_TERMINATE = &H1&
Public Const PROCESS_CREATE_THREAD = &H2&
Public Const PROCESS_VM_OPERATION = &H8&
Public Const PROCESS_VM_READ = &H10&
Public Const PROCESS_VM_WRITE = &H206&
Public Const PROCESS_DUP_HANDLE = &H40&
Public Const PROCESS_CREATE_PROCESS = &H80&
Public Const PROCESS_SET_INFORMATION = &H200&
Public Const PROCESS_QUERY_INFORMATION = &H400&
Public Const PROCESS_ALL_ACCESS = (STANDARD_RIGHTS_REQUIRED Or SYNCHRONIZE Or &HFFF)

'Ham tra ve trang thai ket thuc process
'Gia tri tra ve
'Thanh cong = khac 0
'That bai = 0

Declare PtrSafe Function GetExitCodeProcess Lib "kernel32" _
    (ByVal hProcess As LongPtr, _
    lpExitCode As Long) As LongPtr 'Win64: LongPtr
'Phan doan process ket thuc hay chua
'Neu chua ket thuc se cat STILL_ACTIVE
Public Const STATUS_PENDING = &H103&
Public Const STILL_ACTIVE = STATUS_PENDING
'Ham close process cua tien trinh dang duoc mo
'gia tri tra ve
'thanh cong = khac 0
'that bai = 0
Declare PtrSafe Function CloseHandle Lib "kernel32" _
    (ByVal hObject As LongPtr) As LongPtr

'=======================================
'vi du ve viec tam dung chuong trinh vba cho cho ham Shell ket thuc
'
'
'
'
'=======================================

Sub GetExitCodeProcess_Sample()
    Dim lngProcessID As Long 'Gia tri tra ve cua ham Shell
    Dim lngProcess      As LongPtr 'Gia tri tra ve cua ham OpenProcess 'Win64: LongPtr
    Dim lngExitCode     As Long 'Code ket thuc
    Dim rc              As LongPtr 'Win64: LongPtr
    MsgBox "Sau khi xac nhan NotePad da duoc mo, hay dong NotePad"
    
    'Khoi dong NotePad
    lngProcessID = Shell("Notepad.exe", vbNormalFocus)
    
    'Lay handler cua process object cua ung dung duoc mo boi ham Shell
    lngProcess = OpenProcess(PROCESS_QUERY_INFORMATION, _
                                1, _
                                lngProcessID)
    'GetExitCodeProcess se lay trang thai ket thuc cua process
    'trong khi process chua ket thuc thi thuc hien DoEvents de cap nhat OS
    Do
        rc = GetExitCodeProcess(lngProcess, lngExitCode)
        DoEvents
    Loop While lngExitCode = STILL_ACTIVE
    'Close handler object dang duoc mo
    rc = CloseHandle(lngProcess)
    
    MsgBox "NotePad da ket thuc"

    
End Sub
File demo :
 

Euler

Mod
Thành viên BQT
Các bạn có thể thay thế code của Module2 bằng code dưới đây để có thể chạy được cả với Win32 (và Win64):
Mã:
'Ham tra ve handler cua process object
'gia tri tra ve:
'thanh cong = handler cua process
'that bai = NULL
#If Win64 Then
    Declare PtrSafe Function OpenProcess Lib "kernel32" _
    (ByVal dwDesiredAccess As Long, _
    ByVal InheriHandle As Long, _
    ByVal dwProcessId As Long) As LongPtr 'Win64: LongPtr
#Else
    Declare Function OpenProcess Lib "kernel32" _
    (ByVal dwDesiredAccess As Long, _
    ByVal InheriHandle As Long, _
    ByVal dwProcessId As Long) As Long
#End If

Public Const STANDARD_RIGHTS_REQUIRED = &HF0000
Public Const SYNCHRONIZE = &H100000
Public Const PROCESS_TERMINATE = &H1&
Public Const PROCESS_CREATE_THREAD = &H2&
Public Const PROCESS_VM_OPERATION = &H8&
Public Const PROCESS_VM_READ = &H10&
Public Const PROCESS_VM_WRITE = &H206&
Public Const PROCESS_DUP_HANDLE = &H40&
Public Const PROCESS_CREATE_PROCESS = &H80&
Public Const PROCESS_SET_INFORMATION = &H200&
Public Const PROCESS_QUERY_INFORMATION = &H400&
Public Const PROCESS_ALL_ACCESS = (STANDARD_RIGHTS_REQUIRED Or SYNCHRONIZE Or &HFFF)

'Ham tra ve trang thai ket thuc process
'Gia tri tra ve
'Thanh cong = khac 0
'That bai = 0
#If Win64 Then
    Declare PtrSafe Function GetExitCodeProcess Lib "kernel32" _
    (ByVal hProcess As LongPtr, _
    lpExitCode As Long) As LongPtr 'Win64: LongPtr
#Else
    Declare Function GetExitCodeProcess Lib "kernel32" _
    (ByVal hProcess As Long, _
    lpExitCode As Long) As Long
#End If
'Phan doan process ket thuc hay chua
'Neu chua ket thuc se cat STILL_ACTIVE
Public Const STATUS_PENDING = &H103&
Public Const STILL_ACTIVE = STATUS_PENDING
'Ham close process cua tien trinh dang duoc mo
'gia tri tra ve
'thanh cong = khac 0
'that bai = 0
#If Win64 Then
    Declare PtrSafe Function CloseHandle Lib "kernel32" _
    (ByVal hObject As LongPtr) As LongPtr
#Else
    Declare Function CloseHandle Lib "kernel32" _
    (ByVal hObject As Long) As Long
#End If

'=======================================
'vi du ve viec tam dung chuong trinh vba cho cho ham Shell ket thuc
'
'
'
'
'=======================================

Sub GetExitCodeProcess_Sample()
    Dim lngProcessID As Long 'Gia tri tra ve cua ham Shell
    #If Win64 Then
        Dim lngProcess      As LongPtr 'Gia tri tra ve cua ham OpenProcess 'Win64: LongPtr
        Dim rc              As LongPtr 'Win64: LongPtr
    #Else
        Dim lngProcess      As Long
        Dim rc              As Long
    #End If
    Dim lngExitCode     As Long 'Code ket thuc
    
    MsgBox "Sau khi xac nhan NotePad da duoc mo, hay dong NotePad"
    
    'Khoi dong NotePad
    lngProcessID = Shell("Notepad.exe", vbNormalFocus)
    
    'Lay handler cua process object cua ung dung duoc mo boi ham Shell
    lngProcess = OpenProcess(PROCESS_QUERY_INFORMATION, _
                                1, _
                                lngProcessID)
    'GetExitCodeProcess se lay trang thai ket thuc cua process
    'trong khi process chua ket thuc thi thuc hien DoEvents de cap nhat OS
    Do
        rc = GetExitCodeProcess(lngProcess, lngExitCode)
        DoEvents
    Loop While lngExitCode = STILL_ACTIVE
    'Close handler object dang duoc mo
    rc = CloseHandle(lngProcess)
    
    MsgBox "NotePad da ket thuc"

    
End Sub
 

giaiphapvba

Administrator
Thành viên BQT
4.4 Gửi đi tin nhắn không thông qua hàng đợi tin nhắn
Để tin nhắn được gửi đi mà không thông qua hàng đợi, chúng ta sẽ sử dụng hàm SendMessager. Ở ví dụ lần này, tôi sẽ dùng hàm này để gửi đi thông báo cho hệ điều hành windows đóng ứng dụng Notepad.
Module3:
'Gui messeger toi window duoc chi dinh
'Gia tri tra ve: Tuy thuoc vao kieu messeger ma gia tri tra ve khac nhau
#If Win64 Then
    Declare PtrSafe Function SendMessager Lib "user32" _
    Alias "SendMessageA" _
    (ByVal hwnd As LongPtr, _
    ByVal wMsg As Long, _
    ByVal wPara As Long, _
    lParam As Any) As LongPtr
#Else
    Declare Function SendMessager Lib "user32" _
    Alias "SendMessageA" _
    (ByVal hwnd As Long, _
    ByVal wMsg As Long, _
    ByVal wPara As Long, _
    lParam As Any) As Long
#End If
'Messeger duoc gui toi window (cai ma system menu duoc thao tac)
Public Const WM_SYSCOMMAND = &H112
'Ket thuc window
Public Const SC_CLOSE = &HF060&
'That bai
Public Const ERROR_SUCCESS = 0&

Sub SendMessage_Sample()
    #If Win64 Then
        Dim hwnd As LongPtr
        Dim rc   As LongPtr
        
    #Else
        Dim hwnd As Long
        Dim rc   As Long
    #End If
    'Lay handler cua window notepad
    hwnd = FindWindow("Notepad", vbNullString)
    
    If hwnd = ERROR_SUCCESS Then
        MsgBox "Notepad chua duoc khoi dong", vbExclamation
    End If
    
    rc = SendMessager(hwnd, WM_SYSCOMMAND, SC_CLOSE, 0)
    
End Sub
Để chạy thử code, trước hết bạn cần mở Notepad, sau đó chạy code trên, chương trình sẽ gửi đi thông báo đóng Notepad, hệ điều hành sẽ thực thi đóng Notepad.
File demo:
(Còn nữa)
 
Top