カテゴリー: Excel/VBA/.net

ExcelVBA覚書 VBAプロジェクトロック解除

こんなんで解除できるパスワードって、なんのためについているのやら・・・
と思ったりするが、まぁパスワード忘れたときは非常にありがたいのだ。

参考URL
ホームページ制作のサカエン「Excel VBA マクロ パスワードを解除する方法 32ビット 64ビット」

ここからは64bit版だけ転載
(LongPtrをLongに、PtrSafeを削除すれば32bit版になる)
——————————-

Option Explicit
  
Private Const PAGE_EXECUTE_READWRITE = &H40
  
Private Declare PtrSafe Sub MoveMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As LongPtr, Source As LongPtr, ByVal Length As LongPtr)
Private Declare PtrSafe Function VirtualProtect Lib "kernel32" (lpAddress As LongPtr, ByVal dwSize As LongPtr, ByVal flNewProtect As LongPtr, lpflOldProtect As LongPtr) As LongPtr
Private Declare PtrSafe Function GetModuleHandleA Lib "kernel32" (ByVal lpModuleName As String) As LongPtr
Private Declare PtrSafe Function GetProcAddress Lib "kernel32" (ByVal hModule As LongPtr, ByVal lpProcName As String) As LongPtr
Private Declare PtrSafe Function DialogBoxParam Lib "user32" Alias "DialogBoxParamA" (ByVal hInstance As LongPtr, ByVal pTemplateName As LongPtr, ByVal hWndParent As LongPtr, ByVal lpDialogFunc As LongPtr, ByVal dwInitParam As LongPtr) As Integer

Dim HookBytes(0 To 5) As Byte
Dim OriginBytes(0 To 5) As Byte
Dim pFunc As LongPtr
Dim Flag As Boolean
  
Private Function GetPtr(ByVal Value As LongPtr) As LongPtr
  GetPtr = Value
End Function
  
Private Sub RecoverBytes()
  If Flag Then MoveMemory ByVal pFunc, ByVal VarPtr(OriginBytes(0)), 6
End Sub
  
Public Function Hook() As Boolean
  Dim TmpBytes(0 To 5) As Byte
  Dim p As LongPtr
  Dim OriginProtect As LongPtr
  
  Hook = False
  pFunc = GetProcAddress(GetModuleHandleA("user32.dll"), "DialogBoxParamA")
  If VirtualProtect(ByVal pFunc, 6, PAGE_EXECUTE_READWRITE, OriginProtect) <> 0 Then
    MoveMemory ByVal VarPtr(TmpBytes(0)), ByVal pFunc, 6
    If TmpBytes(0) <> &H68 Then
      MoveMemory ByVal VarPtr(OriginBytes(0)), ByVal pFunc, 6
      p = GetPtr(AddressOf MyDialogBoxParam)
      HookBytes(0) = &H68
      MoveMemory ByVal VarPtr(HookBytes(1)), ByVal VarPtr(p), 4
      HookBytes(5) = &HC3
      MoveMemory ByVal pFunc, ByVal VarPtr(HookBytes(0)), 6
      Flag = True
      Hook = True
    End If
  End If
End Function
  
Private Function MyDialogBoxParam(ByVal hInstance As LongPtr, ByVal pTemplateName As LongPtr, ByVal hWndParent As LongPtr, ByVal lpDialogFunc As LongPtr, ByVal dwInitParam As LongPtr) As Integer
  If pTemplateName = 4070 Then
    MyDialogBoxParam = 1
  Else
    RecoverBytes
    MyDialogBoxParam = DialogBoxParam(hInstance, pTemplateName, hWndParent, lpDialogFunc, dwInitParam)
    Hook
  End If
End Function
  
Public Sub VBAProjectパスワード解除()
  If Hook Then
    MsgBox "VBAProjectのパスワード解除成功!", vbInformation, "Congratulations"
  End If
End Sub

Access覚書 リンクテーブルをローカルテーブルに変換(VBA)

リンクテーブルをローカルテーブルに変換したい!VBAで!!

と、色々調べてみたのだが、
同じようなことをしたいという質問に、
「エクスポートしたら?」とか、「手作業でできるよね?」
とか、そういう話ばっかりで、
「いやいや、質問者の意図わかってんの?」
って回答しかなくってウンザリ。

で、苦手な英語、といってもカタコト英語だが、
convert linktable to localtable
みたいな感じで調べてみたら、外国人はちゃんとVBAでの解決策を答えてくれていた。

で、回答。

DoCmd.SelectObject acTable, "TABLE1", True
DoCmd.RunCommand acCmdConvertLinkedTableToLocal

“TABLE1” のところを変換したいリンクテーブル名にしてやる。

