出直し!! ヘルプ

連載中

連載 終了

2023/08/31

ワークショップ『Notesアプリ開発を完全理解! @関数で誰でもワークフロー』のご案内

学習目標

  1. フォームやビュー、フレームセットを使ったNotesアプリを開発できる
  2. @関数を使ってワークフローアプリを開発することができる
  3. 作成者・読者フィールドを使ったセキュアなワークフローアプリを開発することができる

日時    2023/ 9/20(水) ~  21(木)
費用    22,000


コスパ 最高 のワークショップとなっております。

申し込みは 9 月 9 日(土)23:30 までですので、この機会をお見逃しなく!


Notesアプリ開発を完全理解! @関数で誰でもワークフロー

↑ お申し込みはこちら!


私の知る限り、これ以上の詳しく効率的に習得できるワークショップは、他にはないと思います。

ノーツコンソーシアムの新任担当者向けワークショップに参加した方、その続きを学習したい方にもおすすめです。

ぜひともご参加ください!


-----


と、広告じみた始まり方をしましたが...

このワークショップ、HCLSoftware 日本語メールマガジンやノーツコンソーシアムのメールマガジンでも案内されおりますが、実は、私がテキストを作成し講師を担当することになっています。

これまでの経験を総動員して準備しました。手前味噌ですが、わかりやすくても密度が濃い、よいテキストができた思っております。

一人でも多くの方に受講いただきたいと考えております。そこで今回は、”コスパ 最高”とご紹介したこのワークショップの”推し”をご紹介します。


推し①:コスト

費用は、22,000 円です。

今回のコンテンツでの開催は初回となり、パイロット的位置づけでの開催となります。

参加費用は、パソコン付きの会場のレンタルやハンズオン環境の構築費用、テキスト印刷代などの実費分をご負担いただく設定となっており、開催準備や当日の運営は有志によるボランタリーな活動で実施します。このような経緯から一般的なオンサイトワークショップに比べて”はるかに安い”価格となっております。

この前提でコスパを語るとビジネスで活動されている方に失礼に当たりますが、超おトクな価格設定と言えますね。

なお、パイロット開催とはいえ、内容は妥協していません。安かろう悪かろうでは、コスパを語る資格はないですよね!


推し②:実践的な内容

これまで、多数の後輩や外注業者さんの指導や依頼を受けて開催した研修実績があります。これら経験から、必要な知識を網羅しつつ体系立てて習得できるようにコース設計をしております。また、無理なく習得できるよう密度にも配慮しています。

効率よく習得できるので、タイパ もばっちりです。

業務に戻れば、即 ”使える” 実践的な内容としております。具体的には、後述の目次ページの画像を参照ください。


推し③:豊富なハンズオンで定着

ハンズオンは動けば OK という単純な内容ではありません。

例えば、ノーツアプリで根幹となる ”作成者/読者フィールド” の演習では、ユーザを切り替えつつ、挙動を確認します。その仕組みや動作の ”理解” に重点を置いています。

テキストには、講習内容に沿った 20 の演習と講習内容を応用して解決する課題を用意しています。ハンズオンの時間を多く確保していますので、しっかり習得できます。


推し④:充実したテキスト

図解や画面キャプチャを多用して視覚的にも理解しやすいよう作成しています。テキストの本編は 256 ページを超えるボリュームでしっかり解説します。

また、主要な@関数と@コマンドのリファレンス、式言語の文法などノーツアプリ開発に必要な知識をまとめた Appendix(128 ページ以上)付きです。デザイナーヘルプでは、単調でわかりずらい解説を取捨選択してわかりやすく再編集しています。また、サンプルは具体的で実用的な事例に書き直しています。


講座の内容

1日目は、ノーツの基礎知識とノーツアプリ開発の基礎知識を解説します。フォームやビューの作成方法などアプリ開発を0から順を追って説明します。その中で、フィールドの型やプロパティ設定も解説。フレームやアウトラインでメニュー作成を行います。

基本操作を習得した上で、式言語の文法を詳細に解説し、実際に式を使ってみる演習を行います。

2日目は、式言語を応用してワークフローアプリを作成します。ワークフローアプリを題材にして、次のような実践的な開発手法を習得します。

  • 入力チェック(フィールド / アクションボタン)
  • 権限管理の機能(ACL / ロール、作成者 / 読者フィールド)
  • 申請者や承認者など役割に応じて権限設定をする方法
  • メールの送信
  • 編集時の制御(承認権限を持っていても内容は編集できなくする方法)
  • 文書削除の制御(承認中の文書を承認者が削除できなくする方法)
  • エージェントの使い方


あらためて

今回のワークショップは、ノーツアプリ開発の基礎から(@関数での)学習し、ワークフローアプリを構築するための知識をまとめて効率的に学習できるコースとなっております。

しかも、パイロット開催のため 超おトク な設定です。今回だけの特別な設定となりますので、このチャンスは今だけです!

繰り返しになりますが、
申し込みは 9 月 9 日(土)23:30 までですので、この機会をお見逃しなく!

2023/08/30

LotusScript の デバッグ時に実行行が正しく表示されない件

先日、ノーツでQRコード の連載の中で、LotusScript のデバッガが挙動不審になる現象をご報告させていただきました。

ノーツでQRコード:#10)初期テストと挙動不審なデバッガ


サポートに問い合わせた結果を原因と回避策を提供いただきましたので、ご報告します。


原因

もちろん、今回の現象は、Excel VBA のエディタからプログラムをコピペしたことに起因します。

LotusScript と VBA は互換性があるのですが、コードをコピペした際、内部ではさらに何らかの変換をしているようです。この変換でずれの元が発生しているようです。これがきっかけで、デバッガが挙動不振になっていたとのことです。

この状態で、なぜエディタで正常に動作しコードが問題なく修正できるのかなど、釈然としない点はあるのですが、すべての行をデザイナーで書き直したら治りそうだという想像はあっていたようです。


なお、上記原因は私が理解できない部分、確証が持てない部分があったので、あえてぼやかした記述にしています。ご了承ください。


回避方法

回避方法は Eclips ベースのエディタの使用をやめ、旧来の LotusScript エディタで保存すれば治るとのことです。

作成いただいた技術情報に手順が記載されておりますので、以下のリンクをご確認ください。

LotusScript の デバッグ時にステップインしても正しいコードが表示されません


こんな方法があったんですね。エディタで何か変な現象が発生した場合、切り分け作業に使えそうです...

また、今回の事例でしか確認していませんが、この回避策によりいったん正常動作するようになると、以降エディタの設定を Eclipse ベースに戻しても現象は再発しないようです。


レアケースの問題といえますので、この回避策があれば、完全な解決でなくともおおむね問題ないですね...


感謝・感激

ところで今回の問い合わせなのですが、現象の報告後、次の返答が、

  • 原因の解説
  • 新規問題番号の発行(CSAOCUW4G8)
  • 技術情報の掲載とリンクの報告
  • 回避策の連絡

と完ぺきな状態で、迅速かつ気持ちのいい対応をいただきました。


Excel がらみの問い合わせのため、塩対応も覚悟していたのですが、良い意味で裏切られました。今回のような対応をいたけると、モチベーションが上がりますよね。

ありがとうございました。今後ともどうぞよろしくお願いいたします。

2023/08/29

DXL ことはじめ:#8)フィールドの取得

前回は、文書の中身の全体である document ノードと属性の取得を整理しました。属性では、文書 ID や フォーム名など、文書のプロパティ的な要素が取得できました。フォーム名は、フィールド内に保持しているのですが、DXL では特別扱いされているようですね。

今回は、フィールドの取得法についてまとめます。

