Quantcast
Channel: いつも隣にITのお仕事
Viewing all 2089 articles
Browse latest View live

【エクセルVBA入門】シートのデータがある最終行番号を求めるステートメントを徹底解説

$
0
0
row-end

photo credit: marcoverch LIDL Shopping carts via photopin (license)

みなさん、こんにちは!
タカハシ(@ntakahashi0505)です。

エクセルVBAを使ってバラバラの経費精算書データを集約するシリーズです。

前回の記事はコチラ。

【エクセルVBA入門】For Each~Next文でフォルダ内のブック全てを開く方法
エクセルVBAを使ってバラバラの経費精算書のデータを集約するシリーズです。今回は、For Each~Next文でフォルダ内の複数のワークブックの全てを順番に参照して処理していく方法についてお伝えします。

フォルダの中のブック全てについて開くという繰り返し処理の作り方をお伝えしました。

今回ですが、この経費収集プロシージャについて、実行をしても最終行以降にデータを追加していけるように修正をします。

そのために、エクセルVBAで最終行を求めるステートメントについて徹底的に解説をしていきます。

では、行ってみましょう!

前回のおさらい

まずおさらいからです。

マクロを記述しているブックには以下のようなデータを集めるための「経費データ」というシートがあります。

経費データフォーマット

各スタッフがそれぞれ作成した経費精算書が、同じフォルダの「data」というフォルダ配下にゴソっと格納されていて、その経費データを収集しようというものです。

例として太郎さんの経費精算書はこんな感じです。

経費精算書ひな形

それで、フォルダ内のファイルすべてについて開いて、データを収集するプログラムがコチラです。

Sub 経費精算データ取り込み()

Dim fso As FileSystemObject
Set fso = New FileSystemObject

Dim di As Long: di = 2
Dim f As File
For Each f In fso.GetFolder(ThisWorkbook.Path & "\data").Files
    With Workbooks.Open(f.Path)
        With .Worksheets(1)
        
            Dim month As Date: month = .Range("G4").Value
            Dim departmentName As String: departmentName = .Range("G6").Value
            Dim staffId As Long: staffId = .Range("G8").Value
            Dim staffName As String: staffName = .Range("G7").Value
            
            Dim i As Long: i = 12
            Do While .Cells(i, 1).Value <> ""
                wsData.Cells(di, 1).Value = month '1 対象月
                wsData.Cells(di, 2).Value = .Cells(i, 1).Value '2 日付
                wsData.Cells(di, 3).Value = ""  '3 部署No
                wsData.Cells(di, 4).Value = departmentName '4 部署
                wsData.Cells(di, 5).Value = staffId '5 社員No
                wsData.Cells(di, 6).Value = staffName '6 氏名
                wsData.Cells(di, 7).Value = .Cells(i, 2).Value '7 科目
                wsData.Cells(di, 8).Value = .Cells(i, 5).Value '8 摘要
                wsData.Cells(di, 9).Value = .Cells(i, 6).Value '9 金額
                wsData.Cells(di, 10).Value = .Cells(i, 7).Value '10 備考
                i = i + 1: di = di + 1
            Loop
        End With
        .Close
    End With
Next f

End Sub

フォルダを取り扱うために、FileSystemオブジェクトを使っています。

また、For Each~Nextでフォルダ内のすべてのファイルについて処理をする繰り返しの中でデータの転記を行っています。

月が変わっても続きにデータを追加していきたい

さて、新しい月になったらその分の新たな経費精算書が集まってきますね。

例えば、2018年7月のデータの収集が以下のように完了しているとします。

7月分の収集が完了している経費データシート

それで、dataフォルダの中に2018年8月分の経費精算書を集めて、経費精算データ取り込みのプロシージャを実行してみます。

すると…以下のように、7月のデータの2行目から、8月のデータがある分だけ上書きになっちゃうんですね。

シートに追加したデータが上書きされてしまった

データ、新たに9行目から追加していいですよね…

データの最終行を求める

プログラムでいうと6行目。

「経費データ」シートのセル位置をつかさどるカウント変数diの開始位置が

Dim di As Long: di = 2

と、2行目にセットされているのが原因ですね。そりゃそうです。

毎回、実行のたびに2行目からの書き込みになってしまいます。

従ってこれを

Dim di As Long: di = wsData.Cells(Rows.Count, 1).End(xlUp).Row + 1

とします。これで、すでにあるデータの次の行から書き込みを開始するようになります。

以下の記事でお伝えしている通りです。

【初心者向けエクセルVBA】行の数をカウントする&不要な行を隠す
今回は行数をカウントする、行を隠す、などの「行を取り扱うテクニック」を紹介しています。いずれもデータや帳票を扱ったエクセルVBAではかなり重宝するテクニックですので、知っておいて損はありませんよ。

てか、なんでさっきのステートメントで最終行の次の行数が指定できるんですかね?

ステートメントの意味、ちゃんと理解して使わないと…ですよね!

ということで、今回詳しく解説していきます。

Rows.Countとは

まず、「Rows.Count」

この部分を見てみましょう。

Countってくらいですから、何かの数を勘定しているんですね。

イミディエイトウィンドウで、以下のように打って Enter してみましょう。

? Rows.Count

以下のように出力されますよね。

イミディエイトウィンドウにRows.Countを出力
RowsはRowsプロパティで、全ての行を表すRangeオブジェクトを取得します。

鋭い方はお気づきだと思いますが、ExcelのシートのMAX行数です。

で、実はこの「Rows」の前に対象となるオブジェクトが省略されているのですが、その正体はActiveSheetです。

つまり、「Rows」により作業中のワークシートのすべての行をRangeオブジェクトとして取得します。

そして、そのRangeオブジェクトに、Countプロパティを使うことで、ワークシートのすべての行の行数、つまり1048576という整数を取得できるということになります。

Rows.Count

結果的として、

Worksheetオブジェクト.Cells(Rows.Count, 1)

は、1048576行目、つまりシートの最大行の1列目のセルを表すRangeオブジェクト、ということになります。

さて、Rangeオブジェクトに対するCountプロパティ…これが、なぜ「セルの数」ではなくて「行数」になるのか、これはけっこう深い問題そうですね…

機会があれば、お伝えしたいです。

Endプロパティで終端まで移動したセルを取得する

続いて、Endというプロパティがあります。

これは、指定したRangeオブジェクトから、キーボードでいう Ctrl + 方向キー の操作で移動した先のRangeオブジェクトを返します。

書式はこうです。

Rangeオブジェクト.End(移動する方向)

「移動する方向」には、以下のいずれかの定数を指定します。

定数 内容
xlUp 上方向
xlDown 下方向
xlToLeft 左方向
xlToRight 右方向

つまり、以下はシートの最大行の1列目のセルから Ctrl + で移動した先のRangeオブジェクトということになります。

Worksheetオブジェクト.Cells(Rows.Count, 1).End(xlUp)

実際にやってみるとわかりますが、以下の位置。つまり、データがある行の最終行の1列目セルになります。

シートのデータがある行の最終行の1列目のセル

RowプロパティでRangeオブジェクトの行番号を取得する

さあ、あと少しです。

最後のRowプロパティですね。対象はRangeオブジェクトです。

Rangeオブジェクト.Row

Rowプロパティは、Rangeオブジェクトの先頭行の行番号を返します。

今回は単体セルなので、そのセルの行番号ということになります。

ですから、以下はデータがある行の最終行の行番号、ということになりますね。

Worksheetオブジェクト.Cells(Rows.Count, 1).End(xlUp).Row

これにプラス1した行番号の行に、新たなデータを書き込み始めればよいということになりますよね。

経費データを追加していくプロシージャ

以上を踏まえて、修正した経費データ収集プロシージャがコチラです。

Sub 経費精算データ取り込み()

Dim fso As FileSystemObject
Set fso = New FileSystemObject

Dim di As Long: di = wsData.Cells(Rows.Count, 1).End(xlUp).Row + 1
Dim f As File
For Each f In fso.GetFolder(ThisWorkbook.Path & "\data").Files
    With Workbooks.Open(f.Path)
        With .Worksheets(1)
        
            Dim month As Date: month = .Range("G4").Value
            Dim departmentName As String: departmentName = .Range("G6").Value
            Dim staffId As Long: staffId = .Range("G8").Value
            Dim staffName As String: staffName = .Range("G7").Value
            
            Dim i As Long: i = 12
            Do While .Cells(i, 1).Value <> ""
                wsData.Cells(di, 1).Value = month '1 対象月
                wsData.Cells(di, 2).Value = .Cells(i, 1).Value '2 日付
                wsData.Cells(di, 3).Value = ""  '3 部署No
                wsData.Cells(di, 4).Value = departmentName '4 部署
                wsData.Cells(di, 5).Value = staffId '5 社員No
                wsData.Cells(di, 6).Value = staffName '6 氏名
                wsData.Cells(di, 7).Value = .Cells(i, 2).Value '7 科目
                wsData.Cells(di, 8).Value = .Cells(i, 5).Value '8 摘要
                wsData.Cells(di, 9).Value = .Cells(i, 6).Value '9 金額
                wsData.Cells(di, 10).Value = .Cells(i, 7).Value '10 備考
                i = i + 1: di = di + 1
            Loop
        End With
        .Close
    End With
Next f

End Sub

実行すると、以下のように実行を重ねてもデータを追加していくことができているのが確認できます。

エクセルVBAで経費データを最終行の次の行以降に追加した

まとめ

以上、エクセルVBAで最終行を求めるステートメントについて詳しく解説をしてきました。

長いステートメントですが、ひとつずつ紐解くとその意味はわかってきますよね。

さて、次回は人による入力ミスをいかにして防ぐか…という観点で、ファイル名を活用してみたいと思います。

【エクセルVBA入門】開いたブック名から文字列を抽出して人為的なミスを回避する方法
エクセルVBAを使ってバラバラの経費精算書データを集約するシリーズです。人が入力する場合は必ず人為的なミスが入り込みます。それ回避する方法として開いたブックからファイル名を取得して利用する方法をお伝えします。

どうぞお楽しみに!

連載目次:エクセルVBAで経費データをデータベースに集約する

請求書シリーズと逆のパターンですが、バラバラの帳票からデータ一覧つまりデータベースに情報を集めて蓄積していく、というお仕事も多いと思います。ここでは各担当者から提出された経費精算書をデータベースに蓄積するプログラムを目標にして進めていきます。
  1. 【エクセルVBA入門】バラバラの経費精算書をデータにまとめる
  2. 【エクセルVBA入門】Do While~Loop文で条件を満たす間繰り返し
  3. 【エクセルVBA入門】繰り返しを使ってデータの転記をするときの2つのポイント
  4. 【エクセルVBA入門】With文でプログラムをスッキリわかりやすく書く
  5. 【エクセルVBA入門】他のワークブックをWithで開く&保存せずに閉じる
  6. 【エクセルVBA入門】フォルダやファイルを操作するFileSystemオブジェクトとその使い方
  7. 【エクセルVBA入門】For Each~Next文でフォルダ内のブック全てを開く方法
  8. 【エクセルVBA入門】シートのデータがある最終行番号を求めるステートメントを徹底解説
  9. 【エクセルVBA入門】開いたブック名から文字列を抽出して人為的なミスを回避する方法

【エクセルVBA入門】マクロを作るときに知っておきたいマスタデータのこと

$
0
0
avoid

photo credit: XoMEoX Constriction via photopin (license)

みなさん、こんにちは!
タカハシ(@ntakahashi0505)です。

エクセルVBAを使ってバラバラの経費精算書データを集約するシリーズです。

前回の記事はコチラ。

【エクセルVBA入門】開いたブック名から文字列を抽出して人為的なミスを回避する方法
エクセルVBAを使ってバラバラの経費精算書データを集約するシリーズです。人が入力する場合は必ず人為的なミスが入り込みます。それ回避する方法として開いたブックからファイル名を取得して利用する方法をお伝えします。

みんなから集めたエクセルファイルのファイル名を使って、入力ミスを避けるテクニックについてお伝えしました。

今回もその続き。もっと運用上ミスが減らせるようにマスタというものを準備します。

ということで、エクセルVBAでマクロを作るときに知っておきたいマスタデータのことについてお伝えしていきます。

では、行ってみましょう!

前回のおさらい

毎月、社内の皆さんから受け取る経費精算書ですが、以下の部分は手入力なので、人為的なミスが混入する可能性がありました。

経費精算書の手入力箇所にミスがある

そのミスを回避すべく、ファイル名から「対象月」を取得してしまおう、という作戦をとってコードを作成しました。

コチラです。

Sub 経費精算データ取り込み()

Dim fso As FileSystemObject
Set fso = New FileSystemObject

Dim di As Long: di = wsData.Cells(Rows.Count, 1).End(xlUp).Row + 1
Dim f As File
For Each f In fso.GetFolder(ThisWorkbook.Path & "\data").Files
    With Workbooks.Open(f.Path)
        With .Worksheets(1)
            
            Dim period As String: period = Left(f.Name, 6)
            Dim month As Date: month = DateSerial(Left(period, 4), Right(period, 2), 1)
            Dim departmentName As String: departmentName = .Range("G6").Value
            Dim staffId As Long: staffId = .Range("G8").Value
            Dim staffName As String: staffName = .Range("G7").Value
            
            Dim i As Long: i = 12
            Do While .Cells(i, 1).Value <> ""
                wsData.Cells(di, 1).Value = month '1 対象月
                wsData.Cells(di, 2).Value = .Cells(i, 1).Value '2 日付
                wsData.Cells(di, 3).Value = ""  '3 部署No
                wsData.Cells(di, 4).Value = departmentName '4 部署
                wsData.Cells(di, 5).Value = staffId '5 社員No
                wsData.Cells(di, 6).Value = staffName '6 氏名
                wsData.Cells(di, 7).Value = .Cells(i, 2).Value '7 科目
                wsData.Cells(di, 8).Value = .Cells(i, 5).Value '8 摘要
                wsData.Cells(di, 9).Value = .Cells(i, 6).Value '9 金額
                wsData.Cells(di, 10).Value = .Cells(i, 7).Value '10 備考
                i = i + 1: di = di + 1
            Loop
        End With
        .Close
    End With
Next f

End Sub

12,13行目で、以下の形式のファイル名から「YYYYMM」の部分を抽出して、それらから「対象月」の日付型データを生成しています。

YYYYMM経費精算書_名前_XXXX.xlsx

エクセルファイル内よりもファイル名のほうが入力ミスをしづらい、または入力ミスを発見しやすいということを期待できます。

今回は、「XXXX」の社員Noの部分を利用して、部署No、部署、氏名について対策をとるべく準備を進めていきます。

マスタデータとは

まず、前準備として、現在マクロを書いているブックにシートを追加して「マスタデータ」を整備していきます。

マスタデータ 【 master data 】
マスタデータとは、企業内データベースなどで、業務を遂行する際の基礎情報となるデータのこと。また、それらを集約したファイルやデータベースのテーブルなど。単に「マスタ」と省略するのが一般的である。
(引用:IT用語辞典)

社員マスタを準備する

例えば、以下ような「社員マスタ」を準備します。

エクセルの社員マスタ

社員Noは社員ごとに一意に定められているものとすると、社員Noさえわかれば

  • 氏名
  • 部署No
  • 部署

は、おのずと決まってきます。

それら社員Noに紐づくデータを「コチラ側」に正しい情報として持っておいて、それを参照をするのです。

その役割を担う「コチラ側」のデータをマスタ、もしくはマスタデータと言うわけです。

部署マスタを準備する

さてもう一つ、今回は以下のような「部署マスタ」という別のマスタも用意しておきます。

エクセルの部署マスタ

前述の「社員マスタ」のD列「部署」は、C列「部署No」をキーにして「部署マスタ」から
VLOOKUP関数で引っ張ってきています。

例えば、「社員マスタ」のセルD2であれば、以下のような関数が仕込まれています。

=VLOOKUP(C2,部署マスタ!A:B,2,FALSE)

こうしておけば、社員の部署が変更された場合は「社員マスタ」の「部署No」を変更すれば、「部署」も連動して変更できます。

また、部署の名称が変更になった場合は「部署マスタ」の「部署」を変更することで、「社員マスタ」も連動して変更できます。

ユーザーの入力とその影響は最小限に

このように、マスタデータを準備さえしておけば、「社員No」のみをキーに、その他のデータは正しいものを引っ張ってくることができます。

そして、その社員Noはファイル名に仕込まれていますので、エクセルファイル内のユーザーの入力内容に依存せずに経費データを収集、蓄積できるようになります。

ですから、業務フローにもよりますが、そもそもシート上の入力欄自体なくしてもいいのかもしれません。

このように、ユーザーが入力すべきデータとそのミスによる影響範囲を最小限に留めておく、という仕組みづくりは、プログラミング自体とともに重要なポイントと言えます。

社内のマスタ管理の注意点

さて、今回のシリーズでは経費精算書だけにフォーカスをしていますが、部署や社員のデータというのは、他の業務でも使用される可能性は十分にあります。

ですから、本来はこれらの「おおもとのマスタ」は、経理担当者のローカルPC内のいちエクセルファイルで完結する場合は、あまりないかも知れません。

一般的には「おおもとのマスタ」は、管理システムにあったり、共有サーバーにあったりすることもありますね。

ですから、その「おおもとのマスタ」と、VBAで使用するマスタをいかにして連動させるか、というのは運用上考えておくべき課題となります。

その点、念頭に置いておいていただければと思います。

まとめ

以上、エクセルVBAでマクロを作る際に知っておきたいマスタデータのことについてお伝えしました。

繰り返しになりますが、ユーザーの入力にはどうやってもミスが混入します。

まずは、入力自体が最小限になること、そしてその入力のミスによる影響を最小限に留めておくことを目指してみましょう。

次回は具体的に、ファイル名から社員番号を抜き出す方法をお伝えします。

どうぞお楽しみに!

連載目次:エクセルVBAで経費データをデータベースに集約する

