はもちくわ

コードについて自分なりの解釈を書いてます。

【Excel】関数だけで勤務時間表の先月末の勤怠実績データを自動で反映させる方法(過去記事の合体技)

ああ、、、非正規社員にとって恐怖の、、、、、、あの大型連休が、、、、、
ゴールデンウィークがついにやってきてしまいますね。
全米は泣かないけど非正規社員は全員泣いてるはず(苦笑)

とりあえず!今のうちに、やれるだけガンガン仕事して、収入を蓄えておかないと、、、、連休あとの給料日が怖い!!(笑)
ってことで、、、ただいま休みも睡眠時間もゴリゴリ削って対策中っす!!

こんな状況なのに、なんでこんなもの?!書いているのか?といいますと、
実は、前回書いた、「動的にシートを指定する」記事に説明不足があることにふと気が付きまして、、、
直接過去記事に補足説明を追加しようかなと、思っていたのですが、いや、どうせ書くなら、実務で使えるやつを作りたいっ!!

それでも、さすがにゼロから新しいものを作る余裕は無いので、過去記事の「関数だけで「24時間対応」の勤務時間表をつくってみる」がちょうどいい題材なので、この記事で書いた表を使って、前回記事の「セルの値によって参照するシートを変化させる方法」の補足説明をしたいなと思います。



では、早速。。。。
以前つくった24時間対応の勤務時間表を下のように準備します。

このように、2名分、2ヶ月ずつの勤務実績ダミーデータを入れたシートを準備しまして、1枚だけデータ無しで新しい年月のシートを準備しました。
もし、試しに同じように作ってみたいという方は、連動して参照先シートが変わった時に、変わったことがすぐわかるような、ダミーデータにしておくとデバッグが楽でいいと思います。

そうしましたら、勤務実績データの入っていない、新しい年月のシートを使って次のような動きをさせます。

使うメインの関数はINDEX+MATCHの縦横検索です。この関数の組み合わせの中で、セル、配列の参照する場所がありますが、そこでINDIRECTを使ってシートを動的に指定します。
ここのセルD6に入れる関数式はこうなります。見やすいようにインデントつけてみます。このインデントがついたまま、Excelのセルにコピペしても動きます。

=INDEX(
  INDIRECT(TEXT(DATE(YEAR($A$1),MONTH($A$1),0),"'yyyy年m月") & " " & $F$1 & "!A:F"),
    MATCH($B6,INDIRECT(TEXT(DATE(YEAR($A$1),MONTH($A$1),0),"'yyyy年m月") & " " & $F$1 & "!B:B1"),0),
 COLUMN()
)

過去記事で使った関数しか使ってませんので、改めてそれぞれの関数の説明はしませんが、前回の記事で説明不足だった点がこの中にあります。
前回、INDIRECT関数を使って、シート名を指定するとき、'(シングルクォーテーション)で括ってませんでしたが、今回は括っています。 ADDRESS関数を使ってシート名を指定する場合は、勝手に'が入るので意識しませんが、自分で文字列をつなげてシート名を作成する場合で、「半角スペース」がシート名に入る場合は、'でシート名を括らないといけません。
「半角スペース」が入らないなら括らなくてもいいですし、「全角スペース」の場合も括らなくてもいいです。

なんで半角スペーズはダメで、全角スペースはいいの??って言われると困りますが(笑)

えー、これは、私なりの解釈ですが、、、全角は人間界で使う言語、半角はPC界で使う言語・・・・。
みたいな感じです(笑)。
PCさんにとって、全角文字は別の意味を持たない「絵」みたいなものなのですが、半角文字は別の意味として理解できちゃうものがあるんですね。
なので、「半角スペース」として名前に使いたいときは、PCさんに「これは半角スペースです!」って説明しないといけないわけです。
PCさんは、寂しかりやなので、自分にわかるメッセージがあると、うれしくなって勝手に自分用に解釈しちゃうんです。

なので、PCさんによく話しかけてあげていると、括らなくてもいいらしい。。。。(←ウソ)

 

あとは、入力したセルから第1週目の実績入力欄に書式なしフィルすれば反映されるようになります。

こんな感じですね。
先月の日付が出る可能性があるのは第1週目だけですから、関数をいれておくのはここだけでいいです。
そして、参照先の先月シートには、今月の日付はありませんので、今月の日付になっているところから、エラー表示が出ています。
エラーを消す処理をしてもいいのですが、フォーマット用のシートとして、運用するならこのままの方が分かりやすいと思います。

なぜかというと、今月の実績を入力するためにシートを準備するのはおそらく、先月末から今月初めの間の月1回だけですね。
その準備は、フォーマットシートをコピーして、年月と名前を入力して、シート名を変えて、、、というように準備していくと思います。
そして、準備が整い、実際にデータを入力しようとする前に、不要な関数式を消しておきたいですが、、、月1回しか使わない上に、エラー表示も出ないだと、、、、多分、消し忘れちゃいます(笑)
さらに、こういった実績入力系は、自分だけが使う状況というのは少ないと思うので、不要な関数式を消し忘れてしまうと、
「これ消していいんですか?」とか、
「自動で実績が入らないですが・・・」とか
「関数が間違ってますよ?」とか、
いろいろ面倒なやりとりをせねばならなくなります(笑)
それは避けたいところですよね。
まあ、好みではありますので、エラーを消したい!と思われる方は、関数の大外にエラー回避の条件分岐をつけて使ってください。

 



さて、これで終わり・・・
とはいかなくて(笑)

このまま上の関数式を法定休日欄にフィルしてみますと・・・・

とまあ、よく見る「あるある」です(笑)
なので回避方法も、よく見る「あるある」でしょうね。
そして、いくつか方法があります。

まず、関数式でなんとかする方法。

めちゃくちゃ式が長くなるので、この方法は避けたい、、、、と、思うかもしれませんが、IF文の中に、同じ式を2ついれるだけなので、コピペで簡単につくれます。式もここだけなので、私はアリだと思います。

 

そして、次の方法は本当によく見るやつ。

このように「見えなくする」と「受け側の式を工夫する」の組み合わせです。

どれが一番いいですか??ってことはなくて、どれも一緒です。
自分が理解しやすくて、使いやすいのが一番かなと思います。

 

最後に、おまけです。
機能として、あっても、なくてもいいものですが、もし、気に入ってもらえて、使えるPC環境なら使ってみてください。

今回はシート名にルールをつけましたが、フォルダ名やファイル名、人の名前の入力方法なんかでも、こういった名前などをつけるルールを社内や部署内で決めたとします。


でも、、、、結局、「ルールは破るためにある!」って状況になりませんか?(笑)
特に全角、半角の数字やスペースね。
半角数字と全角数字が混在しているケースもよくみたな。見つけて直すのに結構大変だったっけ(笑)
あと、氏名の間にスペースを入れるのか、入れないのか?全角なのか半角なのか?!問題もよくありますね(笑)

このようなことは、人間の目だと意味もわかるし、区別もつくので、大きな問題にならないことが多いですが、PCさんから見た場合、全角と半角は全くの別物です。
特に、今回のような、セルに入力された値から一致するシート名を探すというのは、値が一致するという前提があって、はじめて成り立つ関数管理方法です。このような管理方法にとって、入力ルールが破られることは、、、、かなりの致命傷になります。

なので、そういったことを少しでも回避できるようにするため、アラートを表示させる方法のご紹介です。

使う関数はCELL関数。
これは、セルの情報を表示する関数です。
先に使い方を書いておきます。

=CELL("情報の種類",参照先)