フィールドは、document ノードの直下にエレメントノードとして存在します。通常のフィールドは、"item" というタグのエレメントノードで、"name" というアットリビュートにフィールド名がセットされています。

それでは、フィールドの取得方法を確認しましょう。

NotesDOMElementNode クラスには、タグ名で配下のエレメントを検索するメソッドがあります。以下のプログラムは、DXL から全フィールド順に取得して、フィールド名を順にメッセージボックスで表示します。

'document ノードの取得
Dim denDoc As NotesDOMElementNode
Set denDoc = ddn.DocumentElement


Dim dnl As NotesDOMNodeList
Dim denFld As NotesDOMElementNode
Dim dan As NotesDOMAttributeNode
Dim i As Integer

Set dnl = denDoc.GetElementsByTagName("item")
For i = 1 To dnl.NumberOfEntries
   Set denFld = dnl.GetItem(i)

   Set dan = denFld.GetAttributeNode("name")
   MsgBox "フィールド名 = " & CStr(dan.NodeValue)
Next

検索は、GetElementsByTagName メソッドで、引数に検索するタグ名を文字で指定します。

戻り値は、NotesDOMNodeList のオブジェクトです。このクラスは、属性の場合の NotesDOMNamedNodeMap と同様に、コレクションのように振舞います。このクラスを使用して、フィールドのエレメントノードを順に取得します。

フィールド名は属性として記録されていますので、"name" を取得して、メッセージボックスに表示しています。


実際のコーディングでは、フィールド名を指定して、ノードを取得するのが一般的かと思います。次のような関数を作っておくと便利かもしれませんね。

document ノード と 取得したいフィールド名を引数にして、見つかったフィールドを NotesDOMElementNode のオブジェクトで返します。

Function xGetField(vdenDoc As NotesDOMElementNode, vsFldName As String) As NotesDOMElementNode
   Dim dnl As NotesDOMNodeList
   Dim denFld As NotesDOMElementNode
   Dim dan As NotesDOMAttributeNode
   Dim i As Integer

   Set xGetField = Nothing
   Set dnl = vdenDoc.GetElementsByTagName("item")
   For i = 1 To dnl.NumberOfEntries
      Set denFld = dnl.GetItem(i)
      Set dan = denFld.GetAttributeNode("name")
      If dan.NodeValue = vsFldName Then
         Set xGetField = denFld
         Exit For
      End If
   Next
End Function


前回 DXL ことはじめ 次回

2023/08/27

リッチテキスト:#9)タブの設定

今回はリッチテキストにタブをセットする方法です。

タブの設定は、段落ごとに設定することになるので、NotesRichTextParagraphStyle クラスが担当します。


タブの設定

タブを設定するには SetTab メソッドを利用します。構文とパラメータは以下の通りです。

   Call notesRichTextParagraphStyle.SetTab(position, type%)

  • position

Long型。タブの位置を指定します。 

インデントの設定を同じく、単位は twip です。1 cm = 567 twip、1 inch = 1440 twip です。RULER_ONE_CENTIMETER と RULER_ONE_INCH を使用すると容易に計算できます。 

  •  type%

Integer型。タブの種類です。以下の定数が使えます。

定数
0 TAB_LEFT
1 TAB_RIGHT
2 TAB_DECIMAL
3 TAB_CENTER


サンプルプログラム

SetTab メソッドを使用したサンプルは次の通りです。

   Set ndb = ns.CurrentDatabase
   Set nd = ndb.CreateDocument()

   nd.Form = "RichText"
   nd.Title = "リッチテキスト:#9)タブの設定"

   Set nrti = nd.CreateRichTextItem("Body")
   Set nps = ns.CreateRichTextParagraphStyle()

   '1行目
   Call nps.SetTab(4 * RULER_ONE_CENTIMETER, 0)
   Call nrti.AppendParagraphStyle(nps)
   Call nrti.AppendText("ABC" & Chr(9) & "DEF" & Chr(9) & "GHI")

   '2行目
   Call nps.SetTab(6 * RULER_ONE_CENTIMETER, 0)
   Call nrti.AppendParagraphStyle(nps)
   Call nrti.AppendText("ABC" & Chr(9) & "DEF" & Chr(9) & "GHI")

   Call nd.Save(True, False)

文字列にタブを挿入するには、文字コードの 9 をセットします。LotusScript だと、Chr(9) となります。

実行すると以下のようになります。


1行目は 4 cm にタブをセットしています。

2行目は 6 cm にセットしています。この時、1行目をセットした NotesRichTextParagraphStyle のオブジェクトを使いまわしているので、4 cm のタブに 6 cm のタブを追加するという動作になっています。このように、SetTab を何度もコールすることで複数のタブを1つの段落にセットできます。


タブの種類

SetTab の2つ目の引数はタブの種類の指定となっていました。

実は、これまでこの引数は全く意識しておらず、いつも 0 を設定していました。何のためにあるんだろう?とまで思っておりました。

この記事を書くために検証して初めて価値を知りました。

この機能は、タブで間隔をあけた文字をどのように表示するのか指定する機能でした。試しに次のようなコードに修正してみました。

   Set nrti = nd.CreateRichTextItem("Body")
   Set nps = ns.CreateRichTextParagraphStyle()

   'TAB_LEFT
   Call nps.SetTab(6 * RULER_ONE_CENTIMETER, TAB_LEFT)
   Call nrti.AppendParagraphStyle(nps)
   Call nrti.AppendText("TAB_LEFT" & Chr(9) & "1,234.56")

   'TAB_RIGHT
   Call nps.ClearAllTabs()
   Call nps.SetTab(6 * RULER_ONE_CENTIMETER, TAB_RIGHT)
   Call nrti.AppendParagraphStyle(nps)
   Call nrti.AppendText("TAB_RIGHT" & Chr(9) & "1,234.56")

   'TAB_DECIMAL
   Call nps.ClearAllTabs()
   Call nps.SetTab(6 * RULER_ONE_CENTIMETER, TAB_DECIMAL)
   Call nrti.AppendParagraphStyle(nps)
   Call nrti.AppendText("TAB_DECIMAL" & Chr(9) & "1,234.56")

   'TAB_CENTER
   Call nps.ClearAllTabs()
   Call nps.SetTab(6 * RULER_ONE_CENTIMETER, TAB_CENTER)
   Call nrti.AppendParagraphStyle(nps)
   Call nrti.AppendText("TAB_CENTER" & Chr(9) & "1,234.56")

ちなみに、ClearAllTabs() メソッドは、これまでのタブの設定をすべて削除します。これで毎行別のタイプのタブを設定しています。

実行した結果は次の通りです。

TAB_LEFT、TAB_RIGHT と TAB_CENTER はタブ位置を起点に文字をどちらに寄せるかを指定します。

TAB_DECIMAL は、小数点の位置で揃っています。ほかの値をセットしてテストしましたが、小数点以下の桁数に関係なく、揃えてくれました。小数点がない場合は、TAB_RIGHT と同じ結果となります。なかなか使えそうな機能ですね。

前回 リッチテキストの基本操作

2023/08/24

100 回記念:現在・過去・未来?

2023 年 3 月からスタートしたこのブログ。もう少しで半年となります。思いつくままに話を展開させていただいておりますが、早いもので、この記事で 100 本目となります。

記念すべき 100 回目はいつもと違って、思い出話からスタートです。


ノーツとの出会いと魅力

はるか昔 1994 年、最初に就職した会社で、あるツールに出会いました。そのツールの名前は、もちろん”ノーツ”です。入社した会社がノーツを担いでいた関係で、ノーツアプリの開発担当者としてノーツに携わるようになりました。


当時のバージョンは R3J。

設計要素は、フォームとビュー、マクロ(現エージェント)ぐらいしかなく、アプリ開発言語としては@関数しかありませんでした。そういえば、当時は、サーバも”ノーツ”と呼んでおり、ドミノという言葉は存在していませんでしたね。