請求書シリーズと逆のパターンですが、バラバラの帳票からデータ一覧つまりデータベースに情報を集めて蓄積していく、というお仕事も多いと思います。ここでは各担当者から提出された経費精算書をデータベースに蓄積するプログラムを目標にして進めていきます。
  1. 【エクセルVBA入門】バラバラの経費精算書をデータにまとめる
  2. 【エクセルVBA入門】Do While~Loop文で条件を満たす間繰り返し
  3. 【エクセルVBA入門】繰り返しを使ってデータの転記をするときの2つのポイント
  4. 【エクセルVBA入門】With文でプログラムをスッキリわかりやすく書く
  5. 【エクセルVBA入門】他のワークブックをWithで開く&保存せずに閉じる
  6. 【エクセルVBA入門】フォルダやファイルを操作するFileSystemオブジェクトとその使い方
  7. 【エクセルVBA入門】For Each~Next文でフォルダ内のブック全てを開く方法
  8. 【エクセルVBA入門】シートのデータがある最終行番号を求めるステートメントを徹底解説
  9. 【エクセルVBA入門】開いたブック名から文字列を抽出して人為的なミスを回避する方法

GASやVBAでスクレイピングができない理由として考えるべきJavaScriptのこと

$
0
0

HTMLのイメージ

みなさん、こんにちは!うえはら(@tifoso_str)です。

Google Apps ScriptでWebスクレイピングしていて、値が取得できないということはありませんか?

別のWebサイトではちゃんと動いているのに、特定のサイトではWebスクレイピングできていない。 

原因はWebサイトの表示にJavaScriptを利用しているからなのですが、これだけではよくわからないですよね。

そこで【JavaScriptで動作するWebページを色々な言語でスクレイピング】してその原因と解決法をお伝えしていきます。

Google Apps Scriptでは説明が難しいので、まずはVBAで解説していきます。

今回は「VBAでスクレイピングができない理由として考えるべきJavaScriptのこと」をお伝えします。

Google Apps Scriptでも解決法をお伝えしますので、何回かVBAにお付き合い下さい。

スクレイピングするページをChromeで検証する

まずは、今回スクレイピングするJavaScriptを使用しているWebページと、スクレイピングするのに必要になるタグの探し方を説明します。

Webスクレイピングするホームページ

今回は、沖縄県企業局のページで、ダムの貯水率を確認します。

今回スクレイピングする値は、日付全11ダム合計の貯水率です。

データ取得日

本日の貯水率

Chromeの検証機能

WebページはHTMLという言語で記述されていて、「ページのソース」等と呼ばれています。

Chromeでは右クリックのメニューで「ページのソースを表示」で見ることができます。

ページのソースを表示

Internet Explorerでは「ソースの表示」です。

Webスクレイピングではこの「ページのソース」を全て取得して、必要部分を抜出しています。

一度、ページのソースを表示してもらえるとわかるとおもいますが、この中から、目的の箇所を探すのは大変です。

こんなとき、威力を発揮するのが、Chromeの検証機能です。

右クリックで出てくるメニューの「検証」で、確認できます。

検証

検証機能を利用すると、画面右側に色々と出てくると思います。

(場所はメニューで変更できるので、画面下だったり別ウィンドウだったりするかもしれません。)

Elementsに表示されるHTMLにカーソルを合わせるとWebページの該当部分が強調表示されます。

検証機能

これで、該当箇所が簡単に見つけられますね!

HTML構文は入れ子構造になっていることが多いので、目的の箇所にたどり着くまでには「▼」を何回かクリックする必要があると思います。

今回でいえば、日付と貯水率はそれぞれ下記の部分です。

日付
<span id="chosui_hiduke">06月21日</span>
 
貯水率
72.1

VBAのHTTP通信でWebスクレイピング

それでは、VBAのHTTP通信を利用してWebスクレイピングしてみます。

VBAでHTTP通信をするには「Microsoft XML, v6.0」ライブラリを利用します。

詳細については、こちらの記事をご覧下さい。

エクセルVBAでHTTPリクエストをする最も簡単なプログラム
今回はエクセルVBAでHTTPリクエストをする最も簡単なプログラムをお伝えします。HTTP通信、リクエストやレスポンスとは何か、またリクエストを送信して取り出すまでに必要なメソッドやプロパティを紹介します。

Webページからソースを読み込んで、日付と貯水率をイミディエイトウィンドウに表示させます。

Sub HTTP通信()
    Dim httpReq As XMLHTTP60
    Set httpReq = New XMLHTTP60
    
    httpReq.Open " GET", "https://www.eb.pref.okinawa.jp/kassui/"
    httpReq.send 'HTTPリクエスト送信
    
    Do While httpReq.readyState < 4 '処理待ち
        DoEvents
    Loop
    
    Dim htmlDoc As Object
    Set htmlDoc = New HTMLDocument
    htmlDoc.write httpReq.responseText
    
    Dim hiduke As IHTMLElement
    Set hiduke = htmlDoc.getElementById("chosui_hiduke")
    Debug.Print "■日付は「" & hiduke.innerHTML & "」です。"

    Dim chosuiritsu As IHTMLElement
    Set chosuiritsu = htmlDoc.getElementById("ritsu_today4")
    Debug.Print "■本日の貯水率は「" & chosuiritsu.innerHTML & "」です。"

    Set httpReq = Nothing
    Set htmlDoc = Nothing
End Sub

実行して、イミディエイトウィンドウを確認してみます。

HTTP確認イミディエイトウィンドウ

取得できてないですね

原因は、このWebページがサーバーからソースを読み込んだ後に、ブラウザー(Chromeなど)でJavaScriptを実行して画面に表示しているからです。

下の図を例に、もう少し簡単に説明しますね。

HTTP通信

Webページを見るときは、サーバーから情報をもらうために「HTTPリクエスト」を送ります。

それを受けて、サーバーは「HTTPレスポンス」でWebページの情報を返します。

このときにボックスには、日付は入っていません。

先程、Webスクレイピングしたときは、この情報を取得していいたので、日付がなかったんです。

それではいつ日付が入るかです。

サーバーから返ってきた情報には「下のBoxに今日の日付を表示して」というJavaScriptの命令が入っています。

この命令をブラウザーが実行して日付が表示されます。

Webページを表示するのに、裏ではいろいろやってるんですね。

さて、これを解決する為にどうすればいいかというと、一度、ブラウザーでこのWebページを処理させてあげればいいわけです。

その後に、必要な値を取得すればO.K.ですね。

まとめ

このまま、解決法も書いていきたいのですが、ちょっと長くなったので、今回はここまでとします。

今回は「VBAでスクレイピングができない理由として考えるべきJavaScriptのこと」をお伝えしました。

最近のホームページは、見る人に合わせていろいろと情報が変わる動的サイトになっています。

そのなかでも、JavaScriptで動作するWebページをスクレイピングするためには、ブラウザで処理する必要があることがわかりました。

VBAでInternetExplorerを操作できるの?と思われている方もいらっしゃると思います。

はい、それができるんです!

次回の記事で、VBAでInternetExplorerを操作して、Webスクレイピングしていきます。

お楽しみに!

【エクセルVBA入門】開いたブックのファイル名から番号を取り出して数値に変換する

$
0
0
id-number

photo credit: duncan 300 via photopin (license)

みなさん、こんにちは!
タカハシ(@ntakahashi0505)です。

エクセルVBAでバラバラの経費精算書のデータをまとめるマクロの作り方をお伝えしています。

前回の記事はコチラ。

【エクセルVBA入門】マクロを作るときに知っておきたいマスタデータのこと
エクセルVBAを使ってバラバラの経費精算書データを集約するシリーズです。今回は、エクセルVBAでマクロを作るときに知っておきたいマスタデータのこと、またその準備の仕方についてお伝えしていきます。

マスタデータとは何か、またその作り方についてお伝えしました。

さて、今回はそのマスタデータでデータを取り出すキーとなる社員Noをファイル名から取り出していきますよ。

ということで、エクセルVBAで開いたブックのファイル名から番号を取り出して数値に変換する方法をお伝えします。

では、行ってみましょう!

これまでのおさらい

社員の皆さんから集めたエクセルでできた経費精算書のデータがあります。

これらを、同じフォルダに放り込んでおいてマクロを走らせれば、全ての経費データを自動で集めてくれるというマクロを作成しています。

ここまでで作成したコードはコチラです。

Sub 経費精算データ取り込み()

Dim fso As FileSystemObject
Set fso = New FileSystemObject

Dim di As Long: di = wsData.Cells(Rows.Count, 1).End(xlUp).Row + 1
Dim f As File
For Each f In fso.GetFolder(ThisWorkbook.Path & "\data").Files
    With Workbooks.Open(f.Path)
        With .Worksheets(1)
            
            Dim period As String: period = Left(f.Name, 6)
            Dim month As Date: month = DateSerial(Left(period, 4), Right(period, 2), 1)
            Dim departmentName As String: departmentName = .Range("G6").Value
            Dim staffId As Long: staffId = .Range("G8").Value
            Dim staffName As String: staffName = .Range("G7").Value
            
            Dim i As Long: i = 12
            Do While .Cells(i, 1).Value <> ""
                wsData.Cells(di, 1).Value = month '1 対象月
                wsData.Cells(di, 2).Value = .Cells(i, 1).Value '2 日付
                wsData.Cells(di, 3).Value = ""  '3 部署No
                wsData.Cells(di, 4).Value = departmentName '4 部署
                wsData.Cells(di, 5).Value = staffId '5 社員No
                wsData.Cells(di, 6).Value = staffName '6 氏名
                wsData.Cells(di, 7).Value = .Cells(i, 2).Value '7 科目
                wsData.Cells(di, 8).Value = .Cells(i, 5).Value '8 摘要
                wsData.Cells(di, 9).Value = .Cells(i, 6).Value '9 金額
                wsData.Cells(di, 10).Value = .Cells(i, 7).Value '10 備考
                i = i + 1: di = di + 1
            Loop
        End With
        .Close
    End With
Next f

End Sub

手入力によるミスや揺らぎによる影響を避けるために

ただ、以下の部分は各スタッフさんの手入力になるのですが、入力ミスや揺らぎが混入する可能性があるわけです。

経費精算書の手入力箇所にミスがある

それを防ぐために、以下のような「社員マスタ」を用意しました。

エクセルの社員マスタ

社員Noだけ指定してあげれば、部署No、部署、氏名は全部マスタから抽出できるわけです。

それで、社員Noはどこから抽出するかというと、以下の形式で構成されている、経費精算書ファイルのファイル名です。

YYYYMM経費精算書_名前_XXXX.xlsx

末尾の4桁のXXXXが社員Noにあたりますので、これを抜き出し、かつ数値に変換する方法を紹介していきます。

ファイル名から社員番号を切り出す

さあ、上記の形式で構成されるファイル名から社員Noを抜き出していきましょう。

Replace関数で拡張子を取り除く

まず、ファイル名の末尾には拡張子「.xlsx」がありますのでこれを取り除きます。

特定の文字列を取り除きたい場合はReplace関数を使います。

Replace(対象とする文字列, 置換する文字列, 置換後の文字列)

取り除くというか、Replace関数のもともとの機能は”置換”なのですが、置換後の文字列を「””」と指定することで取り除くのと同様の働きをします。

今回のケースでは、以下のようにすればOKです。

Replace(f.Name, ".xlsx", "")

Right関数で末尾から文字を切り出す

拡張子が取り除ければ、以前使用したRight関数を使って末尾から文字を切り出して取得することができます。

つまり、以下のようにすれば、社員Noを取得することができます。

Right(Replace(f.Name, ".xlsx", ""), 4)

Val関数で文字列型を数値型に変換する

ただ、実はもう一工夫必要です。

というのも、実際の社員Noは「数値型」なのですが、上記ファイル名から取得した値は「文字列型」です。

型が異なるので、「数値型」に変換して揃える必要があります。

それで、文字列を数値に変換するときはVal関数を使います。

Val(文字列)

今回の場合ですが、社員Noを表す数値型の変数はstaffIdですから、以下のようにすることで社員Noを数値型で取得することができます。

Dim staffIdAs Long
staffId= Val(Right(Replace(f.Name, ".xlsx", ""), 4))

まとめ

以上、エクセルVBAで開いたブックのファイル名から番号を取り出して数値に変換する方法をお伝えしました。

Replace関数、Right関数、Val関数を使いました。

どれも使用頻度の高い関数ですので、ぜひ使いこなせるようになりたいですね。

さて、まとめのコードはコチラです。

Sub 経費精算データ取り込み()

Dim fso As FileSystemObject
Set fso = New FileSystemObject

Dim di As Long: di = wsData.Cells(Rows.Count, 1).End(xlUp).Row + 1
Dim f As File
For Each f In fso.GetFolder(ThisWorkbook.Path & "\data").Files
    With Workbooks.Open(f.Path)
        With .Worksheets(1)
            
            Dim period As String: period = Left(f.Name, 6)
            Dim month As Date: month = DateSerial(Left(period, 4), Right(period, 2), 1)
            Dim departmentName As String: departmentName = .Range("G6").Value
            Dim staffId As Long: staffId = Val(Right(Replace(f.Name, ".xlsx", ""), 4))
            Dim staffName As String: staffName = .Range("G7").Value
            
            Dim i As Long: i = 12
            Do While .Cells(i, 1).Value <> ""
                wsData.Cells(di, 1).Value = month '1 対象月
                wsData.Cells(di, 2).Value = .Cells(i, 1).Value '2 日付
                wsData.Cells(di, 3).Value = ""  '3 部署No
                wsData.Cells(di, 4).Value = departmentName '4 部署
                wsData.Cells(di, 5).Value = staffId '5 社員No
                wsData.Cells(di, 6).Value = staffName '6 氏名
                wsData.Cells(di, 7).Value = .Cells(i, 2).Value '7 科目
                wsData.Cells(di, 8).Value = .Cells(i, 5).Value '8 摘要
                wsData.Cells(di, 9).Value = .Cells(i, 6).Value '9 金額
                wsData.Cells(di, 10).Value = .Cells(i, 7).Value '10 備考
                i = i + 1: di = di + 1
            Loop
        End With
        .Close
    End With
Next f

End Sub

さて、次回ですがこの社員Noをキーにして、マスタから他のデータを引っ張ってきたいと思います。

どうぞお楽しみに!

連載目次:エクセルVBAで経費データをデータベースに集約する

請求書シリーズと逆のパターンですが、バラバラの帳票からデータ一覧つまりデータベースに情報を集めて蓄積していく、というお仕事も多いと思います。ここでは各担当者から提出された経費精算書をデータベースに蓄積するプログラムを目標にして進めていきます。
  1. 【エクセルVBA入門】バラバラの経費精算書をデータにまとめる
  2. 【エクセルVBA入門】Do While~Loop文で条件を満たす間繰り返し
  3. 【エクセルVBA入門】繰り返しを使ってデータの転記をするときの2つのポイント
  4. 【エクセルVBA入門】With文でプログラムをスッキリわかりやすく書く
  5. 【エクセルVBA入門】他のワークブックをWithで開く&保存せずに閉じる
  6. 【エクセルVBA入門】フォルダやファイルを操作するFileSystemオブジェクトとその使い方
  7. 【エクセルVBA入門】For Each~Next文でフォルダ内のブック全てを開く方法
  8. 【エクセルVBA入門】シートのデータがある最終行番号を求めるステートメントを徹底解説
  9. 【エクセルVBA入門】開いたブック名から文字列を抽出して人為的なミスを回避する方法
  10. 【エクセルVBA入門】開いたブックのファイル名から番号を取り出して数値に変換する

【エクセルVBA】指定した範囲内から値を検索するFindメソッドとその使い方

$
0
0

find,method,eyecatch,excel.vba

みなさまこんにちは、ノグチです。

前回は、エクセルのChangeイベントと、入力規則をVBAで操作するValidationオブジェクトを使って、セルに入力した値をマスタシートから検索し、ヒットした値をセルにプルダウンメニューに表示する方法をご紹介しました。

【エクセルVBA】Changeイベントと組み合わせてもっと便利に!検索にヒットした値をドロップダウンリストに表示する方法
エクセルのChangeイベントと、Validationオブジェクトを用いたドロップダウンリストを作成する方法を組み合わせて、セルに入力した値をマスタシートから検索し、検索にヒットした値を入力したセルのドロップダウンリストに表示させる方法をご紹介しています。この方法を使えば、入力作業が楽になるかもしれません。

その記事の中で使っていたFindメソッド

値の検索にはワークシート関数のVlookupなどがありますが、このFindメソッドも値を検索するときにとっても便利なのです。

ということで今回は、このFindメソッドを使って、セルに入力された値で指定範囲を検索する方法をご紹介します!

Findメソッドとは?

Findメソッドは、指定したRange型の範囲から指定した文字列を持つセルを返すメソッドです。

Findメソッドの記述方法

記述方法はこちら。

Rangeオブジェクト.Find(パラメータ)

検索したい値が見つかった場合はそのセルを表すRangeオブジェクトを、検索したい値が指定した範囲の中で見つからなかった場合にはNothingを返してくれます。

Findメソッドのパラメータ

Findメソッドにはいくつかパラメータがありますが、個人的によく使うパラメータが以下です。

パラメータ 役割と指定する値
What 検索したい値を指定するパラメータで、もちろん指定必須です。
LookAt 検索したい値で、部分一致検索なのか全体一致検索なのかを指定します。部分一致の場合はxlPartを、全体一致の場合はxlWholeを指定します。
SearchOrder 行方向に検索するのか列方向に検索するのかを指定します。行の場合はxlByColumnsを、列の場合はxlByRowsを指定します。省略した場合はxlByColumnsがデフォルトでセットされます。

パラメータを使ってFindメソッドを書いてみると、こんな感じになります。

rngSearch = ThisWorkBook.worksheet("マスタデータ").Range("A2:A6").Find (What:="AbC", LookAt:=xlWhole)

