派遣事務員の迷走

派遣事務員コロ子。会社の犬。顔出しNG。常に迷走している。

VBA ひな型を壊さないようにするには?(ひな型の形を変える処理がある場合)

こんにちは。
派犬事務員のコロ子です。

前回からの続き
koroko.hatenablog.com

で、
「転記の処理の途中で、行を削除したり、列を追加したり、ひな型の形を変更する処理がある場合はどーするの?」

ですよね?

この場合は、さらに作業用のシートを用意するので、ちょっとややこしい。

① ひな型をコピーする(作業ブック)
② 作業ブックを開く
③ リストデータの数だけループ
  【ループの中で】
  ③-1 作業ブックのひな型シートをコピーする(作業シート)
  ③-2 作業シートを処理
  ③-3 作業シートを名前を付けて保存する
  ③-4 作業シートを削除する
④ 作業ブックを閉じる
⑤ 作業ブックを削除

リストデータの数だけ、シートをコピーしたり、削除したりするので、ちょっと重い処理になる。もっと良い方法があるのかもしれないけど、ひとまずコードを書いてみた。

Sub 注文書作成_3()

    Application.ScreenUpdating = False

    
    '作業用ブックの名前は「作業用年月日時分秒.xlsx」にする
    Dim tmp As String
    tmp = "\作業用" & Format(Now, "yyyyddmmhhnnss") & ".xlsx"

    '注文書ひな型をコピーして作業ブックを作成する
    FileCopy ThisWorkbook.Path & "\注文書ひな型.xlsx", ThisWorkbook.Path & tmp
    

    Dim wb As Workbook
    Dim ws As Worksheet
    
    '作業用ブックを開く
    Set wb = Workbooks.Open(ThisWorkbook.Path & tmp)

    Dim i As Long
    For i = 2 To Cells(Rows.Count, 1).End(xlUp).Row
    
        'ひな型シートを別シートにコピーする。
        wb.Worksheets("ひな型").Copy After:=Worksheets("ひな型")
        Set ws = ActiveSheet
    
        Dim strName As String
        strName = Cells(i, 2).Value
        
        '途中ポイントごとに作業ブックを保存する
        wb.Save
        
        'リストのデータを作業用ブックに転記
        ws.Range("C5").Value = strName '名前
        ws.Range("C10").Value = Cells(i, 1).Value '日付
        ws.Range("D10").Value = Cells(i, 3).Value '品名
        ws.Range("E10").Value = Cells(i, 4).Value '金額
        
        '行を削除する処理とか
        ws.Rows("12:20").Delete
        
        'wsシートを別ブックにコピー
        ws.Copy
        
        '名前を付けて保存
        ActiveSheet.Parent.SaveAs ThisWorkbook.Path & "\領収書(" & strName & ").xlsx"
        
        '閉じる
        ActiveSheet.Parent.Close
        
        'wsを削除
        ws.Delete
        
    Next i
    
    '作業用ブックを閉じる
    wb.Close SaveChanges:=False
    
    '作業ブックを削除
    Kill ThisWorkbook.Path & tmp
    
    Application.ScreenUpdating = True

End Sub

VBA ひな型を壊さないようにするには?

こんにちは。
派犬事務員のコロ子です。

今月から「ノンプログラマーのためのスキルアップ研究会」初心者講座VBA コース6期が始まりました。講師はichihuku@VBAerさんです。コロ子は僭越ながらTA(ティーチングアシスタント)をさせて頂いてます。

TAは何度やっても勉強になる

コロ子ばっかり何度もTAやらせてもらってありがとうございます。TAは本当に何度やっても勉強になる。
ノンプロ研の講座は事前課題がかなり大量に出るけど、みんなちゃんとやってくるのがすごい。事前課題でもらった質問の解決法を書いてみます。

ひな型が上書きされちゃう問題

【課題】
元の課題はこれだけど、
atmarkit.itmedia.co.jp

説明を簡単にするためにちょっとアレンジします。

下記の書式の注文書を

注文書ひな型.xlsx

リストのデータ分だけ作りたい。

リスト.xlsm

