Bàn về "Tắt các chế độ cập nhật màn hình và các chế độ khác để tăng tốc thực hiện .." (3 người xem)

  • Thread starter Thread starter VetMini
  • Ngày gửi Ngày gửi
Liên hệ QC

Người dùng đang xem chủ đề này

VetMini

Ăn cùng góc phố
Tham gia
21/12/12
Bài viết
17,829
Được thích
24,695
Xin phép được nhắc đến bài "Tắt các chế độ cập nhật màn hình và các chế độ khác để tăng tốc thực hiện code" của levanduyet ở đây:

Diễn đàn > Lập trình với Excel > Excel và các ngôn ngữ lập trình khác > Thư viện mã lập trình

Trong bài này có nói đến hai hàm SpeedOn(...) và SpeedOff(). Mục đích của hàm thứ nhất là tắt một số chế độ trong lúc chạy đoạn code và sau khi chạy xong thì dùng hàm thứ hai mở ra trở lại.

Tôi không muốn bàn về code trong hàm. Tuy nhiên, người viết code không nói rõ về cách thức và trường hợp sử dụng cho nên tôi chú thêm để góp ý.

Theo nguyên tắc lập trình, khi phải dùng đến công cụ ảnh hưởng đến chế độ của ứng dụng (application, ở đây là Excel) người lập trình phải cẩn trọng bằng cách trả nó về tình trạng ban đầu sau khi đoạn code làm việc xong. Ở đây, đôi hàm này cũng làm theo nguyên tắc đó: hàm thứ nhất thay đổi chế độ và hàm thứ hai trả về chế độ ban đầu.

Điểm chủ quan của đôi hàm này là ở cụm từ "trạng thái/chế độ ban đầu". Làm sao biết thế nào là trạng thái ban đầu? Hàm thứ hai làm như vậy có bảo đảm là đúng không?

Xin trả lời: đối với project nhỏ thì được, nhưng gặp project lớn thỉ sẽ có vấn đề (project ở đây là VBAProject của Excel)

Ví dụ cụ thể: trong một module nào đó, ta có hàm A, bên trong hàm A, ta gọi hàm B. Để tăng tốc một đoạn code nào đó trong hàm A, ta gọi SpeedOn(), sau đó gọi SpeedOff(). Rất tiếc là nếu B cũng có gọi SpeedOn() và SpeedOff() thì SpeedOff() của B sẽ vô hiệu hóa SpeedOn() của A.

Trên thực tế, khi lập trình một project lớn, người ta không gọi hàm khơi khơi như vậy. Trước khi thay đổi trạng thái ứng dụng, người ta trữ lại trạng thái hiện hành và khi cần trả về thì trả về trạng thái này chứ không phải trạng thái mặc định như code trong hàm SpeedOff()

Làm thế nào để trữ lại trạng thái ban đầu? Dùng biến toàn cục? Xin thưa là không bởi vì chỉ cần một vài lần trữ không có ngăn xếp (stack) là sai bấy hết. Dùng biến cục bộ của hàm và truyền cho SpeedOn(), SpeedOff() qua tham số? Xin thưa là có tất cả 8 biến, nếu phải bảo trì thêm 8 biến này thì cũng hơi cực. Có thể giảm thiểu bằng cách dùng cấu trúc (type, tương tự như struct trong C)? Được nhưng vẫn còn hơi phiền phức và dễ bị lỗi (error prone).

Giải pháp người ta thường dùng là áp dụng đối tượng. Theo giải pháp này, người ta đặt ra một lớp (class module) – ví dụ SpeedOnOff. Trong lớp này, ta đặt 8 thuộc tính private để chứa tình trạng hiện hành (private vì bên ngoài không cần biết đến chúng). Lớp chứa hai hàm public SpeedOn() và SpeedOff(). Trước khi tắt chế độ màn hình, SpeedOn() ghi lại 8 thuộc tính hiện hành. SpeedOff() chỉ việc dùng các thuộc tính này để khôi phục lại chế độ ban đầu.

Cách dùng:

[code bình thường…]
Dim mySpeed As SpeedOnOff
Set mySpeed = New SpeedOnOff
mySpeed.SpeedOn ‘ tắt chế độ hiển thị màn hình
[code cần chạy nhanh]
mySpeed.SpeedOff ‘ trả về chế độ ban đầu
[code bình thường…]

Tương đối giản dị và an toàn.
Đối tượng gói kín thuộc tính (dữ liệu) riêng của nó nên không code nào chạm đến được ngoài cách gọi hàm SpeedOn()/SpeedOff(). Mỗi hàm module cần bật tắt màn hình đều phải tạo đối tượng riêng nên dữ liệu được bảo đảm.

(*) thành thật xin lỗi mọi người, tôi không được phép làm việc trong thư mục nói trên nên bắt buộc phải mở ở đây.