Findメソッドを使った検索コードの例

このメソッドを使って、前回の記事でもご紹介した、請求書シートに入力した値でマスタシートを検索し、入力したセルに検索結果を返す、というコードを書いてみましょう。

請求書シートがこちら、

excel,vba,event,請求書

マスタシートがこちらです。

excel,vba,event,マスタシート

請求書シートのB5セルに入力した値で、マスタシートの得意先リストを部分一致検索したい場合、このようなコードになります。

Dim rngSearch, varSearch
Dim myRange As Range

With ThisWorkbook
    
    varSearch = .Worksheets("請求書").Range("B5")
    Set myRange = .Worksheets("マスタ").Range("A1:A6")
    
    Set rngSearch = myRange.Find(What:=varSearch, LookAt:=xlPart)
    .Worksheets("請求書").Range("B5").Value = rngSearch

End With

戻り値がNothingの時の処理も大切

Findメソッドを使う場合は、戻り値がNothingだったとき(検索にヒットする値を持つセルが見つからなかったとき)のことも考えておきましょう。

上のコードで戻り値がNothingだった場合、9行目で検索にヒットした値をセルに出力するときにエラーになってしまいますので、戻り値がNothingだった場合の処理もセットで書くようにしましょう。

検索にヒットするセルがある場合と、Nothingの場合をIF文を使って分岐させると、こんな感じのコードになります。

Dim rngSearch, varSearch
Dim myRange As Range

With ThisWorkbook
    
    varSearch = .Worksheets("請求書").Range("B5")
    Set myRange = .Worksheets("マスタ").Range("A1:A6")

    Set rngSearch = myRange.Find(What:=varSearch, LookAt:=xlPart)
    
    If Not rngSearch Is Nothing Then
        .Worksheets("請求書").Range("B5").Value = rngSearch
    Else
        MsgBox "該当する得意先はありません。"
    End If

End With

End Sub
または、On Error Resume Nextを差し込んでおく方法もありますよ。
Dim rngSearch, varSearch
Dim myRange As Range

With ThisWorkbook

    varSearch = .Worksheets("請求書").Range("B5")
    Set myRange = .Worksheets("マスタ").Range("A1:A6")
    
    Set rngSearch = myRange.Find(What:=varSearch, LookAt:=xlPart)
    On Error Resume Next
    .Worksheets("請求書").Range("B5").Value = rngSearch

End With

いずれにせよ、戻り値がNothing の場合の処理は、必ず書き込んでおくのが安心ですね。

検索した値を取得してみる

では、上のコードを実際に動かしてみます。
検索値を入力するセルに”AAA”と入力します。
エクセル検索値,入力,請求書シート

マスタシートでは、”AAA”を含む得意先名は「AAA株式会社」だけなので、B5セルに「AAA株式会社」と出力されるはずです。

コードを動かしてみると…

エクセル,Find,検索結果

このとおり、ちゃんとB5セルに「AAA株式会社」が出力されていますね。

最後に

今回は、Findメソッドを使って、セルに入力された値で指定範囲を検索する方法をご紹介しました。

次回は、検索値に対して複数の値がヒットする可能性がある場合に使える、FindNextメソッドをご紹介します。

それでは最後までお読みいただき、ありがとうございました!

【エクセルVBA入門】Vlookupメソッドを使ったときに発生するエラーを回避する方法

$
0
0
error

photo credit: Keith Allison Yasmany Tomas via photopin (license)

みなさん、こんにちは!
タカハシ(@ntakahashi0505)です。

エクセルVBAでバラバラの経費精算書を一つのデータベースにまとめるマクロの作り方についてお伝えしています。

前回の記事はコチラ。

【エクセルVBA入門】マクロでVLookupメソッドを使ってデータを検索する方法
エクセルVBAを使ってバラバラの経費精算書データを集約するシリーズの7回目です。今回は、業務で有効なテクニックとしてもう一つ、マスタシートからVLookupメソッドでデータを取得してくる方法についてお伝えしていきます。

VLookupメソッドを使って、マスタからデータを抽出する方法についてお伝えしました。

ですが、VLOOKUP関数に慣れている方はこう思うはずです。

「ヒットしなければエラーになってしまうのでは…?」

そうなんです。

ということで、今回はエクセルVBAのVLookupメソッドを使ったときに発生するエラーを回避する方法についてお伝えします。

では、行ってみましょう!

前回までのおさらい

Sub 経費精算データ取り込み()

Dim fso As FileSystemObject
Set fso = New FileSystemObject

Dim di As Long: di = wsData.Cells(Rows.Count, 1).End(xlUp).Row + 1
Dim f As File
For Each f In fso.GetFolder(ThisWorkbook.Path & "\data").Files
    With Workbooks.Open(f.Path)
        With .Worksheets(1)
            
            Dim period As String: period = Left(f.Name, 6)
            Dim month As Date: month = DateSerial(Left(period, 4), Right(period, 2), 1)
                                    
            Dim staffId As Long: staffId = Val(Right(Replace(f.Name, ".xlsx", ""), 4))
            Dim staffName As String: staffName = WorksheetFunction.VLookup(staffId, wsStaff.Range("A:D"), 2, False)
            Dim departmentId As Long: departmentId = WorksheetFunction.VLookup(staffId, wsStaff.Range("A:D"), 3, False)
            Dim departmentName As String: departmentName = WorksheetFunction.VLookup(staffId, wsStaff.Range("A:D"), 4, False)
            
            Dim i As Long: i = 12
            Do While .Cells(i, 1).Value <> ""
                wsData.Cells(di, 1).Value = month '1 対象月
                wsData.Cells(di, 2).Value = .Cells(i, 1).Value '2 日付
                wsData.Cells(di, 3).Value = ""  '3 部署No
                wsData.Cells(di, 4).Value = departmentName '4 部署
                wsData.Cells(di, 5).Value = staffId '5 社員No
                wsData.Cells(di, 6).Value = staffName '6 氏名
                wsData.Cells(di, 7).Value = .Cells(i, 2).Value '7 科目
                wsData.Cells(di, 8).Value = .Cells(i, 5).Value '8 摘要
                wsData.Cells(di, 9).Value = .Cells(i, 6).Value '9 金額
                wsData.Cells(di, 10).Value = .Cells(i, 7).Value '10 備考
                i = i + 1: di = di + 1
            Loop
        End With
        .Close
    End With
Next f

End Sub

15行目でファイル名からスタッフIDを抜き出しています。

また、16~18行目にVLookupメソッドを使って、社員マスタのシートwsStaffからスタッフIDをキーとして社員名、部署ID、部署名を取得しています。

ですが、以下のような場合はVLookupメソッドが成功しません。

  • ファイル名が間違っていてスタッフIDが正しく取得できなかった場合
  • 社員マスタに不備がありスタッフIDがヒットしなかった場合

この場合、どうなっちゃうんでしょうか?

VLookupメソッドを失敗させてみる

試しに、ファイル名を以下のように変更してみました。

ファイル名のスタッフIDを誤ったものにしてみる

本来は4桁ないとダメなんですが、3桁になっちゃってます。

これで実行しますと、以下のように「実行時エラー ‘1004’: WorksheetFunction クラスの VLookup プロパティを取得できません。」というエラーが発生します。

VLookupメソッドで実行時エラーが発生

「デバッグ」ボタンを押してみましょう。

VLookupメソッドでエラーが発生した場所とスタッフIDの値

変数staffIdの内容を見てみると、スタッフIDの値は「0」、マスタに「0」は存在しないので、VLookupメソッドでエラーが発生したということになります。

これを回避する方法についてお伝えしなければなりません。

On Errorステートメント

さて、VBAでは、一般的に実行時エラーの発生はエラーメッセージの表示と、処理の停止を意味します。

しかし、その実行時エラー発生時の処理をコントロールするためのOn Errorステートメントという構文が用意されています。

その使い方は以下の通り、3種類があります。

ステートメント 内容
On Error Goto 行数(またはラベル) エラー発生時に行数で指定した行番号またはラベルに処理を分岐する
On Error Resume Next 以降エラーが発生しても続行し、次のステートメントに処理を移す
On Error Goto 0 以降のエラーハンドラーを無効にする

On Error Resume Nextでエラーを無視する

今回は、そのうちOn Error Resume Nextを使ってみましょう。

エラーが発生しても、それを無視する魔法の言葉です。

On Error Resume Next

この一文を入れた以降は、エラーを無視してエラーが発生した次の行から処理を続行します。

これを先ほどのVLookup命令の前に入れてあげて、実行してみましょう。

すると、プログラムは最後まで実行され、以下のようにデータ収集がされます。

On Error Resume Nextで実行した結果

プログラム自体は中断せずに実行されましたが、本来太郎さんのデータが入るべき箇所に、花子さんのデータが入ってしまいました。

これはこれで問題ですね…

まとめ

エクセルVBAのVLookupメソッドによるエラーを回避する方法についてお伝えしました。

また、実行時エラー発生時の処理をコントロールするOn Errorステートメントについても紹介しました。

ですが、お伝えした通り、これだけでは解決になっていないので、次回に解決をしていきたいと思います。

どうぞお楽しみに!

連載目次:エクセルVBAで経費データをデータベースに集約する

請求書シリーズと逆のパターンですが、バラバラの帳票からデータ一覧つまりデータベースに情報を集めて蓄積していく、というお仕事も多いと思います。ここでは各担当者から提出された経費精算書をデータベースに蓄積するプログラムを目標にして進めていきます。
  1. 【エクセルVBA入門】バラバラの経費精算書をデータにまとめる
  2. 【エクセルVBA入門】Do While~Loop文で条件を満たす間繰り返し
  3. 【エクセルVBA入門】繰り返しを使ってデータの転記をするときの2つのポイント
  4. 【エクセルVBA入門】With文でプログラムをスッキリわかりやすく書く
  5. 【エクセルVBA入門】他のワークブックをWithで開く&保存せずに閉じる
  6. 【エクセルVBA入門】フォルダやファイルを操作するFileSystemオブジェクトとその使い方
  7. 【エクセルVBA入門】For Each~Next文でフォルダ内のブック全てを開く方法
  8. 【エクセルVBA入門】シートのデータがある最終行番号を求めるステートメントを徹底解説
  9. 【エクセルVBA入門】開いたブック名から文字列を抽出して人為的なミスを回避する方法
  10. 【エクセルVBA入門】マクロを作るときに知っておきたいマスタデータのこと
  11. 【エクセルVBA入門】開いたブックのファイル名から番号を取り出して数値に変換する
  12. 【エクセルVBA入門】マクロでVlookupを使ってデータを検索する方法
  13. 【エクセルVBA入門】Vlookupメソッドを使ったときに発生するエラーを回避する方法

【エクセルVBA入門】エラーが発生したときに分岐処理を追加する方法

$
0
0
alert

photo credit: kolix incomplete walk via photopin (license)

みなさん、こんにちは!
タカハシ(@ntakahashi0505)です。

エクセルVBAでバラバラの経費精算書ファイルのデータを収集するマクロの作り方をお伝えしています。

前回の記事はコチラ。

【エクセルVBA入門】Vlookupメソッドを使ったときに発生するエラーを回避する方法
エクセルVBAでバラバラの経費精算書を一つのデータベースにまとめるマクロの作り方についてお伝えしています。今回はVLookupメソッドを使ったときに発生するエラーを回避する方法についてお伝えします。

VLookupメソッドが失敗しても処理を継続するためにOn Error Resume Nextステートメントを追加しました。

ただ、エラーが発生したことがわかりづらくなっちゃったんですよね。

ということで、今回はエクセルVBAでエラーが発生したときにメッセージを表示する処理を追加する方法をお伝えしていきます。

では、行ってみましょう!

前回のおさらい

前回作成したコードはコチラです。

Sub 経費精算データ取り込み()

Dim fso As FileSystemObject
Set fso = New FileSystemObject

Dim di As Long: di = wsData.Cells(Rows.Count, 1).End(xlUp).Row + 1
Dim f As File
For Each f In fso.GetFolder(ThisWorkbook.Path & "\data").Files
    With Workbooks.Open(f.Path)
        With .Worksheets(1)
            
            Dim period As String: period = Left(f.Name, 6)
            Dim month As Date: month = DateSerial(Left(period, 4), Right(period, 2), 1)
                                    
            On Error Resume Next
            Dim staffId As Long: staffId = Val(Right(Replace(f.Name, ".xlsx", ""), 4))
            Dim staffName As String: staffName = WorksheetFunction.VLookup(staffId, wsStaff.Range("A:D"), 2, False)
            Dim departmentId As Long: departmentId = WorksheetFunction.VLookup(staffId, wsStaff.Range("A:D"), 3, False)
            Dim departmentName As String: departmentName = WorksheetFunction.VLookup(staffId, wsStaff.Range("A:D"), 4, False)
                      
            Dim i As Long: i = 12
            Do While .Cells(i, 1).Value <> ""
                wsData.Cells(di, 1).Value = month '1 対象月
                wsData.Cells(di, 2).Value = .Cells(i, 1).Value '2 日付
                wsData.Cells(di, 3).Value = departmentId  '3 部署No
                wsData.Cells(di, 4).Value = departmentName '4 部署
                wsData.Cells(di, 5).Value = staffId '5 社員No
                wsData.Cells(di, 6).Value = staffName '6 氏名
                wsData.Cells(di, 7).Value = .Cells(i, 2).Value '7 科目
                wsData.Cells(di, 8).Value = .Cells(i, 5).Value '8 摘要
                wsData.Cells(di, 9).Value = .Cells(i, 6).Value '9 金額
                wsData.Cells(di, 10).Value = .Cells(i, 7).Value '10 備考
                i = i + 1: di = di + 1
            Loop
        End With
        .Close
    End With
Next f

End Sub

15行目にOn Error Resume Nextステートメントを入れたことで、その後のVLookupメソッドが失敗したとしても、処理を続行するようになりました。

ただ、その場合の結果がコチラですね。

On Error Resume Nextで実行した結果

正しくないデータが入ってしまいました。

もっとエラーが発生したことがわかるようにしたいですよね。

Errオブジェクトとは

そんな時のためにErrオブジェクトを使って、もう少しエラーが発生したことをわかりやすくしてあげましょう。

Errオブジェクトには、エラーが発生したときにどんなエラーが発生したのかという情報が格納されます。

これを使ってエラーが発生したときに何らかのアクションを起こすという処理を追加することができます。

Numberプロパティを使ってエラーの発生で分岐

ErrオブジェクトのNumberプロパティには初期値は0が格納されていて、エラーが発生するとエラーの種類に応じて0より大きい番号が格納されます。

Errオブジェクト.Number

例えば、以下のエラーメッセージであれば「1004」がエラーの番号になります。

実行時エラーの番号

ですから例えば、以下のようなコードを挿入すると

If Err.Number <> 0 Then
    MsgBox f.Name & " の処理でエラーが発生しました。"
End If

それ以前にエラーが発生していれば、メッセージダイアログが表示されます。

On Error GoTo 0でエラーハンドラーを無効化する

しかし、一点問題がありまして、On Error Resume Nextステートメントは、VLookupメソッド以外のエラーについても継続をしてしまいます。

想定していないエラーまで、全てもみ消してしまうわけです。

それはそれで問題ですので、On Error GoTo 0ステートメントで、エラーハンドラーを無効化します。

On Error GoTo 0

「エラーが発生したら0行目に飛ぶ」、みたいな命令に見えますが、そうではなくて

  • On Error~で有効になっているエラーハンドラーを無効化する
  • Errオブジェクトを初期化する

という役割を果たします。

つまり、今回の場合は、On Error Resume Nextの効果をかき消して、さらにErrオブジェクトをリセット(Numberプロパティも0に)することになります。

エラー処理を加えた経費精算データ収集マクロ

以上を踏まえたコードはこちらになります。

Sub 経費精算データ取り込み()

Dim fso As FileSystemObject
Set fso = New FileSystemObject

Dim di As Long: di = wsData.Cells(Rows.Count, 1).End(xlUp).Row + 1
Dim f As File
For Each f In fso.GetFolder(ThisWorkbook.Path & "\data").Files
    With Workbooks.Open(f.Path)
        With .Worksheets(1)
            
            Dim period As String: period = Left(f.Name, 6)
            Dim month As Date: month = DateSerial(Left(period, 4), Right(period, 2), 1)
                                    
            On Error Resume Next
            Dim staffId As Long: staffId = Val(Right(Replace(f.Name, ".xlsx", ""), 4))
            Dim staffName As String: staffName = WorksheetFunction.VLookup(staffId, wsStaff.Range("A:D"), 2, False)
            Dim departmentId As Long: departmentId = WorksheetFunction.VLookup(staffId, wsStaff.Range("A:D"), 3, False)
            Dim departmentName As String: departmentName = WorksheetFunction.VLookup(staffId, wsStaff.Range("A:D"), 4, False)

            If Err.Number <> 0 Then
                MsgBox f.Name & " の処理でエラーが発生しました。"
                staffId = 0
                staffName = ""
                departmentId = 0
                departmentName = ""
            End If
            On Error GoTo 0
                      
            Dim i As Long: i = 12
            Do While .Cells(i, 1).Value <> ""
                wsData.Cells(di, 1).Value = month '1 対象月
                wsData.Cells(di, 2).Value = .Cells(i, 1).Value '2 日付
                wsData.Cells(di, 3).Value = departmentId  '3 部署No
                wsData.Cells(di, 4).Value = departmentName '4 部署
                wsData.Cells(di, 5).Value = staffId '5 社員No
                wsData.Cells(di, 6).Value = staffName '6 氏名
                wsData.Cells(di, 7).Value = .Cells(i, 2).Value '7 科目
                wsData.Cells(di, 8).Value = .Cells(i, 5).Value '8 摘要
                wsData.Cells(di, 9).Value = .Cells(i, 6).Value '9 金額
                wsData.Cells(di, 10).Value = .Cells(i, 7).Value '10 備考
                i = i + 1: di = di + 1
            Loop
        End With
        .Close
    End With