【作成方法】
① 注文書ひな型を開く
② リストデータを注文書ひな型に転記する
③ 名前をつけて保存(ファイル名:注文書(名前).xlsx)
④ ②③をリストのデータの数だけ繰り返す
⑤ 注文書ひな型を閉じる(保存しない)


リスト.xlsmのシートモジュールに下記コードを記載する

Sub 注文書作成()

    Dim wb As Workbook
    Dim ws As Worksheet
    
    '注文書ひな型を開く
    Set wb = Workbooks.Open(ThisWorkbook.Path & "\注文書ひな型.xlsx")
    Set ws = wb.Worksheets("ひな型")
    
    'リストをデータの数だけループする
    Dim i As Long
    For i = 2 To Cells(Rows.Count, 1).End(xlUp).Row
    
        'リストのデータをひな形に転記
        
        Dim strName As String
        strName = Cells(i, 2).Value
        
        ws.Range("C5").Value = strName '名前
        ws.Range("C10").Value = Cells(i, 1).Value '日付
        ws.Range("D10").Value = Cells(i, 3).Value '品名
        ws.Range("E10").Value = Cells(i, 4).Value '金額
        
        'ブックを別名で保存
        wb.SaveAs ThisWorkbook.Path & "\注文書(" & strName & ").xlsx"
        
    Next i
    
    '注文書ひな型を閉じる(保存しない)
    wb.Close

End Sub

コードを実行すると、各々の注文書ができている。

注文書ができてる

このような処理は本当に良く使う。

で、頂いた質問は「注文書ひな型にデータが入った状態で上書きされちゃった」
うわー、これ、本当によくある!
そして、ひな型が上書きされると非常に困ることも良くある。
処理の途中で行を追加したり削除したりしてる場合などは最悪!
(このサンプルでは問題ないけど。)

上書きされる原因は、処理の途中で何らかのエラーが出て止まってしまった場合、ひな型をうっかり上書き保存しちゃった、がほとんどだと思う。あのフロッピーマークは無意識に押してしまう。

コード作成中にもあり得るし、運用中にもあり得る。(自分以外の人が使う場合は絶対落ちないコードを書かなくちゃいけないけど、それは今は置いておこう)
そうなると、舌打ちしながらひな型を元に戻す作業が生じる。

そんな場合の解決法は
①ひな型をコピーする(作業ブック)
 FileCopy コピー元ブック名, コピー先ブック名
②作業ブックを開いて処理する
③途中、要所要所で作業ブックを上書き保存
④作業ブックを閉じる
⑤作業ブックを削除
 Kill ファイル名

エクセルを開いて作業してるときに勝手にできるテンポラリーファイルみたいなイメージかな?(ちょっと違う?)

作業ブックを使って処理しているので、途中エラーで止まってしまっても元のひな型はそのままなので安心。
正常に終了していれば、作業ブックは削除されているので、作業ブックが存在していたら、何かしらエラーで止まったことが分かる。さらに要所要所で保存しているので、自分以外の人が使った場合、エラーの場所が推測がしやすくなる。

Sub 注文書作成_2()

    
    '作業用ブックの名前は「作業用年月日時分秒.xlsx」にする
    Dim tmp As String
    tmp = "\作業用" & Format(Now, "yyyyddmmhhnnss") & ".xlsx"

    '注文書ひな型をコピーして作業ブックを作成する
    FileCopy ThisWorkbook.Path & "\注文書ひな型.xlsx", ThisWorkbook.Path & tmp
    

    Dim wb As Workbook
    Dim ws As Worksheet
    
    '作業用ブックを開く
    Set wb = Workbooks.Open(ThisWorkbook.Path & tmp)
    Set ws = wb.Worksheets("ひな型")
    
    Dim i As Long
    For i = 2 To Cells(Rows.Count, 1).End(xlUp).Row
        
        Dim strName As String
        strName = Cells(i, 2).Value
        
        '途中ポイントごとに作業ブックを保存する
        wb.Save
        
        'リストのデータを作業用ブックに転記
        ws.Range("C5").Value = strName '名前
        ws.Range("C10").Value = Cells(i, 1).Value '日付
        ws.Range("D10").Value = Cells(i, 3).Value '品名
        ws.Range("E10").Value = Cells(i, 4).Value '金額
        
        'ブックを別名で保存
        wb.SaveAs ThisWorkbook.Path & "\注文書(" & strName & ").xlsx"
        
        wb.Save
        
    Next i
    
    wb.Close
    
    '作業ブックを削除
    Kill ThisWorkbook.Path & tmp