これ調べるのに半日かかったわ。
でも、もう最悪、テーブルを別ファイルにエクスポートしてからインポートしようかとさえ思っていたので、本当に助かった。
やっぱり、いろんな言語ができたほうがいいのね。
(日本語しかできません。ってか、日本語もできません。)

参考URL:
https://stackoverflow.com/questions/59038993/access-vba-convert-linked-table-to-local

Access覚書 引数を引き渡して起動

Access起動時にパラメータを引き渡し、
渡されたパラメータによって処理をかえる方法。

起動スイッチというのがあるらしい。

参照:Microsoft Office 製品のコマンド ライン スイッチ

ということで

"C:¥.........¥MSACCESS.EXE" "......accdb" /cmd "para1" "para2" "para3" ...

順に
Accessのexe 起動するAccessファイル /cmd パラメータ(必要分)
をそれぞれダブルクォーテーションで括って設定するのが重要らしい。

そして、Access側のVBAはこの引数を引き取って処理を行う。

Command関数
参照:Microsoft Support Command関数

このようにパラメータで渡された文字列は、AutoExecマクロ内で処理をすることにする。

  1. マクロを追加 名称は「AutoExec」にする
  2. 作ったマクロに「プロシージャの実行」を追加し、3.で作成するFunctionプロシージャ名を記載する
  3.   ex) RunProc() ← 必ず()をつける

  4. ここからVBA
  5. 標準モジュールを追加して、2.のマクロで実行させるFunctionプロシージャを追加する
      ex) Function RunProc() …. End Function
    Functionでないとダメで、SubだとCommandを認識しなかった。

  6. 3.のFunctionプロシージャ内に引数を使った処理を記載する
  7.    Function RunProc()
           ' Trimを忘れずに.これはパラメータが複数の時なので1つしかない時は、Trim(Command())でOK
           Dim cmd() As String
           cmd = Split(Trim(Command()), " ")     
           ' 1つめの引数で処理を分岐
           Select Case cmd(0)
           Case "para1"
              ' ここに処理
           Case "paraX"
              ' ここに処理
           Case Else
              ' ここに処理
           End Select
       End Function
    

<注意点>
Command関数の結果は必ずTrimすること。起動コマンドで””で括っているが後ろにスペースが入って渡ってくる。

起動コマンドで最初にexeを指定したくない、ランタイム起動でも問題ない、という場合は、

"......accdb" /runtime /cmd "para1" "para2" "para3" ...

と、/runtimeを挟むと、exeの指定をせずにCommandが引き渡され、処理が走る。


んで、次。
もう1つ別のやり方があるのだが、それは /x スイッチ。

"......accdb" /runtime /x "macro1" /cmd "para1" "para2" "para3" ...

という感じにすると、
/x の後ろに入れたマクロ「macro1」を実行させられるようになる。
で、さっきと同じくプロシージャの実行をマクロに追加して、実行させるFunctionプロシージャを指定する。
VBA側は下記のようにしてパラメータを読み込んでおけばいい。

   Function RunMacro1()
       Dim cmd() As String
       cmd = Split(Trim(Command()), " ")
       ' ここに処理
   End Function

このやり方だと、わざわざ分岐処理を書く必要がないので、見た目が良いかなと思ったりする。


さて、最後。
起動コマンドからランタイム実行(/runtime 付)させたときに、AutoExecは走らせたくないなぁって思ったら、
AutoExecで動くFunction(またはSub)内の先頭に、こういう1文を先頭に入れておくといい。

If SysCmd(acSysCmdRuntime) Then Exit Function

普通に起動させたときは後続処理が走るが、ランタイム起動させたときはプロシージャを抜けるようにしておくこともできるわけだ。

奥が深いぜ、Accessさんよぉ

※ランタイム:Accessから開発機能(フォーム、クエリ、レポート、VBAの作成編集機能)を除いたもの

AccessVBA覚書 郵便番号から住所を出す(MsYubin7.dll)

Accessで住所支援入力というのがあるが、プロパティで設定せずにロジックで行うにはどうしたらよいかという問題への対応策。
使うのは住所支援入力で使っているライブラリと同じもの(だとは思う)。MsYubin7.dll。

3つのテキストボックスPrefText、CityText、TownTextに、都道府県、市区町村、町域をそれぞれ設定する前提で。

まず呼出元。

Dim res() As String
ConvZip2arrAddr("1600005",res)
PrefText = res(0)
CityText = res(1)
TownText = res(2)

まず呼出先。これは別モジュールを作成して記載しておけばいい。

Private Declare PtrSafe Function zcGetZipDecision Lib "MSYubin7.dll" Alias "GetZipDecision" _
                                                                            (ByVal ZipCode As String, _
                                                                            ByVal szKen As String, _
                                                                            ByVal szCty1 As String, _
                                                                            ByVal szCty2 As String, _
                                                                            ByVal szTwn As String, _
                                                                            ByVal szTwnExt As String) As Long