Next f

End Sub

21~28行目が追加した処理ですね。

実行時エラーが発生したときに、メッセージの表示に加えて、マスタから取得すべきデータを0または空にしています。

エラーが発生した場合、以下のようなメッセージが表示されつつ

実行時エラーが発生したときに表示されるメッセージ

実行結果は以下のように、余計なデータが含まれないようになります。

経費精算書マクロの実行結果

まとめ

以上、エクセルVBAでエラーが発生したときに分岐処理を追加する方法についてお伝えしました。

On Errorステートメントと分岐処理について解説をしました。

これで、ファイル名にミスがあっても拾えるようになりましたが、他にも色々なパターンがありますので、ベストな方法を模索してみてくださいね。

次回は、別のエラー発生要因について考えてみたいと思います

どうぞお楽しみに!

連載目次:エクセルVBAで経費データをデータベースに集約する

請求書シリーズと逆のパターンですが、バラバラの帳票からデータ一覧つまりデータベースに情報を集めて蓄積していく、というお仕事も多いと思います。ここでは各担当者から提出された経費精算書をデータベースに蓄積するプログラムを目標にして進めていきます。
  1. 【エクセルVBA入門】バラバラの経費精算書をデータにまとめる
  2. 【エクセルVBA入門】Do While~Loop文で条件を満たす間繰り返し
  3. 【エクセルVBA入門】繰り返しを使ってデータの転記をするときの2つのポイント
  4. 【エクセルVBA入門】With文でプログラムをスッキリわかりやすく書く
  5. 【エクセルVBA入門】他のワークブックをWithで開く&保存せずに閉じる
  6. 【エクセルVBA入門】フォルダやファイルを操作するFileSystemオブジェクトとその使い方
  7. 【エクセルVBA入門】For Each~Next文でフォルダ内のブック全てを開く方法
  8. 【エクセルVBA入門】シートのデータがある最終行番号を求めるステートメントを徹底解説
  9. 【エクセルVBA入門】開いたブック名から文字列を抽出して人為的なミスを回避する方法
  10. 【エクセルVBA入門】マクロを作るときに知っておきたいマスタデータのこと
  11. 【エクセルVBA入門】開いたブックのファイル名から番号を取り出して数値に変換する
  12. 【エクセルVBA入門】マクロでVlookupを使ってデータを検索する方法
  13. 【エクセルVBA入門】Vlookupメソッドを使ったときに発生するエラーを回避する方法
  14. 【エクセルVBA入門】エラーが発生したときに分岐処理を追加する方法

【エクセルVBA入門】オートフィルタや行の非表示で隠れている行を全て表示する

$
0
0
hidden

photo credit: Reva G Peeking moon via photopin (license)

みなさん、こんにちは!
タカハシ(@ntakahashi0505)です。

エクセルVBAでバラバラの経費精算書のデータを収集するマクロの作り方をお伝えしています。

前回の記事はコチラ。

【エクセルVBA入門】エラーが発生したときに分岐処理を追加する方法
エクセルVBAでバラバラの経費精算書ファイルのデータを収集するマクロの作り方をお伝えしています。今回はエクセルVBAでエラーが発生したときにメッセージを表示する処理を追加する方法をお伝えしていきます。

マクロの実行時にエラーが発生したときに、処理を分岐する方法についてお伝えしました。

今回は、ちょっと別方面の「エラー」について対策をしていきます。

その「別方面」というのは、オートフィルタも含めて行の非表示をしているときに起こるエラーです。

ということで、今回はエクセルVBAでオートフィルタや行の非表示で隠れている行を全部表示する方法です。

では、行ってみましょう!

前回のおさらい

前回までに作成したマクロはコチラです。

Sub 経費精算データ取り込み()

Dim fso As FileSystemObject
Set fso = New FileSystemObject

Dim di As Long: di = wsData.Cells(Rows.Count, 1).End(xlUp).Row + 1
Dim f As File
For Each f In fso.GetFolder(ThisWorkbook.Path & "\data").Files
    With Workbooks.Open(f.Path)
        With .Worksheets(1)
            
            Dim period As String: period = Left(f.Name, 6)
            Dim month As Date: month = DateSerial(Left(period, 4), Right(period, 2), 1)
                                    
            On Error Resume Next
            Dim staffId As Long: staffId = Val(Right(Replace(f.Name, ".xlsx", ""), 4))
            Dim staffName As String: staffName = WorksheetFunction.VLookup(staffId, wsStaff.Range("A:D"), 2, False)
            Dim departmentId As Long: departmentId = WorksheetFunction.VLookup(staffId, wsStaff.Range("A:D"), 3, False)
            Dim departmentName As String: departmentName = WorksheetFunction.VLookup(staffId, wsStaff.Range("A:D"), 4, False)

            If Err.Number <> 0 Then
                MsgBox f.Name & " の処理でエラーが発生しました。"
                staffId = 0
                staffName = ""
                departmentId = 0
                departmentName = ""
            End If
            On Error GoTo 0
                      
            Dim i As Long: i = 12
            Do While .Cells(i, 1).Value <> ""
                wsData.Cells(di, 1).Value = month '1 対象月
                wsData.Cells(di, 2).Value = .Cells(i, 1).Value '2 日付
                wsData.Cells(di, 3).Value = departmentId  '3 部署No
                wsData.Cells(di, 4).Value = departmentName '4 部署
                wsData.Cells(di, 5).Value = staffId '5 社員No
                wsData.Cells(di, 6).Value = staffName '6 氏名
                wsData.Cells(di, 7).Value = .Cells(i, 2).Value '7 科目
                wsData.Cells(di, 8).Value = .Cells(i, 5).Value '8 摘要
                wsData.Cells(di, 9).Value = .Cells(i, 6).Value '9 金額
                wsData.Cells(di, 10).Value = .Cells(i, 7).Value '10 備考
                i = i + 1: di = di + 1
            Loop
        End With
        .Close
    End With
Next f

End Sub

前回は21~28行目。実行時エラーが起きたときに、ErrオブジェクトのNumberプロパティを見て、エラーが発生しているかどうかを判定。それにより分岐処理をするという部分を作成しました。

しかし、このマクロでは特定の条件では、うまく動作しません。

以下説明をしていきますね。

行の非表示でマクロが正しく動かない

このコードでは以下のようなケースで不具合が起こることがあります。

まず、経費精算書のデータを集めるシートである「経費データ」シートには、既に2018年7月のデータが以下のように入力されている状態とします。

既に経費精算書に蓄積されているデータ

2行目から8行目まで、全7件のデータが記録されていますね。

ただ、色々と作業をする上で、以下のようにオートフィルタなどを使用して、4~8行目が非表示になっているとします。

オートフィルタなどで行が非表示になっている
このように、一部の行が非表示になっている状態で、2018年8月の経費精算書データを追加で収集します。

すると…おや?9行目以降に経費データが追加されるはずなのですが、何も追加されていないようです。

マクロの実行をすると最終行以降にはデータが追加されていない

フィルタを外して、行を再表示してみると、以下のように4行目から6行目に、上書きでデータが追加されてしまいました。

データが上書きで追加されてしまった

これは明らかに問題です。前のデータが上書き、つまり消えてしまうなんて困りますもんね。

行の非表示でデータが上書きされてしまう理由

この原因はコードの6行目の以下の部分です。

di = wsData.Cells(Rows.Count, 1).End(xlUp).Row + 1

このステートメントは、シートのA列の1番下からショートカットキー Ctrl + をしたときの行番号にプラス1をした数値を返します。

もうお察しだと思いますが、例えば行の非表示をした状態で、実際に Ctrl + をしてみてください。

以下のように、3行目にカーソルがいってしまいます。

行の非表示をしているときのCtrl+↑

ですから、diには「4」が入り、4行目からデータの入力がはじまってしまったのです。

このように、行が非表示になっている場合は、マクロの動作に影響をしてしまうことがあります。

運用上、ワークシートで行の非表示をするようなする操作をする可能性がある場合は、対策をうっておくと良いでしょうか。

隠れている行を表示する

この問題を解決する方法はいくつかあります。

今回は、最もシンプルな方法で、隠れている行を表示してあげるという方法です。

それで、非表示になっているケースも、オートフィルタで非表示になっている場合と、行の非表示で非表示になっている場合では、解除の方法が違うので、両方やっておきます。

オートフィルタを解除する方法

オートフィルタを解除するには、WorksheetオブジェクトのAutoFilterModeプロパティの値を設定してあげます。

Worksheetオブジェクト.AutoFilterMode

AutoFilterModeプロパティは、指定したワークシートにオートフィルタが設定されているかどうかをBoolean型で表します。

ですから、これをFalseにしてあげれば、オートフィルタが解除され、それに伴ってオートフィルタで非表示になっている行も表示されるということになります。

つまり、以下のステートメントを冒頭などに入れてあげればOKです。

wsData.AutoFilterMode = False

なお、AutoFilterModeプロパティはTrueに設定することはできません!

適用をするには、AutoFilterメソッドを使います。ご興味あれば、調べてみてください。

行の非表示を解除する方法

行の非表示で隠されているのであれば、それを解除するための別のプロパティを使います。

Hiddenプロパティというものです。

書き方としては以下の通りです。

Rangeオブジェクト.Hidden

ここで対象となるRangeオブジェクトは行全体、または列全体を表すRangeオブジェクトである必要があります。

対象のRangeオブジェクトが非表示ならTrue、非表示でないならFalseとなります。

今回は、全ての行について非表示を解除したいので、以下のステートメントを冒頭などに入れてあげればOKですね。

wsData.Rows.Hidden = False

もし、列の非表示も気になるのであれば、以下も入れてしまうという手もあります。

wsData.Columns.Hidden = False

まとめ

以上、エクセルVBAでオートフィルタや行の非表示で隠れている行を全て表示する方法についてお伝えしました。

エクセルシートについて、オートフィルタや行(または列)の非表示をするような操作の可能性があるのであれば、念の為入れておくと良いですね。

まとめのコードはコチラです。

Sub 経費精算データ取り込み()

Dim fso As FileSystemObject
Set fso = New FileSystemObject

With wsData
    .AutoFilterMode = False
    .Rows.Hidden = False
    Dim di As Long: di = .Cells(Rows.Count, 1).End(xlUp).Row + 1
End With

Dim f As File
For Each f In fso.GetFolder(ThisWorkbook.Path & "\data").Files
    With Workbooks.Open(f.Path)
        With .Worksheets(1)
            
            Dim period As String: period = Left(f.Name, 6)
            Dim month As Date: month = DateSerial(Left(period, 4), Right(period, 2), 1)
                                    
            On Error Resume Next
            Dim staffId As Long: staffId = Val(Right(Replace(f.Name, ".xlsx", ""), 4))
            Dim staffName As String: staffName = WorksheetFunction.VLookup(staffId, wsStaff.Range("A:D"), 2, False)
            Dim departmentId As Long: departmentId = WorksheetFunction.VLookup(staffId, wsStaff.Range("A:D"), 3, False)
            Dim departmentName As String: departmentName = WorksheetFunction.VLookup(staffId, wsStaff.Range("A:D"), 4, False)

            If Err.Number <> 0 Then
                MsgBox f.Name & " の処理でエラーが発生しました。"
                staffId = 0
                staffName = ""
                departmentId = 0
                departmentName = ""
            End If
            On Error GoTo 0
                      
            Dim i As Long: i = 12
            Do While .Cells(i, 1).Value <> ""
                wsData.Cells(di, 1).Value = month '1 対象月
                wsData.Cells(di, 2).Value = .Cells(i, 1).Value '2 日付
                wsData.Cells(di, 3).Value = departmentId  '3 部署No
                wsData.Cells(di, 4).Value = departmentName '4 部署
                wsData.Cells(di, 5).Value = staffId '5 社員No
                wsData.Cells(di, 6).Value = staffName '6 氏名
                wsData.Cells(di, 7).Value = .Cells(i, 2).Value '7 科目
                wsData.Cells(di, 8).Value = .Cells(i, 5).Value '8 摘要
                wsData.Cells(di, 9).Value = .Cells(i, 6).Value '9 金額
                wsData.Cells(di, 10).Value = .Cells(i, 7).Value '10 備考
                i = i + 1: di = di + 1
            Loop
        End With
        .Close
    End With
Next f

End Sub

次回は、今回の問題を別の切り口から解決してみます。

どうぞお楽しみに!

連載目次:エクセルVBAで経費データをデータベースに集約する

請求書シリーズと逆のパターンですが、バラバラの帳票からデータ一覧つまりデータベースに情報を集めて蓄積していく、というお仕事も多いと思います。ここでは各担当者から提出された経費精算書をデータベースに蓄積するプログラムを目標にして進めていきます。
  1. 【エクセルVBA入門】バラバラの経費精算書をデータにまとめる
  2. 【エクセルVBA入門】Do While~Loop文で条件を満たす間繰り返し
  3. 【エクセルVBA入門】繰り返しを使ってデータの転記をするときの2つのポイント
  4. 【エクセルVBA入門】With文でプログラムをスッキリわかりやすく書く
  5. 【エクセルVBA入門】他のワークブックをWithで開く&保存せずに閉じる
  6. 【エクセルVBA入門】フォルダやファイルを操作するFileSystemオブジェクトとその使い方
  7. 【エクセルVBA入門】For Each~Next文でフォルダ内のブック全てを開く方法
  8. 【エクセルVBA入門】シートのデータがある最終行番号を求めるステートメントを徹底解説
  9. 【エクセルVBA入門】開いたブック名から文字列を抽出して人為的なミスを回避する方法
  10. 【エクセルVBA入門】マクロを作るときに知っておきたいマスタデータのこと
  11. 【エクセルVBA入門】開いたブックのファイル名から番号を取り出して数値に変換する
  12. 【エクセルVBA入門】マクロでVlookupを使ってデータを検索する方法
  13. 【エクセルVBA入門】Vlookupメソッドを使ったときに発生するエラーを回避する方法
  14. 【エクセルVBA入門】エラーが発生したときに分岐処理を追加する方法
  15. 【エクセルVBA入門】オートフィルタや行の非表示で隠れている行を全て表示する

VBAでIEを操作してJavaScriptで動作するWebページをスクレイピング

$
0
0

VBAでIE操作

みなさん、こんにちは!うえはら(@tifoso_str)です。

JavaScriptで動作するWebページを色々な言語でスクレイピング】するシリーズの第二弾です。

前回は、JavaScriptで動作するWebページはVBAのHTTP通信ではスクレイピングできないことを確認しました。

GASやVBAでスクレイピングができない理由として考えるべきJavaScriptのこと
「JavaScriptで動作するWebページ(動的サイト)を色々な言語でスクレイピング」することをシリーズでお伝えしています。 今回はJavaScriptで動作するWebページは普通にスクレイピングできない原因とその解決法をお伝えします。

なぜ、スクレイピングできないか?というと、Webページがサーバーからソースを読み込んだ後に、ブラウザー(Chromeなど)でJavaScriptを実行して画面に表示しているからでした。

HTTP通信

これを解決する為には、ブラウザーでこのWebページを処理させてあげればいいわけです。

そこで、今回は、「VBAでInternet Explorerを操作してJavaScriptで動作するWebページをスクレイピング」していきます。

VBAでInternet Explorerを操作する準備

VBAでInternet Explorerを操作するには下記のライブラリを追加する必要があります。

  • Microsoft HTML Object Library
  • Microsoft Internet Controls

VBEの「ツール」、「参照設定」から追加してください。

参照設定

Microsoftで始まるライブラリは結構な数があるので、見落とさないようにしてくださいね。

参照設定でオブジェクト選択

VBAでIEを操作するための準備は、下記記事で詳しく解説していますので、ご覧下さい。

【エクセルVBAでIE操作】10分で終わるセッティングとWEBページの閲覧確認
エクセルVBAでInternetExplorerを操作するシリーズの導入編です。今回はIEを操作するときに最初にすべきセッティングと実際にWEBページを開く動作確認までをやってみたいと思います。

VBAでInternet Explorerを操作するスクリプト

下記は、VBAでInternetExplorerを操作して、沖縄県企業局のホームページから貯水率を取得するスクリプトです。

Webページへの接続、処理待ち、タイトル表示までは下記シリーズで解説しているので、その次から説明していきます。

【エクセルVBAでIE操作】ブラウザの読み込み待ちをしないとダメなのです
エクセルVBAでIEを操作するシリーズの第3回、今回はIEの読み込み待ちの処理を入れていきます。この処理はVBAでIEを扱う限りはほとんどの場合で必要となる処理ですので、ぜひ覚えて頂ければと思います。

Sub scrapingByIE()

    Dim objIE As InternetExplorer
    Set objIE = CreateObject("Internetexplorer.Application")
    objIE.Visible = True
    objIE.navigate "https://www.eb.pref.okinawa.jp/kassui/"

    Do While objIE.Busy = True Or objIE.readyState < 4
        DoEvents '処理待ち
    Loop

    Application.Wait Now + TimeValue("0:00:01")'HTMLの読み込み待ち

    Dim htmlDoc As HTMLDocument
    Set htmlDoc = objIE.document
    
  Debug.Print htmlDoc.Title 'タイトル

    Dim hiduke As IHTMLElement
    Set hiduke = htmlDoc.getElementById("chosui_hiduke")
    Debug.Print "■日付は「" & hiduke.innerText & "」です。"

    Dim ritsu As IHTMLElement
    Set ritsu = htmlDoc.getElementById("ritsu_today4")
    Debug.Print "■本日の貯水率は「" & ritsu.innerText & "」です。"
    
    objIE.Quit 'IEを閉じる
    Set objIE = Nothing
End Sub

JavaScriptの実行待ちの処理を入れる

12行目は、InternetExplorerがJavaScriptを実行するための実行待ち時間です。

