# Khoá học: Lập trình Macros trên Excel với VBA
:::info
Khoá học sử dụng 2 nguồn tài liệu chính:
- Sách **Excel VBA Programming (6th Edition)** của tác giả **Dick Kusleika** (Nhà xuất bản Wiley - For Dummies)
- Sách **VBA Automation for Excel 2019 Cookbook** của tác giả **Mike Van Niekerk** (Nhà xuất bản Packt)
Trong **Mô-đun 1: Cơ bản về Macros và VBA**, học viên sẽ nắm được các nội dung kiến thức sau:
- Cách ghi (record) một Macro.
- Làm quen với giao diện VBA Editor.
- Lưu sổ tính dưới dạng `.xlsm`
- Lựa chọn Use Relative References
- Excel VBA Object Model và lập trình hướng đối tượng
- Gán Macros
- Subs và Functions trong VBA
Học viên có thể truy cập để lấy bài tập khoá học [tại link này](https://drive.google.com/drive/folders/1-MYze3FvRT2j6Vm2Du2kl7dWUhBPeibT?usp=sharing).
:::
## Giao diện Developer
- Bật developer tab: Excel Options --> Customize Ribbon --> tích chọn **Developer**

:::warning
Khi mở một file Excel có macro (đuôi `.xlsm`), phần macro được tắt đi (mặc định), để cho phép macro được chạy, ta cần [Enable Content]. Tương tự, khi lưu một file có chứa macros, ta cũng phải chọn đúng định dạng.
Tuy nhiên, cũng có thể thay đổi tuỳ chọn này trong phần **Trust Center**

Học viên hãy thử tải về file `01_VBA_Code.xlsm` trong thư mục `Module 1` để thử nghiệm.
:::
Tab **Developer** được chia làm các nhóm chính như sau:
- Code group
- **Macros**: hiện ra các macro (hoặc subroutine) hiện có
- Nút Run
- Nut Edit (sẽ mở Editor)
- **Record Macro**: ghi macro mới (Record Macro dialog box)
:::info
Hướng dẫn tạo mới một macro:

- Chọn range `C1:F2`, giữ `Ctrl` và chọn thêm range `A3:B7:`
- Cho in đậm vùng vừa được khối
- Tiếp tục, chọn range `C3:F8`
- Chuyển sang định dạng tiền tệ (currency) đô la Mỹ
- Stop Recording
- Mở macro vừa ghi trong VBA Editor và đọc
- Chạy thử macro đó với 2 bảng tính còn lại (Macros --> Chọn --> Run hoặc bằng phím tắt đã gán trước)
- Dòng cuối cùng của đoạn code VBA ứng với macro, hãy thử thay đổi.
:::
- **Visual Basic** (VBA Editor) có thể mở nhanh bằng `Alt + F11`

- **Relative References**: làm rõ ở phần sau
- Lưu sổ tính có chứa Macros

- Macro Security



- Gán Macro lên Ribbon (xem bài tập số 1)
### Bài tập số 1
:::info
Học viên vui lòng truy cập vào [thư mục bài tập](https://drive.google.com/drive/folders/1-MYze3FvRT2j6Vm2Du2kl7dWUhBPeibT?usp=sharing) của mô-đun này để lấy bài tập.
:::
Học viên tìm và tải file **`02_RecordMacro.xlsx`** và thực hiện theo các yêu cầu sau:
- Tái hiện toàn bộ các yêu cầu trong khung **Hướng dẫn tạo mới một macro**.
- Học viên gán Macro vừa tạo lên Ribbon theo hướng dẫn sau.
- Mở **Customize the Ribbon** (Chuột phải vào vùng Ribbon hoặc vào File --> Options --> Customize Ribbon.)
- New Tab --> New Group --> (tên tab và tên group theo ý của bạn)
- Ở mục *Choose commands from*, hãy chọn *Macros* và chọn đến tên macro bạn đã tạo, thêm vào group vừa tạo và chọn Icon
- Kết quả sẽ trông như sau:

- Lúc này hãy thử chạy macro từ ribbon
:::success
Sau khi hoàn thành, học viên lưu lại bài làm và nộp lại (nếu được yêu cầu).
:::
## VBE (Visual Basic Editor)
:::info
Việc đóng và mở VBE có thể thực hiện bằng tổ hợp phím `Alt + F11` hoặc Developer --> Visual Basic.
Project Explorer (mở nhanh bằng `Ctrl + R`) và Toolbar nên được mở, có thể vào View để điều chỉnh. Ngoài ra có thể mở thêm Properties window, Intermedia window, Code window (Code pane)...
:::
### Các thành phần chính

### Tạo module để lưu trữ sub procedure
Mặc định chỉ có 2 Microsoft Excel Objects nhìn thấy được với một sổ tính mới: `Sheet1` và `ThisWorkbook`.

:::info
Hãy thử dán đoạn code này vào module vừa tạo:
```vb=
Sub Hello()
MsgBox "Xin chao"
End Sub
```
Quay lại Excel và chạy thử
:::
:::success
Như vậy, ta có 2 cách để tạo ra một macro mới:
1. Dùng chức năng Record macro (Excel tự tạo module)
2. Insert --> Module
Ngoài ra, bạn có thể copy code từ nơi khác và dán vào để chạy.
:::
Bạn có thể tuỳ chỉnh VBE bằng Tools --> Options (nói thêm ở phần sau)
:::warning
Thêm về Explorer:
- Microsoft Excel Objects: These are modules that are linked to user interface elements like workbooks and worksheets.
- Modules: Excel calls these simply modules, but everyone else refers to them as standard modules or VBA modules. These contain the code that doesn’t go in any of the other three module types.
- Forms: These are modules that have a form user-interface.
- Class Modules: These are modules where you create your own objects.

:::
### Bài tập số 2
:::info
Học viên vui lòng truy cập vào [thư mục bài tập](https://drive.google.com/drive/folders/1-MYze3FvRT2j6Vm2Du2kl7dWUhBPeibT?usp=sharing) của mô-đun này để lấy bài tập.
:::
Học viên tạo một sổ tính mới và thực hiện theo các yêu cầu sau:
- Hãy tạo ra một module mới và thêm vào đoạn mã nguồn VBA dưới đây này
```vb=
Sub Ten_Ban_La_Gi()
Msg = "Ten Ban La " & Application.UserName & " Dung Khong?"
Ans = MsgBox(Msg, vbYesNo)
If Ans = vbNo Then MsgBox "Oh, Khong Sao."
If Ans = vbYes Then MsgBox "Xin Chao!"
End Sub
```
- Hãy thử chạy macro và đoán xem đoạn mã nguồn trên làm những việc gì? (chạy trong VBE (phím `F5`) hoặc từ Excel)
:::success
Sau khi hoàn thành, học viên lưu lại bài làm và nộp lại (nếu được yêu cầu).
:::
### Tuỳ chỉnh VBE
#### Thẻ Editor

- **Auto Syntax Check**: The Auto Syntax Check setting determines whether the VBE pops up a dialog box if it discovers a syntax error while you’re entering your VBA code. The dialog box tells roughly what the problem is. If you don’t choose this setting, the VBE flags syntax errors by displaying them in a different color from the rest of the code, and you don’t have to deal with any dialog boxes popping up on your screen.
- **Require Variable Declaration**: If the Require Variable Declaration option is set, VBE inserts the following statement at the beginning of each new VBA module you insert:
```vb
Option Explicit
```
- **Auto List Members** : If the Auto List Members option is set, the VBE provides some help when you’re entering your VBA code. It displays a list that would logically complete the statement you’re typing. This bit of magic is sometimes called IntelliSense.

- **Auto Quick Info** : If the Auto Quick Info option is set, the VBE displays information about functions and their arguments as you type, which can be very helpful. The figure shows this feature in action, telling you about the arguments for the MsgBox function.

- **Auto Data Tips**: If the Auto Data Tips option is set, the VBE displays the value of the variable over which your cursor is placed when you’re debugging code. When you enter the wonderful world of debugging, you’ll appreciate this option.
- **Auto Indent**: The Auto Indent setting determines whether the VBE automatically indents each new line of code the same as the previous line.
- **Tab Width**: Determines how wide your tab characters are in the equivalent number of spaces.
- **Drag-and-Drop Text Editing**: The Drag-and-Drop Text Editing option, when enabled, lets you move or copy and paste text by dragging and dropping with your mouse.
- **Default to Full Module View**: The Default to Full Module View option sets the default state for new modules. (It doesn’t affect existing modules.) If this option is set, procedures in the code pane appear as a single scrollable list. If this option is turned off, you can see only one procedure at a time.
- **Procedure Separator**: Separator bars appear between procedures in a code pane.
#### Thẻ Editor Format

#### Thẻ General

The most important setting is **Error Trapping**. It’s considered a best practice not to use the **Break on All Errors** setting. If you use this setting, your error-handling code won’t work.
#### Thẻ Docking

## The Excel VBA Object Model
:::success
Microsoft đã định nghĩa trước hàng trăm đối tượng khác nhau mà bạn có thể sử dụng, chúng là các thành phần của giao diện người dùng Excel, như Workbook, Worksheet hoặc PivotTable.
:::

Thử nhập một giá trị bất kỳ cho ô `A1`, như vậy ta có thể truy cập vào giá trị này thông qua việc sử dụng dấu `.` trên các đối tượng trên cây như sau:
```vb=
Application.Workbooks("Book1.xlsx").Worksheets(1).Range("A1").Value
```
Đôi khi, ta chỉ cần:
```vb=
Range("A1").Value
```
Lý do là Excel ngầm định các đối tượng trước đó là `Application`, `ActiveWorkbook` và `ActiveSheet`. Thậm chí nếu `A1` đang được chọn, ta chỉ cần:
```vb=
ActiveCell.Value
```
### Bài tập số 3
:::info
Học viên vui lòng truy cập vào [thư mục bài tập](https://drive.google.com/drive/folders/1-MYze3FvRT2j6Vm2Du2kl7dWUhBPeibT?usp=sharing) của mô-đun này để lấy bài tập.
:::
Học viên tạo một sổ tính mới và thực hiện theo các yêu cầu sau:
- Hãy tạo ra một module mới và thêm vào đoạn mã nguồn VBA dưới đây này
```vb=
Sub ChangeFont()
Sheets(Array("Sheet1", "Sheet2", "Sheet3", "Sheet4")).Select
Sheets("Sheet1").Activate
Cells.Select
With Selection.Font
.Name = "Arial"
.Size = 12
End With
Selection.Font.Size = 11
Range("A1").Select
End Sub
```
- Hãy thử chạy macro và đoán xem đoạn mã nguồn trên làm những việc gì? (chạy trong VBE hoặc từ Excel)
- Thử tạo một **Button** trong Excel và gán macro (Assign Macro...) này cho nút vừa tạo: Developer --> Insert --> Button (Form Control).
***Lưu ý: ta còn có thể gán macro cho bất kỳ đối tượng Shape nào (chuột phải --> Assign Macro)***
:::success
Sau khi hoàn thành, học viên lưu lại bài làm và nộp lại (nếu được yêu cầu).
:::
### Hiểu thêm về Excel Object Model (đọc thêm)

The **Application** object is at the very top of the hierarchy in the object model. It represents Excel itself and every other object is a child or grandchild of the **Application** object. The following are some of the more useful objects contained just below **Application** in the hierarchy:
```vb=
Addin
Window
Workbook
WorksheetFunction
```
- Each object contained in the **Application** object can contain other objects. For example, the following are some objects that can be contained in a **Workbook** object:
```vb=
Chart '(which is a chart sheet)
Name
VBProject
Window
Worksheet
```
- In turn, each of these objects can contain still other objects. Consider a **Worksheet** object, which is contained in a **Workbook** object, that is contained in the **Application** object. Some of the objects that can be contained in a **Worksheet** object are
```vb=
Comment
Hyperlink
Name
PageSetup
PivotTable
Range
```
> Range ⇒ contained in Worksheet ⇒ contained in Workbook ⇒ contained in Excel
- Referring to objects: `.`
- Using Collection objects:
- **Workbooks**: A collection of all currently open Workbook objects
- **Worksheets**: A collection of all Worksheet objects contained in a particular Workbook object
- **Charts**: A collection of all Chart objects (chart sheets) contained in a particular Workbook object
- **Sheets**: A collection of all sheets (regardless of their type) contained in a particular Workbook object. To get to a Workbook object, you can start with the Application object like in the following code:
```vb=
' All collection objects have an Item property to get to a singular instance of an object they collect
Application.Workbooks.Item("Book1").Path ' Using Collection objects
Application.Workbooks.Item(2).Worksheets.Item(1) ' Using Collection objects
Application.Workbooks.Item(1).Worksheets.Item("Sheet1") ' Considering generic collection objects
Application.Workbooks.Item(1).Sheets.Item("Sheet1") ' Considering generic collection objects
Application.Workbooks.Item("Book1.xlsx").Worksheets.Item(2).Range("A1").Value ' Simplifying object references
Application.Workbooks("Book1.xlsx").Worksheets(2).Range("A1").Value ' Simplifying object references
Workbooks("Book1.xlsx").Worksheets(2).Range("A1").Value ' the Application object is always assumed
Worksheets(2).Range("A1").Value ' If you’re sure that Book1.xlsx is the active workbook
Range("A1").Value ' If you know the second worksheet is the currently active worksheet
```
### Diving into Object Properties and Methods
- Setting object properties
```vb=
Sub ShowValue()
Contents = Worksheets("Sheet1").Range("A1").Value
MsgBox Contents
End Sub
```

```vb=
Sub ChangeValue()
Worksheets("Sheet1").Range("A1").Value = 994.92
End Sub
```
> If the active workbook doesn’t have a sheet named Sheet1, the result of executing that macro is an error message
- A collection is also an object. This means that a collection also has properties. For example, you can determine how many workbooks are open by accessing the `Count` property of the `Workbooks` collection. The following VBA procedure displays a message box that tells you how many workbooks are open:
```vb=
Sub CountBooks()
MsgBox Workbooks.Count
End Sub
```
- Taking action with object methods
```vb=
Sub ClearRange()
Range("A1:A12").ClearContents
End Sub
Sub CopyOne()
Worksheets("Sheet1").Activate
Range("A1").Copy Range("B1")
End Sub
```
> Another way to specify an argument for a method is to use the official name of the argument followed by **a colon and an equal sign**. Using named arguments is optional, but doing so can often make your code easier to understand, particularly if the method takes many parameters. The second statement in the CopyOne procedure could be written like this
```vb=
Range("A1").Copy Destination:=Range("B1")
```

> Because a collection is also an object, collections have methods. The following macro uses the Add method for the Workbooks collection:
```vb=
Sub AddAWorkbook()
Workbooks.Add
End Sub
```
- Triggering actions with object events
Events are specific things that you do inside Excel that Excel is constantly listening for (such as switching sheets or changing cells). For example, when you type a value into a cell, a Worksheet Change event occurs. You could, for example, have a VBA macro that is designed to execute whenever someone changes the value of a particular cell.
## VBA Sub và Function Procedures
### Mở đầu về hàm
Như vậy, mỗi macro chính là một sub procedure trong VBA. Để tạo nhanh một precedure, hãy vào phần Insert --> Procedure. Tại đây sẽ có 3 thứ mà ta có thể tạo trong một module: **Sub**, **Function**, và **Property**. Đối với lựa chọn phạm vi (scope), có 2 loại là **Public** (có thể truy cập được từ tất cả các module VBA khác) và **Private** (chỉ có thể truy cập trong cùng module VBA).
Giờ ta sẽ thử tạo một hàm trong VBA và gọi nó từ Excel.
```vb=
Function Luy_Thua(x As Integer, y As Integer) As Integer
Luy_Thua = x ^ y
End Function
```
Để gọi được hàm này, ta có 2 thường dùng:
- Gọi nó như một hàm Excel bình thường `=Luy_Thua(10,2)`
- Gọi từ một sub procedure (hoặc một function khác). Như vậy, sub procedure cũng có thể được gọi từ một sub procedure khác.
***Lưu ý: Việc dùng dấu ngoặc là chỉ cần thiết cho một số trường hợp và ta cũng không cần thiết dùng từ khoá `Call` trước tên hàm hoặc thủ tục (đã lỗi thời).***
Ngoài ra, trong giao diện VBE, ta có thể sử dụng Immediate window:
:::warning
Nếu chưa xuất hiện cửa sổ này, vào View ⇒ Immediate Window hoặc `Ctrl+G`.
:::
Hãy nhập vào lệnh:
```
?luy_thua(2,2)
```

### Macro Recorder: Relative References
Giờ ta sẽ thử dùng **Use Relative References** từ thẻ Developer trong lúc ghi macro.
Hãy thử ghi một macro đơn giản làm nhiệm vụ đánh số cho vùng xung quanh ô đang chọn như sau (ví dụ ban đầu chọn ô `C3`):

Sau đó xem qua mã nguồn VBA của macro vừa ghi và nhận xét.
### Bài tập số 4
:::info
Học viên vui lòng truy cập vào [thư mục bài tập](https://drive.google.com/drive/folders/1-MYze3FvRT2j6Vm2Du2kl7dWUhBPeibT?usp=sharing) của mô-đun này để lấy bài tập.
:::
Học viên tạo một sổ tính mới và thực hiện theo các yêu cầu sau:
- Nhập vào một số ở ô `A1`
- Hãy tạo ra một module mới và tự viết một sub procedure (macro) mới thực hiện các thao tác sao:
- Tính giá trị cho ô `A2` bằng bình phương giá trị `A1` (mũ 2)
- Tính giá trị cho ô `B2` bằng lập phương giá trị `A1` (mũ 3)
- Tính giá trị cho ô `C2` bình phương của bình phương giá trị `A1` (mũ 4)
- Hãy thử chạy macro và xem kết quả

- Hãy nâng cấp macro trên để khi chọn vào bất kỳ ô nào, lần lượt các ô phía dưới sẽ hiện ra các kết quả tương tự

- Tạo một nút với nhãn bất kỳ và gán macro này cho nút đó, thử bấm vào nút vừa tạo để thực thi macro.
:::success
Sau khi hoàn thành, học viên lưu lại bài làm và nộp lại (nếu được yêu cầu).
:::
### Chọn Subs hay chọn Functions
```vb=
Sub ShowMessage()
MsgBox "That's all folks!"
End Sub
Sub ShowMessage(MyMessage)
MsgBox MyMessage
End Sub
Function CubeRoot(number)
CubeRoot = number ^ (1 / 3)
End Function
```
> When you record a macro with the Excel macro recorder, the result is always a Sub procedure that takes no arguments.
> VBA allows you to specify what type of information (also known as data type) is returned by a Function procedure.
### Đặt tên cho Subs và Functions
* You can use letters, numbers, and some punctuation characters, but the first character must be a letter.
* You can’t use spaces or periods in the name.
* VBA does not distinguish between uppercase and lowercase letters.
* You can’t use any of the following characters in a procedure name: #, $, %, &, @, ^, *, or !. In other words, your procedure name can’t look like comic-strip curse words.
* If you write a Function procedure for use in a formula, avoid using a name that looks like a cell address (for example, A1 or B52). Actually, Excel allows such function names, but why make things more confusing than they are already?
* Procedure names can’t be VBA keywords like Open, Dim, or Input. You can, however, name them the same as object names in the object model, but you probably shouldn’t as it would be confusing to anyone maintaining your code.
* Procedure names can be no longer than 255 characters. (Of course, you would never make a procedure name this long.)
## Macro Recorder: dùng sao cho đúng (đọc thêm)
### Recording Basics
* The macro recorder is for Sub procedures only. You can’t use the macro recorder to create Function procedures.
* You might also find the macro recorder helpful for developing more complex macros. That is to say, you can record some actions and then copy the recorded code into another, more complex macro. In most cases, you need to edit the recorded code and add some new VBA statements.
* The macro recorder cannot generate code for any of the following tasks, which are described later in the book:
* Performing any type of repetitive looping
* Performing any type of conditional actions (using an If-Then statement)
* Assigning values to variables
* Specifying data types
* Displaying pop-up messages
* Displaying custom dialog boxes
### Preparing to Record
Ultimately, the success of a recorded macro depends on five factors:
1. How the workbook is set up while you record the macro
2. What is selected when you start recording
3. Whether you use absolute or relative recording mode
4. The accuracy of your recorded actions
5. The context in which you play back the recorded macro
6. The importance of these factors becomes more clear as you go through the recording process.
### Choosing Between Relative and Absolute Mode
```vb=
Sub Absolute()
'
' Absolute Macro
'
Range("B1").Select
ActiveCell.FormulaR1C1 = "Jan"
Range("C1").Select
ActiveCell.FormulaR1C1 = "Feb"
Range("D1").Select
ActiveCell.FormulaR1C1 = "Mar"
Range("B1").Select
End Sub
Sub Relative()
'
' Relative Macro
'
ActiveCell.FormulaR1C1 = "Jan"
ActiveCell.Offset(0, 1).Range("A1").Select
ActiveCell.FormulaR1C1 = "Feb"
ActiveCell.Offset(0, 1).Range("A1").Select
ActiveCell.FormulaR1C1 = "Mar"
ActiveCell.Offset(0, -2).Range("A1").Select
End Sub
```
### Watching the Macro Recorder in Action

### Specifying Recording Options for Your Macro

- Store Macro In: The Store Macro In option tells Excel where to store the macro that it’s recording. By default, Excel puts the recorded macro in a module in the active workbook. If you prefer, you can record it in a new workbook (Excel opens a blank workbook), or in your Personal Macro Workbook.
> Your Personal Macro Workbook is a hidden workbook that opens automatically when Excel starts. This is a good place to store macros that you’ll use with multiple workbooks. The Personal Macro Workbook is named PERSONAL.XLSB. This file doesn’t exist until you specify it as the location for a recorded macro. If you’ve made any changes to this file, Excel prompts you to save it when you exit.
### Streamlining Code Generated by the Macro Recorder
To demonstrate just how inefficient the macro recorder’s code can be, try this:
1. Turn on the macro recorder.
1. Choose Page Layout ⇒ Page Setup ⇒ Orientation ⇒ Landscape.
1. Turn off the macro recorder.
To take a look at the macro, activate the Module1 sheet. This single (very simple) command generates the following code:
```vb=
Sub Macro1()
'
' Macro1 Macro
'
'
Application.PrintCommunication = False
With ActiveSheet.PageSetup
.PrintTitleRows = ""
.PrintTitleColumns = ""
End With
Application.PrintCommunication = True
ActiveSheet.PageSetup.PrintArea = ""
Application.PrintCommunication = False
With ActiveSheet.PageSetup
.LeftHeader = ""
.CenterHeader = ""
.RightHeader = ""
.LeftFooter = ""
.CenterFooter = ""
.RightFooter = ""
.LeftMargin = Application.InchesToPoints(0.7)
.RightMargin = Application.InchesToPoints(0.7)
.TopMargin = Application.InchesToPoints(0.75)
.BottomMargin = Application.InchesToPoints(0.75)
.HeaderMargin = Application.InchesToPoints(0.3)
.FooterMargin = Application.InchesToPoints(0.3)
.PrintHeadings = False
.PrintGridlines = False
.PrintComments = xlPrintNoComments
.PrintQuality = 600
.CenterHorizontally = False
.CenterVertically = False
.Orientation = xlLandscape
.Draft = False
.PaperSize = xlPaperLetter
.FirstPageNumber = xlAutomatic
.Order = xlDownThenOver
.BlackAndWhite = False
.Zoom = 100
.PrintErrors = xlPrintErrorsDisplayed
.OddAndEvenPagesHeaderFooter = False
.DifferentFirstPageHeaderFooter = False
.ScaleWithDocHeaderFooter = True
.AlignMarginsHeaderFooter = True
.EvenPage.LeftHeader.Text = ""
.EvenPage.CenterHeader.Text = ""
.EvenPage.RightHeader.Text = ""
.EvenPage.LeftFooter.Text = ""
.EvenPage.CenterFooter.Text = ""
.EvenPage.RightFooter.Text = ""
.FirstPage.LeftHeader.Text = ""
.FirstPage.CenterHeader.Text = ""
.FirstPage.RightHeader.Text = ""
.FirstPage.LeftFooter.Text = ""
.FirstPage.CenterFooter.Text = ""
.FirstPage.RightFooter.Text = ""
End With
Application.PrintCommunication = TrueEnd Sub
```
- You might be surprised by the amount of code generated by this single command. Although you changed only one print setting, Excel generated code that set many other print-related properties.
This is a good example of macro-recording overkill.
```vb=
Sub Macro1()
With ActiveSheet.PageSetup
.Orientation = xlLandscape
End With
End Sub
```
---