Public Sub ConvZip2arrAddr(ByRef zipCd As String, arrAddr() As String)
    
    Dim pref    As String * 40
    Dim city1   As String * 40
    Dim city2   As String * 40
    Dim town1   As String * 40
    Dim town2   As String * 500
    Dim arrRet(4)    As String
    
    On Error GoTo ErrFunc
    
    If zipCd = vbNullString Then Exit Sub
    If Len(zipCd) <> 7 Then Exit Sub

    If Val(zipCd) Then
        zcGetZipDecision zipCd, pref, city1, city2, town1, town2
        arrRet(0) = Left$(pref, InStr(pref, vbNullChar) - 1)
        arrRet(1) = Left$(city1, InStr(city1, vbNullChar) - 1)
        arrRet(2) = Left$(city2, InStr(city2, vbNullChar) - 1)
        arrRet(3) = Left$(town1, InStr(town1, vbNullChar) - 1)
        arrRet(4) = Left$(town2, InStr(town2, vbNullChar) - 1)
        ReDim arrAddr(0 To 2)
        arrAddr(0) = arrRet(0)
        arrAddr(1) = arrRet(1) & arrRet(2)
        arrAddr(2) = arrRet(3) & arrRet(4)
    End If
    Exit Sub
    
ErrFunc:
    Debug.Print "No." & Err.Number & ":" & Err.Description
End Sub

Access2019で試したが、他のバージョンでもそれほど違いはないと思う。

ExcelVBA覚書 On Error エラー発生後のエラー

VBAの例外処理の話。
JavaやC#とかだと、Try-CatchのCatchの中でのエラーについては、Catch内にさらにTry-Catchを入れるという手法を取ると思うのだけれど、VBAになると、少々めんどくさい。いやかなりめんどくさい話になる。

ブイサバ【Excel VBAサバイバル】~とりあえずここに来れば解決できる~『VBA|エラー処理を2回目以降も処理できる方法』

この記事でいくと、VBAの例外処理(On Error文)で気をつけておかなければならないことがあるようだが、
端的に言えば、下の2つのことを考慮するとよいということになる。

  1. エラー処理中のエラーはトラップされないヨ
  2. とはいえ、Resumeステートメントで、仕切り直しできるんだよ

というので実践編。

Sub Sample1   
    On Error Goto ErrLabel1
    *** エラー発生(1) ***
    Exit Sub
ErrLabel1:
    MsgBox "No." & Err.Number & Err.Description , "Error"
    Resume ErrLabel2    '←←←←ココで仕切り直し!!
ErrLabel2:
    On Error Resume Next
    *** エラー発生(2) ***
End Sub

まずは「On Error Goto ErrLabel1」で例外処理へスキップする処理を入れる。
エラー発生(1)の箇所でエラーが発生すると、ErrLabel1ラベルに飛ぶ。
エラーを表示させた後の「Resume ErrLabel2」というところが仕切り直しの箇所。
なので、Errをウォッチすると、「Resume ErrLabel2」の箇所ではエラーNoが設定されているが、ErrLabel2ラベル下の「On Error Resume Next」の行になると、エラーはクリアされている。

よって、ErrLabel2以降で、再度トラップ可能なOn Error文を指定してあげればよい。
(ここではResume
Nextにしているので、エラーがあると次行へ進むようになっている)

因みに、こういうことをやらずにErr.Clearとかしてみたらどうなるのか試したのだが、これだと2つ目のエラーはトラップされなかった。
んじゃぁ、Resumeじゃなくて、Gotoだったら?とやってみたが、この場合はErrにそのままエラー情報が残った状態で、ErrLabel2に飛んでいた。

なので、「Resume ラベル名」
面倒だがこういう手を使わなければならない。

ExcelVBA覚書 AutoExecを走らせないようにAccessファイルを開く

AccessではAutoExecなどという起動時に走らせるマクロがある。
ExcelならばAuto_Openなどというプロシージャで昔は作っていたようだ。(今だとThisWorkbook_Openとかだろうか)

Accessファイルを起動するとき、このマクロを起動させたくなければ、一旦Shiftキーを押しながらファイルを開くのだが、これをVBAで実施したいときどうすればよいのだろうか・・・

Excelファイルを開くときは、

Excel.Application.EnableEvents = False

とすればよいのだが、Access.Applicationにはそのようなプロパティがない。

というので、前にちらっとSendKeyでShiftキーを押して起動しているコードを見たこともあり、同じようなものを作ってみることにした。
ただ、SendKeyはちょっと嫌なのでSendInputというAPIを使った方法にすることにした。(どっちも嫌だけど)

まずはTypeの定義

Private Type KEYBDINPUT
    VK          As Integer
    Scan        As Integer
    Flags       As Long
    Time        As Long
    ExtraInfo   As Long
    Dummy1      As Long
    Dummy2      As Long
