Wykorzystanie Immediate Window z UserForm'a i ComboCooki | strona główna: A po co ten Excel ;-) |
|||
Wersja (2) poprawione kilka aspektów pomysłu. | ||||
Najpierw dwa słowa na temat całego pomysłu. Dość często wykorzystuję do różnych celów okno Immediate Edytora VBA. Można je | ||||
włączyć uruchamiając Edytor VBA (L_Alt + F11) i Menu View / Immediate Window (Ctrl+G). Wykorzystuje się je najczęściej w instrukcji | ||||
Debug.Print które następujące po tej komendzie instrukcji zwraca do tego okna. Można jednak robić za jego pomocą wiele różnych | ||||
rzeczy: dokonywać zmian w arkuszach, skoroszytach, i ich obiektach, formułach, komentarzach.. (no sam nie wiem co jeszcze pisać) | ||||
słowem wszystko co można zapisać "jedną linią" i wykonać jak procedurę. | ||||
Mi się wymarzyło żebym mógł wykorzystywać te możliwości okna Immediate bez wywołania okna Edytora VBA. Najlepiej z UserForm'a. | ||||
I tak żeby to mogło działać w każdym otwartym skoroszycie. Jak bym sobie tego nie wyobrażał to byłem pewien że z podpowiedzi VBA | ||||
trzeba będzie zrezygnować a o literówkę nie trudno. Może więc trzeba dodać możliwość wyboru najczęściej używanych komend z listy. | ||||
Po to właśnie wymyśliłem ComboCooki ;-P Jest to zwykły ComboBox jednak zawartość jego listy jest pobierana z pliku txt w którym z | ||||
poziomu samego combo będziemy mogli dokonywać zapisów tych komend które chcemy zapamiętać. | ||||
Jak się do czegoś takiego zabrać? | ||||
Żeby pomysł działał w każdym skoroszycie postanowiłem wszystko tworzyć w skoroszycie makr osobistych (Personal.xls) i wywoływać | ||||
ze skrótu klawiaturowego Ctrl+Shift+I. | ||||
W mod.Thisworkbook (Ten_skoroszyt E2010) wstawiamy kod: | ||||
Option Explicit | ||||
Private Sub Workbook_Open() | ||||
Application.OnKey "^+i", "StartImmediate" | ||||
End Sub | ||||
W mod.Standardowym | ||||
Sub StartImmediate() | ||||
UserForm1.Show 0 | ||||
End Sub | ||||
W kodzie UserForm'a | ||||
Option Explicit | ||||
Private Declare Function FindWindow _ | ||||
Lib "user32" _ | ||||
Alias "FindWindowA" ( _ | ||||
ByVal lpClassName As String, _ | ||||
ByVal lpWindowName As String) As Long | ||||
Private Declare Function FindWindowExA _ | ||||
Lib "user32" ( _ | ||||
ByVal hWnd1 As Long, _ | ||||
ByVal hWnd2 As Long, _ | ||||
ByVal lpsz1 As String, _ | ||||
ByVal lpsz2 As String) As Long | ||||
Private Declare Function GetWindowText _ | ||||
Lib "user32" _ | ||||
Alias "GetWindowTextA" ( _ | ||||
ByVal hwnd As Long, _ | ||||
ByVal lpString As String, _ | ||||
ByVal cch As Long) As Long | ||||
Private Declare Function GetForegroundWindow _ | ||||
Lib "user32" () As Long | ||||
Private Declare Function ShellExecute _ | ||||
Lib "shell32.dll" _ | ||||
Alias "ShellExecuteA" ( _ | ||||
ByVal hwnd As Long, _ | ||||
ByVal lpOperation As String, _ | ||||
ByVal lpFile As String, _ | ||||
ByVal lpParameters As String, _ | ||||
ByVal lpDirectory As String, _ | ||||
ByVal nShowCmd As Long) As Long | ||||
Private Function GetImmediateWindowHwnd() As Long | ||||
Dim hwnd As Long, hMain As Long | ||||
Dim sMain As String: sMain = Application.VBE.MainWindow.Caption | ||||
Const strClassName As String = "VbaWindow" | ||||
Const strWindowText As String = "Immediate" | ||||
Dim sWindowText As String, r As Long | ||||
Do | ||||
hMain = FindWindow("wndclass_desked_gsk", sMain) | ||||
hwnd = FindWindowExA(hMain, hwnd, strClassName, vbNullString) | ||||
If hwnd = 0 Then | ||||
Exit Do | ||||
Else | ||||
sWindowText = Space(255) | ||||
r = GetWindowText(hwnd, sWindowText, 255) | ||||
sWindowText = Left(sWindowText, r) | ||||
If strWindowText = sWindowText Then | ||||
GetImmediateWindowHwnd = hwnd | ||||
Exit Do | ||||
End If | ||||
End If | ||||
Loop | ||||
End Function | ||||
Private Sub ComboBox1_KeyDown(ByVal KeyCode As MSForms.ReturnInteger, _ | ||||
ByVal Shift As Integer) | ||||
Select Case KeyCode | ||||
Case vbKeyEscape | ||||
Unload Me | ||||
Application.VBE.MainWindow.SetFocus | ||||
Application.SendKeys "%{F4}", True | ||||
Application.OnTime Now() + TimeSerial(0, 0, 0), "XLAppActivate" | ||||
Case vbKeyReturn | ||||
If bSave Then | ||||
Dim nr As Integer: nr = VBA.FreeFile | ||||
Open ThisWorkbook.Path & "\txt\ComboCooki.txt" For Append As #nr | ||||
Print #nr, Me.ComboBox1.Text | ||||
Close #nr | ||||
End If | ||||
SendToImmediate Me.ComboBox1.Text | ||||
Case vbKeyF1 | ||||
ShellExecute 0, _ | ||||
"OPEN", _ | ||||
ThisWorkbook.Path & "\txt\ComboCooki.txt", _ | ||||
vbNullString, _ | ||||
vbNullString, _ | ||||
vbNormalFocus | ||||
End Select | ||||
End Sub | ||||
Sub SendToImmediate(strText As String) | ||||
Dim hwnd As Long | ||||
hwnd = GetImmediateWindowHwnd | ||||
Dim objClipboard As New MSForms.DataObject | ||||
If hwnd > 0 Then | ||||
With objClipboard | ||||
.SetText strText | ||||
.PutInClipboard | ||||
End With | ||||
SetFocus hwnd | ||||
With Application | ||||
.SendKeys "^v", True | ||||
.SendKeys "~", True | ||||
End With | ||||
Application.OnTime Now() + TimeSerial(0, 0, 0), "ClearClipboard" | ||||
End If | ||||
End Sub | ||||
Private Sub UserForm_Activate() | ||||
lUFhWnd = GetForegroundWindow | ||||
End Sub | ||||
Private Sub UserForm_Initialize() | ||||
Dim strTxtPAth As String: strTxtPAth = ThisWorkbook.Path & "\txt\ComboCooki.txt" | ||||
Dim objFSO As Object 'Scripting.FileSystemObject | ||||
Dim objFile As Object 'Scripting.TextStream | ||||
Const ForReading = 1 | ||||
Set objFSO = CreateObject("Scripting.FileSystemObject") | ||||
With objFSO | ||||
If Not .FolderExists(ThisWorkbook.Path & "\txt") Then _ | ||||
.CreateFolder ThisWorkbook.Path & "\txt" | ||||
If .FileExists(strTxtPAth) Then | ||||
Set objFile = .OpenTextFile(Filename:=strTxtPAth, _ | ||||
IOMode:=ForReading, _ | ||||
Create:=False) | ||||
Me.ComboBox1.List = Split(objFile.ReadAll, vbCrLf) | ||||
objFile.Close | ||||
End If | ||||
End With | ||||
Set objFile = Nothing | ||||
Set objFSO = Nothing | ||||
End Sub | ||||
Fragmentami: | ||||
- UserForm_Initialize | ||||
Set objFSO = CreateObject("Scripting.FileSystemObject") | ||||
With objFSO | ||||
If Not .FolderExists(ThisWorkbook.Path & "\txt") Then _ | ||||
.CreateFolder ThisWorkbook.Path & "\txt" | ||||
Posługując się obiektami biblioteki Scripting.Runtime tworzę folder ..\txt na ścieżce Thisworkbook.Path jezel itaki folder nie istnieje. | ||||
Będzie to folder dla pliku | ||||
Dim strTxtPAth As String: strTxtPAth = ThisWorkbook.Path & "\txt\ComboCooki.txt" | ||||
w którym będę przychowywał zapisane do tej pory elementy listy mojego Combo. | ||||
If .FileExists(strTxtPAth) Then | ||||
Set objFile = .OpenTextFile(Filename:=strTxtPAth, _ | ||||
IOMode:=ForReading, _ | ||||
Create:=False) | ||||
Me.ComboBox1.List = Split(objFile.ReadAll, vbCrLf) | ||||
objFile.Close | ||||
End If | ||||
Jeżeli ten plik istnieje to go otwieram, czytam - dzieląc po znaku nowej linii, zapisuję do listy Combo i plik txt zamykam. | ||||
- UserForm_Activate | ||||
Private Sub UserForm_Activate() | ||||
lUFhWnd = GetForegroundWindow | ||||
End Sub | ||||
lUFhWnd to zmienna publiczna w której przechowuję uchwyt okna naszego UserForm'a. | ||||
- ComboBox1_KeyDown | ||||
Czemu ComboBox? Bo zależało mi na podpowiedziach (MatchEntry). Jak obsłużyć działanie takiego pomysłu? Wg mnie nieźle działa | ||||
oprogramowując zdarzenie KeyDown naszego Combo. A wiec: Select Case KeyCode | ||||
Case vbKeyEscape | ||||
Unload Me | ||||
Application.VBE.MainWindow.SetFocus | ||||
Application.SendKeys "%{F4}", True | ||||
Application.OnTime Now() + TimeSerial(0, 0, 0), "XLAppActivate" | ||||
Pod Esc. Chcę wyłączyć Userform i chciałby żeby aktywnym oknem był arkusz na którym pracowałem. Jednak w różnych sytuacjach | ||||
różnie to działało. Jeżeli odpalimy UserForm i natychmiast zrezygnujemy z pracy na nim -> Esc to wystarczyłoby Unload Me. Jeżeli | ||||
jednak wpiszemy choć jedną komendą to po zamknięciu formy aktywnym oknem będzie Edytor VBA. Stąd następne 3 linijki. | ||||
Procedura XLAppActivate to maleństwo z mod. Standardowego. | ||||
Sub XLAppActivate() | ||||
SetForegroundWindow Application.hwnd | ||||
End Sub | ||||
Case vbKeyF1 | ||||
ShellExecute 0, _ | ||||
"OPEN", _ | ||||
ThisWorkbook.Path & "\txt\ComboCooki.txt", _ | ||||
vbNullString, _ | ||||
vbNullString, _ | ||||
vbNormalFocus | ||||
Pod F1 chciałbym móc włączyć mój plik ComboCooki.txt żeby, w razie potrzeby, dokonać w nim ręcznych zmian. | ||||
Case vbKeyReturn | ||||
If bSave Then | ||||
Dim nr As Integer: nr = VBA.FreeFile | ||||
Open ThisWorkbook.Path & "\txt\ComboCooki.txt" For Append As #nr | ||||
Print #nr, Me.ComboBox1.Text | ||||
Close #nr | ||||
End If | ||||
SendToImmediate Me.ComboBox1.Text | ||||
No i pod Enterem. | ||||
bSaveto zmienna publiczna która ustawiona na True zezwoli na zapis Me.ComboBox1.Text do pliku ComboCooki.txt. Jak określić wartość | ||||
tej zmiennej na True/False? Oczywiście poprzez nasze Immediate :-) | ||||
- wywołujemy okno UserForma (ctrl+shift+I) | ||||
- wpisujemy: bSave = True i klikamy enter | ||||
Następne komendy potwierdzone enterem zostaną zapisane do ComboCooki.txt. | ||||
- bSave = False | ||||
- reset UserForm'a | ||||
I mamy możliwość wyboru na naszym Combo zarówno poprzez strzałki jak i podpowiedź (zaczynając pisać). | ||||
- SendToImmediate Me.ComboBox1.Text | ||||
Sub SendToImmediate(strText As String) | ||||
Dim hwnd As Long | ||||
hwnd = GetImmediateWindowHwnd | ||||
Dim objClipboard As New MSForms.DataObject | ||||
If hwnd > 0 Then | ||||
With objClipboard | ||||
.SetText strText | ||||
.PutInClipboard | ||||
End With | ||||
SetFocus hwnd | ||||
With Application | ||||
.SendKeys "^v", True | ||||
.SendKeys "~", True | ||||
End With | ||||
Application.OnTime Now() + TimeSerial(0, 0, 0), "ClearClipboard" | ||||
End If | ||||
End Sub | ||||
Fragmentami: | ||||
Dim hwnd As Long | ||||
hwnd = GetImmediateWindowHwnd | ||||
Zapisuję uchwyt okna Immediate do zmiennej hwnd funkcją GetImmediateWindowHwnd. Funkcja ta: | funkcja | |||
GetImmediateWindowHwnd | ||||
Private Function GetImmediateWindowHwnd() As Long | napisana na podstawie | |||
Dim hwnd As Long, hMain As Long | ||||
Dim sMain As String: sMain = Application.VBE.MainWindow.Caption | Function GetXlApps | |||
Const strClassName As String = "VbaWindow" | ||||
Const strWindowText As String = "Immediate" | ||||
Dim sWindowText As String, r As Long | ||||
Do | ||||
hMain = FindWindow("wndclass_desked_gsk", sMain) | ||||
hwnd = FindWindowExA(hMain, hwnd, strClassName, vbNullString) | ||||
If hwnd = 0 Then | ||||
Exit Do | ||||
Else | ||||
sWindowText = Space(255) | ||||
r = GetWindowText(hwnd, sWindowText, 255) | ||||
sWindowText = Left(sWindowText, r) | ||||
If strWindowText = sWindowText Then | ||||
GetImmediateWindowHwnd = hwnd | ||||
Exit Do | ||||
End If | ||||
End If | ||||
Loop | ||||
End Function | ||||
Jeżeli mam już uchwyt.. | ||||
Dim objClipboard As New MSForms.DataObject | ||||
With objClipboard | ||||
.SetText strText | ||||
.PutInClipboard | ||||
End With | ||||
Kopiuję do schowka tekst komenty. | ||||
SetFocus hwnd | ||||
With Application | ||||
.SendKeys "^v", True | ||||
.SendKeys "~", True | ||||
End With | ||||
Przełączam Focus na okno immediate i wysyłam do niego Ctrl+v (wklej) i Enter | ||||
Application.OnTime Now() + TimeSerial(0, 0, 0), "ClearClipboard" | ||||
Nastepnie "bezzwłocznie" po wykonaniu tej procedury... | ||||
Sub ClearClipboard() | ||||
OpenClipboard (0&) | ||||
EmptyClipboard | ||||
CloseClipboard | ||||
SetFocus lUFhWnd | ||||
With UserForm1 | ||||
.Hide | ||||
.Show | ||||
End With | ||||
End Sub | ||||
- czyszczę schowek | ||||
- przełączam focus na UserForma | ||||
- i go resetuję. | ||||
No i chyba tyle nt. Teorii. Teraz przykłady wykorzystania :-) | ||||
Selection.Clear | ||||
Mamy jakiś zakres z wartościami i formatowaniem (kolor, obramowanie) Jak to wyczyścić? | ||||
- zaznaczyć zakres | ||||
- Przycisk DEL - czyści zawartość komórek | ||||
- (E2003) Pasek Formatowanie / kolor wypełenienia / kliknąć na DropdownButton / Brak Wypełeninia | ||||
- (E2003) jw.. / Obramowanie / "bez krawędzi" | ||||
MASA Roboty. Zawsze mnie to denerwowało :-). A poprzez Immediate: jedno polecenie. | ||||
inne :-) | ||||
ActiveWorkbook.Worksheets.Add | ||||
ActiveSheet.Delete | ||||
Application.Workbooks.Add | ||||
ActiveWorkbook.Close False | ||||
ale i.. | ||||
[A1].AddComment "test" | ||||
ActiveSheet.TextBoxes.Add 100,100,200,50 | ||||
ActiveSheet.TextBoxes.Delete | ||||
i czego dusza zapragnie :-DDD | ||||
Można też zapisywać wartości do Arkusza1 pliku Personal.xls i je tam przechwowywać. | ||||
Arkusz1.[A1] = "test" | ||||
Arkusz1.Parent.Save | ||||
i nawet po resecie pliku, Excela ... | ||||
MsgBox Arkusz1.[A1] | ||||
zwróci okienko MsgBOx'a z wartością test | ||||
Skąd wiem że Arkusz1 to Arkusz w Personal.xls? | ||||
[A2] = Arkusz1.Parent.FullName | ||||
zwróci do kom.A2 aktywnego Arkusza C:\Program Files\Microsoft Office\OFFICE11\xlstart\PERSONAL.XLS lub analogiczną | ||||
na innym systemie, na innym Office'się. A poprzez Arkusz1 należy rozumieć nazwę kodową arkusza Arkusz1 w pliku personal.xls. | ||||
Trzeba być tego świadomym pisząc komendy! | ||||
W załączniku: | ||||
- wyeksportowany UserForm i jego kod do plików | ||||
- wyeksportowany mod.Standardowy z Funkcjami, Procedurami, zmiennymi Publicznymi. | ||||
- Thisworkbook.txt - kod do modułu Thisworkbook Personala. | ||||
Zaimportujcie sobie do Projektu VBA Personal.xls (z poziomu Edytora VBA) | ||||
Poprawka głównie dotyczy mozliwości zapisywania do pliku ComboCooki.txt Pomysł ze zmienną bSave nie sprawdzał się jeśli całość | do pobrania (wersja 2) | |||
miała trafić do Personala. Szybkie objeście tego problemu polegało na procedurze zmieniającej tę zmienną jednak musiała to być procedura | ||||
publiczna a ja chciałem żeby takich nie było żeby po Alt+F8 nie były widoczne w oknie makr do wywołania. | imm.zip | |||
Pod przyciskiem Add obok ComboBox'a jest procedura która zapisze zawartość Combo do ComboCooki.txt pod warunkiem że ciąg nie | ||||
znajduje się już w tym pliku. Realizuje to procedura… | ||||
Private Sub CommandButton1_Click() | ||||
With Me.ComboBox1 | ||||
If Len(.Text) = 0 Then Exit Sub | ||||
If TypeName(Application.Match(.Text, .List, 0)) <> "Error" Then MsgBox "Było": Exit Sub | ||||
Dim nr As Integer: nr = VBA.FreeFile | ||||
Open ThisWorkbook.Path & "\txt\ComboKuki.txt" For Append As #nr | ||||
Print #nr, .Text | ||||
Close #nr | ||||
End With | ||||
End Sub | ||||
Jest kilka innych zmian ale niewielkich. Nie zdołałem jeszcze pozbyć się mrugania okna spowodowanego aktywowaniem okna VBE :-| | ||||
Application.VBE.MainWindow.Visible nie pomogło, ale cóż - taka uroda metody :-P | ||||