まず、参照先ですね。これは情報を得たいセルを指定するところです。省略もできますが、省略するとアクティブになっているセルの情報になるので、セルの選択を変えるたびにうるさく値が変化し、動作も遅くなります。なので、省略の使い方は、、、、よく考えた方がよいかと(笑)
次に、情報の種類ですが。これはたくさんありまして、書式だったり、列数、行数、色、などなど、、、、なんかVBAでオブジェクトから情報を引き出すみたいな動きで、とても便利に使えそうなのですが、、、ほとんどが他の関数でできるので、CELL関数の使い道はほぼないかと思います(笑)。残念!
そんな残念なCELL関数ですが、情報の種類の中のfilenameだけは、「シート名を得る」と検索したら絶対に出てくる、シート名を得るために生まれた救世主!唯一無二の関数なのです!(笑)
まあ、誇張しすぎて、言い過ぎに聞こえますが、、、実際、シート名を得るにはこれしかないです。シート名だけを得る方法もほぼ1つに確立されていますので、検索してみてはいかがでしょうか?


「えっ?ここで説明はしないの???」

ははは、、、、今回、シート名は参照しますが、抜き出しません!
では、どのように使うのかといいますと、こんな感じで使います。

そして、それぞれのセルに入っている関数式ですが、
まず、年月の下、セルA3です。

=IFERROPR(IF(FIND(TEXT($A$1,"yyyy年m月"),CELL("filename",$A$1))>0,"","想定外の結果です。管理者に連絡を!"),"注!シート名と年月が違います")

そして、名前の下、セルF3です。

=IFERROPR(IF(FIND($F$1,CELL("filename",$F$1))>0,"","想定外の結果です。管理者に連絡を!"),"注!シート名と名前が違います")

となっています。
CELL("filename",$A$1)をするとフルパスのファイル名+シート名が得られます。その文字列の中から年月、名前と同じ文字列があるかどうかをFIND関数で探し、ヒットすれば文字の位置が得られますので、結果0より大きくなり、異常なしで何も表示させないようにします。
そして、同じ文字列が見つからなかった場合、FIND関数は0ではなく、エラーになります。なので数式の大外にエラー判定を置いておいて、エラーなら文字列がなかったことになり、コメントをセルに表示します。
さらに、0にはならないはずなのに0になったら、、、、もう想定外です。これ出るなら是非見てみたいので管理者を呼んでもらいましょう(笑)
このようにコメントは管理者の言葉として伝えたいことを書けばよいです。フォントや色も設定すればよりよいかと思います。

 

このようにシート名を確認し、それによってアラートを出し、ユーザー自身に確認してもらう方法をとっています。
シート名かセルの値のどちらかがルール通りならば、ガードの意味は出ますが、どちらも同じ間違いをした場合は、ガードをすり抜けます。さすがにどっちかはルールを守っててほしいもんですけどね(笑)


さて、なぜ、このように抜け道がある不完全なアラートを出す方法にしているのかといいますと、
シート名とセルの値を一致させるだけが目的であれば、シート名からそれぞれ年月と名前を抜き出し、セルの値としてやれば簡単です。その場合は、先ほど検索してみては?と案内した方法を使って実現すればよいと思います。絶対にセルの値とシート名は一致しますからね。ですが、この方法はシート名同士の入力ルールの一致はわかりませんね。
今回一番実現したいことは、動的に別シートを参照することです。
ということは、個別のシートごとにシート名とセルの値が一致していることが大事なわけではなく、すべてのシート名がルールに沿っていることが大事なわけです。
なので、手入力するたびにアラートを出すガードを設定しておいて、ルールにあってますか?と気づいてもらう機会を増やす方法をとっているわけです。
不完全なガードなので、結局は管理者がしっかり確認するしかないのですが、管理者の代わりに微力ではありますがアラートの文面が一役かっているという感じです。なので、文面は管理する人が考えましょう。
また、このアラートと一緒に、入力ガードをつけておくのも手です。もうこれ以上長く書くとバイトがシンドくなるのでサラッといかせてもらいますが、例えば、名前のセル側に全角、半角ガードをつけるのも手ですね。こんな感じ。

=IF($F$1=ASC($F$1),"","おい!全角スペース入れただろ!")

これで全角スペースが入ったらアラートでます。
ちなみに、このASCをJISにすれば全角チェックになります。

そして、年月の場合、そもそもこのシートは日付としてセルに入力してもらっているので、データが日付かどうかで判断すればいいっすね

=IFERROPR(IF(VALUE($A$1)>0,"","なんで0いれた?"),"日付で指定しろって言ったやん!")

なんてガードもかけれますね(笑)




さて、今回はこれで終わりです。
何か役に立つものがあったでしょうか?あればいいのですが(笑)
それでは、ここまで読んでいただきありがとうございます。

誤字脱字はまた後日確認いたします。。。。ご勘弁を。

【Excel】関数だけでつくる!セルの値によって参照するシートを変化させる方法(売上前年比など、時系列で値を比較する時に便利!)

今回は関数だけでシート名を動的に指定する方法です。

この方法は、同じフォーマットの表が継続的に続き、一定のルールで前のシートの値を参照するような場面に使えます。
具体的には、売上管理や人件費管理、予算管理など、ある一定期間で、比較参照するデータがある場合に適してますね。
私も実際によく使いました。覚えておくと便利ですよ!


それでは、売上前年比だけを算出するサンプルを作成しながら説明していきます。サンプルは次のように作成します。

このように、年月でシート管理されている表の、年と月を変えると、万年カレンダーが変化し、それに合わせて対応する前年数値が変化するものを作成します。

 

この緑色の箇所、万年カレンダーを制御している関数は、過去記事でやってますので、サラッと式の説明だけにします。詳しく知りたい方は、過去記事の万年業務カレンダー(日付固定)をご覧ください。

まず、日付からいきます。表の上の部分、年と月を変えると、その該当月の日付だけ表示させるようにしてます。月によって月末数字が変化するというわけです。
関数式が入っているのは、「2」以降のセルA6より下です。「1」は常に「1」なのでベタ打ちです。
セルA6には、次のように入力しています。

=IF(A5="","",IF(DAY(DATE($A$2,$E$2+1,0))<A5+1,"",A5+1))

このまま表の一番下までオートフィルです。
式の意味としては、
「自分より1つ上の数字に+1をして、月末数字以下なら表示、それ以外は空白を表示する」
となっています。ポイントは月末数字を求める、「来月の0日は月末」というDATE関数の使い方です!


お気づきの方もいらっしゃると思いますが、この式、、、ここではセルA6から入れいますが、本当は、28日以降だけでいいです。なぜなら月の数字が変化するのは28日以降だけですからね。なので2〜27は、1と同じくベタ打ちでもいいです。
まあ、、、これは好き好きなのですが、、、、私は、月に1回だけ整備するような表だと、どこをどうやって作ったのか忘れてしまう(笑)ことが多いので、同じように表記できる箇所はなるべく同じにて、自分が見返しやすいようにしているんです。
なので、、、、みなさんも自分が管理しやすいように、お好きな方法でどうぞ!(←大事!)



そして、次は曜日の表示です。
ここはB列全部関数入ってます。一番上のセルB5は次のようにしています。

=IF(A5="","",TEXT(DATE($A$2,$E$2,A5),"aaa"))

これもこのまま下方向に一番下までオートフィルです。
曜日の表示方法は、本当〜〜にたくさんあります。ここでは値を文字列で扱える方法にしました。
余計なお世話(笑)なのかもしれませんが、売上管理の場合、文字列の方がいいと思いますよ。売上って結構な頻度で曜日特性を出したりすると思うんですね。そんなときに使う関数、SUNIFとかAVERAGEIFとか、、、、ここが文字列で絞れるので、使いやすくなります。
当然、日付からWEEKDAY関数使って、数値で絞り込む方法もありです。でも、、、、、この方法だと作成する人や部署によって日曜始まりか月曜始まりかが違ったりしませんか??確認しようとしたとき、いちいち辿るの面倒くさい!!(笑)