End Type

Private Type INPUT_TYPE
    IType           As Long
    KI              As KEYBDINPUT
End Type

WinAPIの定義(64bitの場合PtrSafeをお忘れなく)

Private Declare Function SendInput Lib "user32.dll" (ByVal nInputs As Long, pInputs As INPUT_TYPE, ByVal cbsize As Long) As Long

利用する定数の定義

Private Const VK_SHIFT        As Long = &H10          'Shiftキー
Private Const KEYEVENTF_KEYUP As Integer = &H2        'KeyUp(KeyDownのほうは0)
Private Const KEYEVENTF_EXTENDEDKEY As Integer = &H1  '拡張コード
Private Const INPUT_KEYBOARD As Integer = 1           'KeyboardイベントでSendInputを利用する

ここまでで定義が済んだので、Accessファイル起動処理を書いていく。
SendInputについての情報は以下の通り。

  • SendInputはMouseイベントなどでも利用できるので、Keyboardイベントであることを指定する。
  • Shiftキーを押した→ファイルを起動した・・・とここまでで終わりではなく、Shiftキーを戻す(KeyUp)の処理まで行わないと、ずっとShiftキーが押された状態のままになってしまう。
  • SendInputの最初の引数はイベントの数で今回は1つなので1。例えば、Dキー⇒Oキー⇒Enterキーと押す場合は3。
  • SendInputの第2引数は押下するキーの情報(配列)、第3引数は第2引数のデータ長。
    Dim ac As Object
    set ac = CreateObject("Access.Application")
    Dim it(0) As INPUT_TYPE

    'Shiftキー押下
    With it(0)
        .IType = INPUT_KEYBOARD        
        .KI.Vk = VK_SHIFT             
        .KI.Scan = 0
        .KI.Flags = KEYEVENTF_EXTENDEDKEY Or 0                'DOWN
        .KI.Time = 0
        .KI.ExtraInfo = 0
    End With
    SendInput 1, it(0), Len(it(0))
    
    'Accessファイルを開く
    ac.OpenCurrentDatabase {起動するファイル名}, True, {パスワード}
        
    'Shiftキー押下の戻し
    it(0).KI.Flags = KEYEVENTF_EXTENDEDKEY Or KEYEVENTF_KEYUP   'UP
    SendInput 1, it(0), Len(it(0))

最終的にAccessを閉じて処理終了

    ac.Quit acQuitSaveNone

フラグを渡すのに「KEYEVENTF_EXTENDEDKEY Or 」というのをつけないと、反映されなかった。
ここに手間取り結構時間がかかったのと、あとはとにかく「こんなことやりたいねん!」って検索しても出てこない、っていうところで時間がかかった。
VBA Access Autoexec 無効 とかキーにしても、Shiftキー押しながら起動すればいいよ!としか出なくて腹立つぅ~~~!!
って思ってしまった。検索能力ってIT技術の1つだなぁ~とつくづく実感。

参考URL
VBレスキュー(花ちゃん) 『3.SendInput 関数を使ってプログラム上からキーボードを操作する(12_Key_03)』

Microsoft Windowアプリ開発『仮想キー コード』

StringBuilderってどうよ?

StringBuilderの効果ってどうなのか?というところに疑問を持ったので、調べてみることにした。

Javaの文字列結合はStringBuilderでいいのか

結論的には、

・複数ステートメントで
・結合される側の文字列を複数スレッドで共有しない場合は
メモリアロケーションの回数が少ないStringBuilderが一番高速で文字列結合できる

なのだが、どのような処理になっているのでこうなる!というのが書かれていて、非常に参考になった。

スレッドを使ったときは不具合が起きるというのは、以下のページに書いてある。

【Java】+演算子、StringBuilder、StringBufferの違い(実際に測ってみた。)

StringBuilderとStringBufferの使い分けについても書いてあるので、これも参考に。

以下はVB(.NET)のStringBuilderについての記載。

Visual Basic で StringBuilder を使うべき場合とその利点

こちらもJavaと同じような話。(SE2年目でこの記載は凄いなぁ。うちの社員にもこういう人欲しい。)

使い方については、

意外と知られてないStringBuilderに関する初歩的なTips【Java・C#】

なんか、どうでしょう。
インスタンスをコロコロ変えたくないなぁと思っているので、こういうのはありがたい。

が、少し待て!

男(かどうか知らんが)がすなる時間計測といふものを女もしてみむとてするなり・・・