小生の環境ではJavaScriptの処理に8ミリ秒必要だったので、1秒あれば十分だと思います。

VBAの処理を指定の時間まで止めるにはApplicationオブジェクトのWaitメソッドを使用します。

Application.Wait 指定時間

この指定時間は、Excelの日付形式で指定します。

今回は実行時刻に1秒足した時間まで停止するようにします。

実行時刻はNow関数で取得できます。

1秒足した時間を表すのにTimeValue関数を使用します。

TimeValue(時刻文字列)

この停止時間は、Webページのソース量やパソコンの性能にもよりますので、時間は適宜調整してください。

なお、今回はJavaScriptの実行待ちでよかったですが、Webページによってはボタンの操作だったり、その他の処理が必要になることがあります。

それぞれのWebページに合わせて、処理を変化させる必要がありますので、ご注意ください。

HTMLタグのIDを利用して要素を取得する

今回は下記のように取得する要素にIDがあるので、IDを元に要素が取得できます。

日付
<span id="chosui_hiduke">06月21日</span>
 
貯水率
<td align="right" id="ritsu_today4">72.1</td>

IDから要素を取得するときは、getElementByIdメソッドを使用します。

HTMLドキュメント.getElementById(ID)

今回でいうとIDは「chosui_hiduke」と「ritsu_today4」になります。

欲しい要素のHTMLタグにIDが設定してあると、ピンポイントで簡単に取得できます。

スクレイピングするときは、要素のタグにIDが設定されていないかまず確認してみてください。

スクレイピング結果の確認

上記のスクリプトを実行すると、Internet Explorerが立ち上がり、目的のWebページが表示されます。

イミディエイトウィンドウに結果が表示された後、Internet Explorerが自動で閉じます。

これくらいだと、あっという間に取得しますね!

イミディエイトウィンドウを確認すると下記のように日付と貯水率が表示されます。

イミディエイトウィンドウで貯水率の確認

今回は、ちゃんと日付と貯水率が取得できました!

まとめ

今回は、VBAでInternet Explorerを操作してJavaScriptで動作するWebページをスクレイピングしました。

Internet Explorerを操作するというと、難しく感じるかもしれませんが、HTTP通信するときとさほど変わらないですよね!

次回はGoogle Apps ScriptでJavaScriptで動作するWebページをスクレイピングします。

お楽しみに!

連載目次:JavaScriptで動作するWebページを色々な言語でスクレイピング

Webスクレイピングしていて、値が取得できないということはありませんか?

そんな時は、Webサイトの表示にJavaScriptを利用しているからです。

本連載では、色々な言語でその対応をご紹介します!

  1. GASやVBAでスクレイピングができない理由として考えるべきJavaScriptのこと
  2. VBAでIEを操作してJavaScriptで動作するWebページをスクレイピング
  3. Google Apps ScriptでJavaScriptで動作するWebページをスクレイピングする準備

【エクセルVBA】FindNextメソッドで指定範囲内の検索条件にヒットする値をすべて取得する方法

$
0
0
vba,findnext,eyecatch

皆様こんにちは、ノグチです。

前回の記事では、指定した範囲内で値を検索するFindメソッドをご紹介しました。

【エクセルVBA】指定した範囲内から値を検索するFindメソッドとその使い方
エクセルシートに入力した値から、VBAのFindメソッドを使ってシート内を検索し、結果を返す方法をご紹介しています。Findメソッドは部分一致or全体一致、列方向or行方向など検索の条件を色々指定できて使い勝手が良いのでオススメですよ。

Findメソッドは、指定した条件で指定範囲内を検索してくれる、とっても便利なメソッド。

でも、このFindメソッド、範囲の中で1件でも検索にヒットするとそこで検索を終えてしまうのですよね。

探したい値は1件だけじゃないかもしれない…

Findメソッドと同じ条件で指定範囲の最後まで検索したい!

そんな時に使えるのが、Findメソッドとセットで使えるFindNextメソッド。

今回は、このFindNextメソッドで、検索に複数ヒットする値の取得方法をご紹介します。

前回のおさらい

前回、Findメソッドを使って指定範囲内を検索したコードがこちら。

Dim rngSearch, varSearch
Dim myRange As Range
With ThisWorkbook

    varSearch = .Worksheets("請求書").Range("B5")
    Set myRange = .Worksheets("マスタ").Range("A1:A6")
    Set rngSearch = myRange.Find(What:=varSearch, LookAt:=xlPart)

    If Not rngSearch Is Nothing Then
        .Worksheets("請求書").Range("B5").Value = rngSearch
    Else
        MsgBox "該当する得意先はありません。"
    End If
End With
End Sub

そして請求書シートと
excel,vba,event,請求書

マスタシートがこちら。

excel,vba,event,マスタシート

請求書シートのB5セルに入力した値から、マスタシートの得意先リストを検索し、検索にヒットした得意先名称を請求書シートのB5セルに返す…というコードでした。

コードを見てみると、指定範囲内でFindメソッドによって最初にヒットした値をB5セルに返していますね。

そして指定した範囲のうち、最初にヒットした値以降のセルは検索されずに終わっています。

「探したい値が複数あるんだけど…」という場合、これでは困ってしまいます。

そんな時に使えるのがFindNextメソッドです。

FindNextメソッドの記述方法とパラメータ

FindNextメソッドは、Findメソッドで指定したRange型範囲の検索をFindメソッドで指定したパラメータで指定したセルから検索を続行してくれるメソッドです。
記述方法はこちら。
Range.FindNext(検索対象範囲内のセル)

戻り値はFindメソッドと同様、検索に値がヒットした場合はそのセルのRangeオブジェクトを、ヒットしなかった場合はNothingを返してくれます。

パラメータに指定したセルの次のセルから検索を続行するので、Findメソッドでヒットしたセルを指定しておけば、ヒットした次のセルから検索を続けてくれますよ。

検索対象範囲は、Findメソッドで指定した範囲を勝手に引き継いでくれますので、改めて指定する必要はありません。

FindNextメソッド単体では使えない

ちなみに、FindNextメソッドのパラメータは省略可能ですが、Findメソッドを記述しないでFindNextメソッドを使ってしまうと、実行時エラーにはならないものの、戻り値にNothingを返してくるだけで使い物になりませんので注意が必要です。

FindNextメソッドのパラメータは検索を続行するセルを指定できるのみで、検索したい値や検索範囲、検索の向きなどを指定できるものがありません。

よって、単体で使えないというのは頷けるのですが、「Findメソッドが無いよ」というメッセージくらい出してくれてもいいのでは…と思ってしまいます。

検索にヒットした値をすべてメッセージボックスに表示してみる

では、FindNextメソッドで指定した範囲内を全部検索して、検索にヒットした値をメッセージボックスに表示してみましょう。

コードはこちら。

Private Sub CustomerSearch()

Dim rngSearch As Range
Dim myRange As Range
Dim strMsg As String
Dim strAdr As String

With ThisWorkbook

    Set myRange = .Worksheets("マスタ").Range("A1:A6")

    Set rngSearch = myRange.Find(What:=.Worksheets("請求書").Range("B5"), LookAt:=xlPart)

    If rngSearch Is Nothing Then

        '1件もヒットしなかったらメッセージを表示する
        MsgBox "該当する得意先はありません。"

    Else
        '1件目を文字列にセット
        strMsg = strMsg & vbCrLf & rngSearch

        'ヒットした値のセルを退避
        strAdr = rngSearch.Address
        Do
            Set rngSearch = myRange.FindNext(rngSearch)
            If rngSearch Is Nothing Then
                Exit Do
            Else
                If strAdr <> rngSearch.Address Then
                    strMsg = strMsg & vbCrLf & rngSearch
                End If
            End If

        Loop While rngSearch.Address <> strAdr

        MsgBox strMsg
    End If

End With
End Sub

Do~LoopのWhileの後に、一番最初に検索にヒットしたセルのアドレスと次に検索にヒットしたセルのアドレスを比較していますね。

FindNextメソッドはマスタシートの最後のセルまで検索したら、指定した範囲の先頭に戻って検索を続けます。

先頭に戻ってから最初に検索にヒットしたセルは、Findメソッドで最初に検索にヒットしたセルと同じセルということになりますよね。

つまり、指定した範囲はFindメソッドとFindNextメソッドで検索を終えたということなので、そこで検索を終える為に、一番最初に検索にヒットした値のセルと最後に検索にヒットした値のセルを比較して、同じであれば検索のループを抜ける、という処理にしているのです。

さて、請求書シートに『株』を入力した場合、マスタシートの得意先リストには『株』の文字を持つ値が3つありますので、メッセージボックスに3件の得意先名が表示されるはずです。
早速実行してみましょう。
FindNextメソッド,検索結果
この通り、検索にヒットした値がすべてメッセージボックスに表示されていますね。

最後に

今回は、FindNextメソッドで指定した範囲で検索に複数ヒットした場合の値の取得方法を紹介しました。

エクセルツールの目的によって、検索の一番最初にヒットした値を持ってくればよいのか、ヒットした値全てを持ってくればよいのかは変わってきますよね。

Findメソッドだけを使うのか、FindNextメソッドも一緒に使うのか、やりたいことによって使い分けたいですね。

それでは、最後までお読み頂きありがとうございました!

GASでデフォルトのGoogleカレンダーにイベントを追加する簡単なスクリプト

$
0
0
calendar

photo credit: wuestenigel First Day of Summer Calendar via photopin (license)

みなさん、こんにちは!
タカハシ(@ntakahashi0505)です。

Googleカレンダー、使っていらっしゃいますか?

いつでもどこでも予定の確認できますし、修正も簡単。

繰り返しの予定を入れるのも一発ですし、メンバーの招待をすれば勝手にメールを送ってくれたりします。

便利ですよね!

ただ、繰り返しではない複数の予定を入れるとき、ちょっと面倒だな~って思いません?

弊社でいうと、企業様向けの研修とか8回とか12回とかあるのですが、これをブラウザでちまちま入れていくの面倒なんです。

ということで、このシリーズでは、Google Apps ScriptでGoogleカレンダーに複数のイベントをまとめて追加するスクリプトを作っていきます。

今回はまず、Google Apps Scriptでデフォルトのカレンダーにイベントを追加する方法です。

では、行ってみましょう!

デフォルトのカレンダーを取得する

本ツールでは、実行者である自分のカレンダーにイベントを追加することを想定していますので、まずはその操作対象である自分のカレンダーを取得する必要があります。

GASでは、実行者である自分のカレンダーのことを「デフォルトのカレンダー」といいます。

それで、デフォルトのカレンダーを取得する専用のメソッドとして、getDefaultCalendarメソッドという便利なやつが用意されています。

使い方はこうです。

CalendarApp.getDefaultCalendar()

戻り値はCalendarオブジェクトになります。

デフォルトのカレンダーを取得するスクリプトの例

例えば、こんなスクリプトを作って実行してみましょう。

function createEvents() {
  var calendar = CalendarApp.getDefaultCalendar();
  Logger.log(calendar.getName());
}

getNameメソッドはCalendarオブジェクトの名前を表示するメソッドです。

Calendarオブジェクト.getName()

実行してログを確認すると、以下のように取得したデフォルトカレンダーの名前が表示できるはずです。

デフォルトカレンダーのカレンダー名を確認する

Googleカレンダーにイベントを作成する

では、取得したデフォルトカレンダーにイベントを作成してみましょう。

イベントっていうのは、日本語でいえばいわゆる「予定」ですね。

Googleカレンダーにイベントを作成するには、createEventメソッドを使います。

書式はこちら。

Calendarオブジェクト.createEvent(イベント名, 開始時間, 終了時間, オプション)

イベントは予定の名前で文字列で指定します。

開始時間と終了時間は、Dateオブジェクトで指定します。

オプションはオブジェクト形式で、以下の項目を設定することができます。オプションは指定しなくてもOKです。

オプション 説明
description 文字列 イベントの説明
location 文字列 イベントの場所(住所など)
guests 文字列 ゲストとして追加するユーザーの電子メールアドレス(複数の場合はカンマ区切り)
sendInvites 真偽値 招待メールを送信するかどうか(デフォルトはfalse)

Googleカレンダーにイベントを追加するスクリプトの例

例えば、以下のようなスクリプトを実行してみましょうか。

function createEvents() {
  var calendar = CalendarApp.getDefaultCalendar();
  var title = '研修1回目';
  var startTime = new Date('2018/7/16 09:00:00');
  var endTime = new Date('2018/7/16 12:00:00');
  var option = {
    description: 'イントロダクション',
    location: '〒105-0011 東京都港区芝公園4丁目2−8'
  }
  
  calendar.createEvent(title, startTime, endTime, option);
}

すると、以下のようにデフォルトカレンダーにイベントを追加することができます。

GASで追加したイベント

まとめ

Google Apps ScriptでデフォルトのGoogleカレンダーにイベントを追加する方法をお伝えしました。

今回は、一つの予定をしかもスクリプト内にベタ打ちのデータをもとに作成しただけですが、次回以降これを便利ツールに変えていきますよ。

次回はスプレッドシートからイベントリストを読み取っていくところを作成していきます。

GASでスプレッドシートの入力からGoogleカレンダーに複数のイベントを追加
GASでGoogleカレンダーに複数の予定をまとめて追加するスクリプトを作成する方法をお伝えしています。今回は、スプレッドシートの値を二次元配列で取得して、複数のイベントを追加するスクリプトを作成します。

どうぞお楽しみに!

連載目次:GASでカレンダーイベントをまとめて追加するツールを作る

Googleカレンダー便利ですよね!ただ、複数のイベントをいくつも追加しなければいけないとき…少し面倒です。そんなときのための便利ツールとして、スプレッドシートの入力情報をもとにカレンダーイベントをまとめて追加するツールを作成していきます。
  1. GASでデフォルトのGoogleカレンダーにイベントを追加する簡単なスクリプト
  2. GASでスプレッドシートの入力からGoogleカレンダーに複数のイベントを追加

GASでスプレッドシートの入力からGoogleカレンダーに複数のイベントを追加

$
0
0

みなさん、こんにちは!
タカハシ(@ntakahashi0505)です。

GASでGoogleカレンダーに複数の予定をまとめて追加するスクリプトを作成する方法をお伝えしています。

前回の記事はこちら。

GASでデフォルトのGoogleカレンダーにイベントを追加する簡単なスクリプト
Google Apps ScriptでGoogleカレンダーに複数のイベントをまとめて追加するスクリプトを作っていきます。今回は、GASでデフォルトのカレンダーを取得してイベントを追加する方法です。

Googleカレンダーに一つのイベントを追加する簡単なスクリプトについて紹介しました。

今回は、GASでスプレッドシートに入力した内容をもとに、複数のイベントを追加できるようにしていきたいと思います。

では、行ってみましょう!

前回のおさらい

前回は、GASでデフォルトのカレンダーに単一のイベントを追加する方法をお伝えしました。

イベントを追加するメソッドはcreateEventメソッドです。

Calendarオブジェクト.createEvent(イベント名, 開始時間, 終了時間, オプション)

それで、作成したスクリプトがこちらですね。

function createEvents() {
  var calendar = CalendarApp.getDefaultCalendar();
  var title = '研修1回目';
  var startTime = new Date('2018/7/16 09:00:00');
  var endTime = new Date('2018/7/16 12:00:00');
  var option = {
    description: 'イントロダクション',
    location: '〒105-0011 東京都港区芝公園4丁目2−8'
  }
  
  calendar.createEvent(title, startTime, endTime, option);
}

今回は、スプレッドシートに複数のイベント情報を入力しておいて、それをもとにイベントを追加できるようなスクリプトを作っていきたいと思います。

イベント情報を入力したスプレッドシート

まず、スプレッドシートを準備してみました。

こちらです。

カレンダーに追加するデータをスプレッドシートに入力

各列の構成は以下のようになっていますよ。

見出し データ型 説明
A列 イベントタイトル String イベント名
B列 開始時間 Date イベントの開始日時
C列 終了時間 Date イベントの終了日時
D列 場所 String 場所
E列 説明 String 説明

ほら、createEventメソッドで必要な引数に忠実に作ってみましたよ。

スプレッドシートから取得したデータの型を調べる

そういえば、createEventメソッドの開始時間、終了時間はDateオブジェクトで与える必要がありましたよね…

スプレッドシートで日付型で記述しているからって、それってgetValuesメソッドなどで取得したら、それはDateオブジェクトになるんですかね?

文字列型になっちゃったりしませんかね?

心配な方は、以下のようなスクリプトを実行してみましょう。

function checkType() {
  var calendar = CalendarApp.getDefaultCalendar();
  var values = SpreadsheetApp.getActiveSheet().getDataRange().getValues();

  for(var i = 1; i < values.length; i++){
    Logger.log('value:%s, type:%s',values[i][1], typeof values[i][1]);    
  } 
}

3行目ですが、バインドしているスプレッドシートのアクティブシートの使用しているデータ範囲の値を二次元配列で取得しているステートメントになります。

そして、5行目からのfor文ですが、その二次元配列の見出し行の次から要素がある分だけ繰り返しをするというものです。

forループの処理ですが、2列目つまりB列の値とそのデータ型をログ出力しています。

typeof演算子で値のデータ型を確認する

typeof演算子は、値のデータ型を取得します。

typeof

前述のスクリプトを実行すると、以下のようなログ出力を得ました。

スプレッドシートの日時についてtypeof演算子でデータ型を確認

タイプは「object」となっていますね。おそらく、Dateオブジェクトとして取得されていると考えて良いでしょう。

スプレッドシートに入力した複数のイベントをカレンダーに追加するスクリプト

では、安心してスプレッドシートのイベントリストから、Googleカレンダーにイベントを追加するスクリプトを作ってみました。