はい、いろいろ書きましたが、、、、今回のサンプルは、この曜日が他の関数に影響することはありませんので、日付と同じく、みなさんのやりやすい、お好きな方法でどうぞ!(←結局これが一番大事!)

 

 

さて、次は前年の同曜日の日付を導き出す方法です。
これは関数というより、考え方がポイントですね。

まずは月初だけで考えてみましょう。
図にまとめました。下の図を見てください。

いかがでしょうか?
このように、前年と今年の月初の曜日をWEEKDAY関数で数値にして、その差を出せば、ズレの日数が分かります。そのズレを起点になる日からズラしてあげればいいわけです。やっていることは簡単ですね。
ただ、残念ながら、まだ式がちょっと不完全でして、このままでは使えません。なぜかと言いますと、上の図の前年と今年の曜日の関係が逆になる場合があるからです。
例えば、今年が日曜日で、前年が土曜日の場合、先程の式では、今年から前年を引いてますので、1−7となり、−6のズレとなってしまいます。でも実際は土曜日と日曜日って1日しかずれてませんね。
なので、そのような時には、次のように考えます。

はい、このように逆転した場合は7を足せばいいんです。わかってしまうと単純なことですね。
それでは!これを式にしていきたいところですが、、、、まだ考えておかねばならないことがありまして、、、
実は、この後の処理で、日付の数字だけだと困る点がでてくるんです。
何に困るのかといいますと、、、、月末の箇所を見ながらご説明します。

このように、月初がズレてるので、そのまま月末は翌月にズレ込みます。
月が変わるので、参照先のシートも同じく変えなくてはなりません。
これをあとで万年カレンダーと連動させるわけですが、日付数字だけですと、少し面倒くさくなるんです。なので、少しでも簡単にするため、ここの値は日付としておきます。なので、C列の表示形式は「d」だけに設定をお願いします。
(表示形式 について、詳しくは過去記事関数で万年カレンダー(曜日固定)をつくってみるをご覧ください)

それでは、セルC5に入る関数です。

=IF(A5="","",DATE($A$2-1,$E$2,$A5)+WEEKDAY(DATE($A$2,$E$2,1),1)-WEEKDAY(DATE($A$2-1,$E$2,1),1)
 +IF(WEEKDAY(DATE($A$2,$E$2,1),1)-WEEKDAY(DATE($A$2-1,$E$2,1),1),7,0))

このまま最後まで下方向にオートフィルです。
関数式は、ちょっと長いですが、先ほど図で説明したことをやっています。
前年の起点日に、今年と前年の曜日のズレを足し、もし、前年と今年の曜日の配置が逆転していたら、さらに7を足すということをしてます。
素直に月末あたりの計算結果をみると、3月32日になるなど、ありえない日付になってますが、、、日付計算の結果というのは、しっかり正規の日付として表示されるようになっています。おそらく、PC側は起点日のシリアル値から計算しているからでしょう。。。。本当のところは知りません(笑)




はい、やっと本題に入ります。シートを動的に指定できるようにします。
使う関数はINDIRECT関数です。
このINDIRECT関数は文字列をセルの参照に変えてくれる関数です。
関数の使い方は過去記事でも書いてますが、、、その記事が長すぎてリンクつけても辿り着けない(笑)と思いますので、ここでも説明しようかと思います。下の図をみてください。

このようにセルC1をINDIRECT関数で指定すると、セルC1に入力されている「A1」という文字列が「セルの参照」に変換してくれるので、結果はセルA1に入力されている「100」になるという仕組みです。
使い方ですが、

=INDIRECT(文字列,セルの参照形式)

となっています。
このセルの参照形式というは、A1形式とR1C1形式が選択できるというものです。省略もしくはTRUEとするとA1形式、FALSEとするとR1C1形式になります。
A1形式というのは、A1とかB1とC3とか、、、みなさんもよく使っている、通常のアルファベットと数字でセルの参照を表す形式のことです。
そして、今回は馴染みが薄い、R1C1形式使います。これは大昔の参照形式で、RはROW(行)CはCOLUMN(列)の略です。これの便利なところが、RとCのあとに数字をいれれば、座標指定でセルが参照できるので、計算に使えるという点ですね。
確か、Excelの前身、Microsoftスプレッドシートというソフトでは、この方法だけしか使えなかった気が、、、、、まあ、昔のことはどうでもいいです(笑)
では、順番に説明していきます。



まず、前年数値の参照先を考えていきます。
ちょうどいいので、先ほど見たシートの参照が変わる月末ポイントで見てみましょう。

本年の3/19と3/30の前年数値の参照先は上のようになります。
次に、この参照先の数値部分をシートにある数値で表してみます。まずは3/19だけみてみます。

このようになりました。
このCOLUMN関数を使って表している、最後の「6」ですが、列の指定なので、縦方向の行番号はどこでもいいです。$F$1にして絶対参照にしてもいいですし、上の図のように相対参照で表示シートと同じ場所でもよいです。
また、COLUMN関数を使わずに「6」とベタ打ちでもいいです。列は日付が変わっても絶対にかわりませんので問題ありません。
では、なぜここでわざわざシートの列を使っているのかと言いますと、フォーマットの列が削除、挿入されて変更された場合、関数がくずれにくいという理由からです。ただ、崩れにくいだけで、参照先シート、表示シートのどちらかだけ変更されれば崩れてしまいます。。。。まあ、こういった連続する管理表は全体のフォーマットが変わることが多いと思われるので、せめてそれぐらいは対応しておこうってことですね。

それでは、3/30の参照先シートが変わるポイントはどうでしょう?
3/19と同じ関数で見てみます。

このように、C列が前年日付データになっているおかげで、先ほどと同じ関数でできました!先ほど上で説明しましたが、日付数字だけだと面倒になると言ったことは、ご理解いただけたかと思います。
それではこれを関数式にして、表の前年数値の一番上、セルD5に入れるように書いてみると、こうなります

=IFERROR(IF(C5="","",
INDIRECT( YEAR($C5) & "年" & MONTH($C5) & "月!" & "R" & DAY($C5)+4 & "C" & COLUMN(F5),FALSE))
                             ,"") 

参照シートがない場合や日付データが空白だった場合、エラー表示がでますので、大外に回避する条件分岐をつけてますが、INDIRECT関数の箇所は上で説明した通りです。
また、先ほど「6」をベタ打ちでもいいと書きましたが、その場合、表示形式をA1形式にすれば、下のように少し簡単にかけます。

=IFERROR(IF(C5="","",
INDIRECT( YEAR($C5) & "年" & MONTH($C5) & "月!" & "F" & DAY($C5)+4))
                             ,"") 

さらに、ADDRESS関数を使って参照するセルの文字列を作成する方法もあります。
こうなります。

=IFERROR(IF(C5="","",
INDIRECT( ADDRESS( DAY($C5)+4,COLUMN(F5), , ,YEAR($C5) & "年" & MONTH($C5) & "月")))
,"") 

この方法は、「6」をベタ打ちしない方法と同じ結果になり、フォーマットが変わっても連動できます。
ADDRESS関数は、行番号と列番号で指定したセルの場所を文字列で表示するという関数です。
使い方ですが、

ADDRESS(行番号, 列番号, 絶対・相対参照の指定, セルの参照形式, シート名)

行、列番号以外は省略可能で、シート名は省略するとアクティブシートのことになります。
絶対・相対参照の指定は、関数の結果に$がつくかつかないかの違いです。結果をさらに加工する場合は必要ですが、今回は結果をそのまま指定アドレスとするのでどちらでも問題ありません。
セルの参照方法はさきほとのINDIRECT関数と指定方法も意味も一緒ですが、INDIRECTは指定する方法、ADDRESSは表示する文字列となっています。なので、今回は指定するINDIRECTがA1形式なら、それに合わせてADDRESS側もA1形式に設定すればよいです。