VC#(.NET Framework4.8)でやってみた。
<1> Length=0でクリアする
<2> 新たなインスタンスを作る
<3> Clearを使う(C#はClearメソッドがある)

        static void Main(string[] args) {
            var sw = new System.Diagnostics.Stopwatch();

            // <1>
            for (int j = 0; j < 10; j++) {
                sw.Start();
                StringBuilder sb1 = new StringBuilder("");
                for (int i = 0; i < 999999; i++) {
                    sb1.Length = 0;
                    sb1.Append(i.ToString());
                }
                sw.Stop();
                Console.WriteLine($"1) {sw.ElapsedMilliseconds}ms");
            }

            // <2>
            for (int j=0;j<10;j++) {
                sw.Restart();
                for (int i = 0; i < 999999; i++) {
                    StringBuilder sb2 = new StringBuilder("");
                    sb2.Append(i.ToString());
                }
                sw.Stop();
                Console.WriteLine($"2) {sw.ElapsedMilliseconds}ms");
            }

            // <3>
            StringBuilder sb3 = new StringBuilder("");
            for (int j = 0; j < 10; j++) {
                sw.Restart();
                for (int i = 0; i < 999999; i++) {
                    sb3.Clear();
                    sb3.Append(i.ToString());
                }
                sw.Stop();
                Console.WriteLine($"3) {sw.ElapsedMilliseconds}ms");
            }
        }

結果発表!

<1> Length=0でクリアする <2> 新たなインスタンスを作る <3> Clearを使う
1) 693ms 2) 1440ms 3) 583ms
1) 1303ms 2) 1495ms 3) 503ms
1) 2398ms 2) 1455ms 3) 472ms
1) 3421ms 2) 1401ms 3) 514ms
1) 4656ms 2) 784ms 3) 520ms
1) 5780ms 2) 692ms 3) 500ms
1) 6798ms 2) 580ms 3) 498ms
1) 7786ms 2) 599ms 3) 519ms
1) 8868ms 2) 553ms 3) 485ms
1) 9937ms 2) 643ms 3) 469ms

<1> Length=0でクリアだと、処理速度がどんどん上がっていくので、嫌な感じ。
<2> 新たなインスタンスを作るは、<1>より速くて<3>と同等かもしくは遅いレベル。
<3> Clearを使うのが一番コンスタントに速い。

Javaは試していないので申し訳ないのだが、VC#ならば、用意されているClearメソッドを利用したほうがインスタンスを毎度作るよりは速いということになる。

色々調べてみるものだ、うん。
「無知の知」というのは大事だなぁと思う午後のひと時であった・・・

AccessVBA覚書 Public変数が変わらない

Accessは嫌いです。
理由はわかんないから。

オイオイ・・・

って、そんな理由かい!って思う人もいるのだろうけど、わからないというのは
・想定通り動いてくれない
・Debugしながら動かしたときと、Debugしないで動かしたときで動作が変わる
という2点からだ。

この間VB6のソースを見ていた時も実は同じようなことがあって、
・Debugしながら動かしたときと、Debugしないで動かしたときで動作が変わる
というのは、どうしたらいいのかさっぱりわからないのだ。

そして、VBAをDebug.Printを使って変数値を見ながら実行させてみたらば、Public変数に値を入れているのに値が置き換わっていないことが判明。

で、調べてみたらこんな記事があった。

Public宣言された変数の有効期間 [VBA]

なるへそ。
ってかさ、別に参照設定とかPublic変数を変えたとか、致命的なエラーが出たとかなら、リセットされるのはわかりますよ。
でもね、実行中にリセットする必要ありますか?

何のためのPublic変数なんですか!!!!

と。

で、上述の記事でテーブルに入れるのがいいと書いてあったので、結局その通りいたしました。
Excelでこんなこと起きたことはないんですけどね。
でも起きたことがないだけで、起きることがあるんだと思うとExcelのマクロも作れませんな・・・

大丈夫かMicrosoft。

ExcelVBA覚書 CSV読込とSchema.ini

CSVファイルを読み込むときに、ADODBを使って読み込むやり方というのがあって、これには
1,ADODB.Streamを使う
2,ADODB.CommandとADODB.RecordSetのGetStringを使う(SELECT * FROM [{ファイル名}])
という2種類の方法があるというので、サンプルを実行してみたら、
1より2のほうが若干処理が速い。
ただ、2には落とし穴があって、それは
★勝手に型を変えてしまう
★読み込み方によっては正しくすべてのデータが取り込めない
ということだと分かった。

前0がついていようが数値にしよるし、日付は勝手に日付型でシートに出力されてしまうは、設定の仕方によっては思い通りのデータ取込ができなくなってしまう。

ってかさ、こんないい加減なサンプル送ってくんなよ!!
(他のところで実行時エラー起きてるし・・・)

ってなことであったが、どうすれば改善できるのか知らない私。
いろいろやってみて途方に暮れたところでGoogle先生の厄介になってみたら、

Schema.ini使うんです!