function createEvents() {
  var calendar = CalendarApp.getDefaultCalendar();
  var values = SpreadsheetApp.getActiveSheet().getDataRange().getValues();

  for(var i = 1; i < values.length; i++){
    var title = values[i][0];
    var startTime = values[i][1];
    var endTime = values[i][2];
    var option = {
      description: values[i][3],
      location: values[i][4]
    }
    
    calendar.createEvent(title, startTime, endTime, option);    
  }
}

このスクリプトを実行すると、以下のようにイベントが追加されたことを確認できました。

スプレッドシートのデータから追加したGoogleカレンダーのイベント

まとめ

以上、GASでスプレッドシートの入力をもとにGoogleカレンダーに複数のイベントを追加するスクリプトを紹介しました。

次回以降、このツールをもう少し便利にしていきたいと思います。

どうぞお楽しみに!

連載目次:GASでカレンダーイベントをまとめて追加するツールを作る

Googleカレンダー便利ですよね!ただ、複数のイベントをいくつも追加しなければいけないとき…少し面倒です。そんなときのための便利ツールとして、スプレッドシートの入力情報をもとにカレンダーイベントをまとめて追加するツールを作成していきます。
  1. GASでデフォルトのGoogleカレンダーにイベントを追加する簡単なスクリプト
  2. GASでスプレッドシートの入力からGoogleカレンダーに複数のイベントを追加

Google Apps ScriptでDateオブジェクトの複製や時刻のセットで注意すること

$
0
0
date

photo credit: themostinept 22nd January 2017 via photopin (license)

みなさん、こんにちは!
タカハシ(@ntakahashi0505)です。

GASでGoogleカレンダーに複数の予定を簡単に登録できるツールを作成しています。

前回の記事はこちら。

GASでスプレッドシートの入力からGoogleカレンダーに複数のイベントを追加
GASでGoogleカレンダーに複数の予定をまとめて追加するスクリプトを作成する方法をお伝えしています。今回は、スプレッドシートの値を二次元配列で取得して、複数のイベントを追加するスクリプトを作成します。

スプレッドシートの情報をもとに、カレンダーにイベントを複数追加するスクリプトを作成しました。

…え、まだ何かやることあるの?

というふうに思われるかもしれませんが、イベントのデータの準備をもっと楽にできるんです。

ただ、それに伴って、ちょっとDateオブジェクトの使い方を気にする必要があります。

ということで、今回はGoogle Apps ScriptでDateオブジェクトの複製や時刻のセットで注意することについてお伝えします。

では、行ってみましょう!

前回のおさらい

前回作成したスクリプトはこちらです。

function createEvents() {
  var calendar = CalendarApp.getDefaultCalendar();
  var values = SpreadsheetApp.getActiveSheet().getDataRange().getValues();

  for(var i = 1; i < values.length; i++){
    var title = values[i][0];
    var startTime = values[i][1];
    var endTime = values[i][2];
    var option = {
      description: values[i][3],
      location: values[i][4]
    }
    
    calendar.createEvent(title, startTime, endTime, option);    
  }
}

バインドしているスプレッドシートのアクティブシートのイベントをカレンダーにまとめて追加することができます。

それで、そのスプレッドシートがこちらでした。

カレンダーに追加するデータをスプレッドシートに入力

日時と開始時間、期間を入力データとしたい

で、作ってみてちょっと思うことがあるんですね。

B列の開始時間と、C列の終了時間。

年、月、日、時、分を同じセルに入力しなければいけません。

…ちょっと面倒ですよね?

それで、例えば以下のようなスプレッドシートのほうが入力しやすいと思うのです。

スプレッドシートの構成を変更
日付と開始時間、そして期間が決まれば、終了時間も決まりますもんね。

日付と開始時間、期間からカレンダーイベントを追加するスクリプト

それで、変更後のスプレッドシートに合わせてスクリプトも変更しました。

こちらです。

function createEvents() {
  var calendar = CalendarApp.getDefaultCalendar();
  var values = SpreadsheetApp.getActiveSheet().getDataRange().getValues();

  for(var i = 1; i < values.length; i++){
    var title = values[i][0];
    var startTime = new Date(values[i][1]);
    startTime.setHours(values[i][2].getHours());
    startTime.setMinutes(values[i][2].getMinutes());
    
    var endTime = new Date(startTime);
    endTime.setHours(endTime.getHours() + values[i][3].getHours());
    endTime.setMinutes(endTime.getMinutes() + values[i][3].getMinutes());

    var option = {
      description: values[i][3],
      location: values[i][4]
    }
    calendar.createEvent(title, startTime, endTime, option);    
  }
}

…んー、ちょっと複雑ですかね。

でも、大丈夫。解説していきます。

ポイントはDateオブジェクトですね。

オブジェクトを複製したいならnewしよう

まず、7~9行目が、開始時間を生成している部分、11~13行目が終了時間を生成する部分です。

それぞれ、newキーワードで新たなDateオブジェクトを生成して変数startTimeとendTimeにセットしています。

この部分、newキーワードを使わずに、うっかり以下のようにしたくなりますが…

var startTime = values[i][1];
startTime.setHours(values[i][2].getHours());
startTime.setMinutes(values[i][2].getMinutes());

var endTime = startTime;
endTime.setHours(endTime.getHours() + values[i][3].getHours());
endTime.setMinutes(endTime.getMinutes() + values[i][3].getMinutes());

これで実行すると、実はstartTimeがendTimeと同じ日時にセットされちゃいます。さらに言うと、values[i][1]も同じ日時にセットされちゃいます。

Dateオブジェクトをはじめ、オブジェクトを変数にセットした場合、そのオブジェクトへの参照を変数にセットすることになります。

物理的にメモリの別の場所にオブジェクトを複製するのではなくて、同じオブジェクトを表すことになっちゃうんですね。

ですから、values[i][1]、startTime、endTimeはいずれも同じDateオブジェクトを参照することになっているのです。

なので、物理的に別のDateオブジェクトを生成したいのであれば、ちゃんとnewキーワードを使って生成しないと駄目なんです。

別のDateオブジェクトの時、分をセットする

Dateオブジェクトの時間や分を変更したいのであれば、setHoursメソッド、setMinutesメソッドを使って、時や分を数値で指定してあげます。

Dateオブジェクト.setHours(時間)
Dateオブジェクト.setMinutes(分)

それで、別のDateオブジェクトの時間や分を参照してセットしたいのであれば、スクリプト内にあるように、以下のようにすることになります。

Dateオブジェクト.setHours(Dateオブジェクト2.getHours())
Dateオブジェクト.setMinutes(Dateオブジェクト2.getMinutes())

getHoursメソッドやgetMinutesメソッドはDateオブジェクトの時間や分を数値で取得するメソッドです。

スクリプトの12,13行目では、開始時間と期間の両方から時間、分を取得してそれぞれ加算したものを、endTimeにセットしていますね。

ちょっと回りくどいですが、GASの時刻の取り扱いではこのような使い方は比較的使いますので、マスターください。

今回は、秒単位でスプレッドシートのデータを準備することを想定していませんが、万が一秒単位が必要であれば、setSecondsメソッド、getSecondsメソッドも使用する必要があります。

まとめ

以上、Google Apps ScriptでDateオブジェクトの複製や時刻のセットで注意することについてお伝えしました。

カレンダー以外にも、スプレッドシートやフォームなどで日時の取り扱いは比較的起こりえます。

GASのDateオブジェクトの取り扱いでは、複製をしたいとき、時刻をセットしたいときに、若干のコツがいりますのでつかんでおくと良いですね。

次回は、今回作成した処理を関数化してみたいと思います。

どうぞお楽しみに!

連載目次:GASでカレンダーイベントをまとめて追加するツールを作る

Googleカレンダー便利ですよね!ただ、複数のイベントをいくつも追加しなければいけないとき…少し面倒です。そんなときのための便利ツールとして、スプレッドシートの入力情報をもとにカレンダーイベントをまとめて追加するツールを作成していきます。
  1. GASでデフォルトのGoogleカレンダーにイベントを追加する簡単なスクリプト
  2. GASでスプレッドシートの入力からGoogleカレンダーに複数のイベントを追加
  3. Google Apps ScriptでDateオブジェクトの複製や時刻のセットで注意すること

Google Apps Scriptで日付関連の処理を関数化する例とその際のポイント

$
0
0
component

photo credit: blairwang Find Your View via photopin (license)

みなさん、こんにちは!
タカハシ(@ntakahashi0505)です。

GASでGoogleカレンダーにまとめてイベントを登録できるツールを作成しています。

前回の記事はこちら。

Google Apps ScriptでDateオブジェクトの複製や時刻のセットで注意すること
GASでGoogleカレンダーに複数の予定を簡単に登録できるツールを作成しています。今回はGoogle Apps ScriptでDateオブジェクトの複製や時刻のセットで注意することについてお伝えします。

イベントの登録に必要なスプレッドシートの入力は簡単にできる一方で、日付関連の処理にコツが必要だったので、それについて解説をしました。

今回はその続きで、若干複雑化したスクリプトを関数化することで、よりシンプルにしていきます。

Google Apps Scriptで日付関連の処理を関数化する例とそのポイントについて解説をしていきます。

では、行ってみましょう!

前回のおさらい

Googleカレンダーに登録するイベントを入力するスプレッドシートは以下のようなものです。

カレンダーに追加するデータをスプレッドシートに入力

そして、このスプレッドシートの入力をもとに、デフォルトカレンダーにイベントを登録するスクリプトはこちらです。

function createEvents() {
  var calendar = CalendarApp.getDefaultCalendar();
  var values = SpreadsheetApp.getActiveSheet().getDataRange().getValues();

  for(var i = 1; i < values.length; i++){
    var title = values[i][0];
    var startTime = new Date(values[i][1]);
    startTime.setHours(values[i][2].getHours());
    startTime.setMinutes(values[i][2].getMinutes());
    
    var endTime = new Date(startTime);
    endTime.setHours(endTime.getHours() + values[i][3].getHours());
    endTime.setMinutes(endTime.getMinutes() + values[i][3].getMinutes());

    var option = {
      description: values[i][3],
      location: values[i][4]
    }
    calendar.createEvent(title, startTime, endTime, option);    
  }
}

7行目から13行目まで、Dateオブジェクト関連の若干ややこしめの処理が入りました。

このあたりを関数として部品化することで、シンプルなコードに変更していきたいと思います。

なお、関数の作り方は以下の記事を復習しておいてくださいね。

【初心者向けGAS】Google Apps Scriptで別の関数を呼び出すfunctionの書き方
Google Apps ScriptでBotを作りながらその基本を学ぶシリーズです。今回は、関数から別の関数を呼び出す方法です。functionの書き方、引数、仮引数、戻り値などについても解説します。

共通部分を関数化する

さて、スクリプトですが、よくご覧いただくと、7~9行目と、11~13行目って、処理として似ていますよね…?

処理として似ているところは、うまくやると共通化して外に出す、つまり関数化することができます。

  • 7~9行目
    1. values[i][1]をベースにDateオブジェクトstartTimeを生成し
    2. values[i][2]の時間、分をセットする
  • 11~13行目
    1. startTimeをベースにDateオブジェクトendTimeを生成し
    2. values[i][3]の時間、分をそれぞれ加算する

ちょっと文面が違うので、工夫して同じ文面に揃えてみちゃいましょうか。

  • 7~9行目
    1. values[i][1]をベースにDateオブジェクトstartTimeを生成し
    2. values[i][2]の時間、分をセットして
    3. 0時間0分を加算する
  • 11~13行目
    1. startTimeをベースにDateオブジェクトendTimeを生成し
    2. startTimeの時、分をセットして
    3. values[i][3]の時間、分を加算する

なんか揃いましたね。

どちらかに不足している部分があれば、両方で辻褄が合うように揃えればOKです。例えば、「加算する」がどちらかになければ、そちらは「0を加算する」とすれば良いわけです。

このように似ている処理は共通化することができます。

共通部分を関数化したスクリプト

この考えのもと、別関数として部品化したのが以下のスクリプトです。

function createEvents() {
  var calendar = CalendarApp.getDefaultCalendar();
  var values = SpreadsheetApp.getActiveSheet().getDataRange().getValues();

  for(var i = 1; i < values.length; i++){
    var title = values[i][0];

    var startTime = addTime(new Date(values[i][1]), values[i][2]);
    var endTime = addTime(new Date(values[i][1]), startTime, values[i][3]);

    var option = {
      description: values[i][3],
      location: values[i][4]
    }
    calendar.createEvent(title, startTime, endTime, option);    
  }
}

/*
 * 日付のみのDateオブジェクトに与えられた時刻をセットしさらに時刻の加算をする
 *
 * @param {Date} 日付を表すDateオブジェクト
 * @param {Date} 時刻を表すDateオブジェクト
 * @param {Date} 加算する時刻を表すDateオブジェクト、デフォルトはnew Date(0,0,0,0,0,0);
 * @return {Date} 日付をベースに、時刻をセットし、加算する時刻を加算したDateオブジェクト
 */
function addTime(date, time, howLong){
  if(!howLong) {
    howLong = new Date(0,0,0,0,0,0);
  }
  date.setHours(time.getHours() + howLong.getHours());
  date.setMinutes(time.getMinutes() + howLong.getMinutes());
  return date;
}

関数createEventsでは、startTime、endTimeを求める部分はそれぞれ一行、合わせて二行にまとめることができました。

そして、それらを求めるための関数がaddTimeですね。

なお、関数addTimeで第1引数のDateオブジェクトについては、setHoursメソッド、setMinutesメソッドの対象としますので、ここでもちゃんとnewキーワードで生成したものを渡すという点が注意としてありますよ。

関数addTimeの役割

関数addTimeは、ドキュメンテーションコメントを入れている通り

  • 日付のベースとなるDateオブジェクト
  • セットをする時刻を表すDateオブジェクト
  • さらに加算する時刻を表すDateオブジェクト

の3つの引数を渡すと、ベースの日付に時刻をセットし、さらに加算する時刻を加算したDateオブジェクトを返す関数になります。

まとめ

以上、Google Apps Scriptで日付関連の処理を関数化する例とその際のポイントについてお伝えしました。

このように、処理が似ているところは、積極的に関数化することでスクリプトをシンプルにすることができますし、関数の再利用も可能になります。

とくにDateオブジェクト周りは処理が複雑になりがちなので、積極的に関数化を狙っていきたいですね。

次回、関数addTimeで使っているテクニックである、引数の省略について解説します。

どうぞお楽しみに!

連載目次:GASでカレンダーイベントをまとめて追加するツールを作る

Googleカレンダー便利ですよね!ただ、複数のイベントをいくつも追加しなければいけないとき…少し面倒です。そんなときのための便利ツールとして、スプレッドシートの入力情報をもとにカレンダーイベントをまとめて追加するツールを作成していきます。
  1. GASでデフォルトのGoogleカレンダーにイベントを追加する簡単なスクリプト
  2. GASでスプレッドシートの入力からGoogleカレンダーに複数のイベントを追加
  3. Google Apps ScriptでDateオブジェクトの複製や時刻のセットで注意すること
  4. Google Apps Scriptで日付関連の処理を関数化する例とその際のポイント

GASでJavaScriptで動作するWebページをスクレイピングするPhantomJsとは

$
0
0

phantomJS
みなさん、こんにちは!うえはら(@tifoso_str)です。

JavaScriptで動作するWebページを色々な言語でスクレイピング】するシリーズの第三弾です。

前回は、VBAでIEを操作してJavaScriptで動作するWebページをスクレイピングしました。

VBAでIEを操作してJavaScriptで動作するWebページをスクレイピング
「JavaScriptで動作するWebページ(動的サイト)を色々な言語でスクレイピング」することをシリーズでお伝えしています。 今回はVBAでInternet Explorerを操作してJavaScriptで動作するWebページをスクレイピングします。

今回から、Google Apps Scriptを使ってのスクレイピングです。

Google Apps Scriptでは、VBAでInternet Explorerを操作したように、ブラウザ操作ができません。

そのため、ブラウザに変わってJavaScriptを実行する仕組みが必要です。

それを、解決してくれるのがPhantomJs Cloudです。

ということで、今回は「GASとPhantomJsでJavaScriptで動作するWebページをスクレイピングする準備」部分をお伝えします。

ホントはChrome操作できたらべんりなんですけどね~

JavaScriptを考慮せずにスクレイピング

まずは、JavaScriptを考慮せずにスクレイピングしてみます。

スクリプトは下記のようになります。

function scraping() {
 
  const URL = 'https://www.eb.pref.okinawa.jp/kassui/';//沖縄県企業局のダム貯水率            
  
  var response = UrlFetchApp.fetch(URL);
  var html = response.getContentText('UTF-8');
  
  var myRegexp = /<title>([\s\S]*?)<\/title>/;
  var title = html.match(myRegexp);
  Logger.log("タイトル:%s",title);
 
  var myRegexp = /id=\"chosui_hiduke\">([\s\S]*?)<\/span>/;
  var day = html.match(myRegexp);
  Logger.log("日付:%s",day);
  
  var myRegexp = /id=\"ritsu_today4\">([\s\S]*?)<\/td>/;
  var day = html.match(myRegexp);
  Logger.log("貯水率:%s",day);
}

Google Apps ScriptでHTMLドキュメントを取得する方法は下記記事をご覧下さい。

Google Apps ScriptでREST APIを使って郵便番号住所変換スプレッドシート関数を作る
Google Apps Scriptを使ってスプレッドシートの自作関数を作っています。今回はREST APIを使って郵便番号から住所を求めるスプレッドシート関数を作ります。API初心者にもおすすめです。

また、正規表現については下記記事をご覧下さい。

Google Apps Scriptで正規表現を使って必要な情報を抽出する最も簡単なスクリプト
Google App Scriptを使ってGmailで届いたフォーム送信情報をスプレッドシートに蓄積する方法の初回。正規表現とは何か、またGASで正規表現により文字列を抽出する最も簡単なスクリプトを紹介します。

実行して、ログを確認すると下記のようになります。

GASスクレイピング確認

やはり、値を取得できていないですね。