このどの方法でも下方向にオートフィルして使います。



いつも同じこと書いてますが、、、、このように、同じことさせるにも、たくさんの方法があります。
どれをチョイスするのかは、もう好みです(笑)
そして、自分が一番管理しやすい方法をとりましょう!
コピペして使うにしても、コピペ元からすぐにリカバーできるような工夫が欲しいですね〜
このサンプルのような場面のINDIRECT関数を使うなら、私はADDRESS関数を入れる方法を使います。
私、よくシート記号の「!」(エクスクラメーション)忘れちゃうんですよ(笑)
その点、ADDRESS関数は純粋にシート名だけなので、「!」は不要なんです(笑)
理由なんてこんなもんかと(笑)




さて、これで完成になります。今現在、毎回シートを作る手間に悩まされてる方がいらっしゃるなら、この方法は、一気に入力が楽になると思います。ぜひ、参考にしてみてはいかがでしょうか?

逆に、月1回程度しか整備しない書類に、わざわざこんな面倒なことするより、手入力した方が早いよ、、、、と、言う方もいらっしゃるかな。。。。
でも、まあ、せっかくPCにデータが入れてあるなら、紙ベースみたいなことしないで活用しなきゃ!って思うんです。
今後、管理拠点が増えた時用に、作ってみてはいかがでしょうか!?



と、ここまで書いてきましたが、、、、私個人の考えとしては、こういったデータの運用は、シートで管理するのではなく、できればデータ一覧にして、しっかり検索、集計ができる形のデータベースにするのが一番よいと思います。
永続的に営業、業務が続いてゆく中、このように日々累積していくデータは、様々な分析をすることによって、やっと資源化すると思うんですよ。

でも、、、けっこう会社って、こういったシート管理、もっとヒドいとファイル管理になっていることが多いことないですか?続いていくデータがブツ切れに管理されていて、使うときにわざわざ引っ張り出してつなげるみたいな。。。。
まあ、私がたまたまそういう場面に当たっているだけかもしれませんが、、、(笑)
そういったことが多いので、関数でごちゃごちゃやって、なんとかつなげるスキルがついちゃうって、、、、どうよ?って思います。(笑)

それでは、ここまで読んでいただきありがとうございます。

2024年3月31日追記
この記事の方法だけだとシート名に半角スペースが入ると参照できません。下の記事で回避方法書きました。よかったらこの後に読んでいただければ、、、

 

hamo440.hatenablog.com

 

自分のサイトをフリーのレンタルサーバーから有料レンタルサーバーへ移行することにしました。

私、無料レンタルサーバーで、転職活動と勉強用にポートフォリオ的なサイトを作ってたんですね。

メインの言語はPHP、クライアントサイド動かしたい時はJavaScriptを使って、ゴニョゴニョやってたんです、、、、

 

でも、、、、結局、転職活動では全く役立たず、、、、と言って、ガッツリ勉強に時間を使えるほど、甘い世の中では無い、この物価高のご時世。。。。。

 

もう、転職はあきらめ、生活のためにバイトを掛け持ちでやって、バイト三昧な生活になったら、余裕は全く無くなって(笑)

そんなんだから、サイトは更新せずに2年ぐらい放置になってしまいました。。。。


実は、結構前の記事で、サイトを整理するって話を書いていたんですね。気にはなっていたんですが、、、、でも疲労には勝てず(笑)

 

いや、流石に整理しないと!

 

、、、と、久しぶりにサーバーの管理パネルを開けてみたところ、いつの間にか、運営会社が変更になって、しかも高スペックフリーサーバーに移行できるようになってたんです。

 

「やった!ラッキー」と思って、早速、乗り換えのボタンをポチッとしたところ、、、、

 

人気殺到で新規受付一時停止になってました(笑)そして、受付再開も未定(笑)

 

うーん、今のフリーサーバーはSSL通信できないし、PHPも7.1と古い、、、、サイトを刷新するにもこれではいかんな、、、、、と思いまして、、、

 

思い切って有料のレンタルサーバーに変えてやれと、、、、、バイトのくせに奮発しました!

 

そんなわけで、サーバーの引越しですっ!

 

って言っても、私はワードプレスなどのツールは使ってないし、開発環境はクライアントサイドの私のPC内にあるファイルだけなので、新しいサーバーのどこのフォルダにファイルを置けばいいのかさえわかれば、FTTPで送ればいいだけです。

PHPを使うから、コンポーザーは使えるようにしておかねば困るけど、ひとまずなくてもいいか。

データベースの引越しは、MySQLでやるから作業があるだけです。テーブルごとSQL形式でエクスポートしてコピー、新しいデータベース側のSQLにペーストして実行。この繰り返し。。。。

 

ってな感じで、テストページはすぐに動いて、データベースもPHP側から繋がるのを確認できました。ちょっとデータベースにつなぐパスワードを仮パスのままにしてて、つながらん!ってなったけど、まあ、すぐに気がついたからよし(笑)

 

で、やってて気がついたんですが、新しいphpmyadminSQLタブが見やすくなってるんですよ!これが値段の差かっ!(笑)

バージョン比べてみたら、フリー側のバージョンが2、有料側が5(笑)

、、、、、フリーやべえな(笑)と思いました。

 

そんな感じで、まだテストページだけですが、これから、バイトの合間にコツコツ作っていこうと思います。

そのうち、ここの過去記事で書いてる、Excelのサンプルファイルでもアップしようかな。。。。

 

有料になって、サーバー容量増えたし(笑)

なんたってSSLだし!

 

また、進んだら書いていきます。

今日は、ただのご報告でした。

 

【Excel】関数だけでデータ一覧から必要なデータだけを別シートに上詰でリスト化する方法(見積書、請求書などに!)

以前、バイト先でハンドスキャナで読み取ったシリアルを出荷先別にシートに分けて出力するというツールをVBAで作ったのですが、データベースへの上書きフローの箇所で重複データを量産してしまうというバグを出してしまいまして、、、ひとまず読み取ったデータは間違ってないので、データベースの重複データを整理しながらシート別に分けて出力するものをパッパッと関数だけで作ってリカバーしたんですね。
で、バグの原因もループ処理の判定ミスだったんですぐに修正して、使えるようにしたんですが、、、、、社員さんがバグにびっくりしちゃって、もう使わないってなっちゃんです(笑)まあ、しょうがないですね。。。。ツール自体は自分でも会心の出来栄えで、現場作業員の間では「すごい楽になった!」「考えずにスキャンしても間違えない!」と、とても好評だったんですが、、、、残念です。もったいないですが削除しました(泣)

で、そのときリカバーに使った、関数だけでデータ一覧から必要な分だけデータを抽出し、別のシートへ上詰してリスト表示する、、、、これぐらいは記事にして残してやろうかなって思いまして、、、、

悔しいので(笑)

さて、このような動きをさせる状況って結構ありますよね。販売データ一覧から請求書や見積書にしたりと、、、領収書なんかもそんな感じですかね。。。

今回、リカバーで急遽つくったのは、出荷データを出荷先別に分けて出力するっていう動きでした。
どうでしょうか?こういった場面は、みなさんも何度か対応したことあるのではないでしょうか?多分、いろいろ方法ありますよね。

私の過去記事でも可変する範囲を使ってリスト化する話を書いてます。「関数だけでランキングを作成する」なんかはいい例だと思います。ただ、この可変する範囲を使う方法だと、範囲設計を考えないといけないから、ちょっと面倒か。。。
今回は、時間も余裕もない中、簡単に、すばやくリカバーしなければならない!という危機的状況でしたので、もっと原始的で簡単な方法にしてます。なので、「こんなアイデアもあるよ!」ってことでみていただければと思います。

 