Ghi chú thêm: bạn nào đọc bài về lớp/class của NguenDuyTuan mà thấy khó hiểu thì cũng có thể coi đây là một ví dụ đơn giản ứng dụng class.
 
Xin phép được nhắc đến bài "Tắt các chế độ cập nhật màn hình và các chế độ khác để tăng tốc thực hiện code" của levanduyet ở đây:

Diễn đàn > Lập trình với Excel > Excel và các ngôn ngữ lập trình khác > Thư viện mã lập trình

Trong bài này có nói đến hai hàm SpeedOn(...) và SpeedOff(). Mục đích của hàm thứ nhất là tắt một số chế độ trong lúc chạy đoạn code và sau khi chạy xong thì dùng hàm thứ hai mở ra trở lại.

Tôi không muốn bàn về code trong hàm. Tuy nhiên, người viết code không nói rõ về cách thức và trường hợp sử dụng cho nên tôi chú thêm để góp ý.

Theo nguyên tắc lập trình, khi phải dùng đến công cụ ảnh hưởng đến chế độ của ứng dụng (application, ở đây là Excel) người lập trình phải cẩn trọng bằng cách trả nó về tình trạng ban đầu sau khi đoạn code làm việc xong. Ở đây, đôi hàm này cũng làm theo nguyên tắc đó: hàm thứ nhất thay đổi chế độ và hàm thứ hai trả về chế độ ban đầu.

Điểm chủ quan của đôi hàm này là ở cụm từ "trạng thái/chế độ ban đầu". Làm sao biết thế nào là trạng thái ban đầu? Hàm thứ hai làm như vậy có bảo đảm là đúng không?

Xin trả lời: đối với project nhỏ thì được, nhưng gặp project lớn thỉ sẽ có vấn đề (project ở đây là VBAProject của Excel)

Ví dụ cụ thể: trong một module nào đó, ta có hàm A, bên trong hàm A, ta gọi hàm B. Để tăng tốc một đoạn code nào đó trong hàm A, ta gọi SpeedOn(), sau đó gọi SpeedOff(). Rất tiếc là nếu B cũng có gọi SpeedOn() và SpeedOff() thì SpeedOff() của B sẽ vô hiệu hóa SpeedOn() của A.

Trên thực tế, khi lập trình một project lớn, người ta không gọi hàm khơi khơi như vậy. Trước khi thay đổi trạng thái ứng dụng, người ta trữ lại trạng thái hiện hành và khi cần trả về thì trả về trạng thái này chứ không phải trạng thái mặc định như code trong hàm SpeedOff()

Làm thế nào để trữ lại trạng thái ban đầu? Dùng biến toàn cục? Xin thưa là không bởi vì chỉ cần một vài lần trữ không có ngăn xếp (stack) là sai bấy hết. Dùng biến cục bộ của hàm và truyền cho SpeedOn(), SpeedOff() qua tham số? Xin thưa là có tất cả 8 biến, nếu phải bảo trì thêm 8 biến này thì cũng hơi cực. Có thể giảm thiểu bằng cách dùng cấu trúc (type, tương tự như struct trong C)? Được nhưng vẫn còn hơi phiền phức và dễ bị lỗi (error prone).

Giải pháp người ta thường dùng là áp dụng đối tượng. Theo giải pháp này, người ta đặt ra một lớp (class module) – ví dụ SpeedOnOff. Trong lớp này, ta đặt 8 thuộc tính private để chứa tình trạng hiện hành (private vì bên ngoài không cần biết đến chúng). Lớp chứa hai hàm public SpeedOn() và SpeedOff(). Trước khi tắt chế độ màn hình, SpeedOn() ghi lại 8 thuộc tính hiện hành. SpeedOff() chỉ việc dùng các thuộc tính này để khôi phục lại chế độ ban đầu.

Cách dùng:

[code bình thường…]
Dim mySpeed As SpeedOnOff
Set mySpeed = New SpeedOnOff

mySpeed.SpeedOn ‘ tắt chế độ hiển thị màn hình
[code cần chạy nhanh]
mySpeed.SpeedOff ‘ trả về chế độ ban đầu
[code bình thường…]

Tương đối giản dị và an toàn.
Đối tượng gói kín thuộc tính (dữ liệu) riêng của nó nên không code nào chạm đến được ngoài cách gọi hàm SpeedOn()/SpeedOff(). Mỗi hàm module cần bật tắt màn hình đều phải tạo đối tượng riêng nên dữ liệu được bảo đảm.

(*) thành thật xin lỗi mọi người, tôi không được phép làm việc trong thư mục nói trên nên bắt buộc phải mở ở đây.

Ghi chú thêm: bạn nào đọc bài về lớp/class của NguenDuyTuan mà thấy khó hiểu thì cũng có thể coi đây là một ví dụ đơn giản ứng dụng class.