みたいな記事発見。で、「ADODB Schema.ini」で検索をかける。
ふむふむ、取り込む列の型とか、文字エンコードとか、このファイルの中で指定してあげればよいのだね!
————-
[{ファイル名}]
ColNameHeader=False
CharacterSet=65001
Format=CSVDelimited
Col1=列1 Text
Col2=列2 Text
Col3=列3 Text
Col4=列4 Text
Col5=列5 Text
—————
{ファイル名}は実際に読み込むファイル名(パスはなしで、ファイル名だけでいい)
ColNameHeader は、ヘッダー有無。(Trueはあり)
Format=CSVDelimited は、CSV形式(カンマ区切り)
CharacterSet=65001 は文字エンコードがUTF-8である。(S-JISなら932)
下に読み取り用のサンプルソースをつけているが、そこでCharacterSetを指定しているので,iniには必要ない。
(どちらかに設定があればOK)
Col1=*** の *** は、列のタイトル、その右のTextが型

【VBA】ADOを使用してテキストファイル(CSV)をDB操作する方法のまとめ

を参照したところ、型は整数はShortやLong、日付はDateと記載されている。
Char型で50文字なら、「Char Width 50」といったように記載されているので、固定長であればこういう記載になるのかと思う。
(試してはいない)

で、このSchema.iniをcsvファイルのあるフォルダパスと同じところに入れておくのだが、

チョイ待ち!!

読み込むcsvファイルが常に同じところに置かれているとは限らんではないか!
ということになった。
そう、読み込むCSVは、ダイアログで選ばせるのだ・・・
となると、csvと同じところにschema.iniを配置させねばファイルの意味がないし、いや、そもそもダイアログで選ぶファイル名もSchema.iniで定義したファイル名と異なっていたら読み込んでくれへんやん!!!

と、また1つ問題が生じてしまった。
ということで、最終的には、

a) 一時保存用のフォルダを作る
b) a)で作ったフォルダにSchema.iniを入れる
c) ダイアログで選択されたCSVファイルを a)で作成したフォルダに固定のファイル名にコピーする
  (固定のファイル名というのが、Schema.iniに定義する{ファイル名}と同じになるように)
d) c)のコピーされたほうのファイルをADODBでSELECTして読み込む
e) 読込が終わったら、c)のコピーされたほうのファイルを削除

として、どの場所にあっても、同じフォーマットで取り込まれるようにしたのだった。
ってなると、ADODB.Streamを使うより時間がかかってしまったので、結局Streamのほうが速い!という結論に至った。

とはいえ、CSVファイルの全データが取得できなかった点も、Schema.ini、Properties(“Extended Properties”)や、GetStringメソッドを見直して何とかとりあえずうまく行くようにはなった。
Officeは2016。

    Dim con As ADODB.Connection
    Dim cmd As ADODB.Command
    Dim rs  As ADODB.Recordset

    Set con = New ADODB.Connection
    With con
        .Provider = "Microsoft.ACE.OLEDB.12.0"
        .Properties("Extended Properties") = "Text;CharacterSet=65001;"    '65001=UTF-8 ,932=SJIS
        .Open {csvファイルのあるフォルダパス}
    End With
    Set cmd = New ADODB.Command
    Set cmd.ActiveConnection = con
    cmd.CommandText = "SELECT * FROM [{CSVファイル名}] "
    Set rs = New ADODB.Recordset
    rs.CursorType = adOpenStatic
    rs.Open cmd
    
    Dim dmp As String
    dmp = rs.GetString(adClipString, , ",", vbCrLf, "")  '行はCRLF, 列はカンマで区分するという設定に
    rs.MoveFirst
    Do Until rs.EOF
        '****** 1行分の処理を記載 ***********  
        rs.MoveNext
    Loop
   
  ’どばっとExcelに貼りつけるなら、Range.CopyFromRecordsetメソッドで 
    ' Activesheet.Cells(1,1).CopyFromRecordset rs            みたいな感じ

   rs.Close
    Set rs = Nothing
    Set cmd = Nothing
    con.Close
    Set con = Nothing

GetString メソッド (ADO)

中途半端なサンプルを送りつけられたせいで悩んだが、かえっていい勉強になったわぃ!
ということにしておこう。

VB.NET覚書 バックグラウンドプロセスからExcelが消えない

VB2019で、ライブラリMicrosoft.Office.Interop.Excelを使ってExcel出力を行っている。
Officeは2019だ。
COMオブジェクトなので、ReleaseComObjectでメモリ解放をしているのだが、

だが、処理が終わってもバックグラウンドプロセスからExcelが消えない・・・

リリースしてまっせ!
と思うのだが、何故か残る。

というので調べた。

・Rangeとか変数定義してるところ、リリースしてる?
 全部リリースするのよ!
・リリースはしてても時差があったりするよ!

んーーーーーー、そっか。でも頑張ってBookもSheetもRangeも処理いれたんだけど。

ということで、最後。ReleaseComObjectをぐるぐる回す。