End Sub

どうでしょうか?

ActiveXコントロール チェックボックスでフィルターOn/Off

こんにちは。
派犬事務員のコロ子です。

最近ブログサボってました・・・。


オートフィルターって便利だけど、▽クリックして、検索文字を入力したり、チェックボックスをポチポチしたり、なんかめんどくさい。

f:id:SNegishi:20220123181840p:plain
検索したり、ポチポチしたり

色で検索できたり、多機能だけど、日常的には文字列での検索しか使ってない。

f:id:SNegishi:20220123182606p:plain

こんな表に新規のデータを入力する。

コロ子:えっと、会社名は○○
Aさん:ちゃんと○○株式会社と入力してください。後でフィルターするときの検索が面倒になるので。
コロ子:はい。すみません。
コロ子:フルーツ、すいか
Aさん:フルーツじゃなくて果物と入力してください。それから、すいかは果物ではなく野菜です。
コロ子:すみません・・・。
Aさん:入力するときに過去に同じようなのがあるのでそれを参考にしてください。
オートフィルターで絞って探せますよ。前回の入力時に注意事項も入力しているので、そちらも必ず参考にしてください。
コロ子:はい・・・。

フィルタでポチポチしながらデータを絞る。
○○株式会社はみかんだけど、オレンジでもいいのかー。注文書に今回はグレープフルーツも可とかいてある。よし、備考に書いておこう。
△△株式会社の胸肉は皮なしなのね。

最初はいいけど、これを1日何十回。
フィルタの解除も▽をクリックして「全て選択」をポッチ。しかも解除すると、なんかへんな位置にカーソルが行ってる。今入力してるの一番下の行なんだけど!
ちっちゃいイラっとも溜まればストレス。
なのでチェックボックスのOn/Offでフィルターできる機能を付けてみた。

チェックオン:カーソル位置の値で絞る
チェックオフ:絞り解除。元のカーソル位置に戻る

ActiveXコントロール チェックボックス

先ずは、チェックボックスを挿入する

開発タブ

挿入

チェックボックス
を選択する
f:id:SNegishi:20220123205921p:plain

先頭行に配置する
f:id:SNegishi:20220123210343p:plain

先頭行は固定にしておく。(フィルター機能付けてるので当然だけど)
f:id:SNegishi:20220123211153p:plain


準備できたらコードを書いてみよう。
ActiveXコントロールはシートのイベントが使える。素晴らしい!

f:id:SNegishi:20220123212005p:plain

シートモジュールのオブジェクトボックスに先ほど作成したCheckBox1~5がいる。
となりのプロシージャボックスからClickを選択する。

'最初のカーソル位置を記憶しておく
Private myRan As Range 

Private Sub CheckBox1_Click()
    Call CheckBox_Filter(CheckBox1.Value, 3)
End Sub

Private Sub CheckBox2_Click()
    Call CheckBox_Filter(CheckBox2.Value, 4)
End Sub

Private Sub CheckBox3_Click()
    Call CheckBox_Filter(CheckBox3.Value, 5)
End Sub

Private Sub CheckBox4_Click()
    Call CheckBox_Filter(CheckBox4.Value, 6)
End Sub

Private Sub CheckBox5_Click()
    Call CheckBox_Filter(CheckBox5.Value, 7)
End Sub
'ckBox As Boolean チェックボックス値(オン/オフ)
'c As Long フィルターする列
Private Sub CheckBox_Filter(ckBox As Boolean, c As Long)

    Application.ScreenUpdating = False
    
    If ckBox Then
    
        '元のカーソル位置を記憶(チェックボックスが全てオフのとき)
        If myRan Is Nothing Then
            Set myRan = ActiveCell
        End If
        
        '元のカーソル行でチェックが入ったチェックボックスの列の値を含む文字列でフィルタする
        Dim searchStr As String
        searchStr = Cells(myRan.Row, c)
        
        Range("A1").AutoFilter c, "*" & searchStr & "*"
        
    Else
    
        Range("A1").AutoFilter 'オートフィルタをはずす
        Range("A1").AutoFilter 'オートフィルタを付ける
    
        '全てのチェックボックスのチェックをはずす
        Dim ckb As OLEObject
    
        For Each ckb In OLEObjects
    
            If InStr(ckb.Name, "CheckBox") <> 0 Then
                ckb.Object.Value = False
            End If
    
        Next
        
        Application.ScreenUpdating = True
        
        '最初の位置を選択してmyRan変数をリセット
        If Not myRan Is Nothing Then
            myRan.Select
            Set myRan = Nothing
        End If

    End If
    