それでも、この時点でノーツらしい機能はすべて出来上がっていて、ホワイトカラーの生産性向上や BPR の推進に十分なポテンシャルを持っていました。懐かしいフレーズと思われる方は、同世代ですね、きっと(笑)


ノーツはグループウェアとしてメール機能を持っていました。

ユーザのメールボックスは、ノーツのデータベースファイルである nsf の形式です。nsf ですので、もちろんデザイナーで設計要素が確認できます。自己責任とはなりますが気に入らなければ、カスタマイズもできます。

そしてメールだけでなく公開アドレス帳(現ドミノディレクトリ)などのシステム管理用 DB も同様で、デザイナーから操作できました。

公開アドレス帳の中には、ユーザやサーバの設定が文書で入っています。これらは、普通のノーツの文書できていて、編集もノーツクライアントで行います。

ノーツをつかさどる機能がノーツできている...ノーツを面白いと感じた瞬間ですね。そして、カスタマイズできる自由度がユルくて心地よかったことを覚えています。工夫次第でどんどん活用できそうな可能性を感じました。


当時のバージョンはこういった標準 DB の設計は単純な仕様でした。そもそも@関数しかなかったですし...。そして、標準のテンプレートも同様に単純でした。これらの設計を参照しながら、より深くノーツを知り、開発スキルも身につくという、効率が良いシステムだと感じましたね。


そういえば、バージョン情報画面の中に、ノーツ開発者(?)のムービーが見れるイースターエッグなんてのもありましたね(笑)。そして、標準DB内には、まれにヘルプに乗っていない@関数や@コマンドが隠されていて、お宝探しができる一風変わったビジネス製品でした(笑)


古き良き時代

R5 が登場したころまではノーツの絶頂期でした。幕張メッセをはじめ、全国各地のイベント会場やホテルでセミナーや展示会が開催され、そのたび自分たちで作ったノーツアプリの展示や説明会をしました。当時はまだバブルの残り香があり、イベントも派手で楽しかったですね(笑)

書店に行くと Notes Domino Magazine という、ノーツ専門の月間誌が普通に販売されていました。

そんな古き良き時代の思い出の品がこちら!


イベントで使用していた R5 の行灯はロータス五反田事務所移転時に社員さんに分けていただきました。

そして、この Notes Domino Magazine が私のメジャーデビュー(?)の記念号です(匿名でしたが...)。

具体的にどのキャラが誰と編集部から通知いただいていませんが、どうやら緑の人のようです。こんなでかい態度どったつもりはないのですが...


アニバーサリーイヤー

こんな経験を日々しながら、それ以降もずっとノーツを担当してきました。

ノーツはどんどんバージョンアップしていきます。私はというと、途中何度か転職をしました。バージョンアップというと体裁がいいですが...。ただ一つ言えることは、すべての転職はノーツが縁でうまくいきました。

そして、今もノーツに携わっております。社会人人生をまるっとノーツにお世話になったといっても過言ではありません。


来年でノーツ担当 30 周年です。

その記念として、ノーツにお礼参り(?)する活動をしております。


ノーツコンソーシアム では、研究会での情報発信やノーツ新任担当者向けワークショップのお手伝いをしています。また、昨年 12月 のイベント DominoHub 2022 では、ライトニングトークの1コマを担当させていただきました。

今年 9 月開催の『Notesアプリ開発を完全理解! @関数で誰でもワークフロー』や昨年 9 月開催の『プログラミングとオブジェクト指向の基礎を学ぶ LotusScript講座』の講師とテキスト作成を担当しています。

もちろん、このブログもその活動の一環です。


この個人的なアニバーサリーイヤーに向け、引き続きいろいろ活動してまいります。今後とも、どうぞよろしくお願いいたします。


今後にも期待

先ほど過去の思い出を ”古き良き時代” なんて表現しましたが、確かに当時は勢いがありました。

イベントなどは盛況でした。一見するとよいのですが、大勢いるので情報の伝達は一方通行になりがちです。そして自分の頭が理解できる前に次の情報が入り、結局忘れてしまいます。


それに比べて現在はというと、ブームに乗っただけのなんちゃってユーザはすでにこの界隈にいません。

なので、ノーツの良さを理解して使い続けている方やもっと活用したいと本気で考えている方の密度がグッと上がっていると思います。最近はイベントなどに参加すると、コミュニケーションが双方向で活発、より具体的で踏み込んだ話ができます。

期せずして、良いフィルターがかかった状態と言えるのではないでしょうか? 現時点では、Google 先生でも AI でもできないことですよね...

情報過多の時代、これはありがたいですね!


私は ”古き良き時代” より、今の方が濃密で刺激的だと感じています。


今後もいろいろな場所に顔を出す予定です。機会がありましたら、うわべだけじゃない濃密な情報交換をしましょう。ぜひとも、よろしくお願いいたします。


現在ノーツは バージョン 12 です。今年中には 14 が出るようです(13 は欠番)。

日々進化しているノーツですが、過去のバージョンの機能はそのまま動きます。息の長いソフトウェアで、この点がしっかりしているのは安心感があり、ありがたいですね。

過去の資産を継承しつつ、新機能も使える。ノーツって素晴らしいですね。

2023/08/22

DXL ことはじめ:#7)文書の中身にアクセス

いよいよ、文書の中身を LotusScript でアクセスします。まずは、文書全体を表す document ノードのオブジェクトを取得します。


document ノードのアクセス

このノードを取得するには専用のプロパティがあるので簡単です。

NotesDOMDocumentNode クラスの DocumentElement プロパティで取得でき、NotesDOMElementNode のオブジェクトを返します。

前回までのコードに以下を追加すると document ノードのオブジェクトが取得できます。

 ・・・
'DOM ツリーのルートを取得
Dim ddn As NotesDOMDocumentNode
Set ddn = dprs.Document


Dim den As NotesDOMElementNode
Set den = ddn.DocumentElement


今回の DXL であれば次のコードでも取得可能です。

Set den = ddn.LastChild

ただ、DXL の構造上 "document" というノードが必ず”最後”なのかは定かではありません。ですので、DocumentElement プロパティを使うほうが良いと思います。


これで document ノードが NotesDOMElementNode のオブジェクトとして取得できました。これまで利用してきた DXL を使って、このオブジェクトのメソッドやプロパティの使い方を整理しましょう。


属性の取得

まずは、属性(”アットリビュート”と書くと長いのでこう表現します)名がわかっていて、その値を取得する方法です。

NotesDOMElementNode クラスの GetAttributeNode メソッドを使用します。構文は単純で、引数に属性名を指定するだけです。

例えば、次のような感じです。

'document ノードの取得
Dim den As NotesDOMElementNode
Set den = ddn.DocumentElement

Dim dan As NotesDOMAttributeNode
Set dan = den.GetAttributeNode("form")


MsgBox xGetMsgText(dan)

属性に対しても専用ノードのクラス NotesDOMAttributeNode が存在します。そのオブジェクトを取得してプロパティをメッセージボックスで表示しています。

実行すると次のような結果となります。

NodeName に属性名、NodeValue にその値 "zip" がセットされています。


続いて、すべての属性を取得して順に処理する方法です。まず、すべての属性は、Attributes プロパティで取得できます。型は専用のクラス NotesDOMNamedNodeMap となっています。このクラスは、NotesDocumentCollection のような振る舞いをし、オブジェクト内のノード数を取得するプロパティと単一のノードを番号で指定して取得するメソッドがあります。

サンプルは次の通りです。

'document ノードの取得
Dim den As NotesDOMElementNode
Set den = ddn.DocumentElement