Nếu chỉ thế thì người dùng code của bạn trong một code dài dằng dặc của mình sẽ phải nhiều lần tự tạo đối tượng và sau đó hủy đối tượng. Vả lại code hơi bị rườm rà

Theo tôi đã giúp "người ta" thì giúp "trọn gói". Người ta chỉ cứ gọi On, Off mà thôi.

Vd. On, On, On, On, On, Off, On Off, Off, Off, Off, Off

Thậm chí người ta cũng chả cần biết là sau "cánh gà" có 1 object nào đó đang giật dây điều khiển. Thậm chí người ta cũng chả phải biết class nó là cái gì. Chả phải đọc bài của cao thủ nọ, sư phụ kia về class.

... bởi họ đã nhận được sự hỗ trợ trọn gói rồi.
 
Lần chỉnh sửa cuối:
Như tôi đã trình bày qua, nếu chỉ phải gọi on/off thì giản dị quá rồi, còn gì phải bàn.

Trong ví dụ tôi cũng có nêu rõ rằng trong một project phức tạp sẽ có khả năng đoạn code B gọi Off làm vô hiệu hoá tình trạng On của đoạn code A.

Dùng đối tượng chỉ là một phương pháp khắc phục, không hẳn đã là cách duy nhất. Bởi vậy mới có từ "bàn", ngụ ý mời các bạn ra ý kiến khắc phục hai điểm trong code nguyên thuỷ:

1. Hàm Off chỉ khởi lại chế độ màn hình mà không cần biết tới trước khi gọi On thì application đang ở trạng thái gì.

2. Mỗi lần gọi một hàm nào đó thì lại phải xét xem hàm này có gọi on/off hay không. Vì nếu có thì hàm con sẽ vô hiệu hoá chế độ của hàm cha.

Xin mời
 
Như tôi đã trình bày qua, nếu chỉ phải gọi on/off thì giản dị quá rồi, còn gì phải bàn.

Trong ví dụ tôi cũng có nêu rõ rằng trong một project phức tạp sẽ có khả năng đoạn code B gọi Off làm vô hiệu hoá tình trạng On của đoạn code A.

Bạn không hiểu ý tôi.
Tôi hiểu tại sao bạn dùng class. Tôi không ngu tới mức đọc mà không hiểu.

Ý tôi là là bạn nên "bước thêm một bước" nữa để "người ta" không phải gọi 10, 20 lần tạo đối tượng.
Bạn hiểu chứ? Vẫn dùng class như bạn, nhớ hiện hành --> thay đổi --> cuối cùng phục hồi. Chỉ là hoàn thiện hơn.
Bạn là người đề xuất nên tôi chỉ góp ý. Bạn thấy làm như thế là đủ rồi thì thôi. Tôi góp ý chứ không áp đặt.
 
Chịu thua, không nghĩ ra.

Tạm có giải pháp dùng một array làm biến toàn cục. Hàm On có nhiệm vụ nhét dữ liệu vào array và set on. Hàm Off có nhiệm vụ lấy dữ liệu được nhét vào gần nhất, dùng để reset và xoá khỏi array. Như vậy sẽ giông như ngăn xếp (stack). Hy vọng dùng được, để khi nào rảnh code thử
 
Chịu thua, không nghĩ ra.

Tạm có giải pháp dùng một array làm biến toàn cục. Hàm On có nhiệm vụ nhét dữ liệu vào array và set on. Hàm Off có nhiệm vụ lấy dữ liệu được nhét vào gần nhất, dùng để reset và xoá khỏi array. Như vậy sẽ giông như ngăn xếp (stack). Hy vọng dùng được, để khi nào rảnh code thử

Viết chơi tí.
Mới viết xong và test chỉ ở mức kiểm tra xem có error không mà thôi.
Còn chuyện lớp có chuẩn không thì xin mời mọi người test.
---------------
1. Phiên bản chỉ dùng 1 class module (Module1 dùng để test)

Module1
Mã:
Private OnOffObject As New clsSaveRestoreProp

Private k As Long

Sub test1()
    k = k + 1
    OnOffObject.SpeedOn "Running macro " & k & "..."
End Sub

Sub test2()
    k = k - 1
    OnOffObject.SpeedOff
End Sub

Lớp clsSaveRestoreProp
[GPECODE=vb]
Private m_oPrev As clsSaveRestoreProp
Private m_oNext As clsSaveRestoreProp

Private Calc As Long
Private SUpdating As Boolean
Private EEvents As Boolean
Private DAlerts As Boolean
Private Cursor As Long
Private SBar As Variant
Private ECancelKey As Long

'Private index As Long ' chi dung khi test