Application.ScreenUpdating = True
    
End Sub

Range("A1").AutoFilterはオートフィルターがない場合はつける、ついている場合は外す。
Range("A1").AutoFilterでオートフィルターを外すと絞り込みを解除するだけではなく、オートフィルター自体も解除されてしまうので、もう一度Range("A1").AutoFilterでオートフィルターを付ける。何かダサいので多分もっといい方法があると思う。

複数チェックボックスにチェックを外す場合
ckb.Object.Value = False
でいちいちイベントが発生してしまう。
Application.EnableEvents = False
ckb.Object.Value = False
Application.EnableEvents = True
でイベントの発生を無効にしようと思ったけど、上手くいかない。
ActiveXコントロールはApplicationのイベントではないのかな?

なので何度もこの処理を通るため、元の位置に戻るときにmyRanがnothingかどうか判定した。

【結果】
超快適!


ちなみにActiveXコントロールのプロパティは
①開発タブ

②デザインモード

③対象のオブジェクトを選択

④プロパティ
から設定できる。
設定し終わったらデザインモードをオフにするのを忘れずに。
f:id:SNegishi:20220124062330p:plain

プルダウンでなくてユーザーフォームで選ぶ(プルダウンの文字数が多い場合)

こんにちは。
派犬事務員のコロ子です。

以前書いた「連動したプルダウンを作る」の記事に「リストに入れる文字数が多くなるとVBAが壊れる現象が起きる」というコメントを頂いて、調べてみたら、プルダウンリストには255文字までしか入らないらしい。

tonari-it.com

おお!知らなかった。
でも、コロ子のコードでは「名前付き範囲を指定する」方法なので文字制限は気にしなくて良さそう。実際試してみたら255文字以上も大丈夫だった。

どういう状態なのかな。大丈夫かな?
状況が良く分からないけど、プルダウンで長い文字を選択するのはちょっと大変そう。
そこで、長~い文字を簡単に入力するにはどうすれば良いか考えてみた。
VLOOKUPのパワーアップ版のイメージでユーザーフォームを使うのはどうだろうか?

【例】
この表のコメント列を入力したい。
プルダウンで選択するには文字列が長すぎる。

一覧表シート


①長い文字列の選択用のシートを作成する(選択用コメントシート)

A列:見出し
B列:長い文字列(内容)


②ユーザーフォームを作成する

・表のD列でダブルクリックをするとユーザーフォームが立ち上がる。
・ユーザーフォームのリストに見出しが出る。
・見出しを選択すると、テキストボックスに内容が表示される。
 テキストボックス内で内容の変更も可能。
・OKボタンを押すと表のコメント欄に内容が入る。

ではユーザーフォームを作ってみよう。

ユーザーフォーム名:UserForm2(いろいろいじっていたら2になっちゃった)
上のボックス:ListBox1
下のボックス:TextBox1
※TextBox1は改行可にするためにプロパティのEnterKeyBehavior をTrueにする。
OKボタン:CommandButtonで作成。オブジェクト名もCaptionも「OK」

コード

【Sheet1(一覧表シート)】

Private Sub Worksheet_BeforeDoubleClick(ByVal Target As Range, Cancel As Boolean)

    'D列をダブルクリックした時ユーザーフォームを立ち上げる
    
    If Target.Column = 4 Then
    
        UserForm2.Show
    
    End If

End Sub


【ユーザーフォーム】

'ユーザーフォームが開くタイミングで実行される
Private Sub UserForm_Initialize()

    Dim i As Long
    Dim ws As Worksheet
    
    Set ws = Sheets("選択用コメント")

    For i = 2 To ws.Cells(Rows.Count, 1).End(xlUp).Row
    
        'リストボックスに見出しを追加
        ListBox1.AddItem ws.Cells(i, 1).Value
        
    Next i