余談ですが、正規表現でマッチした全てを返す「g」フラグを付けないと、最初にマッチした結果が配列の1番目に、そしてその中身の部分が配列の2番目に入るようです。

Google Apps Script便利ですね!

JavaScriptが動作するWebページをスクレイピングするPhantomJs Cloudとは

Google Apps Scriptで、JavaScriptで動作するWebページをスクレイピングするに、
PhantomJs Cloudを利用します。

Phantom Js Cloudはクラウドで動作するヘッドレスブラウザです。

簡単に言うと、スクレイピングしたいURLをわたすと、JavaScriptが実行された後のHTMLドキュメントを返してくれるサービスです。

無料プランでは、一日に500ページ程度まで利用できます。

PhantomJs Cloudのアカウント作成

PhantomJs Cloudこちらのページの「Sign up now!」からアカウントを作成します。

SignUoNow

メールアドレスを入力し、私はロボットではありませんにチェックを入れて、「Sign up」します。
phantomJSアドレス入力

先ほど入力したメールアドレスに、確認のメールが届くので、メールの中の「Click to confirm your Email Address and set your password.」をクリックします。

phantomJSアドレス確認

最後にパスワードを設定して、Sign up完了です。

phantomJSパスワード設定

PhantomJs Cloudの使い方

ログインすると下記のようになります。

実際の使い方は右上の「API Docs」から確認することが出来ます。

phantomJS_APIdocs

実際に見てもらうとわかりますが、全て英語です。 

しかも、Google Apps ScriptやJavaScriptの例もないので、ちょっとどこ見ていいかわからないですよね。

「HTTP Endpoint」の下記部分を見ればいいのですが、JSON形式のrequestが必要で、これもエンコードする必要があります。

phantomJS_usage

う~ん、色々難しそうですね。

今回は準備部分と言うことでここまでとします。

まとめ

今回は「GASでJavaScriptで動作するWebページをスクレイピングする準備」部分をお伝えしました。

PhantomJs CloudというWebサービスのアカウントを作成して、API Docsを確認してみました。

次回からはGoogle Apps ScriptでPhantomJS Cloudを利用して、実際にスクレイピングしていきます。

お楽しみに!

連載目次:JavaScriptで動作するWebページを色々な言語でスクレイピング

Webスクレイピングしていて、値が取得できないということはありませんか?

そんな時は、Webサイトの表示にJavaScriptを利用しているからです。

本連載では、色々な言語でその対応をご紹介します!

  1. GASやVBAでスクレイピングができない理由として考えるべきJavaScriptのこと
  2. VBAでIEを操作してJavaScriptで動作するWebページをスクレイピング
  3. Google Apps ScriptでJavaScriptで動作するWebページをスクレイピングする準備

Google Apps Scriptで関数の引数を省略した場合の挙動とデフォルト値の設定方法

$
0
0

みなさん、こんにちは!
タカハシ(@ntakahashi0505)です。

GASでGoogleカレンダーの複数の予定をまとめて登録するツールを作成しています。

前回の記事はこちら。

Google Apps Scriptで日付関連の処理を関数化する例とその際のポイント
GASでGoogleカレンダーにまとめてイベントを登録できるツールを作成しています。今回は、Google Apps Scriptで日付関連の処理を関数化する例とそのポイントについて解説をしていきます。

日付関連の処理を関数化してスクリプトをスッキリさせる例についてお伝えしました。

さて、その関数化した関数なのですが、呼び出すときの引数が省略された場合にデフォルトの値を設定するように工夫していたんですね。

今回は、引数が省略された場合には仮引数はどうなってしまうのか、またその際の不都合を回避するためにはどうすべきかについて解説をしていきます。

Google Apps Scriptで関数の引数を省略した場合の挙動とデフォルト値の設定方法についてです。

では、行ってみましょう!

前回のおさらい

前回作成したスクリプトはこちらです。

function createEvents() {
  var calendar = CalendarApp.getDefaultCalendar();
  var values = SpreadsheetApp.getActiveSheet().getDataRange().getValues();

  for(var i = 1; i < values.length; i++){
    var title = values[i][0];

    var startTime = addTime(new Date(values[i][1]), values[i][2]);
    var endTime = addTime(new Date(values[i][1]), startTime, values[i][3]);

    var option = {
      description: values[i][3],
      location: values[i][4]
    }
    calendar.createEvent(title, startTime, endTime, option);    
  }
}

/*
 * 日付のみのDateオブジェクトに与えられた時刻をセットしさらに時刻の加算をする
 *
 * @param {Date} 日付を表すDateオブジェクト
 * @param {Date} 時刻を表すDateオブジェクト
 * @param {Date} 加算する時刻を表すDateオブジェクト、デフォルトはnew Date(0,0,0,0,0,0);
 * @return {Date} 日付をベースに、時刻をセットし、加算する時刻を加算したDateオブジェクト
 */
function addTime(date, time, howLong){
  if(!howLong) {
    howLong = new Date(0,0,0,0,0,0);
  }
  date.setHours(time.getHours() + howLong.getHours());
  date.setMinutes(time.getMinutes() + howLong.getMinutes());
  return date;
}

スプレッドシートに入力してある予定リストをもとに、デフォルトのGoogleカレンダーにイベントを追加していくというものです。

関数createEventsがメインの関数で、その中の日付関連の共通処理を関数化して分離したものが関数addTimeになります。

引数が省略された場合の仮引数の値

さて、上記のスクリプトですが8,9行目で関数addTimeをそれぞれ呼び出していますね。

それで、その引数の数に注目をすると

  • startTimeを求めるときには、引数が2つ
  • endTimeを求めるときには、引数が3つ

となっています。

一方で、関数addTimeのほうは、その受け皿として仮引数はdate, time, howLongの3つがありますよね。

つまり、startTimeを求めるときには、最後の引数が省略されているわけですが、その仮引数howLongの値ってどうなっちゃってるんですかね?

引数が省略されたら仮引数はundefinedに

試しに、28行目の前に以下のステートメントを挿入して試してみましょう。

Logger.log(howLong);

そうすると、以下のようにログ出力がされます。

GASで与えられてない引数の値はundefinedになる

そうなんです、GAS(というかJavaScript)では、関数呼び出しのときに引数を与えられていない仮引数の値は「undefined」になるのです。

undefinedというのは「未定義」を表す特殊な値です。

引数のデフォルト値を設定する

このままにしておくと、関数addTime内の以降のgetHoursメソッドなどでエラーを起こしてしまうので、引数が省略されたときにデフォルト値を設定しておくと良いということになります。

その部分が、以下のif文の処理ということですね。

if(!howLong) {
  howLong = new Date(0,0,0,0,0,0);
}

このif文のように、変数自体をif文の条件式に指定した場合は、その変数の値が真偽値に型変換されます。

そして、Dateオブジェクトであればnullまたはundefinedでない限りはtrueの判定になります。

一方で、undefinedはfalseになりますから、その場合にif文の処理が実行され、「0」を表すDateオブジェクトが生成されてhowLongにセットされるということになります。

なぜ引数で与えずにデフォルト値の処理を入れるか

なお、そもそも関数addTimeへの第3引数を省略せずに「new Date(0,0,0,0,0,0)」を指定する方法もあります。

ですが、多くの場合、指定すべき引数の数は減らせたほうが良いはずです。

例えば、関数addTimeを第3引数を「new Date(0,0,0,0,0,0)」で何回も使用することがあるとしたら、都度指定するのはどう考えても面倒ですもんね。

まとめ

以上、Google Apps Scriptで関数の引数を省略した場合の挙動とデフォルト値の設定方法についてお伝えしました。

GAS(というかJavaScript)では、関数の引数は省略できること、また省略した場合の仮引数はundefinedになるということですね。

さて、デフォルト値を指定するif文ですが、実はさらにスマートに記述することができます。

次回、それについて解説をしていきますね。

どうぞお楽しみに!

連載目次:GASでカレンダーイベントをまとめて追加するツールを作る

Googleカレンダー便利ですよね!ただ、複数のイベントをいくつも追加しなければいけないとき…少し面倒です。そんなときのための便利ツールとして、スプレッドシートの入力情報をもとにカレンダーイベントをまとめて追加するツールを作成していきます。
  1. GASでデフォルトのGoogleカレンダーにイベントを追加する簡単なスクリプト
  2. GASでスプレッドシートの入力からGoogleカレンダーに複数のイベントを追加
  3. Google Apps ScriptでDateオブジェクトの複製や時刻のセットで注意すること
  4. Google Apps Scriptで日付関連の処理を関数化する例とその際のポイント
  5. Google Apps Scriptで関数の引数を省略した場合の挙動とデフォルト値の設定方法

GASで論理演算子「||」を使って条件分岐の省略をつつ変数に値を代入する方法

$
0
0

みなさん、こんにちは!
タカハシ(@ntakahashi0505)です。

GASでGoogleカレンダーの予定をまとめて登録するツールを作成しています。

前回の記事はこちら。

Google Apps Scriptで関数の引数を省略した場合の挙動とデフォルト値の設定方法
GASでGoogleカレンダーの複数の予定をまとめて登録するツールを作成しています。今回は、Google Apps Scriptで関数の引数を省略した場合の挙動とデフォルト値の設定方法についてです。

日付関連の処理を関数化したのですが、その引数を省略した際の挙動とデフォルト値の設定について解説をしました。

それで、その引数が渡されているかの判定をしてデフォルト値を設定する処理にif文を使っていたのですが、そのif文を省略してオシャレに書いちゃおうと思います。

ということで、今回は、Google Apps Scriptで論理演算子「||」を使って条件分岐の省略をつつ変数に値を代入する方法です。

では、行ってみましょう!

前回のおさらい

前回のスクリプトはこちらです。

function createEvents() {
  var calendar = CalendarApp.getDefaultCalendar();
  var values = SpreadsheetApp.getActiveSheet().getDataRange().getValues();

  for(var i = 1; i < values.length; i++){
    var title = values[i][0];

    var startTime = addTime(new Date(values[i][1]), values[i][2]);
    var endTime = addTime(new Date(values[i][1]), startTime, values[i][3]);

    var option = {
      description: values[i][3],
      location: values[i][4]
    }
    calendar.createEvent(title, startTime, endTime, option);    
  }
}

/*
 * 日付のみのDateオブジェクトに与えられた時刻をセットしさらに時刻の加算をする
 *
 * @param {Date} 日付を表すDateオブジェクト
 * @param {Date} 時刻を表すDateオブジェクト
 * @param {Date} 加算する時刻を表すDateオブジェクト、デフォルトはnew Date(0,0,0,0,0,0);
 * @return {Date} 日付をベースに、時刻をセットし、加算する時刻を加算したDateオブジェクト
 */
function addTime(date, time, howLong){
  if(!howLong) {
    howLong = new Date(0,0,0,0,0,0);
  }
  date.setHours(time.getHours() + howLong.getHours());
  date.setMinutes(time.getMinutes() + howLong.getMinutes());
  return date;
}

関数createEventsから呼び出される関数addTimeですが、引数が2つの場合と3つの場合があります。

引数が2つの場合は、関数addTimeでは仮引数の値がundefindになってしまいます。

ですから、undefindかどうかを判定して、もしそうなら以降の処理に支障をきたさないように、デフォルト値を設定するというのが28~30行目の処理になります。

ただ、この部分。

if文を省略してオシャレに書くことができるんです。

論理演算子「||」を使って仮引数の判定をするスクリプト

結論から言うと、関数addTimeは以下のように記述することができます。

/*
 * 日付のみのDateオブジェクトに与えられた時刻をセットしさらに時刻の加算をする
 *
 * @param {Date} 日付を表すDateオブジェクト
 * @param {Date} 時刻を表すDateオブジェクト
 * @param {Date} 加算する時刻を表すDateオブジェクト、デフォルトはnew Date(0,0,0,0,0,0);
 * @return {Date} 日付をベースに、時刻をセットし、加算する時刻を加算したDateオブジェクト
 */
function addTime(date, time, howLong){
  howLong = howLong || new Date(0,0,0,0,0,0);
  date.setHours(time.getHours() + howLong.getHours());
  date.setMinutes(time.getMinutes() + howLong.getMinutes());
  return date;
}

if文の代わりに、論理演算子を使ったコチラのステートメントが挿入されています。

howLong = howLong || new Date(0,0,0,0,0,0);

…論理演算子「||」…?

こんな使い方、あんまり見たことないですよね。私もそうでした。

論理演算子の本来の意味

本来、論理演算子「||」は、if文などの条件式内で「または」という意味で使っていました。

しかし、それが真偽値ではないデータに用いたらどうなるのか…?

それを明らかにするために、HTMLやJavaScriptなどのウェブ標準についてまとめられているMDNで、各論理演算子の定義を確認してみましょう。

以下のように記載されています。

演算子 使用法 説明
論理AND (&&) expr1 && expr2 expr1 を false と見ることができる場合は、expr1 を返します。そうでない場合は、expr2 を返します。
論理OR (||) expr1 || expr2 expr1 を true と見ることができる場合は、expr1 を返します。そうでない場合は、expr2 を返します。
論理 NOT (!) !expr 単一の演算対象が true と見ることができる場合は、false を返します。そうでない場合は、true を返します。

引用: 論理演算子 – JavaScript | MDN

「論理OR(||)」の説明を見てくださいね。

JavaScriptの場合、全てのデータ型について真偽値への暗黙の型変換がなされます。

ですから、今回のスクリプトの例ですと変数howLongがtrueとみなされれば、howLongにはその値がそのまま採用されますし、falseとみなされればnew Date(0,0,0,0,0,0)が適用されるということになります。

論理演算子「||」を使った変数への代入

つまり、以下のように書いた場合は、変数がtureとみなされれば変数の値がそのまま、変数がfalseとみなされれば値が変数に代入されるということになります。

変数 = 変数 || 値

なお、変数がfalseとみなされるケースにはundefindを含めて以下のような値になります。

  • 0
  • NaN
  • 空文字
  • false
  • null
  • undefind

したがって、変数に何かおそらく有効な値が入っているかどうかを判定して、そうでない場合はデフォルト値を設定したい、そのようなときには論理演算子「||」を使うチャンスということになります。

まとめ

以上、Google Apps Scriptで論理演算子「||」を使って条件分岐の省略をつつ変数に値を代入する方法をお伝えしました。

論理演算子…奥が深いですね!

その意味をちゃんと理解しておくと、コードをオシャレに書くことができるというわけです。

ちなみに、本記事は以下の記事をたいへん参考にさせていただきまいた。ありがとうございます!

JavaScriptの「&&」「||」について盛大に勘違いをしていた件 - Qiita
# 論理演算子「&&」「||」について JavaScriptの基本である論理演算子の `&&` `||` について、 根本的に勘違いをしていたことに最近気付いたので自戒の意味を込めてここに記します。 ## 論理演算子の使い道 #...

これで、イベントを追加する一連のシリーズは終了です。

また、別の便利なツールを紹介していきますね。

どうぞお楽しみに!

連載目次:GASでカレンダーイベントをまとめて追加するツールを作る

Googleカレンダー便利ですよね!ただ、複数のイベントをいくつも追加しなければいけないとき…少し面倒です。そんなときのための便利ツールとして、スプレッドシートの入力情報をもとにカレンダーイベントをまとめて追加するツールを作成していきます。
  1. GASでデフォルトのGoogleカレンダーにイベントを追加する簡単なスクリプト
  2. GASでスプレッドシートの入力からGoogleカレンダーに複数のイベントを追加
  3. Google Apps ScriptでDateオブジェクトの複製や時刻のセットで注意すること
  4. Google Apps Scriptで日付関連の処理を関数化する例とその際のポイント
  5. Google Apps Scriptで関数の引数を省略した場合の挙動とデフォルト値の設定方法

【エクセルVBA】塵も積もればなんとやら VBAにキーボード入力をさせる方法

$
0
0
excel,vba,sendkeys
みなさまこんにちは、ノグチです。

以前の記事で、エクセルのChangeイベントを使って、指定セルに入力した値でマスタを検索して、検索にヒットした値をプルダウンリストに表示する方法をご紹介しました。

【エクセルVBA】Changeイベントと組み合わせてもっと便利に!検索にヒットした値をドロップダウンリストに表示する方法
エクセルのChangeイベントと、Validationオブジェクトを用いたドロップダウンリストを作成する方法を組み合わせて、セルに入力した値をマスタシートから検索し、検索にヒットした値を入力したセルのドロップダウンリストに表示させる方法をご紹介しています。この方法を使えば、入力作業が楽になるかもしれません。

自分で書いておいてなんですが、実はプルダウンリストを作成した後のステップに、ちょっとしたひっかかりを覚えておりました。

それは、「VBAで作ったプルダウンリスト、手動で開くのは面倒臭いな…」ということ。

どうせなら、VBAでプルダウンメニューを作ったら、シート上ですぐに開いてくれた方が嬉しいですよね。

私が使用しているPCの場合、エクセルシート上でプルダウンメニューを開くには、セルを選択してAlt +を押せばよいので、これらのキーを押したということをVBAに投げてやればよいはず…!

ということで今回は、VBAにキーボードを入力させたことにできる、SendKeysステートメントをご紹介します!

SendKeysステートメントとは

SendKesyステートメントは、今アクティブになっている画面上で、人間がでキーボード入力をしたときの動作を、VBAにさせることができるステートメントです。

簡単に言えば、人間がキーボードのEnterキーを押したときのように、VBAに対して「今開いている画面でEnterキーを押しなさい」と命令できるようなもの。

記述方法も非常に単純です。

SendKeys キーストローク , True or False

キーストロークは、キーボードのキーを指定する引数です。

キーストロークは文字列として指定する必要があるので、コードではこんな感じで指定することになります。

SendKeys "{ENTER}"

キーストロークの後に続く引数True or Falseは、以降の処理を、キーを押したことによって実行される処理が終わってから継続するのか(True)キーが押されたという情報が送られたら即継続するのか(False)、によって使い分けることができます。