それでは、簡単なサンプルを作りながら説明していきます。

データを次のように準備しまして、

次のように、出荷日、出荷先を入力して指定すると、それに合ったリストが表示されるってやつを作ります。

主に表示に使う関数はINDEX関数とMATCH関数ですが、今回のポイントは、COUNTIFS関数の使い方です。


検索してヒットした項目を上詰めしてリスト化するという事を、そのまま考えると、リストを上に詰めるには?、、、リストを移動させるには?、、、、と、いろいろ難しそうな問題がありそうに思えるのですが、実は、表示される結果を1行ずつ考えてみるとそうでもないことがわかります。


表示リストの1行目にくるのは、、、「1番目」にヒットしたもの。

じゃあ、2行目にくるのは?


はい、正解!
「2番目」ですね! (笑)

そうなんです。ヒットした順番がわかれば表示する場所が決定できるわけです。

そこで使う関数が、COUNTIFS関数です!この関数なら検索条件が複数あっても、条件にヒットしたものを数えることができます。
ただ、順番を出すためには、使い方に工夫が必要です。
普通に、データ全体から条件に合うものを数えちゃうと順番はわかりません。
なぜなら、条件が一致するものが3つあるとして、それぞれを全体から数えると、どれも同じ「3」になってしまいますね。これだと、どれが何番目になるのか?は、わからないです。
そこで、カウントする範囲を自分から上だけにします。自分より下は数えないのです!
ということは、、、関数で指定する範囲は、行が変わるごとに全部違う必要がある。。。。ということになります。

「えっ?!いちいち関数の範囲を変えないといけないなら、とんでもなく面倒なんですけど、、、、」


大丈夫です!
参照形式を変えればオートフィルでいけます。ほら、よく累計出す時に使う方法ですよ!知っているかたは多いんじゃないですかね?
次のように入力してオートフィルします。

このようにセルの範囲の始まりは絶対参照で固定し、終わりは相対参照で動くようにします。こうすることでオートフィルするだけで、下方向に範囲が自動で増えていきます。どうですか?「ああ、あれかぁ!」って思いました?!(笑)
はい、これでそれぞれのデータが何番目にヒットしたのか?が、わかるようになりました。このヒットした順番=表示行なので、データの項目が「行」になっているんですね。
次は、リストに表示させましょう。

表示させるシートから、条件を見返してみます。

このように下方向に行が移動するにつれて、「行数」は同じように増えていきますが、「出荷日」「納品先」「行数」の3つの条件が一致するものを表示すればいいわけですね。
では、まず、データから整備します。
検索条件は3つありますが、1つずつ条件判定させる方法はとりません!
3つの条件がすべてそろった判定用の列を作ってしまいましょう!
とても簡単です。次のようにします。

はい、このように判定する条件をただ文字列としてつなげるだけです。「+」を使って、数字として計算してしまうと、期待した結果になりませんので注意してください。
この方法なら判定条件が文字でも同じ方法が使えますね。まあ、英文字の場合は、全角半角、大文字小文字の対策は必要ですが、、、

これで判定する目印は完成しました。次は表示側です。

考えなくてはいけないのは、この3つの条件の中で変動する「行数」です。他の2つは固定なので指定するだけでいいですね。
さて、どうやって変動する「行数」を関数の中で表現するか?ですが、、、、、よく見てください。「行数」がわかればいいんです!

そうです!ROW関数で「行数」はわかりますね!
なので、次のようにすれば解決です。

うわっ、、、、細かくなっちゃいました。これ見てわかるでしょうか(笑)
INDEX関数は列と行をそれぞれ指定できます。
行の判定には、先ほどデータ側で整備した、文字列結合している判定列を使います。
この判定は、MATCH関数で行いますので、比較するデータ側と同じように、式の中で文字列を作る作業をしています。このとき、変化する「行数」を、ROW関数で得て、実際の行数[6]と表示行[1]のズレを「−5」して合わせてます。

そして、INDEXに指定する列は、指定範囲を表示範囲に合わせていますので、そのままCOLUMN関数を使うだけでOKというわけです。
で、大外にExcelのお約束処理です。検索一致の処理は、VLOOKUPも含めなんであれ、一致しない時はエラー表示されます。表示には邪魔なので、IFERROR関数で消します。

以前の記事で、「エラーの原因がわかる場合はなるべく直接消す対応をした方がよい」と書いたことがあります。全くそのとおりで、今回も一致しなかったらという条件分岐を大外につけるのが一番よいです。ここで一致するものがないことを判定するには、MATCH関数の中で作る文字列がデータI列の中にいくつあるかをCOUNTIF関数で数え、戻り値が0なら一致するものがないことになりますから、これを大外につけてやればいいですね。

、、、、、ですが、今回は急ぐ必要がある!!、、、、ということでした(笑)
なので、「エラーは全部回避されちゃうから気をつけてないといかんよ」って思いながら操作します(笑)私は、こういうときは、ちょいちょい結果を確認するようにしてます(笑)

さて、いかがでしたでしょうか?同じような動きをさせようと悩んでいた方にとって解決策の一つにでもなったでしょうか??そうであれば嬉しいです。消滅してしまった私のツールも成仏するでしょう(笑)

自分で言うものなんですが、消してしまったツールは、本当によく考えられたいいツールだったんですよ。。。
ハンドスキャナが入力デバイスになるので、マウス、キーボート操作をほぼさせないように、操作用バーコードを使って動くようにしたり、シリアルの重複スキャンを自動チェックしたり、スキャナからの入力が文字化けしたら自動キャンセルしたりと、、、、Excelのわりに動きもスムーズだし、、、、、ただ、入力データを保存するときのループ処理を間違えちゃったんですよね。。。。しかも、レアケースのときだけ出るバグだったんで、デバッグをすり抜けちゃったんですよ。。。。あーあ、入力データの正確性にはかなり自信あったんで、出力だけの問題なら修正は簡単でどうにでもなったのになぁ。。。。。

まあ、、、、いいか。

それでは、愚痴も含め、ここまで読んでいただきありがとうございます。

【ExcelVBA】文字列の中の2個以上繋がった空白を置き換える関数作ったよ!

今日、バイト先で少し余裕ができたので、自作のVBAシステムを見返してたんです。
このシステムは、もう稼働させてから2年ぐらい無事に動いていて、効率化にちゃんと貢献してくれている可愛いやつなんです。
でも、、、私自身は、がっつり肉体労働の現場作業員が本来の業務なので、業務と業務の空き時間の、短い間でコードを追加、修正をするという感じで維持管理してまして、、、
そんなもんだから、中身のコードは、コピペの同じ処理が多発する、手抜き増築だらけなんです(笑)
さすがに整理しないとやばいなと、ずっと思っていたのでいい機会だったんです。
とりあえず、一緒にできる処理は関数化しながら統合して、データベースとして使っているCSVデータも、コード同様に重複しているものが多いので削ってと、、、、すると、CSVデータの中に、なんかカンマ区切りが異様に長いものがあったんです。
これは意図していない空白、、、、う〜ん、デバッグ面倒だな、、、、と、恐る恐る原因を探ってみたところ、、、、、


ここで!突然ですが、みなさんに質問です。
Excelのセルに改行入れるとき、みなさんはどうやって入力しますか??

私はですね、Alt押しながらEnterします。
これが普通だと思ってました。

でも、、、違ったんです!
セルに折り返し表示設定してある場合、改行するまでスペースを入れると改行するんです!

おお、、、、こんな手もあるのか。。。。

教訓です!自分が普通と思っちゃダメ(笑)