Dim dnmap As NotesDOMNamedNodeMap
Dim dan As NotesDOMAttributeNode
Dim i As Integer

Set dnmap = den.Attributes
For i = 1 To dnmap.NumberOfEntries
   Set dan = dnmap.GetItem(i)

   MsgBox xGetMsgText(dan)
Next

GetItem メソッドの戻り値は、NotesDOMNode です。しかし、今回は、属性であることがわかっていますので、NotesDOMAttributeNode のオブジェクト変数に直接代入しています。NotesDOMAttributeNode クラスは、NotesDOMNode を継承しているので型の不一致エラーは発生しません。#5 でも触れた、NotesDOMDocumentNode と NotesDOMNode の関係と同じですね。

実行した結果は以下の通りでした。合計7つの属性が取得できました。

NodeName NodeValue
form zip
xmlns http://www.lotus.com/dxl
version 12.0
confrict false
responce false
replicaid 492589DF0006FF90
maintenanceversion 2.1

DXL に含まれない confrict と responce が出力されました。

DXL 化した文書は、返答文書でなく、競合文書でもありません。値が False なので正しいです。しかし、NotesDXLExporter が出力した DXL(文字列)を NotesDOMParser で解析している”だけ”だとこの結果には納得できません。内部的には、もっと複雑なことをしているのでしょう。

必ずしも DXL 文字列の通りでないこともあるということですね...

前回 DXL ことはじめ 次回

2023/08/20

Notes - Excel 連携:#17)グラフの横軸(日付軸)設定

前回は縦軸の設定だったので、今回は横軸の設定です。

横軸は日付を表します。日付で構成される軸を”日付軸”と呼び、通常の数値の軸とは少し違う部分があります。順に整理していきましょう。

例によって、マクロの記録を有効にして、グラフの横軸を選択します。軸の種類を日付軸に変更し、単位の主を 7 日、表示形式の種類を月日に変更します。  


記録されたマクロを確認すると、以下のようになっています。

   ActiveChart.Axes(xlCategory).Select
   ActiveChart.Axes(xlCategory).CategoryType = xlTimeScale
   ActiveChart.Axes(xlCategory).MajorUnit = 7
   Selection.TickLabels.NumberFormatLocal = "m/d;@"


横軸の設定

まず、横軸のアクセスには、Axes メソッドを使用します。今回は横軸にアクセスするので、引数は、xlCategory となります。

続いて、軸の種類の設定です。CategoryType というプロパティが登場しました。このプロパティは、Axes メソッドの戻り値である Axis オブジェクトに存在します。

Axis オブジェクト (Excel)

Axis.CategoryType プロパティ (Excel)

軸を時間軸に設定するには、このプロパティの設定には、xlTimeScale という定数を使用しています。値を確認して、スクリプトライブラリ ”XlsLib” に登録しておきましょう。

XlCategoryType 列挙 (Excel)


軸のフォーマット設定

続いて、軸に表示する文字(ラベル)の設定です。TickLabels というプロパティにアクセスしていますね。このプロパティは、その名前のオブジェクトを返します。

Axis.TickLabels プロパティ (Excel)

TickLabels オブジェクト (Excel)

TickLabels オブジェクトは、軸ラベルとして表示される文字の集合を表します。Microsft Learn でプロパティを確認すると、Font や Alignment など文字のフォーマットに関するものが存在します。

フォーマットをセットするプロパティは、NumberFormatLocal です。NumberFormat プロパティでもほぼ同様なのですが、日本語環境で使用する限りは、NumberFormatLocal を使用すればよいかと思います。

TickLabels.NumberFormatLocal プロパティ (Excel)


LotusScript の記述

今回の横軸(日付軸)の設定を LotusScript で記述すると、以下のようになります。

   'X 軸の設定
   oChart.Axes(xlCategory).CategoryType = xlTimeScale
   oChart.Axes(xlCategory).MajorUnit = 7
   oChart.Axes(xlCategory).TickLabels.NumberFormatLocal = "m/d;@"

マクロの記録では、軸文字列のフォーマットの設定設定が Selection となっていました。これは、事前に横軸のオブジェクトを選択してたからです。LotusScript では、"oChart.Axes(xlCategory)" と記述し、明確にオブジェクトを指定します。

ここまでのプログラムを実行すると以下のようになります。

初期状態から比べるとずいぶんすっきりしましたね。

前回 Notes - Excel 連携 次回

2023/08/19

HTTP でファイルをダウンロード(いにしえの呪文 - R5 以前)

前回、HTTP でファイルをダウンロード(いにしえの呪文)で、Windows API の一つである、Windows インターネット(WinINet API)を使用する方法をご紹介しました。

そのソースコードでは、API からデータを取得するバッファエリアの確保に Byte 型の配列を使用していました。

   Dim abyRecvData() As Byte


ただ、LotusScript で Byte 型がサポートされたのは Notes 6 以降です。R4.6 や R5.0 では実行できません。そのような環境で実行する方法をご紹介します。

なお、今回の記事は R4.6 利用当時のソースコードをもとに記載しています(R4.5 以前は全く未検証)。テストは 11.0.1 と 12.0.2 で実施しており、当時のバージョンでの再テストは実施できていません。

また、この記事は、API が欲する型が利用できない場合の対策の一例として紹介することは主な目的です。R5 や R4.6 の復権や活用を目的とするものではありません。

あらかじめご了承ください。


対策の例

R4.6 や R5.0 の LotusScript でも使用できる Integer 型の変数を使用する場合を例に記載します。

バッファ配列の型を Integer 型に変更しています。型が変わったので変数名のプリフィックス(接頭文字)を変えています。

LotusScript の場合、Integer 型は 2 バイトです。それを係数として保持させる変数が iByte です。また、バッファ配列の最大インデックス番号を保持する iIdxMax 変数も新設しています。

サンプルコードは次の通りです。変更箇所を赤字で示します。

Public Function HttpDownload(Byval vsUrl As String, Byval vsFileName As String) As Long
   Dim aiRecvData() As Integer
      ・・・
   Dim iByte As Integer
   Dim iIdxMax As Integer


   On Error GoTo ErrProc
      ・・・

   'ファイルのダウンロード
   iByte = 2
   iIdxMax = (WI_READ_BUFFSIZE / iByte) - 1

   Do
      'バッファエリア確保 & 初期化
      ReDim aiRecvData(iIdxMax)

      iReturn = InternetReadFile(lUrlHandle, aiRecvData(0), WI_READ_BUFFSIZE, lReadSize)
      lReadSum = lReadSum + lReadSize

      '終了判断
      If (lReadSize = 0) Or (iReturn = 0) Then Exit Do

      'サイズ調整
      If lReadSize < WI_READ_BUFFSIZE Then
         iIdxMax = Int(lReadSize / iByte) - ((lReadSize Mod iByte) > 0) - 1
         ReDim Preserve aiRecvData(iIdxMax)
      End If

      'ファイルに書き込み
      For i = LBound(aiRecvData) To UBound(aiRecvData)
         Put #iFP, ,aiRecvData(i)
      Next
   Loop
   Close #iFP
       ・・・
End Function

ポイントは、バッファ配列のサイズです。

そもそもバッファ配列は、API の処理でファイルの中身(バイナリデータ)を取得するメモリ領域確保が目的です。定数 WI_READ_BUFFSIZE に定義されている 1024 バイトのエリアがあればいいわけです。

今回、配列の型が Integer となり、2 バイトとなりますので、要素数は半分でいいことになります。

この必要な要素数の計算を行って保持している変数が iIdxMax となります。

ダウンロードファイルの最後の処理、取得したファイルサイズ lReadSize がバッファサイズに満たない場合、バッファサイズを次の通り算出しています。

   iIdxMax = Int(lReadSize / iByte) - ((lReadSize Mod iByte) > 0) - 1