この引数は省略可能で、その場合はTrueをがデフォルトになります。

SendKeysステートメントで指定できるキー

このステートメントで指定できるキーは、例えば以下のようなものがあります。

キーボードのキー 引数に指定する文字列
Enter {ENTER}
PageDown {PGDN}
PageUp {PGUP}
{UP}
{DOWN}
{LEFT}
{RIGHT}
Shift +
Ctrl ^
Alt %

今回の場合は、Alt + を指定したいので、「%{DOWN}」をVBAに記述すればよさそうです。

プルダウンメニューをSendKeysステートメントで開く

コードにSendKeysステートメントを組み込む

では、実際にSendKeysステートメントをコードに記述してみましょう。

今回使用するコードはこちら。

Private Sub Worksheet_Change(ByVal Target As Range)
Dim objCustom As Object
Dim myRange As Range    'マスタのセル範囲
Dim varList As Variant  'プロパティFormula1の文字列作成用
Dim strAdr  As String   '最初にヒットしたセル
'マスタリストの範囲をセット
Set myRange = Worksheets("マスタ").Range("A1:A6")
With ActiveSheet
    If Target = .Range("B5") Then
        On Error Resume Next
        Set objCustom = myRange.Find(what:=Target.Value, LookAt:=xlPart)
        Application.EnableEvents = False
        If objCustom Is Nothing Then
            Target.Value = Target.Value
        Else
            '1件目を文字列にセット
            varList = objCustom
            strAdr = objCustom.Address
            Do
                Set objCustom = myRange.FindNext(objCustom)
                If objCustom Is Nothing Then
                    Exit Do
                Else
                    If strAdr <> objCustom.Address Then
                        varList = varList & "," & objCustom
                    End If
                End If               
            Loop While Not objCustom Is Nothing And objCustom.Address <> strAdr
            
            With Target.Validation
                .Delete
                .Add Type:=xlValidateList, _
                Formula1:=varList
                .ShowError = False
            End With
            
            Target.Select
            SendKeys "%{Down}"

        End If
        Application.EnableEvents = True
    End If

End With
End Sub

以前の記事でご紹介した、シートのB5セルに入力した値をマスタシートから検索して、検索にヒットした値を全て入力セルのプルダウンメニューに表示させるというものです。

検索とプルダウンメニューの作成が終わったら、入力セルを選択して、38行目に差し込んだSendKesysステートメントで、Alt + を示す%{Down}を指定することによって、作成したプルダウンメニューを開くという動作にしています。

VBAを実行してプルダウンメニューを開いてみる

実際に上のコードを実行してみましょう。

SendKesys,入力セル

入力セルに「株」を入力して、実行してみると…

SendKeys,検索,プルダウンメニュー,オープン

この通り、VBAで検索した値が全てプルダウンメニューに表示され、且つ、シート上でプルダウンメニューが自動的に開かれていますね。

つまり、プルダウンメニューが作られた後に、キーボードのAlt + が入力されたことと同じ状態になっています。

これで、面倒臭い手動でプルダウンメニューを開く作業ともオサラバです!

最後に

今回は、今アクティブになっている画面上でVBAでキーボード入力をさせられる、SendKeysステートメントをご紹介しました。

今回のプルダウンメニューを開くように、1回1回の手作業は大した作業量ではないとしても、それがエクセルツールを使うたび、それも頻繁に使うとなると塵も積もればなんとやら、無視できない手間になってきます。

今回のSendKeysステートメントを使えば、そんなちょっとした手作業を軽減できるかもしれませんね。

それでは、最後までお読みいただきありがとうございました!

連載目次:エクセルVBAのイベントを使ってもっと便利なツールにしよう!

エクセルVBAでリストの重複を排除する方法として、Dictionaryオブジェクトを使った重複排除の方法をご紹介しています。

  1. 【エクセルVBA】イベントを使ってもっと便利なツールにしてみよう!WorksheetオブジェクトのChangeイベント
  2. 【エクセルVBA】ChangeイベントとEnableEventsプロパティで部分一致検索をする方法
  3. 【エクセルVBA】Validation.Addメソッドで入力規則のドロップダウンを作る方法
  4. 【エクセルVBA】Changeイベントと組み合わせてもっと便利に!検索にヒットした値をドロップダウンリストに表示する方法

スタンディングデスク&モニター3枚環境を作ってみたそのセッティングと感想

$
0
0

スタンディングデスクとモニターの作業環境、座っているとき

みなさん、こんにちは!
タカハシ(@ntakahashi0505)です。

スタンディングデスク…買いました!

と言っても、もうかれこれ2ヶ月近くも使っているんですね。

時間が経つのは早いものです。

それで、合わせてモニターアームのゴツいやつ購入したり、モニターもトリプルにしてみたりしまして、仕事環境としてはかなり最強に近づいて来ましたので、どんなセッティングか紹介しつつ、2ヶ月近く使ってみてどうかお伝えしたいと思います。

では、スタンディングデスク&モニター3枚環境を作ってみたそのセッティングと感想です。

行ってみましょう!

セッティングについて

まずは、セッティングについて、写真でいうとこんな感じです。

スタンディングデスクとモニターの作業環境、座っているとき

立っているときの高さだとこれくらいです。

スタンディングデスクとモニターの作業環境、立っているとき

ちょっと足元の電源がゴチャついてますが…これは仕方なし。

それで、スタンディングデスクにごっついモニターアームを設置して、23.8インチのモニターを2枚横並びにしてます。

メインで使用しているノートPCのモニターがありますので、モニターは全部で3枚ということになりますね。

ちなみに、右側には無印良品で買った普通のパイン材のデスクがあって、Macを使ったり、書き仕事をしたりしています。

では、アイテムを一つずつ紹介していきます。

スタンディングデスク オカムラ Swift

スタンディングデスクは調べてみるとわかるのですが、このオカムラ社のSwiftシリーズのほぼ一択なのかなという感じです。

全体的に値段は張るのですが、お値段通りの安定のクオリティ。

「スムースフォルムエッジ」といって、手前側のエッジがなめらかにカットされている加工になっているので、腕がたいへん楽に置ける仕様になっています。

様々なサイズからセレクトすることができますが、私はおそらく最も一般的なサイズの幅1200mm×奥行き700mmを購入しました。

148,500円でした。

ただ、モニターアームでディスプレイが宙に浮いているので、その下のエリアも使えるため、デスクはかなり広々と使える感じです。

上下動に関しては、シームレスに動きます。

なんちゃってポモドーロ・テクニックを使っているのですが、だいたい30分または1時間単位で、立ったり座ったりしています。

3つまで高さをメモリーできるので、座っているときと立っているときのそれぞれのベストの高さをメモリーしておけます。

エルゴトロン LX デスクマウント デュアルモニターアーム

モニターアームはモニター2枚を縦横自在に設置できるエルゴトロン社のLX デスクマウント デュアルモニターアームにしました。

複雑に変化する2本のアームを駆使して、縦にも横にも自在にモニターを配置できます。

こんなに頑丈さが必要か?ってくらいゴツく、重量感もあるので、モニターを設置したりなんだりはわりと腕力がいります。

でもおかげでスタンディングデスクで上下動しても、2枚のモニターはビクともしないです。

ちなみに、価格は36,700円でした。

BenQ モニター ディスプレイ GW2406Z 23.8インチ

モニターはBenQ モニター ディスプレイ GW2406Z 23.8インチを2枚、同じものを購入しました。

1枚あたり17,638円です。今はモニターも安いっすね~。

モニターは各メーカーから優秀そうな機種が大量に販売されているので、すごく悩んだのですが、蛍光灯など反射してテカらないかどうかと、薄さで選んだ感じです。

23.8インチを2枚横並びすると、デスクの横幅1200mmとちょうどぴったりというくらいになります。

3枚あると、ある程度役割を決めておかないと混乱します。

左のモニターの左側にEvernoteでメモをとる、右側にGoogleドキュメントで文書を書く、右のモニターでプログラムなどを動かしたり資料を広げる、ノートPCでメールやチャットをするというような役割分担で使っています。

I-O DATA マルチ画面 外付グラフィックアダプター

一般的にノートPCにはモニター出力用のHDMI端子は一つしかついていないと思います。

なので、2枚のモニターをつなごうとするなら、USBなどからHDMIに変換するアイテムが必要になります。

それで購入したのがこのI-O DATA マルチ画面 外付グラフィックアダプター、価格は6,596円でした。

この手のやつは製品の比較が難しいので、レビューで良さそうなやつを適当に選んだという感じです。

スタンディングデスク&モニター3枚で良かったことベスト3

もちろん、ここまでコストをかけたら良くならないといけないのですが、何がどう良かったのか、感動した順に紹介します。

肩が凝らない!

スタンディングデスクのメリットって、「立って仕事ができる」って思いがちで、確かにそれはあるのですが、もっと良いことがありました。

それは、立っているときも、座っているときも、自分のベストの高さに合わせられるということなんですね。

なので、リストレストなどを置いて調整などをしなくても、肩や肘に力が入らないポジションでキーボードを打つことができます。

さらに、駄目押しのスムースフォルムエッジ。

そしてプラス、モニターの高さもアームで調整できるので、首がまっすぐな状態で見られるようにセッティングできます。

とにかく、肩が凝らない…!

けっこう、肩こりはしやすいタイプだったんですけど、すごい楽です。ありがたい!最高!!

立ったり座ったりで体と気分をリフレッシュ!

これはもちろん最初から効果あるだろうと思っていましたが、実際にあります。

座り続けて疲れたな、とかダレたなと思ったら立てばいいし、立ちっぱなしで足が疲れたなと思ったら座ればいい。

体が固まるのを防ぐのはもちろん、気分を変えるのにもうまく使えます。

山ほどウィンドウを開ける!

山ほどはちょっと言い過ぎかも知れませんがw

モニターが増えたので、もちろん開いておけるウィンドウが増えました。

よく使うEvernoteやGoogleドキュメント、チャットワークなどを開きっぱなしにして、右側のモニターでいろいろと作業をする感じです。

おかげで開いたり閉じたりの手間は少なく、効率は上がります。

その分、集中して疲れるのですが、その分をスタンディングデスクの能力でスポイルしてくれている感じはあります。

当初はウィンドウが多い分、そのコントロールに手間取ることも多かったので、2ヶ月でだいぶ慣れてきました。

もっとビュンビュンできそうな感じもしますので、引き続き研究を重ねたいと思います。

まとめ

スタンディングデスク+モニター3枚のセッティングとその効果についてお伝えしました。

全部で20万円ちょっとするので、コストとしてはまあまあかかるように思えるかも知れません。

ですが、デスクとモニターアームはおそらく5年~10年単位で長く使えるでしょうし、モニターも少なくとも数年は使えると思います。

それだけの長期間、ストレスの軽減、集中力と生産性の向上、健康のキープなどの効果があると考えると、かなり効果的な投資だと思います。

世の中の社長さん、ぜひオフィスの作業環境、検討してみては?

なぜ毎年のようにPCを買い替えるべきなのか

$
0
0

lenovo-thinkpad-x1-carbon

みなさん、こんにちは!
タカハシ(@ntakahashi0505)です。

弊社は5月決算でして、6月から新たな期がはじまります。

この6月から4期目がスタートしたことになりますね。

それで、だいたいこの時期に新しいPCを購入しているようにしています。

毎年PCを買い替え…ちょっとコスト高めに見えますが、そうでもないかなと思っています。

常に最新のPCを使うことができというのは大きな意義があるんです。

ということで、なぜ毎年のようにPCを買い替えるべきなのかについて書いていきたいと思いますよ。

Lenovo新旧マシンスペック比較

これまで使っていたのはLenovoのThink Pad x270。2017年6月7日に購入しました。

そして、今回購入したのは同じくLenovoのThink Pad x1 Carbon。2018年6月28日購入。ほぼ一年です。

前回のx270もおそらくLenovoのThink Padシリーズではそれなりに上級のものを選んで購入したはずなのですが、今回のx1 Carbonも今、選べる中ではかなり上級のものです。

今回は、それぞれの価格や機能、スペックがどのように変化しているのかを見てみたいと思います。

一覧表にしてみましたのでご覧ください。

項目 x270 x1 Carbon(2018年モデル)
プロセッサー インテル Core i7-7500U(2.7GHz, 4MB, 2コア) インテル Core i7-8550U(1.80GHz, 8GB, 4コア)
OS Windows 10 Home 64bit Windows 10 Home 64bit
ディスプレイ 12.5型FHD液晶 14.0型FHD液晶
メモリ 16GB 16GB
内蔵カメラ あり あり
指紋センサー なし あり
ハードドライブ 512GB SSD 256GB SSD
バッテリー 3セル 23.2Wh Front+6セル 72Wh Rear 3セル 57Wh
バッテリー駆動時間 最大11時間 約20.9時間
電源 45W ACアダプター 45wW ACアダプター(USB Type-C)
ポート USB3.1×1 USB3.0×2 HDMI×1 USB Type-C×2 USB3.0×2 HDMI×1
重量 約1.49kg 約1.13kg
価格 164,743円(税込) 173,264円(税込)

価格は1万円弱、x1 Carbonのほうが高いですね。

それをベースに各項目について、細かく見ていきたいと思います。

CPU

同じCore i7なのですが、前回は第7世代、今回は第8世代となっています。

ちょうど以下の記事が、バッチリ今回のそれぞれに関して比較をしてくれています。

ノートPC向けCPU「i7 8550U」の性能:i7 7500Uと比較
インテルの第8世代「Coffee Lake」のモバイル向けCPUは、ついに「4コア化」しました。今まで2コア縛りだったUシリーズが4コアになってどれくらい性能が伸びたのか。この記事では「Core i7 8550U」を、先代の「i7 7500U」と性能比較しながら解説してみる。

クロック周波数だけ見ると、減ってんじゃん!ってなっているのですが、記事をご覧いただくと各ベンチマークで優秀な性能を叩き出していることがわかります。

ハードドライブ

ハードドライブはもうすっかりSSDの虜なので、それベースで。

ですが、今回はむしろ容量を256GBと半分に減らしてみました。

というのも、Drive File Streamのおかげでローカルにとっておくストレージ容量は多くは必要なくなっちゃいました。

実際に以前使っているときも256GBに到達することはなかったんです。

ストレージはもうすっかりクラウドですね。

ディスプレイサイズ

今回、ノートPCにも関わらず、なんと1.5インチも大きくなりました!

こちらの記事でお伝えしている通り、通常の仕事場では23.8インチモニターが2発あるので、さほど影響はありません。

スタンディングデスク&モニター3枚環境を作ってみたそのセッティングと感想
スタンディングデスク…買いました!あわせてモニターアーム、モニターもトリプルにしてみたり、かなり環境がバージョンアップしました。それぞれどのようなセッティングをしているのか、またそれによりどのような効果があったのかレポートをします。

ですが、外出中ですよ。

喫茶店など外で作業するとき、1.5インチの差はボディブローのようにきいてきます。

バッテリー

オフィスで仕事をしているときはあまり気にならないのですが、一日中外出のときに気になるのがバッテリー容量です。

x270はデフォルトでは7.5時間しか持たないとのことでしたので、リアで6セルのバッテリーも追加で搭載して、計11時間に増量していたんですね。

しかし、今回のx1 Carbon。なんとデフォルトで20時間超え…マジっすか。

バッテリーの技術革新って、今でもすごいんですね。

重量

そして、持ち運びを考えると重量も重要です。

前回はバッテリーを追加搭載したので、1.5kg程度あったのですが、今回のx1 Carbonはなんと1.13kgとかなり軽量に。

しかも、モニターは大きくなって、バッテリー駆動時間も長くなっているんです。

厚みを比べるとこんな感じです。

新旧ThinkPad PCの厚みを比べる

上がx1 Carbon、下がx270なのですが、かなり薄くなっているのがわかりますよね。

技術革新ってすごいです。

最新PCを常に使用することができるメリット

最良の武器で戦っているかどうか

通常、PCの耐用年数は2年程度と言われていますが、おそらく多くの企業では3年とか、場合によっては5年くらい使い続けているのではないかと思います。

PCは消耗品なので、使い続けると徐々にスピードが遅くなっていく…というのも当然あります。

つまり、今使うべき最良の武器で戦っているかどうか、ということですね。

これまで、ご覧いただいて分かる通り、たった1年間とはいえ、各社の努力による技術革新ってかなり大きいです。

価格は1万円ほど増えていますが、ただそれだけで

  • CPUの処理速度は明確に向上
  • 画面サイズは1.5インチアップ
  • バッテリー駆動時間はほぼ倍に
  • 400g近く軽量化
  • 指紋センサー搭載
  • 電源はUSB-C Typeに

というように、全方位的にアップグレードされています。

逆に言うと、今使っている道具は1年経つだけで、相対的にへっぽこになるということです。

へっぽこの道具を長らく与え続けられ、それで戦えと言われている人の意識や姿勢について、想像をしてみれば、どちらが良いのかは明確です。

1年単位で進化すべき度合いがわかる

さて、今回購入してみて感動した点として、電源コードをまとめるマジックテープとか、配送で送られてくる梱包とか、そういう細かい至るところが改善されていました。

もう、企業努力が素晴らしい。

たった1年で、ThinkPadは大きく進化しました。

自分のスキルとか自社が提供している価値とか、そういう視点では、同じようにもしくはそれ以上にアップデートしていますか?

日々、使う道具をアップデートしていくと、そういう視点も磨かれるのではないかと思います。

まとめ

とうことで、なぜPCを買い替えるべきなのかについてお伝えしました。

実際に、買い換えて使ってみると、本当に性能アップが素晴らしいです。1年の努力が素晴らしいです。

私も負けずに、よりよい価値を提供していけるようにしていきたいと思わされました。

皆さんも、ぜひ最新の素晴らしい道具を使い続けてくださいね。

Viewing all 2089 articles
Browse latest View live


<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>