話を戻しまして、、、
とまあ、不特定多数の誰かが、毎日作成する報告ファイルを自動読み込みし、データベースに追加する仕組みをシステムに入れていたので、この改行をするための空白が、がっつり入っていたというのが原因だったんです(笑)
じゃあ、空白消すかってなったわけです。
ああ、もちろん、Alt+Enterの改行は対策してました。CSVデータに改行が入り込むとレコードがくずれますからね。ちゃんと消さないと使い物にならないですから。

はい、それでは私が作った空白を置き換える関数のコードはこちら


Function space_cancel(moji as String) As String
'2個以上並んだスペースは中点に置き換える関数
Dim sp_n As Integer, sp As String, i As Integer

moji = Replace(moji," "," ")'全角スペースを半角スペースに置き換える

Do While InStr(moji,"  ") <> 0 '半角スペース2個が文字列になければ終わり
   sp_n = 1
   sp="  "'2個の半角スペースを入れる
   
   ’この下のループでスペースを1つずつ増やしながら
   'いくつ繋がったスペースがあるか照合する
   Do While InStr(moji, sp)<> 0
      sp_n = sp_n + 1 'スペースの数
      sp = sp & " "’スペースを1つ増やす
   Loop

   If sp_n > 1 then 'スペースカウントが1より大きいなら処理
      '前のループで作ったspのスペースから1つ半角スペースを消してもOK
      ’ここでは再度作ります
      sp = "" '変数リセット
      For i= 1 to sp_n
          sp = sp & " "'半角スペースを追加していく
      Next i
      
      '上のループで作ったスペースと中点を入れ替える
      moji = Replace(moji,sp,"・")
   End If

Loop'2個繋がったスペースがまだあれば繰り返す

space_cancel = moji

End Function

解説はコード内のコメントでみてください。
スペースはコード見てもわかりにくいですが、コメントのようになってます。
ここでは2個以上の空白があれば、中点に入れ替えてますが、消すなら消すでもいいですし、入れ替える文字を変えれば変更できます。アレンジしてみてはいかがでしょうか?
これを実行すると、「データ          データ2         データ3」みたいなデータは「データ・データ2・データ3」になります。空白がなくなるまでループしますので、空白がいくつかあってもなくなるまで動きます。
私の作ったシステムでは、このままCSVに保存しておいて、使うときにSplit(文字列,"・")で切り出して使ってます。ここでは書いてませんが、改行も中点に置き換えてますので、長いスペースも改行と同じとして使っているわけです。
あと、スペース1つの場合、私の扱っていたデータでは、区切りとして使われることがなく、データの中に入り込んでいるものも多数あったので、データとして生かしてました。

これ、わざわざ面倒な処理つくるより、作業する人に「セルの改行はAlt+Enterにしてよっ!」と、注意をするのも手段としてはありだとは思うのですが、、、、
業務効率化を目指したシステムとか自動化って、出来る限りユーザー側の作業方法、やり方は、変えないほうが効果が高いと思うんですよね。無理矢理やり方を変えてもらって、「すぐに慣れるから」、「楽になるから」、、、、と伝えたところで、、、結構ユーザー側にはストレスかかりますし、やっているうちに前の癖が出て同じことが起きます(笑)
間違えるやつが悪い、ルールを守れないやつが悪いって言っちゃえば楽チンですが、、、多分、一番考えなきゃいけないのは、「誰のための効率化なのか?」ってことじゃないかと思うわけです。

はは、、、、バイトが偉そうに何か言ってら(笑)、、、、と、まあ、私の勝手な持論ではあります。
あとね、私も含めてそうなのですが、何かミスしちゃったとき、「気をつけて作業しなさい」って注意されたりしません?
そんなとき、いつも思うんです。

「いやいや、気をつけてないわけないじゃん」って(笑)
多分、なんでミスが起こるのか?っていう原因をつぶすことをちゃんと考えるの方が大事だと思うんですよね。それと良く似てます。ユーザー側に注意して入力してって伝えても、何回かに1回はやっぱり間違えちゃいますよ。これはもうしょうがない!だって人間だもの(笑)

だからガードかけたり、入力補助いれたり、、、なるべくミスの原因を潰していくことを考えればいいんです!PCは命令すれば間違えませんから(笑)

おっと、いかんいかん、また明日早朝バイトだ(笑)睡眠時間が、、、、
それでは、ここまで読んでいただきありがとうございます。

【ExcelVBA】セルの値を戻すを実装してみる〜なんちゃってアンドゥ(Undo)

 

さて!みなさんは、ExcelVBAで処理をさせているとき、「ここで入力された値によって、セルの値を元に戻したいなぁ。。。」って思う時ないですか?

でも、VBA実行後は「元に戻す」が使えないですよね。。。

じゃあ、Undoメソッドでコード側からなんとか制御できないものかと、、、、残念ながら、これもVBA処理は元に戻せないようです。

私は使ったことないので詳しくないですが、Microsoft Learnによると、このUndoメソッドはプロシージャの一番上、つまり、コードの最初に書かねばならないみたいです。調べてみると、このメソッドは、直前のユーザー操作を元に戻すことができるようですが、ユーザー操作とUndoメソッド実行の間に、VBA処理が入ってしまうとエラーになるらしい。なるほど、それで最初に書くのか、、、、でも、これだと使い道が限られちゃいますね。。。
じゃあ、今回みたいに入力された値によって、自由に操作したいんだよなぁ〜ってとき、どうしましょう??

 



そこでっ!上書きされたセルの値を元に戻す方法をご紹介します。
ただ、この方法は、Excelのリボンにある「元に戻す(Ctrl+z)」とは違い「なんちゃって」です。
前の操作を取り消して、完全に元に戻すわけではありません。その上、しっかり制御しないと、期待した結果にならないこともあります。なので、実装する際にはご自身でデバッグしながら調整が必要です。
また、おそらくこの方法は、大正解じゃないと思います(笑)きっと他にもいろいろ良い方法があるはず!
なので、「こんな方法もあるんだね〜」って、温かい目でご覧ください(笑)




はい、それではやっていきますが、、、実は、今回、コードの紹介というより、どうやって「値を元に戻す」を「表現するか?」を考える感じです。


ここで大事なのは、「直前の処理をなかったことにする」ではなく、、、「セルの値を元に戻す」ことだけを考ることです(笑)
はい、そうなると簡単ですね!
「処理の巻き戻し」なんて難しいことは考えずに、上書き入力されて変更された値を、上書き前の値で、再度上書きすればいいわけです!

それでは、実現したい状況を考えるために、
値が入力されているセルに、上書きしたあと、「元に戻す」処理に至るまでの、人(ユーザー)の動き、対象セルの値、Worksheetイベントのタイミングを、並べてみてみます。

こんな感じでしょうか。
この、③のChangeイベントで前の値を呼び出せればいいわけですね。では、このまま③のChangeイベントで前の値を呼び出せるでしょうか?
上の表をみてください。③のとき、すでに値が上書き後になってしまっています。残念ながら、タイミングがちょっと遅いみたいです。

では、他のタイミングを考えましょう。
上の表の中で、上書き前の値がセルに残っていて、さらに記憶させる処理を実行させるきっかけも一緒にあるのはどこになるのか、、、

はい、①だけしかチャンスはありませんね!

いやいや、②ならまだ値をゲットできんじゃね??って考える方もいらっしゃるでしょうか。
確かに、Excelの入力操作だけ考えてみると、上書き入力を開始して、前の値が消えたとしても、値の確定前にキャンセル(ESC)すれば、上書き前の値に戻りますから、どこかに前の値が隠れているかもしれません。
でも、残念ながら、この②のタイミングで発生するシートイベントがないんですよね。。。。
やはり、①のタイミングしかないですね!