<参照サイト>
ドリリウム『【Interop.Excel】Excelプロセス絶対殺すコード』

このサイトのWhileでReleaseComObjectを回してリリースさせるようにする!っていうのをやってみた。
とりあえず、残らなくなった気がする・・・
気がする。(ちょっと不安やけど)

Visual Studio 2015 インストーラ他

なんだかもう仕事がいやになりました。
色んなことが進みません。

サーバと接続できないし、Visual Studioもインストールできないし・・・

なんで、最新バージョンしかださず、古いバージョンも簡単に表示してくれないんだ!
必要なんだよ!!!

と、怒っていたが、すんなり行くときはいくもので。
とりあえず2015バージョンのダウンロードページを見つけたので貼っておく。

https://my.visualstudio.com/Downloads?q=visual%20studio%202015&wt.mc_id=o~msft~vscom~older-downloads

結局ダウンロードするには、Microsoftのアカウントが必要・・・

Excel VBA覚書 クリップボード

クリップボードに文字列を貼り付けるときに、貼り付かないケースがあったので、
別の方法を調べてみた。

String型変数 buf の内容をクリップボードに設定するということで・・・
a) DataObjectを使う方法1

        Dim cb As Object
        Set cb = New DataObject
        cb.SetText buf
        cb.PutInClipboard

b) DataObjectを使う方法2

        With New MSForms.DataObject
            .SetText buf
            .PutInClipboard
        End With

c) TextBoxを使う方法

       With CreateObject("Forms.TextBox.1")
          .MultiLine = True
          .Text = buf
          .SelStart = 0
          .SelLength = .TextLength
          .Copy
        End With
        If cb.CanPaste Then cb.Paste

Access覚書 リンク貼替

結構間が開いてしまった。
1か月に1度ぐらいはせめて投稿しようと思いつつ。

テーブルやらクエリやらがAサーバのDBをみているのだが、Bサーバのテスト環境のDBをみるように変えたい
となったとき、結構大変である。
標準機能で変更できるのかどうか、やり方がわからん。

ということで、VBAでできないか探してみたら、やはり一括でどべっと変更できるやり方があった。

' テーブルとクエリのリンク先を変更
' 引数 :from_srv = 変更前(例:172.0.0.1)
'    :to_srv   = 変更後(例:196.128.1.10)
Sub ChangeLinks(from_srv As String, to_srv As String )

    Dim db As DAO.Database
    Dim tb As DAO.TableDef
    Dim qr As DAO.QueryDef
    
    Set db = CurrentDb
    On Error Resume Next
    For Each tb In db.TableDefs
        If tb.Connect <> "" Then
            tb.Connect = Replace(tb.Connect, from_srv , to_srv)     'サーバ変更
            tb.RefreshLink  ' テーブルはリフレッシュ
        End If
    Next
    For Each qr In db.QueryDefs
        If qr.Connect <> "" Then
            qr.Connect = Replace(qr.Connect, from_srv , to_srv)     'サーバ変更
        End If
    Next
End Sub

パスワードとかだと、
Replace(tb.Connect, “PWD=***”, “PWD=***”)
ってな感じで変換してやる。

ExcelVBA覚書 Transport関数をうまく使う

SQLを実行してきてデータを取得するまではよいのだが、その後シート上に出力するときに異様に時間がかかる場合がある。
で、端末個体の理由は置いといて、複数のワークブックを開けていた場合に起こるのであれば、対策は、

 処理中は必要なとき以外「自動計算」させない!

ということらしい。

With Application
    .ScreenUpdating = False
    .Calculation =xlCalculationManual
    .EnableEvents = False
End With

としておけば、
 画面を更新せず、
 再計算させず、
 イベント有効にしない
とできるわけである。
これで、余計なイベントも発生しないので良いのだが、再計算してほしいときにはCaluculateメソッドを発行しておかねばならない。

で、Application.Calculation = xlCalculationManualをせず、シート上にバサッと出力したいなぁ~などと思っていたら、

 WorksheetFunction.Transpose()

シート関数のTRANSPORT関数を使って、引数に出力したい配列を設定することで、ドバっとあっという間に出力できてしまうやり方を紹介しているページがあったので、ありがたやありがたやとやってみたところ、本当にあっさりとドバっと出力された。

参考URL:エクセルの神髄「マクロVBAの高速化・速度対策の具体的手順と検証」

入れる配列は(横、縦)の状態にして、Transport関数で縦横逆にして出力!ってなことになっている。
ありがたやありがたや!

ExcelVBA覚書 図形コピー

ワークシート上にある「SHP1」という名前の図形をコピーして、「SUB_SHP1」という名前を付け、B2セル内に配置するロジックを考えてみた。
コピー元の図形のDuplicateメソッドをつかって、新しい図形(コピー先:以下サンプルでは変数「shp」のこと)を作り、その新しい図形のプロパティをいじって、図形の位置とか、図形の名前、図形に紐づくマクロの設定などを行う。
サンプルでは、セルB2の下部中央に配置する方法。