End Sub
'ListBox1(見出し)を選択した時
Private Sub ListBox1_Click()

    Dim i As Long

    i = ListBox1.ListIndex

    'ListBox1のインデックス番号は0から始まる&2行目から始まる→+2
    TextBox1.Value = Sheets("選択用コメント").Cells(i + 2, 2).Value

End Sub
'OKボタンを押した時
Private Sub OK_Click()

    ActiveCell.Offset(0, -1).Value = TextBox1.Value
    
    'ユーザーフォームを閉じる
    Unload UserForm2

End Sub

これじゃダメかな?

VBA 罫線で囲まれた範囲を取得する(田んぼRange)

こんにちは。
派犬事務員のコロ子です。

よくありがちなこんな表。

f:id:SNegishi:20211004223057p:plain

罫線で囲まれた表の外に自由に書き込みがされている。
表の中に空白行があるけど、区切りの意味があるらしく、勝手に削除できない。
この状態に手は加えず、表だけを取得したい。

UsedRange だと、枠の外も使用範囲が全部になってしまう。
f:id:SNegishi:20211004223757p:plain


Range("C3").CurrentRegion だと、連続している範囲になってしまう。
f:id:SNegishi:20211004224053p:plain


罫線で囲まれた範囲だけ取得する関数とかあっても良さそうだけどなー。
ないっぽいので自作してみよう。

Sub Test()

    田んぼRange(Sheet1).Select

End Sub


Function 田んぼRange(ws As Worksheet) As Range
    
    Dim linRange As Range

    Dim ran As Range
    For Each ran In ws.UsedRange
    
        If ran.Borders.LineStyle = xlContinuous Then
        
            If linRange Is Nothing Then
                Set linRange = ran
            Else
                Set linRange = Union(linRange, ran)
            End If

        End If
    
    Next ran

    Set 田んぼRange = linRange

End Function

枠全てに罫線が引かれているセルをUnion関数で繋げて範囲を取得する。

f:id:SNegishi:20211005221504p:plain

できた!
罫線で囲まれた範囲を田んぼRangeと命名しよう。
(ネーミングがダサい?でも分かりやすいでしょ!ちゃんとした名前あるのかな?)
田んぼRange、もしかしたらUsedRangeやCurrentRegionよりもニーズがあるかも!?

おしゃべりがうるさーい!(悩み)

こんにちは。
派犬事務員のコロ子です。

現在「こーゆーの作って」を作る謎の一人部署で毎日VBAを書いている。
一人なので専用の部屋はなく、総務に間借りをしている。

そんな状況で困った事がある。
それは「うるさい」ことだ。
総務なのでいろいろな人が出入りする。その度におしゃべりが盛り上がって非常にうるさい。めちゃめちゃ考えている時に集中できなくて困る。
しかも話の内容丸聞こえなんですけど!
時々「うるさーい!」って叫びそうになるけど、さすがに間借りしている分際でそれは言えない。
昔から音楽を聴きながら、ラジオを聴きながら勉強とかできないタイプだった。
だって一緒に歌っちゃうから。
そんなわけで、聞き耳を立ててるわけでもないのに(できれば聞きたくない話が多い)仕事にならなくて困っている。

何度も上司に相談しようと思い、でもそんな相談されても困るよなぁ、と思いとどまる、を繰り返していた。
が、先日ついに我慢ができなくなり上司に相談に行った。さすがに小学生じゃないからおしゃべりを注意してもらう、とかはないけど、あわよくば在宅勤務とかもちょっと期待しながら。


上司:「ヘッドフォンしていいよ」
ええっーーー!!マジですかー!音楽聞いていいんですか!!
それ、海外ドラマとかで見た事あるー!
ヘッドフォンしながらオフィスで仕事。超憧れるけど、さすがに日本企業じゃおかしくない?しかもかなり古いタイプの会社だし。
コロ子:「ヘッドフォンですか?防音の意味でですよね?」
上司:「そう。それで対処してくれる?」
コロ子:「ちょっとさすがに、角が立たないですか?」
上司:「何か言われたら、私が許可した、って言っていいよ。」
そうは言われても・・・。