さて、覚えるタイミングは①にしたとしても、、、、これだと、ちょっと困ることがありますよね。
そうです。プロシージャが①「前の値を覚えておく処理」と、③「元に戻す処理」で分かれてしまします。
プロシージャで内で宣言された変数は、宣言したプロシージャでしか使えませんから、前の値を変数で覚えておいても、元に戻す処理の時には変数の値を使えないことになります。
じゃあ、画面外の見えない場所を使って、任意のセルに前の値を覚えさせちゃいましょうか??、、、、まあ、それはそれで一つの方法だとは思いますが、シート上のセルって、行、列の挿入や消去に巻き込まれて場所が変わったり、消えちゃったりなど、結構簡単に崩されてしまうので、汎用に向かないと言うか、、、まあ、あまりおすすめできません(笑)

なので!!変数自体を「プロシージャを超えて使える」ようにします。

 

やり方としては、二通り方法があります。

どちらも同じ動きが期待できますが、変数の使える範囲が違ってきます。
1つ目は、モジュール内のプロシージャならどこでも使え、もう1つは、プロジェクト全体で使える方法です。

まず、モジュール内で使える方法です。

このように、プロシージャの外側でDimして宣言すればOKです。モジュール内では一番上になるようにします。簡単ですね。こんなラクチンなら、プロシージャごとに変数宣言しなくても、変数全部ここで宣言しちゃえば楽でいいじゃん!って思っちゃいそうですが、、、、まあ、実際にやってみたらわかりますが、そんなことすると、楽になるどころか、メチャメチャコードが難解になります(笑)
変数の値が追っかけづらくなるんです。

きっと、「あれ?値がおかしい?!どこで変わった?!もう!なんでこんなに見にくいのっ?!」

ってなります(笑)やはり、プロシージャごとに考えられるほうが断然楽っすよ〜。

 

そして、もう一つのプロジェクト全体で使える方法です。

先程のモジュール内で使いまわせる変数と書く場所は一緒ですが、宣言にはPublicを使います。このPublicはどこのモジュールに書いても良いです。
だからと言って、あちこちに書いちゃうと、、、、
「あれ?どこに書いたっけな。。。テヘペロ
ってのは嫌われますよ(笑)しっかりルールを決めて、書く場所は一箇所にまとめておきましょう!

あと、どちらにも言えることですが、使える範囲が広いっていうことは、言い換えると、変更されやすい変数と言えます。なので、通常使う変数と名前の付け方をかえるとか、わかりやすくしておくとよいと思います。あとは、ややこしくなるのでなるべく増やさない(笑)

ところで、ちょっと話がずれますが、VBAでコード書く時、変数の「宣言」を強制して使ってますか??
VBAって「宣言」しなくても変数使えちゃいますから、、、、わざわざ宣言するのは面倒くせ〜って思っちゃいますよね。。。でも、しっかり「Option Explicit」やって宣言を強制しておくと、コンパイルの段階で変数名の間違いをチェックできるので、エラーがずいぶん減ると思います。だまされたと思って、「Option Explicit」して、宣言を強制してみてください。デバッグ回数も減りますよ〜!




それでは、実際に私が作ったツールのコードを参考に、配置について説明していきます。コード自体はとても簡単です。
まずは値を覚える処理。

ちょっと、余分な処理が入っちゃってますが、元に戻すための値を覚えるだけなら、ActiveCellの値を、そのままモジュールレベル、パブリック変数に入れちゃえばいいです。
私のツールでは、値を覚える処理の他に、別の処理も並行してさせていたため、わざわざ別変数を準備しているだけです。
なので、覚える処理としては、コードを配置する場所をSelectionChangeにし、アクティブになっているセルの値をモジュールレベル、パブリック変数に入れるだけです。これで選択されるたびに値が保存されるわけです。
これ、ActiveCellではなく、Targetを使っても良いです。ここでActiveCellになっているのは、私の作成したツールが、値によってセル位置変更など、別の処理も多数つけていたので、意図していたセルからずれてしまうことがあったためです。
まあ、これは個人的な感想ですが、ActiveCellのほうが、ユーザーから見た挙動に沿った動きになりやすいですかね。
このように、シートイベントきっかけで始まる処理は、考えている以上に、他の処理によって邪魔が入り、挙動が変わります。ですから、実際に使う環境で動かしてみて、いい方を選択すると思っていたほうが良いでしょう。

次にChangeイベントで、元に戻す処理ですね。

 

私のツールの中では、値が指定した数字の並びだった時、TargetのValueに上書きしています。このように、変数の使い方は呼び出すだけでいいのでとても簡単です。

基本的な使い方としてはここまでです。
コード自体はとても簡単で、使い方だけ考えればいい感じでした。
変数の使える範囲(スコープ)を広げて、シートイベントごとに覚える、引き出すを実行すればよいだけでしたね。


ですが、、、、先にも書いてますが、、、、実は、実装するにはしっかり制御しないとうまく使えないです。
これは、「こうすれば解決!」っていうものでもなく、実際に使う環境で一度動かしてみて、意図した動きにならなかったら対策するという手法しかないかと思います。

実際に私の作ったツールで対策した実例をいくつかあげておきます。

その1 セルの選択がされずに変更される

これはSelectionChangeが働かないので、前の値が記憶されません。よく起こるのは、シートを選択したら、ちょうど変更したいセルだったという場合。なので、シートイベントのActivateを使って、シートがアクティブになったら、タイトルや項目など、ユーザーの変更対象にならないセルを一回選択するようにして、セルの選択が絶対に実行されるように仕向けたり、ActiveCellの値をActiveteイベントのたびに覚えておくなどで回避します。シートの移動が多い場合、変更対象外のセルを選択する方法を取ると、入力の邪魔になる可能性が高くなるので、最初の一回だけ実行するスイッチを入れるなど工夫が必要です。

その2 範囲選択される

場合によってはエラーになります。Targetのアドレスを取得して、アドレスに「:」(コロン)が入っていたら処理をしないなど工夫が必要です。

その3 イベントを停止している場合

シートイベントのChangeやSelectionChangeを使う時は、コード上で値を変更したり、選択したりすると、その度にイベントが発生して無駄なループが発生します。また、場合によっては無限ループになることもあるので、Application.EnableEvents=Falseでイベントを停止させてから処理を実行させ、終わったらTrueでイベントを再開させるのが得策ですが、これが仇になって値が記憶されない場合があります。セルの値を記憶させたい場所があれば、その箇所だけイベントを再開させるか、その箇所で値を覚えておく処理を入れ込む必要があります。

その4 前の前の値が残ってる

これはよくやっちゃいますね〜。モジュールレベル、パブリック変数は値が変更されやすいって先に書いてますが、値が残り続けるものでもあります。なので、前の値を覚える、破棄するは自動じゃありませんので、その都度必要かどうかを判定して、処理を書く必要があります。

その5 エラーで処理が止まると値が消える

このエラーというのは、該当のプロシージャだけのことではなく、すべての処理についてです。モジュールレベル、パブリック変数を使って値を覚えたとしても、何かしらのエラーが起こって、処理が止まり、デバッグを選択しなかった場合、その時点で値は消えてしまいます。。。こればっかりは気をつけてとしか言いようがない(笑)
あと、、、別件ではありますが、イベント停止を実行してから、処理がエラーで止まった場合、イベントも自動で再開しません。
エラーを修正してから、再び動かしたとき、イベント全部が止まっていて、ビックリなんてこともありますので、イベント再開は手動でできるように復旧用のプロシージャを準備しておきましょう。



さあ、いかがでしたでしょうか?
コードは簡単なんですが、シートイベントはまあまあ面倒くさいですよね(笑)