Dim ws  As Worksheet
Dim shp As Shape

Set ws = Thisworkbook.Worksheets("Sheet1")
Set shp = ws.Shapes("SHP1").Duplicate
With shp
  .Top = ws.Range("B2").Top - .Height -5
  .Left = ws.Range("B2").Left + (ws.Range("B2").Width - .Width) / 2
  .Name = "SUB_SHP1"
  .Visible = msoTrue
  .OnAction = "RunXXX"
End With
Set shp = Nothing
Set ws = Nothing

図形のコピーに「Duplicate」を使うというのは初めて知った。duplicateの意味が「複写する」ってことらしい。

AccessVBA覚書 IsNumeric関数

数値(整数)チェックをしようと、

Function CheckNumber(v as Varient) As Boolean
  CheckNumber = True
  If Len(v)=0 Then Exit Function
  If IsNumeric(v) Then Exit Function
  CheckNumber = False
End Function

とか記載したら、vに「a」とか入れてもTrueで返ってきてしまった。

Function CheckNumber(v as Varient) As Boolean
  CheckNumber = True
  If Len(Cstr(v))=0 Then Exit Function
  If IsNumeric(Cstr(v)) Then 
    If InStr(Cstr(v),".")=0 Then Exit Function '小数じゃなかったら(簡易チェック)
  End If
  CheckNumber = False
End Function

みたいに、文字列に変えてからIsNumericしたらうまくいった。
関数の引数を文字列型にするってのも手だけど、簡易版なのでこれでいいかなと思った。

Excelマクロ覚書 フォームコントロール

Blogの外観をちょいと変えてみた。また変えるかも。

さて、Windows10でExcel2010とか2013とかを使うようになって、Windows7で作ったExcelマクロのActiveXコントロール(コンボボックス)がうまく表示されないようになった。

これは困った・・・

グループ化すれば治る・・・って、治んない。

困りましたな・・・

ってことで、もうフォームコントロールに変えることにした。
面倒だわ・・・
ActiveXコントロールだと、リストの情報はマクロ上で設定すればいいのだが、フォームコントロールはリストの情報をセルに持たせて、そのセルとリンクを貼る・・・という厄介な手順を踏まなければならない。

ということで、ActiveSheet上にあるフォームコントロール「DLIST」のリストにしたい情報が、同じシートのセル「A2~A10」にあるとすると、

With ActiveSheet.Shapes("DLIST").ControlFormat
    .ListFillRange = "A2:A10"
    .LinkedCell = "B2"
End With

参照リストセルが別のシート(仮に「Sheet2」とする)にある場合、ListFillRange の設定をちょっと変える必要がある。

    .ListFillRange = "Sheet2!A2:A10"

でも、コントロールのフォントサイズの変え方がわかんない・・・
もうヤダ。

Excelマクロ覚書 Shellとカレントディレクトリ

Shell関数を使って別のexeを起動。

できんかった・・・・・・

なので調べた。

どうやら、カレントディレクトリをexeのある場所に指定しないとダメな様子。
で、ChDir関数を使ってカレントディレクトリの変更!ってやってみたけど、うまくできなかったので、別の方法でカレントディレクトリを変える。

以下は、Workbookと同じフォルダにあるtest.exeを実行してみるサンプル。
起動できたら、Excelを最小化させる

    Dim p   As String
    Dim res As Integer
    
    On Error Resume Next
    p = ThisWorkbook.Path
    If Right(p, 1) <> "\" Then p = p & "\"
    With CreateObject("WScript.Shell")
        .CurrentDirectory = p    // カレントディレクトリを変える
    End With
    res = Shell("test.exe", vbNormalFocus)
    If res <> 0 Then Application.WindowState = xlMinimized

ExcelVBA覚書 Windowsログイン名の取得

Windowsのログインユーザアカウントをとってきたい!ってのを調べてロジックに組み込んだはいいが、このブログ上にメモしてなかった。

まぁWindowsにログインするときに入力するアカウントのことなんだけど、環境変数関係の情報を取得するときは、Environ関数を使う。

Environ(“USERNAME”)

と、こんな感じ。

引数にどんなものがあるか、どんな戻り値になるか、デバッグして調べてみるときは、こんな風に。

Debug.Print "OS:" & Environ("OS")
Debug.Print "デフォルトドライブ:" & Environ("HOMEDRIVE")
Debug.Print "デフォルトユーザパス(ドライブなし):" & Environ("HOMEPATH")
Debug.Print "tempフォルダパス:" & Environ("TEMP")
Debug.Print "ログインユーザID:" & Environ("USERNAME")
Debug.Print "systemフォルダパス:" & Environ("WINDIR")