Int 関数は切り捨てとなり、端数があると欠けてしまいます。そこで、割り切れない場合は、要素数を 1 加算させています(赤字部分)。

mod は剰余を求める演算子です。比較演算子は条件が成立すると True = -1 を返します。よって、あまりが発生した場合は -1、発生しない場合は 0 となります。それを減算(-)しているので、あまりが発生した場合だけ 1 加算するというわけですね。


このコードでは、ダウンロードサイズ lReadSize が奇数の場合、1 バイト多く処理されます。よって、完全な処理とは言えないと思います。ただ、これまでこのプログラムを運用していて、ファイルが壊れるなど問題は発生したことはありません。


まとめ

今回のような Windows API 連係の場合、必要なサイズの連続したメモリ領域を渡すことが重要となります。その手段として配列を使用しているということになります。

API コール時に引数で aiRecvData(0) を指定しているのは、そのメモリ領域の先頭アドレス(ポインタ)を渡しているということですね。

このあたりの挙動や仕組みが見えていると、API 利用時の応用が利くかもしれませんね...

HTTP でファイルをダウンロード(いにしえの呪文)

前回、Notes 10.x 以降、LotusScript の NotesHTTPRequest クラスを使用したファイルのダウンロード方法を紹介しました。ただ、このメソッドは、64 KB 以上のファイルがダウンロードできない問題がありました。今回はその回避策の一つを紹介します。

その方法は、Windows API の一つである、Windows インターネット(WinINet API)を使用する方法です。


Windows インターネット(WinINet API)とは?

Microsft Learn によると、Microsoft Windows インターネット (WinINet) アプリケーション プログラミング インターフェイス (API) を使用すると、アプリケーションは FTP や HTTP などの標準インターネット プロトコルにアクセスできるとのことです。

実行の要件としては、Windows NT 4.0 以降、または Windows Me/98/95 が必要となっています。いにしえの呪文である理由がわかりますね。

詳しくは以下のサイトを参照ください。

Windows インターネット


サンプルプログラム(スクリプトライブラリ)

まず、WinINet API をコールする部分をまとめたスクリプトライブラリ lsHttpDownload を作成します。

以下は、(Declarations) に記述する API 関数の定義です。使用している API 関数は4つです。

'WinINet API関数の定義
Declare Function InternetReadFile Lib "wininet.dll" (
      Byval hFile As Long, lpBuffer As Any,
      Byval lNumBytesToRead As Long, lNumberOfBytesRead As Long) As Integer
Declare Function InternetOpen Lib "wininet.dll" Alias "InternetOpenA" (
      Byval sAgent As String, Byval lAccessType As Long,
      Byval sProxyName As String, Byval sProxyBypass As String,
      Byval lFlags As Long) As Long
Declare Function InternetOpenUrl Lib "wininet.dll" Alias "InternetOpenUrlA" (
      Byval hInternetSession As Long, Byval sUrl As String,
      Byval sHeaders As String, Byval lHeadersLength As Long,
      Byval lFlags As Long, Byval lContext As Long) As Long
Declare Function InternetCloseHandle Lib "wininet.dll" (
      Byval hInet As Long) As Integer

続いて、同じく (Declarations) に記述する定数の宣言です。

'User_agent
Private Const WI_AGENT_NAME = "WinINetNotes"

'Proxy設定はデフォルトを使用
Private Const WI_PROXY = ""

'レジストリの設定を使う
Private Const WI_ACCESSTYPE = 0

'INTERNET_FLAG_RELOAD( 0x80000000 )
Private Const WI_FLAGS = &H80000000

'InternetReadFile で一度に読み込むサイズ
Private Const WI_READ_BUFFSIZE = 1024

そして、ダウンロードする関数の本体は次の通りです。

Public Function HttpDownload(Byval vsUrl As String, Byval vsFileName As String) As Long
   Dim abyRecvData() As Byte
   Dim lInetHandle As Long 'インターネットハンドル
   Dim lUrlHandle As Long 'URLハンドル
   Dim iReturn As Integer 'InternetReadFile の戻り値
   Dim lReadSum As Long '受信したデータの総サイズ 兼 バッファへのポインタ
   Dim lReadSize As Long '受信したデータのサイズ(1回分)
   Dim iFP As Integer
   Dim i As Integer

   On Error Goto ErrProc

   '入力チェック
   If vsUrl = "" Then Exit Function
   If vsFileName = "" Then Exit Function
   
   'インターネットハンドルを作成
   lInetHandle = InternetOpen(WI_AGENT_NAME, WI_ACCESSTYPE, WI_PROXY, "", 0)
   If lInetHandle = 0 Then Exit Function

   'URLハンドルを作成
   lUrlHandle = InternetOpenUrl(lInetHandle, vsUrl, "", 0, WI_FLAGS, 0)
   If lUrlHandle = 0 Then Error 9000, "INetハンドルが取得できませんでした。"
   
   '書き込むファイルの準備
   iFP = Freefile()
   Open vsFileName For Binary Access Write As iFP

   'ファイルのダウンロード
   Do
      'バッファエリア確保 & 初期化
      Redim abyRecvData(WI_READ_BUFFSIZE-1)

      iReturn = InternetReadFile(lUrlHandle, abyRecvData(0), WI_READ_BUFFSIZE, lReadSize)
      lReadSum = lReadSum + lReadSize

      '終了判断
      If (lReadSize = 0) Or (iReturn = 0) Then Exit Do

      'サイズ調整
      If lReadSize <  WI_READ_BUFFSIZE Then
         ReDim Preserve abyRecvData(lReadSize - 1)
      End If 

      'ファイルに書き込み
      For i = Lbound(abyRecvData) To Ubound(abyRecvData)
         Put #iFP, ,abyRecvData(i)
      Next
   Loop
   Close #iFP

ExitProc:
   If lUrlHandle <> 0 Then InternetCloseHandle(lUrlHandle)
   If lInetHandle <> 0 Then InternetCloseHandle(lInetHandle)

   HttpDownload = lReadSum
Exit Function

ErrProc:
   Print Err & ":" & Error$ & " (line:" & Erl & ")"
   Resume ExitProc
End Function


郵便番号データのダウンロード

それでは、このライブラリを使用したメインルーチンです。前回ダウンロードでエラーが出た全国版のデータでテストします。

Option Declare

Use "lsHttpDownload"