ってなわけで、職場でのヘッドフォンを許可された。
でも突然仕事中にヘッドフォンするのもおかしいよな・・・。
まずは毎日ヘッドフォンをして出社し、仕事中は首から下げてファッションアイテムとして定着させるべきか・・・?

ヘッドフォンは大げさだから、ワイヤレスのイヤフォンをするべきか?髪(耳)で隠せるし。
でも、そんなコソコソした感じにしたら、周りから「派犬のくせに仕事中隠れて音楽聞いてる」って言われるよな・・・。派犬は周りからの評価が仕事に響くんだよ・・・。

ちょっとウサ子に相談してみよう。
ウサ子:「いいな~。仕事中ヘッドフォンしていいんだ~」
コロ子:「全然良くないよ~。いくらうるさいからって、そんな嫌味なことしたら角が立つよ」
ウサ子:「そんなことないよー。とびっきりいいヘッドフォン買いなよー。派犬なんて仕事上のヒエラルキーは最下位なんだから、ヘッドフォンヒエラルキーで最上位になりなよー。」

意味不明。いまだ悩み中。

VBA カッコはどういうときに付けるの?

こんにちは。
派犬事務員のコロ子です。

f:id:SNegishi:20210622222607p:plain
梅雨ですね

実は今ノンプロ研のVBA初級講座でティーチングアシスタント(TA)をしている。前回、中級講座のTAでもテンパったけど、初級講座でも受講生からの鋭い質問にテンパっている。

先日はカッコの付け方についての質問があった。

メッセージボックスで
MsgBox "派犬", vbOKOnly ←実行できる
MsgBox ("派犬",vbOKOnly) ←エラーになる
どうしてカッコを付けるとエラーになるのですか?

うわ~(汗)めちゃめちゃいい質問。

なんとなく雰囲気で戻り値を受ければいいのは分かる。
でも、どうしてカッコが要らないのかは答えに詰まる。
似たようなので
Workbooks.Open
これもいつもカッコが要るのか要らないのかあやふやで、エラーが出たらカッコを取る、みないなことをしている。
長年なんとなく放置した問題についに向かい合う時がきた。

Office TANAKAのサイトでは下記のようなルールだといっている。

返り値を何かに使うときは、引数を括弧で囲まなければなりません。
返り値を何かに使わないときは、引数を括弧で囲ってはいけません。これが、VBAにおける括弧のルールです。

なるほどー。ルールなのかー。
でも、何でもカッコをつければ戻り値が戻るとは思えない。
いったいどんなときにカッコをつければいいのだろう。

オブジェクトブラウザを見てみる

VBEエディタでF2キーを押すとライブラリ一覧が表示される。
f:id:SNegishi:20210624223505p:plain
MsgBoxを検索して下のを見てみると説明が書いてある。
f:id:SNegishi:20210624223722p:plain
Functionとなっている。MsgBoxはFunctionプロシージャなのだ。
Functionプロシージャなので当然、戻り値がある。

他のプロシージャを呼び出すには

Callは他のSubプロシージャーやFunctionプロシージャーを呼び出すことができる。
ただし、Callを使ってFunctionプロシージャーを呼び出す場合は戻り値は破棄される。
ということはCallは呼び出すだけで戻り値はない。

Callを使って他のプロシージャを呼び出すには、引数をカッコで囲む。

Call プロシージャ名(引数1, 引数2,…)

Call は省略できる。その場合はカッコを外す。

プロシージャ名 引数1, 引数2,…

ということで、戻り値がない他のプロシージャを呼び出す書き方は上記の2通り。

MsgBoxはFunctionプロシージャだけど、vbOKOnlyなどボタンが1つで、あえて戻り値が必要ない場合は上記のどちらかの方法で呼び出す。
Callを付けるか、カッコを外すか。たまーにCallがついているのを見かけるけど、カッコを外すのが一般的なんだろう。

Call MsgBox("派犬", vbOKOnly)

MsgBox "派犬", vbOKOnly

ちなみに
Workbooks.OpenのOpenもFunction
Application.RunのRunもFunction
UnloadはSub
Collectionオブジェクト.AddのAddはSub