Private Sub Class_Initialize()
' chi dung voi muc dich de test
' index = Range("D1").Value + 1
' Range("D1").Value = index
' Range("A65535").End(xlUp).Offset(1).Value = "object " & index & " created"
End Sub

Private Sub Class_Terminate()
' chi dung khi test
' Range("B65535").End(xlUp).Offset(1).Value = "object " & index & " destroyed"

With Application
.Calculation = Calc
.ScreenUpdating = SUpdating
.EnableEvents = EEvents
.DisplayAlerts = DAlerts
.Cursor = Cursor
.StatusBar = SBar
.EnableCancelKey = ECancelKey
End With
End Sub

Public Property Get prevObj()
Set prevObj = m_oPrev
End Property

Public Property Set prevObj(obj As clsSaveRestoreProp)
Set m_oPrev = obj

With Application
Calc = .Calculation
SUpdating = .ScreenUpdating
EEvents = .EnableEvents
DAlerts = .DisplayAlerts
Cursor = .Cursor
SBar = .StatusBar
ECancelKey = .EnableCancelKey

.Calculation = xlCalculationManual
.ScreenUpdating = False
.EnableEvents = False
.DisplayAlerts = False
.Cursor = xlWait
.EnableCancelKey = xlErrorHandler
End With
End Property

Public Property Get nextObj()
Set nextObj = m_oNext
End Property

Public Property Set nextObj(obj As clsSaveRestoreProp)
Set m_oNext = obj
End Property

Sub SpeedOn(Optional StatusBarMsg As String = "")
Dim tmp As clsSaveRestoreProp
If Not nextObj Is Nothing Then
nextObj.SpeedOn StatusBarMsg
Else
Set tmp = New clsSaveRestoreProp
Set tmp.prevObj = Me
Set nextObj = tmp
End If
End Sub

Sub SpeedOff()
If Not Me.nextObj Is Nothing Then
Me.nextObj.SpeedOff
Else
If Not Me.prevObj Is Nothing Then
Set Me.prevObj.nextObj = Nothing
End If
End If
End Sub
[/GPECODE]
----------------
2. Phiên bản dùng class module + Module

Module1
Mã:
Private currObject As clsSaveRestoreProp

Sub SpeedOn(Optional StatusBarMsg As String = "")
Dim tmp As clsSaveRestoreProp
    Set tmp = New clsSaveRestore
    If Not currObject Is Nothing Then
        Set tmp.prevObj = currObject
        Set currObject.nextObj = tmp
    End If
    Set currObject = tmp

    Application.StatusBar = StatusBarMsg
End Sub

Sub SpeedOff()
    If Not currObject.prevObj Is Nothing Then
        Set currObject = currObject.prevObj
        Set currObject.nextObj = Nothing
    Else
        Set currObject = Nothing
    End If
End Sub

clsSaveRestoreProp
[GPECODE=vb]
Private m_oPrev As clsSaveRestoreProp
Private m_oNext As clsSaveRestoreProp

Private Calc As Long
Private SUpdating As Boolean
Private EEvents As Boolean
Private DAlerts As Boolean
Private Cursor As Long
Private SBar As Variant
Private ECancelKey As Long

'Private index As Long ' chi dung khi test

Private Sub Class_Initialize()
' chi dung khi test
' index = Range("D1").Value + 1
' Range("D1").Value = index
' Range("A65535").End(xlUp).Offset(1).Value = "object " & index & " created"

With Application
Calc = .Calculation
SUpdating = .ScreenUpdating
EEvents = .EnableEvents
DAlerts = .DisplayAlerts
Cursor = .Cursor
SBar = .StatusBar
ECancelKey = .EnableCancelKey

.Calculation = xlCalculationManual
.ScreenUpdating = False
.EnableEvents = False
.DisplayAlerts = False
.Cursor = xlWait
.EnableCancelKey = xlErrorHandler
End With
End Sub

Private Sub Class_Terminate()
' chi dung khi test
' Range("B65535").End(xlUp).Offset(1).Value = "object " & index & " destroyed"

With Application
.Calculation = Calc
.ScreenUpdating = SUpdating
.EnableEvents = EEvents
.DisplayAlerts = DAlerts
.Cursor = Cursor
.StatusBar = SBar
.EnableCancelKey = ECancelKey
End With
End Sub

Public Property Get prevObj()
Set prevObj = m_oPrev
End Property

Public Property Set prevObj(obj As clsSaveRestoreProp)
Set m_oPrev = obj
End Property

Public Property Set nextObj(obj As clsSaveRestoreProp)
Set m_oNext = obj
End Property
[/GPECODE]
 
Chưa thử code nhưng cứ nhìn vào giải thuật là đủ rồi. Giải pháp dùng linked list của bác rất hay.
 
Web KT

Bài viết mới nhất

Back
Top Bottom