Sub Initialize
   Dim sFileName As String
   Dim sURL As String

   'パラメータの設定
   sFileName = "ken_all.zip"
   sURL = "https://www.post.japanpost.jp/zipcode/dl/oogaki/zip/" & sFileName

   'ダウンロードの実行
   Call HttpDownload(sURL, "c:\lotus\" & sFileName)
End Sub

実行すると、次のようにファイルが作成されます。64 KB を越えるファイルですが、正常にダウンロードできており、zip 圧縮の回答できました。


まとめ

Windows インターネット(WinINet API)を使用する方法は、64 KB 以上のファイルがダウンロードできない問題の暫定的な解決策として紹介しました。

今回の方法では、LotusScript から直接 API をコールしました。よって、ロケーションで行うプロキシ設定など、ノーツクライアントのインターネットの接続設定をすっ飛ばして接続します。ブラウザからは接続できるけど、Notes 経由では接続できない場合でも利用できます。

そもそも、このような状況に陥ることが問題ではあるのですが...

2023/08/18

HTTP でファイルをダウンロード

Notes 10.x 以降、LotusScript の NotesHTTPRequest クラスが提供されるようになり、LotusScript からWEB の世界にアクセスしやすくなりました。このメソッドを使用するとインターネット上の様々なサービスにアクセスでき、様々なリソースを取得できます。

NotesHTTPRequest (LotusScript)


このメソッドが提供されて、最初に利用したのが、日本郵便が毎月公開している郵便番号データのダウンロードです。以前の Project KEEP 体験 の連載で、事例として利用した『郵便番号マスタ』の元データとなる CSV ファイルです。

郵便番号データダウンロード


日本郵便のサイトでは、全国一括と県別の全件データがあります。また、全国に限っては差分データも提供されています。このデータを nsf に取り込み、他のノーツアプリケーションから利用しています。

社内にデータがあると何かと便利なのですが、毎月データを更新するという処理が発生します。その際に最初に発生するのが CSV ファイルのダウンロードです。


郵便番号データのダウンロード

ということで、NotesHTTPRequest クラスを利用したダウンロードプログラムを作成したので紹介します。サンプルプログラムなので、URL や ファイルの保存パスなどはハードコーディングとしています。

Option Declare

Sub Initialize
   Dim ns As New NotesSession
   Dim nhttp As NotesHTTPRequest
   Dim nst As NotesStream
   Dim sFileName As String
   Dim sURL As String
   Dim vReturn As Variant

   'パラメータの設定
   sFileName = "ken_all.zip"
   sURL = "https://www.post.japanpost.jp/zipcode/dl/oogaki/zip/" & sFileName

   'HTTP リクエストの実行
   Set nhttp = ns.CreateHTTPRequest()
   nhttp.TimeOutSec = 120
   vReturn = nhttp.Get(sURL)

   'ファイルの保存
   Set nst = ns.CreateStream
   Call nst.Open("c:\lotus\" & sFileName, "binary")
   Call nst.write(vReturn)
   Call nst.close()
End Sub

LotusScript エージェントを作成して実行すると、鳥取県の郵便番号データが PC 内に保存されます。


ただ、このプログラムは1点問題があります。それは、大きなファイルがダウンロードできないことです。例えば、ダウンロードするファイルを全国版に変更します。

   sFileName = "ken_all.zip"

実行するとエラーが発生して、ダウンロードできません。


このエラーについては、すでに技術情報が出ていて、ダウンロードするファイルサイズが大きすぎることが原因のようです。

NotesHTTPRequest でサイズが 64KB を超えるファイルをダウンロードすることができない


技術情報内には Java を使った回避策が提案されていますが、サンプルコードは掲載されていませんね。私は、Java 使いではないので、LotusScript 登場の頃から利用していた”いにしえの呪文”に戻しました。


今の時代 64 KB を大きすぎるというのは憚られますね。昭和じゃないんだから... フロッピーディスクにもいっぱい入るサイズですよね(笑)

そんな背景から、私はこの問題は早々に改善されると思っています。すでに数年待ってるけど...


ということで、いにしえの呪文の消費期限はもう切れそうです。今のうちなので、次回、その方法を紹介します...

2023/08/16

ノーツで QR コード:#15)自由研究まとめ

今回のシリーズでは、QR コードについて、1. 仕組みを理解すること、2.ノーツで作成できることを目的に調査した結果をレポートしました。

仕組みの理解は残念ながら断念しましたが、ノーツで作成については何とか作成できました。ただ、人の手を借りた関係で、多くの内容が、QR コードではなく、VBA から LotusScript の移植話となってしまいました。VBA からの移植がどれほど必要なのかは不明ですが、何かの参考になれば幸いです。

”QR コードの仕組み” に期待された方、申し訳ありません。私の力不足です。ご容赦ください...


Excel VBA →  LotusScript 移植 まとめ

シリーズ後半で、Excel VBA のライブラリを LotusScript に移植する作業を紹介しました。LotusScript は VBA 互換とは言いますが実際に移植しようとすると様々な作業が発生しました。長くなったので最後に整理しておきます。


まず、移植直後の文法エラーです。

VBA のコードを LotusScript のライブラリに貼り付け、コンパイルが通るようになるまでに実施した対応です。Sheet や Range など、Excel 固有のオブジェクトが直接使えないのは当然として、次のような対応が必要でした。

  • 引数の Optional が使えない
  • 引数の ByRef が使えない
  • 予約語と変数名と衝突
  • Debug オブジェクトがない
  • IsMissing、IIf、Array 関数がない
  • 名称の違う関数がある(AscW )
  • 配列の次元変更できない
  • 定数がない(vbNewLine)


続いて、組み込み時にもいくつか問題が発生しました。

  • TypeName の戻り値に差がある
  • VBA のソースコードをコピペするとデバッガの挙動がおかしい(確証なし)


今回、移植元とした VBA のライブラリだけでこれだけの対応が必要でした。非互換な部分はほかにもあるかと思います。類似の作業を行う際には、ご注意ください。


少しだけ脱線

今回は、VBA のライブラリを LotusScript に移植しました。移植という前提上、既存のコードを残しつつ必要な改造を加える作業をしました。

ただ、途中、QR コードしか使用しないことから不要なコードを掃除したり、大規模な改造を加えました。そこまで修正するならということで、途中で気になっていた点を再検討しました。それは、#12#13 で触れた、エンコードした文字列を QR コードを表す Boolean 型の2次元配列に変換した部分です(ソースコードは #13 を参照)。

もともとの仕様のように Shape オブジェクトで作画しないので、隣のマスと連結して2マス同時に処理する必要がありません。それに伴い、Select Case 文を使い 16 通りの作画パターンを用意しているのも、移植後はコードが複雑に見えます。

そこで、この部分のプログラムを書き直してみました。

Public Sub bc_2Dms_New(xBC As String, rabBC() As Boolean)
   Dim vTmp As Variant
   Dim x As Integer, y As Integer    'QR コード配列の座標
   Dim xx As Integer, yy As Integer    'エンコード文字列の座標
   Dim b As Integer
   Dim sBin As String
  
   'QR コードのサイズ
   vTmp = Split(Trim(xBC), Chr(13) & Chr(10))
   x = Len(vTmp(0))*2 - 1
   y = UBound(vTmp)*2 - 1
   ReDim rabBC(y, x)
  
   'QR コード生成
   For yy = 0 To UBound(vTmp)-1
      y = yy * 2
      For xx = 1 To Len(vTmp(yy))
         x = (xx - 1) * 2
         sBin = Mid(vTmp(yy), xx, 1)
         sBin = Bin(Asc(sBin) - 97)
         sBin = Right("000" & sBin, 4)
         For b = 0 To 3
            If Mid(sBin, 4 - b, 1) = "1" Then
               rabBC(x + (b Mod 2), y - (b >= 2)) = True
            End If
         Next
      Next
   Next
End Sub

基本的な処理の流れは、元のコードと同じなのですが、次のような点を意識して処理をわかりやすくしたつもりです。

  • エンコード文字列を区切り文字で分離し、その数を行(YY)、文字数を列(XX)として、二次元のループで1文字ずつ処理
  • QR コード配列の基準座標(X,Y)はエンコードの文字列の座標(XX, YY)から算出
  • 演算処理や 2 進数を活用し処理を効率化
  • 処理の分岐(Select Case 文)を排除
  • drw 関数を削除(メインルーチンに統合)


ポイントは、QR コード配列を更新する個所です。

4 桁の 2 進数を右から順に処理するためループ(b)を使用していますが、この値を使用して処理する桁(ビット)と配列の座標を決定しています。

rabBC(x + (b Mod 2), y - (b >= 2)) = True

"mod" は剰余を求める演算子で、">=" は比較演算子です。比較演算子は結果を Ture か False で返しますが、True = -1、False = 0 と定義されています。

よって、このループ b においてこれらの値は次のようになります。

b + (b mod 2) - (b >= 2)
0 0 0
1 1 0
2 0 1
3 1 1

これを利用して、処理している桁(ビット)を座標に変換しています。


少々特殊な表現にはなりますが、このような演算を利用すると、プログラムがシンプルに記述できます。


最後に

今回事例で紹介した、QR コードをフォームに文字列で表示する方法なのですが、認識率が少し悪いようです(QR コードリーダによるのかもしれませんが...)。

たぶん、文字間隔が広いため、1マス分のピッチが正しく判定されないのではないかと想定しています。実際に運用する際には、簡易的な方法ではなく、しっかり作画したほうがよさそうです。

前回 ノーツで QR コード

ノーツで QR コード:#14)ライブラリの組み込みと QR コードの表示

Excel VBA で作成された QR コード生成ライブラリをノーツに移植する作業、延々と実施してきましたが、いよいよ大詰めです。

今回は移植できたライブラリを使用して、フォームに QR コードを表示します。


フォームの作成

まずは、QR コードをフォームに表示する仕様についてです。今回はライブラリの検証が目的なので、黒のマスを”■”、白のマスをスペースで表示するだけの単純な対応とします。

新規でフォーム ”QR” を作成し、以下のように作成します。

フィールドはテキストフィールドが2つだけです。ポイントは QR コードを表示するフィールド QRcode です。複数値を有効に設定し、区切り文字を”改行”に設定します。

もう一点が、QR コードの表示が崩れないよう、等幅フォントを指定します。今回は、MS ゴシックを選択しました。


エージェントの作成

続いて、QR コードを作成する処理をエージェントで作成します。

新規で、LotusScript のエージェント ”CreateQRcode” を作成し、移植したライブラリを呼び出します。メインルーチンの処理は次の通りです。

  • QR コード化する文字列を入力
  • エンコード文字列に変換(EncodeBarcode)
  • Boolean 型の2次元配列に QR コードを展開(bc_2Dms)
  • フォーム QR を作成し、QR コードを作画(CreateQRcodeDoc)

サンプルコードは次の通りです。

Option Declare
Use "lsQRcode"

Sub Initialize
   Dim sSrc As String
   Dim sEnc As String
   Dim iLv As Integer
   Dim abQR() As Boolean
   Dim asQR() As String

   iLv = 3 '誤り訂正レベル
   sSrc = InputBox("QRコードに変換する文字列を入力してください。", "QRコード作成")
   If MsgBox("『" & sSrc & "』の QR コードを作成しますか?", 36, "") = 6 Then
      sEnc = EncodeBarcode(sSrc, iLv)
      Call bc_2Dms(sEnc, abQR)
      Call CreateQRcodeDoc(sSrc, abQR)
   End If
End Sub


QR コードを作画する関数 CreateQRcodeDoc は、今回新規に作成し、エージェント内に配置しました。

QR コードの1行分を ”■” とスペースに変換しながら1つの文字列にします。それを行数分の配列に変換した後、フィールドにセットし、文書として保存しています。

Sub CreateQRcodeDoc(vsSrc As String, vabQR() As Boolean)
   Dim ns As New NotesSession
   Dim ndb As NotesDatabase
   Dim nd As NotesDocument
   Dim x As Integer
   Dim y As Integer
   Dim sTmp As String

   'QR コードを文字列に変換
   ReDim asQR(UBound(vabQR, 2))
   For y = 0 To UBound(vabQR, 2)
      For x = 0 To UBound(vabQR, 1)
         If vabQR(x, y) = True Then
            sTmp = "■"
         Else
            sTmp = " "
         End If
         asQR(y) = asQR(y) & sTmp
      Next
   Next

   '文書に保存
   Set ndb = ns.CurrentDatabase
   Set nd = ndb.CreateDocument()
   nd.Form = "QR"
   nd.StrSrc = vsSrc
   nd.QRcode = asQR
   Call nd.Save(True, False)
End Sub


エージェントを実行して、QR コードに変換したい文字列を入力すると、文書が作成されます。文書を開くと、QR コードが表示されます。

前回 ノーツで QR コード 次回

ノーツで QR コード:#13)LotusScript 版ライブラリ 完成

前回、エンコード文字列から QR コードを作成する方法がわかりました。今回は、これを LotusScript に移植します。


QR コードのデータ構造

移植にあたり LotusScript 内で QR コードをどのように表現するか仕様を決定します。

ノーツで QR コードを表示する処理を考えた場合、フォームに表示する方法、HTML に変換して WEB ブラウザで表示する方法、Java や JavaScript などのライブラリに渡す方法などが考えられます。また、本末転倒ですが、Excel に連携して作画することも考えられます。

さまざまな、手段が考えられるからこそ、データ構造はシンプルが良いと考えました。そこで、Boolean 型の2次元配列に True で黒(塗りつぶし)、False で白とします。


QR コードのサイズ

まずは、Boolean 型の2次元配列のサイズの決定です。

エンコード文字列の1文字が、 2 × 2 マスを表していました。そして、改行区切りので構成されていました。この条件で、2次元配列のサイズを決定すると次の通りとなります。

'QR コードのサイズ
Dim abBC() As Boolean
Dim vTmp As Variant
vTmp = Split(p, Chr(13) & Chr(10))
x = Len(vTmp(0))*2 - 1
y = UBound(vTmp)*2 - 1
ReDim abBC(x, y)

横幅 x は、エンコード文字列1行分の文字数 x 2 となるのですが、配列の要素番号が 0 始まりなので -1 しています。

縦の行数 y は、改行区切りの行数で算出します。Split で行を分離して配列に格納します。行数のカウントに Ubound を使用しています。分離した配列の要素が 0 始まりのため、1行少なくなりますが、エンコード文字列の最後にも改行があり、1行多く判定されます。差し引き 0 なので、結果的に横幅と同等の計算式となっています。


bc_2Dms サブルーチン の作成

続いて、前回 Excel 上で掃除した bc_2Dms 関数をノーツに移植します。

移植にあたり、次の作業を行います。

  • サブルーチンはパブリックとして宣言
  • 引数 xNam は未使用なので削除
  • Boolean 型の2次元配列を返すための引数を追加
  • 先の「QR コードのサイズ」のプログラムを挿入(配列は引数を利用)
  • 変数 m を削除し 1 に変更、変数 dm を削除し 2 に変更(基準を 2.5 → 1 )

作業が終わると bc_2Dms 関数は、次のようになります。

Public sub bc_2Dms(xBC As String, rabBC() as BooleanAs Variant
   Dim x, y As Double
   Dim b%, n%, w%, p$

   p = Trim(xBC)
   b = Len(p)
   
   'QR コードのサイズ
   Dim vTmp As Variant
                    ・・・
   ReDim rabBC(x, y)


   'QR コード生成
   x = 0#
   y = 0#

   For n = 1 To b
      w = AscL(Mid(p, n, 1)) Mod 256
      If w = 10 Then
         y = y + 2
         x = 0#
      ElseIf (w >= 97 And w <= 112) Then
         w = w - 97

         Select Case w
         Case 1: Call drw(x, y, 1, 1, rabBC)
         Case 2: Call drw(x + 1, y, 1, 1, rabBC)
         Case 3: Call drw(x, y, 2, 1, rabBC)
                    ・・・
         Case 14: Call drw(x + 1, y, 1, 1, rabBC)
               Call drw(x, y + 1, 2, 1, rabBC)
         Case 15: Call drw(x, y, 2, 2, rabBC)
         End Select

         x = x + 2
      End If
   Next n
End Sub


drw サブルーチンの作成

最後に drw サブルーチンを作成します。移植にあたり、次の点を調整します。

  • Boolean 型の2次元配列を返すための引数を追加
  • Excel の行/列と違い配列は 0 始まり
  • 基準を 2.5 → 1 に変更
  • 上記により座標の演算が不要になるので、引数をそのまま使用
  • Sheet に 1 をセットする代わりに配列に True をセット

調整終了後の drw 関数は、次のようになります。

Sub drw(ByVal xc As Integer, ByVal yr As Integer, ByVal w As Integer, ByVal h As Integer, rabBC() As Boolean)
   rabBC(xc, yr) = True
   If w > 1 Then
      rabBC(xc + 1, yr) = True
   End If
   If h > 1 Then
      rabBC(xc, yr + 1) = True
   End If
   If w > 1 And h > 1 Then
      rabBC(xc + 1, yr + 1) = True
   End If
End Sub


これで、ライブラリは完成です。

次回は、いよいよ、ノーツで、LotusScript だけで、QR コードを表示します。

前回 ノーツで QR コード 次回

ノーツで QR コード:#12)エンコード文字列と QR コード

前回の調査で、これまで対応していたメインルーチンの EncodeBarcode 関数は、変換する文字列をエンコードしているだけで、QR コードとして表示するためには、もうひと手間必要であることがわかりました。

そこで、以前メインルーチンの掃除で 作画部分と判断し削除した bc_2Dms 関数を調査します。

なお、今回の作業はすべて Excel 状の作業となります。


作画部分に関して参照元のサイトに次の情報がありました。

追記:シェイプではなく、セルの色塗り方式に変更

今回参照させていただいた、サンプルコードはすでに、この対応が済んだものです。ただ、 サイト内に記載がありますが、bc_2Dms 関数には元となった Shape オブジェクトを操作するコードが残っています。

まずは、この部分を削除して、全体を把握しやすくします。Excel VBA のエディタを開き、コードをすっきりさせます。

Sub bc_2Dms(xBC As String, Optional xNam As String)
   Dim x, y, m, dm As Double
   Dim b%, n%, w%, p$
   p = Trim(xBC)
   b = Len(p)
   m = 2.5
   dm = m * 2#
   x = 0#
   y = 0#

   For n = 1 To b
      w = AscL(Mid(p, n, 1)) Mod 256
      If w = 10 Then
         y = y + dm
         x = 0#
      ElseIf (w >= 97 And w <= 112) Then
         w = w - 97

         Select Case w
         Case 1: Call drw(x, y, m, m)
         Case 2: Call drw(x + m, y, m, m)
                ・・・
         Case 15: Call drw(x, y, dm, dm)
         End Select

         x = x + dm
      End If
   Next n
End Sub

これで処理の流れが見ますね。細かな点はさておき、次のようなことがわかります。

  • For ループでエンコード文字列を1文字ずつ取得して、単純に処理
  • エンコード文字列は、文字コード 10(LF)と 97(a)から 112(p)で構成
  • 文字コード 16 種ごとに作画関数 drw を実行
  • 変数 x, y は座標、m, dm は作画に関連する値である

ここまで理解できた時点で、Excel をデバッグモードで実行します。bc_2Dms 関数のループ1回ごとに処理を止め、作画を確認します。

QR コードを 2 x 2 に分割し、左上から右に順に、4 マスごとに作画しています。これで、エンコード文字列の1文字が、QR コードの 4 マスにあたることがわかります。また、Select Case の分岐が 16 通り(= 4 ビット)ですべてのパターンを網羅していることもわかります。


作画関数

続いて、作画関数 drw の確認です。次のようなコードになっています。

Sub drw(a, b, c, d)
   Dim xc As Integer
   Dim yr As Integer
   Dim w As Integer
   Dim h As Integer
   xc = CInt((a / 2.5) + 1)
   yr = CInt((b / 2.5) + 1)
   w = CInt((c / 2.5) + 0)
   h = CInt((d / 2.5) + 0)
   Sheets("QR").Cells(yr, xc).Value = 1
   'w
   If w > 1 Then
      Sheets("QR").Cells(yr, xc + 1).Value = 1
   End If
   'h
   If h > 1 Then
      Sheets("QR").Cells(yr + 1, xc).Value = 1
   End If

   If w > 1 And h > 1 Then
      Sheets("QR").Cells(yr + 1, xc + 1).Value = 1
   End If
End Sub

yr と xc のセルを起点に 4 マス分のセルに必要に応じて 1 をセットしているだけです。気になるのは、2.5 で割る処理です。

元となった VBA のコードの bc_2Dms 関数を確認すると、次のような、コメントアウトされたコードが見つかります。

  ・・・
 m = 2.5
 dm = m * 2#
  ・・・
 ’ Set xShape = .AddShape(msoShapeRectangle, x, y + m, dm, m)
  ・・・

Shape を追加するときの座標 x, y の操作やサイズとして使用されているので、この 2.5 は Shape オブジェクトの1マス分の基準サイズであったことがわかります。

drw 関数は、AddShape と同等のインターフェースにして、呼び出し元関数の影響を最小限にするためこのような仕様にしたと考えられます。デバッグしただけだと意味不明な処理ですが、目的が見えてくると納得できますね。

前回 ノーツで QR コード 次回

ノーツで QR コード:#11)エラーの対応と次の課題

前回発生したエラーの調査を再開します。

エラーとして表示しているメッセージボックスは、ライブラリに2か所存在しますが、関数の呼び出し関係から、次の行で表示していることがわかります。

このメッセージが表示されるには前段の If 文を確認する必要があります。

そこで、メッセージボックスを次のように修正して、If 文が False となった原因を確認しやすく調整します。

MsgBox "Integer()" & Chr(13) & TypeName(pa), 16, "Unknown type"

実行すると、以下のように表示されました。原因がよくわかりますね。


LotusScript の TypeName は、

  • 型名は大文字で返す
  • カッコの中にスペースが入る

となるようです。Excel VBA とこんな違いがあるんですね。驚きました...

判定式を変更してこれに対応します。

ElseIf InStr(UCase("Integer( ),Byte( ),Long( ),Variant( )"), UCase(TypeName(pa))) > 0 Then


ライブラリ内を "TypeName" で検索すると、qr_mask 関数の中にも同様のコードが見つかりますので、こちらも修正します。こういった移植作業では、問題を発見した時点で、すぐに検索をかけ、すべての箇所を修正するようにしましょう。時間がたつと、修正したつもりになってしまい、発見しづらくなります。


検証の再開

改めてテストします。デバッガを使ってメインルーチンを抜けたところで実行を停止させて、戻り値 sBC を確認します。

デバッガの変数のタブには、各変数の値が一覧表示されます。確認したい変数をクリックすると、新規の値欄にその変数の値が表示され、ここから値が取得できます。

ところで、この欄の右側にスクロールバーのようなアイコンが表示されています。複数行の値の場合、このような表示になるようです。こんな機能は知りませんでしたが、ここまで対応するならフィールドの高さを可変にしてほしくなりますね(笑)

Ctrl + A で全体を選択し、メモ帳にコピーします。すると次のような値が取得できました。ダブルクォーテーションが最初と最後にあるので、この関数の戻り値は、改行で区切られた文字列を返すようですね。

"hddfoaphehddf
fpffoapnafpff
fdbfoancefdbf
dddbjbnfbdddb
ddbdlafcgckoa
klojpapmjpdna
fgnbganiadgdf
fhjlpfpbjhine
bdcbgahlhdngb
hddflenpfbnhe
fpffnahahhbje
fdbfpijoemhef
dddbbdcabbcdb
"


戻り値の謎

メインルーチンの EncodeBarcode から戻りが取得できるようになりました。ただ、この戻り値が、QR コードか、それに近しいものと想定していたのですが、少し違うようです。

関数名の通り文字列をエンコードしているだけで、QR コード化する処理は別に存在するということになります。次回は、この値と QR コードの関係を謎解きます。

前回 ノーツで QR コード 次回