最初の方にも書きましたが、このシートイベントを使った処理は、どうやって使うのか?によって、しっかり制御しないと、楽するためのツールなのに、面倒なルールをつけないと使えないという、なんだか奇妙なものになる可能性が出てきてしまいます。
過去記事の「Changeイベントのくせもの」でも書いてますが、とにかく面倒なので、導入する価値があるのかどうかはしっかり考えたほうがよいです。

それでは、今回はここまでです。
しかし、このところの物価高。。。おかげさまで、バイトひとつじゃ生きていけなくなりまして(笑)
早朝に掛け持ちバイトぶっ込んでなんとか食いついないでます。
もう毎日眠くて眠くて(笑)
ブログ更新も時間がかかるようになってしまいました。。。。やはり、こういったことは余裕がないと続かないですね〜。。。。って愚痴ってみました。
それでは、ここまで読んでいただきありがとうございます。



【ExcelVBA】1つのプロシージャでボタンごとに処理を変える方法

 

複数のボタンに同じ1つのプロシージャを登録し、押されたボタンによって処理を変える方法のご紹介です。

ボタンによって処理を変える方法で最初に思いつくのは、ユーザー定義関数を引数によって処理を変えるように作成しておき、ボタンごとにプロシージャを準備し、それぞれ関数に渡す引数を変える方法じゃないでしょうか?
この方法は本当によく使いますが、例えばボタンによって足すか、引くかを変えるだけみたいな、処理自体が単純で簡単な場合、ボタンごとにわざわざプロシージャを作って処理するのは、労力に対して対価がなさすぎて、ちょっと抵抗ありません?(笑)もう、関数でいいじゃん!ってなっちゃうみたいな(笑)

そのように感じるのは私だけかも知れませんが、こんな方法もあるよってことで、よかったら参考にしてみてください!

では、説明のために次のようなサンプルを作っていきます。

C3セルに日付の書式設定をしておいて、そのセルの上に画像のようなフォームコントロールのボタンを5つ配置します。
ボタンは押されるごとに、それぞれ次のような処理をさせます。

  • -1週:表示された日付から−7日する。
  • -1日:表示された日付から−1日する。
  • 今日:表示に関わりなく今日の日付を表示する。
  • +1日:表示された日付から+1日する。
  • +1週:表示された日付から+7日する。

この仕組み、、、、私、めっちゃ使うんですよね(笑)

はい、それでは、作成していきます。
まず、どのボタンが押されたのか?を、何で判定させるのかといいますと、ボタンの「名前」を使います。
これはボタンに表示されている文字列ではなく、選択時に左上へ表示されたり、Excelのリボンにある双眼鏡マーク「検索と選択」で見ることができる名前の方です。
それでは、早速、「検索と選択」から名前一覧を表示させましょう。

他に図形やフォームコントロールがなければ上のように番号が飛ぶこともなく、綺麗に名前が並ぶと思います。
名前の番号は、作成した順番で振られていくので、どのボタンがどの名前になるかはしっかり確認して使う必要があります。名前の確認は、名前を選択すればボタン自体も画面で選択されるので、1つ1つ確認しながら作業しましょう。

さて、次の作業で名前を使っていくので、ボタンに合わせて名前を変えていこうと思うのですが、このように綺麗に名前が並ぶなら、名前の変更はしなくても良さそうに思えます。ですが、実際は配置を考えながら追加、削除を繰り返して、ツールを作成していくことがほとんどだと思いますので、名前が綺麗に揃うことなんてあまり無いですよね。
また、マクロの入ったシートをコピーする運用はあまりしないと思いますが、もし、コピーするような運用をする場合、このように自動割り振りされた名前は、使っているフォームの数で再度割り振りされてしまいます。なので、自動割り振りの名前を元に、コードを書いていると、名前とコードがずれて、VBAが使えなくなることがあります。
なので、名前を使う仕組みの時は、名前をつけ変えることをお勧めします。
それでは次のように名前を変えていきます。
「day」という名前の後に0〜4までの数字を振ります。

名前の変更は、一覧から該当の名前をダブルクリックすれば、名前が編集可能になるので変更します。上の画像では番号が綺麗に並んでますが、実際はボタンを作成した順番によって配置が変わると思いますので、このサンプルを作成してみようと思っている方は、それぞれ次のように名前を対応させてください。

  • 今日:day0
  • +1日:day1
  • +1週:day2
  • -1日:day3
  • -1週:day4

はい、これで下準備は完了です。
それでは、標準モジュールに「day_change」というプロシージャを作成します。プロシージャは「Sub」で始まるアレですね。
まずは、ボタンを判定するために「名前」を取得してみます。実は、これがとても簡単(笑)

Application.Caller

はい、これだけです!かんた〜ん!!
で、、先ほど、末尾の数字でボタンを分けました。なので文字列操作をして末尾だけ抜き出しましょう!

x = Val(Right(Application.Caller,1))

末尾なので、このように一番右側から1文字抜き出せばいいですね。
そして、抜き出したままの文字列で扱っても問題ないのですが、、、あとあと比較判断に使う時、数字だから数値として比較したら、期待した結果と違って、エラーの原因がわけわからなくなるという事態は避けたいので、Valでくくって数値化しておきます。

あとは、変数xに入れた数値によって処理を変えるコードを書いて終わりっす。こうすれば、ボタンの名前を変えるだけで同じプロシージャを使えるわけです。コードが少ないうちはいいですが、多くなってくるとこういった使い回しできる仕組みは重宝します。

それでは、最後にサンプルコードを書いて終わりします。


    Sub day_change()
    
    Dim x as Integer,dd as Range
    
    'ボタンによって処理を変えるための処理
    x = Val(Right(Application.Caller,1)'0:今日,1:+1日,2:+7日,3:-1日,4:-7日

    Set dd = Activesheet.Range("C3")
    
    ’今日以外のボタンが押されてセルが空白もしくは0の時は処理を終わる
    If x<>0 And (dd.Value = "" or dd.Value = 0 )then 
       Msgbox "基準となる日付がありません。”,VbOkonly,"基準日エラー"
       Exit Sub
    End if
    
    Select Case x
         Case 0
              dd = Format(Now(),"yyyy/mm/dd")’時間は邪魔なのでカット
         Case 1
              dd = dd + 1’ dd.Value + 1と一緒です。
         Case 2
              dd = dd + 7
         Case 3
              dd = dd - 1
         Case 4
              dd = dd - 7
         Case Else
              dd = "" '想定外のときは空白いれておく
    End Select
    
    End Sub

コードはこれだけです。
コード中の黄文字は注釈ですので、処理の動きには関係ありません。使う時は外しても大丈夫です。

はい、あとは、このプロシージャを全てのボタンに登録して完成です。

 

いかがでしたでしょうか?何かヒントになったり、役にたったりしたでしょうか?
さらにですね、これ応用が効きまして、別のシートなんかでも同じような処理をしたいなって思うことありません?
そういときは、上のコード内に、変数ddへセルを代入している箇所あるじゃないですか。この代入の前に、アクティブになっているシート名を取得して、名前によって代入するセルを変えるんです。すると、このプロシージャだけで使い回しができるってわけですね〜。例えばこんな感じ


    Select Case ActiveSheet.Name
         Case "その1"
             Set dd = Worksheets("その1").Range("A1")
         Case "その2"
             Set dd = Worksheets("その2").Range("C3")
    End Select

ね、便利〜。
それでは今回はここまでです。
次回は入力したことをなかったことにする「安藤」、、、もとい、「アンドゥ(Undo)」っぽい動きをさせるコードを書きます。このまえ作ったツールで使ったのでご紹介です。なんちゃってではありますが、これも便利なのでよかったら、、、、それではまた。

追記
サンプルコードの−7のところ、、、+7になってましたね。読み返してたら気が付きました。すみません。。。