2024/09/15

Notes - Excel 連携:#39)名前アイコン生成 ③

名前を表示したアイコン作成の 3 回目です。今回は残りの名前(テキスト)を表示する関数 xAddText についてまとめます。


名前(テキスト)の作成

先に作成した関数を紹介します。細かな調整や新たなオブジェクト(プロパティ)が登場していますので順に解説します。

Function xAddText(voIcon As Variant, ByVal vdPoint As Double, ByVal vsName As String)
   Dim oText As Variant
   Dim dFont As Double
   Dim dMarginTop As Double
   Dim dMarginLR As Double
   Dim dSpace As Double

   '設定材料算出
   dFont = vdPoint / 3   '2行表示を想定し高さの 1/3
   dSpace = 0.6   '行間
   dMarginTop = dFont * (1 - dSpace)   '行間を詰めた分だけ上にずれる
   dMarginLR = vdPoint / 8   '少し詰めないと 3 文字入る

   'ラベル作成(Shape オブジェクト)
   Set oText = voIcon.Chart.Shapes.AddLabel(msoTextOrientationHorizontal, 0, 0, vdPoint, vdPoint)
   With oText.TextFrame2
      'ラベルの設定
      .VerticalAnchor = msoAnchorMiddle   '垂直に中央揃え
      'マージン
      .MarginLeft = dMarginLR
      .MarginRight = dMarginLR
      .MarginTop = dMarginTop
      .MarginBottom = 0
      'テキストの設定
      .TextRange.Font.Size = dFont   'フォントサイズ
      .TextRange.ParagraphFormat.Alignment = msoAlignCenter   '水平に中央揃え
      .TextRange.ParagraphFormat.SpaceWithin = dSpace   '行間
      'テキスト
      .TextRange.Characters.Text = vsName
      .TextRange.Characters.Font.Fill.ForeColor.RGB = RGB(255, 255, 255)
   End With
End Function


フォントサイズ

今回のアイコンでは、最大 2 行の表示を想定しています。アイコンは円でその中に入れるためには高さの 1/3 をフォントサイズとしました。引数で渡されるアイコンのサイズの単位は Point なので 3 で割るだけでフォントサイズとして使用できます。


テキストの配置

表示するテキストは縦横 2 文字ずつを想定しています。標準の設定では行間が広いので行間を詰めます。次の図は、左が標準の設定で、右が今回採用した 0.6 倍 の設定です。

以下は今回採用する設定(右側の設定)


マージンの設定

行間を詰めた場合、文字はその分上にずれます。これは垂直方向で中央ぞろえした場合でも同じです(要は中央揃えにならない)。そこで、行間のを詰めた分、上のマージンで補います。左がマージン 0、右が行間を詰めた 40%(1-0.6)をフォントサイズに合わせてマージンにセットした状態です。

また、左右のマージンが 0 だと 3 文字入ってしまうため、左右にマージンを設定します。

計算上は 1/3 をマージンにすればいいのですが、幅が広い文字があるあk農政を想定し 1/4 とします。文字は水平方向で中央揃えとするので、左右同じ幅、1/8 を左右それぞれのマージンとします。


TextFrame2 オブジェクト

AddLabel メソッドでの戻り値は、これまでの AddChart2、AddShape メソッドと同じく、Shape オブジェクトです。実際のラベルのオブジェクトにアクセスするためには TextFrame2 プロパティ を使用します。

TextFrame2 オブジェクトのプロパティにマージンの設定と垂直揃えを設定する VerticalAnchor プロパティ が存在します。

セットできる値は MsoVerticalAnchor 列挙 に定義されています。思いのほか細かな設定がありますが、今回は 3 の msoAnchorMiddle を使用して、垂直方向に中央揃えさせます。


TextRange2 オブジェクト

フォントサイズや水平揃え、表示する文字の設定など上記以外の設定は、TextRange2 オブジェクトから行います。オブジェクトは、TextFrame2 オブジェクトのプロパティから取得します。なぜか TextRange プロパティ から TextRange2 オブジェクト が取得できます。ややこしいですね...

水平揃えは、ParagraphFormat プロパティ 経由で Alignment プロパティでセットするのですが、Microsoft Learn のリンクが途切れています。調べる限り ParagraphFormat プロパティは ParagraphFormat2 オブジェクト のようです。プロパティを確認すると  Alignment プロパティ があり、設定値は MsoParagraphAlignment 列挙 に定義されています。


Characters プロパティ

TextRange2 オブジェクト の Characters プロパティ の値は TextRange2 です。今回のコードだけでは判別しかねるのですが、これはテキスト内の一部分に対して文字色を変えるなど部分文字列に対応していからこのような構造になっていると想定します。

Excel のマクロの記録でベースとなる VBA を取得したのですが、その際に Characters プロパティを使用したコードが出力されました。今回は、その通り LotusScript に変換したのですが、構造を理解していると以下のように記述することも可能です。

         ・・・
      .TextRange.ParagraphFormat.SpaceWithin = dSpace '行間

      'テキスト
      .TextRange.Text = vsName
      .TextRange.Font.Fill.ForeColor.RGB = RGB(255, 255, 255)

      '.TextRange.Characters.Text = vsName
      '.TextRange.Characters.Font.Fill.ForeColor.RGB = RGB(255, 255, 255)

   End With
End Function


ライブラリの更新

ここまでで、プロパティに設定する定数がいくつかありましたので、ライブラリに追加します。lsXls ライブラリを開き、(Declarations) に以下を追加します。

'MsoVerticalAnchor 列挙 (Office)
Public Const msoAnchorMiddle = 3   '垂直方向に中央揃え

'MsoParagraphAlignment 列挙 (Office)
Public Const msoAlignCenter = 2   '中央揃え

なお、MsoParagraphAlignment 列挙 のページでは、中央寄せは 1 の msoAlignCenter と記載されています。そのまま実行すると左寄せになりました。実際には、2 を設定すると中央寄せとなりました。これが、Microsoft Learn の間違いなのかは判別できませんが、上記コードでは 2 で設定します。


まとめ

プログラムが完成したら、エージェントを実行してみましょう。指定したフォルダにアイコンファイルが作成されます。

アイコンのサイズは引数化しているので、お好みのサイズで作成してください。現時点では色合いは関数内で埋め込んでいるため固定ですが、引数に追加すれば好みの色で作成できますね。

今回紹介したプログラムでは、オートシェイプを利用しました。オートシェイプには多種多様な部品があります。今回の技を応用すればアイコンだけでなく、バナーなどさまざまな画像が作成できますね。

最後に、今回登場したオブジェクトの関係を整理しておきます。


前回 Notes - Excel 連携


2024/09/14

Notes - Excel 連携:#38)名前アイコン生成 ②

名前を表示したアイコン作成の 2 回目です。今回は前回説明できなかった xMakeIcon 関数についてまとめます。


メインルーチン

以下がアイコン作成のメインルーチンです。Worksheet オブジェクトとアイコンのサイズ(ピクセル)、表示する名前が引数です。

Function xMakeIcon(voSheet As Variant, ByVal viPixcel As Integer, ByVal vsName As String) As Variant
   Dim oIcon As Variant
   Dim dPoint As Double

   'ピクセル -> ポイント
   dPoint = PixcelToPoint(viPixcel-1)

   '画像にするための Chart オブジェクト作成
   Set oIcon = xAddChart(voSheet, dPoint)

   '背景(楕円)
   Call xAddOval(oIcon, dPoint)

   '名前(テキスト)
   Call xAddText(oIcon, dPoint, vsName)

   Set xMakeIcon = oIcon
End Function

処理の流れは、

  1. 画像として保存するため Chart オブジェクトを作成
  2. アイコンの背景となる円を作成
  3. 名前(テキスト)を表示

となっており、各処理はサブ関数化しています(詳細は後述)。戻り値は作成したアイコンのオブジェクトです。

なお、Excel でオートシェイプなどを作る際は、単位がポイントとなるので、初めにライブラリの関数を使用して変換しています。

   dPoint = PixcelToPoint(viPixcel-1)

ちなみに端数処理の関係かはわかりませんが 1 ピクセル大きく出力されたので、1 引いています。


Chart オブジェクト作成

xAddChart 関数については、『#36)帳票を画像で保存』とほぼ同じです。ただ、今回は正方形であることからサイズの引数が 1 つになっています。また、画像の背景を透明にするため、塗りつぶしを非表示にしています。

Function xAddChart(voSheet As Variant, ByVal vdPoint As Double) As Variant
   Dim oShape As Variant

   'Chart を作成(Shape オブジェクト)
   Set oShape = voSheet.Shapes.AddChart2( , , , , vdPoint, vdPoint)
   With oShape
      '背景は透明
      .Fill.Visible = False
      .Line.Visible = False

      'グラフのオブジェクトを削除
      On Error Resume Next
      .Chart.ChartTitle.Delete
      .Chart.Legend.Delete
      .Chart.Axes(xlCategory).Delete
      .Chart.Axes(xlValue).Delete
      .Chart.Axes(xlValue).MajorGridlines.Delete
   End With

   Set xAddChart = oShape
End Function


背景(楕円)の作成

xAddOval 関数で、背景となる円を追加しています。前回紹介した AddShape メソッドを使用しています。作成するオートシェイプは  msoShapeOval、楕円です。

Function xAddOval(voIcon As Variant, ByVal vdPoint As Double)
   Dim oOval As Variant

   '楕円の作成(Shape オブジェクト)
   Set oOval = voIcon.Chart.Shapes.AddShape(msoShapeOval, 0, 0, vdPoint, vdPoint)
   With oOval
      .Line.Visible = False
      '背景色の設定
      .Fill.Visible = True
      .Fill.ForeColor.RGB = RGB(128, 128, 255)
   End With
End Function


オブジェクト構造

上記の 2 つの関数を紹介しました。それぞれのオブジェクト作成において、違和感は感じなかったでしょうか?

   Set oShape = voSheet.Shapes.AddChart2( , , , , vdPoint, vdPoint)

   Set oOval = voIcon.Chart.Shapes.AddShape(msoShapeOval, 0, 0, vdPoint, vdPoint)

そうなんです。どちらも、Shapes オブジェクトのメソッドを使用していますが、その親となるオブジェクトが違います。AddChart2 は Worksheet、AddShape は AddChart2 で作成された Chart オブジェクトとなっています。

図式化すると次のような感じとなります。

Chart オブジェクトにも Shapes オブジェクトが存在しており、そこから AddShape を実行しています。これにより追加したオートシェイプは Chart オブジェクトの上に作成されることとなります。

次回説明する名前(テキスト)も同様の構成となります。オブジェクトの配置に着目して整理すると次のようなになります。

構造が複雑になりますので、だらだらしたコードを書くと現在位置を見失いがちになります。適切に関数化したり、変数名を明確に付ける、コメントをしっかり記述するなど工夫をしましょう。後日見直したときに混乱する元となります。


続きは次回

少し長くなってきたので、残りの名前(テキスト)を作成する関数 xAddText は次回とします。


前回 Notes - Excel 連携 次回


2024/09/13

Notes - Excel 連携:#37)名前アイコン生成 ①

ノーツでは、さまざまなシーンで画像が利用できます。設計では、アプリケーションのアイコンに始まり、ナビゲータ、アウトラインやアクションボタンのアイコン、フォームや表の背景画像など、文書ではリッチテキストに貼り付けたり添付したり、多岐にわたります。

ただ、UI からは画像を利用できるのですが、プログラムからの利用は不得手としています。その補完に Excel を使用する事例を紹介します。今回題材にするのは次のような画像の生成です。最近よく見かける名前を表示したアイコンですね。

せっかく手作りするのですから、4 文字まで対応したいと思います。これなら、薬師丸さんや小比類巻さんも安心ですね。


画像の作成方法

アイコンの作成法は次の通りです。

  1. 正方形のグラフ(Chart)をシートに作成する
  2. そのグラフに合わせた円をを描き、色を付ける
  3. 円の上に名前を上下左右でセンタリングして表示

アイコンのイメージができあがったら、前回 の方法を追加って Chart を画像として保存します。Chart 上に配置したオブジェクトを含めて画像ファイルに保存してくれます。


具体的な作業に入る前に、必要な VBA の主な機能を確認しましょう。


円の作画

グラフ(Chart オブジェクト)を作成するには Shapes オブジェクトの AddChart2 メソッドを使用しました。円の追加も同様に Shapes オブジェクトから行います。メソッドは AddShape です。

Shapes.AddShape メソッド (Excel)

このメソッドは各種オートシェイプを作成することができ、1 つ目の引数でその種類を指定します。指定できる値は MsoAutoShapeType 列挙に定義されています。

MsoAutoShapeType 列挙 (Office)

たくさんの定義がありますが、今回使用するのは 9 の msoShapeOval、楕円となります。


テキストの追加

テキストの追加も Shapes オブジェクトから行います。メソッドは AddLabel です。

Shapes.AddLabel メソッド (Excel)

このメソッドの最初の引数は、ラベル内のテキストの向きとなっており MsoTextOrientation 列挙で定義されています。

MsoTextOrientation 列挙 (Office)

今回は 1 の msoTextOrientationHorizontal、横書きを使用します。


定数の追加

まずは、上記定数をライブラリに定義します。この連載で作成してきた lsXls ライブラリを開き、(Declarations) に以下を追加します。

'MsoAutoShapeType 列挙 (Office)
Public Const msoShapeOval = 9 '楕円

'MsoTextOrientation 列挙 (Office)
Public Const msoTextOrientationHorizontal = 1 '横方向


エージェントの作成

新規でエージェントを作成します。メインルーチンは次の通りです。アイコンに表示するテキストを sName、アイコンのサイズを iPixel に指定します。

Option Declare
Use "lsXls"

Sub Initialize
   Dim oXls As Variant
   Dim oSheet As Variant
   Dim oIcon As Variant
   Dim sName As String
   Dim iPixel As Integer

   Set oXls = CreateObject("Excel.Application")

   Call oXls.Workbooks.Add
   Set oSheet = oXls.Workbooks(1).WorkSheets(1)

   sName = "喜連瓜破"
   iPixel = 64

   'アイコン作成
   Set oIcon = xMakeIcon(oSheet, iPixel, sName)

   '画像として保存
   Call xSaveAsPicture(oIcon, "E:\" & sName & ".png")

   'Excel を UI に表示
   oXls.Visible = True
End Sub

xMakeIcon 関数は Worksheet 上にアイコンを作成し、そのオブジェクト(Shape オブジェクト)を返します。この実体はグラフ(Chart)で、その中に円とテキストがセットされているということになります。この中身については、次回に解説します。

最後にその Shape オブジェクトを画像ファイルとして保存する関数 xSaveAsPicture をコールしています。Export しているだけなので、関数化する必要はないかもしれませんね...

Function xSaveAsPicture(voIcon As Variant, ByVal vsFileName As String)
   voIcon.Chart.Export vsFileName
End Function


次回の予告

途中にも記載しましたが、アイコンを作成する xMakeIcon 関数については次回に解説します。少しクセのある部分がありますので詳しく解説します。


前回 Notes - Excel 連携 次回


2024/09/11

Notes - Excel 連携:#36)帳票を画像で保存

Notes から Excel を操作する方法について紹介する連載『Notes - Excel 連携』の 36 回目です。前回までの帳票作成のプログラムを利用して、変わり種の機能を紹介します。それは、Excel のワークシートを画像として保存する方法です。

この機能は、ワークシートを画像に変換する機能と画像ファイルとして保存する機能を組み合わせて、実現します。まずはそれぞれの機能を確認しましょう。


ワークシートを画像に変換

Excel の操作でセルの範囲をクリップボードにコピーします。貼り付けるときの形式に[図]を指定すると画像として貼りつく機能があります。

VBA にもこれと同等の機能があります。その命令は Range オブジェクトの CopyPicture メソッドです。このメソッドを実行すると Range オブジェクトの範囲が画像としてクリップボードに入ります。

Range.CopyPicture メソッド (Excel)


画像ファイルとして保存するには

グラフの右クリックメニューには[図として保存]があり、グラフを画像ファイルとして保存することができます。

VBA では Chart オブジェクトの Export というメソッドとして利用できます。

Chart.Export メソッド (Excel)


帳票を図として保存

それでは、この 2 つの機能を利用して、帳票を画像として保存しましょう。『#33)帳票を PDF で保存』で作成したエージェントをベースに修正を加えます。

まず、今回のメイン関数となる帳票を図として保存する機能を実現する xSaveAsPicture を作成します。引数は、帳票が存在するシートオブジェクトと明細の行数、そして保存するファイル名です。

Function xSaveAsPicture(voSheet As Variant, ByVal viDoc As Integer, ByVal vsFileName As String)
   Dim oRange As Variant
   Dim s As String
   Dim oShape As Variant

   '画像に変換するエリアを取得
   s = GetRangeString(2, 2, xciHeaderRows + viDoc + 1, 9)
   Set oRange = voSheet.Range(s)

   '図として保存(クリップボードへ)
   oRange.CopyPicture

   '画像にするための Chart オブジェクト作成
   Set oShape = xAddChart(voSheet, oRange.Width, oRange.Height)

   '貼り付け場所はここ
   oShape.Select

   '図として貼り付け
   oShape.Chart.Paste

   '画像として保存
   oShape.Chart.Export vsFileName

   'Chart オブジェクト削除
   oShape.Delete
End Function

まず、帳票全体を表す Range オブジェクトの範囲文字列を変数 s に取得しています。ヘッダ行の行数 xciHeaderRows と引数の明細行数を使用して選択範囲を動的に決定しています。

その後は、プログラム内のコメントの通りです。Range オブジェクトとして取得して、クリップボードに画像としてコピー、グラフ(Chart オブジェクト)を作成し、そこに貼り付けて画像として保存しています。


Chart オブジェクトの作成

上記関数では xAddChart 関数で Chart オブジェクトを作成する処理を行っています。

#19)グラフ関連オブジェクトまとめ』でまとめたように、Shapes オブジェクトの AddChart2 メソッドで Chart オブジェクトを追加します。ただ、このメソッドの戻り値は Shape オブジェクトで Chart オブジェクトはそのプロパティであることがポイントです。

Function xAddChart(voSheet As Variant, ByVal vdWidthPt As Double, ByVal vdHeightPt As Double) As Variant
   Dim oShape As Variant

   'Chart を作成(Shape オブジェクト)
   Set oShape = voSheet.Shapes.AddChart2( , , , , vdWidthPt, vdHeightPt)
   With oShape
      '背景を白に
      .Fill.Visible = True
      .Fill.ForeColor.RGB = RGB(255, 255, 255)
      .Line.Visible = False

      'グラフのオブジェクトを削除
      On Error Resume Next
      .Chart.ChartTitle.Delete
      .Chart.Legend.Delete
      .Chart.Axes(xlCategory).Delete
      .Chart.Axes(xlValue).Delete
      .Chart.Axes(xlValue).MajorGridlines.Delete
   End With

   Set xAddChart = oShape
End Function

今回 Chart オブジェクトは、画像として保存するためだけに使用しており、グラフとしては全く利用しません。ですが、AddChart2 メソッドで Chart を作成した場合、シートの状態によって自動でグラフタイトルや凡例などのオブジェクトが作成されることがあります。そこで、この関数では、不要なものを順に削除しています。ただ、オブジェクトが作成されていない場合、エラーとなりますので、 On Error Resume Next でエラーを無視するようにしています。


エージェントの修正

仕上げに、エージェントの Initialize を修正します。前回の PDF で保存する機能は不要なのでコメントアウトし、代わりに今回作成した関数 xSaveAsPicture をコールします。

最後に Excel シートを画面に表示させ終了しています。

  ・・・
   '印刷設定
   Call xPageSetup(oSheet)

   '作成した帳票を PDF で保存
   'oXls.DisplayAlerts = False
   'Call oXls.Workbooks(1).ExportAsFixedFormat(xlTypePDF, "E:\Notes-Excel#33.pdf")
   'Call oXls.Workbooks(1).Close()


   '画像として保存
   Call xSaveAsPicture(oSheet, iDoc, "E:\Notes-Excel#36.png")

   'Excel を UI に表示
   oXls.Visible = True
End Sub


まとめ

実行すると指定した場所に帳票が画像として保存されます。前回の PDF と同様に内容を修正されたくない場合に利用できます。

#32 で Excel 形式、#33 で PDF、そして今回は画像として保存する方法をまとめました。さまざまな形式に対応できるようになるとアプリの自由度が増しますね。


前回 Notes - Excel 連携 次回


2024/09/09

リッチテキスト:#18)リッチテキスト入力チェック

LotusScript でリッチテキストを操作する方法を紹介している連載『リッチテキストの基本操作』の 18 回目です。約半年ぶりの更新です。

第 15 回 に続き、ヘルプ依頼の対応ログです。今回も日ごろから大変お世話になっている方から相談でした(もちろん同じ方)。最近は、ノーツコンソーシアムだけでなく、キャンプなど私的にもお世話になっているので、聞かないわけにはいきません(笑)。もはや、任務か指令状態です。仕事以上に気合を入れて対応させていただきました。

要件は『リッチテキストフィールドに文字か添付ファイルのいずれかが入力されているかチェックしたい』というものでした。

あれ? ちょっと待てよ?? このリッチテキストの連載、確か直近がこのネタだったのでは???

ただ、見直してみると別々の記事なので、今回の用件に端的には答えていない部分もあります。そこで、今回はこの要件に特化してまとめます。


メインプログラム

今回は入力チェックですから、Querysave イベントにスクリプトを記述します。保存の直前に実行されるイベントで、引数の Continue に False をセットすると、保存されません。

作成したスクリプトは次の通りです。

Sub Querysave(Source As Notesuidocument, Continue As Variant)
   Dim nuiw As New NotesUIWorkspace
   Dim nuid As NotesUIDocument
   Dim nd As NotesDocument

   Set nuid = nuiw.CurrentDocument
   Set nd = nuid.Document

   '添付ファイルを認識させる
   Call nuid.Refresh(True)

   Continue = xHasText(nd, "Body")
   Continue = Continue Or xHasFile(nd, "Body")

   If Continue = False Then
      Msgbox "文字を入力するかファイルを添付してください。", 16
   End If
End Sub

まず、#17)編集中のリッチテキスト で触れた Refresh メソッドで UI での添付ファイルをバックエンド文書 nd に反映します。その後、文字が入力されているか判定する関数 xHasText と 添付ファイルが貼り付けられているか判定する関数 xHasFile を実行しています。どちらの関数もチェック対象の入力がある場合 True を返します。

今回では ”いずれか” が要件なので論理和の or を使用しています。


文字の入力判定

まずは、文字の入力を判定する関数 xHasText です。第 16 回 のサンプルコードとほぼ同様です。引数で指定したフィールドを取得するようになっている点が少し違いますね。

リッチテキストフィールドが取得できれば、Text プロパティを使用して、文字の入力を判断するだけですね。

Function xHasText(vnd As NotesDocument, Byval vsFldName As String) As Boolean
   Dim ni As NotesItem
   Dim nrti As NotesRichTextItem

   xHasText = False

   'フィールドを取得
   Set ni = vnd.GetFirstItem(vsFldName)
   If ni.Type = RICHTEXT Then
      'リッチテキストなので文字の入力をチェック
      Set nrti = ni
      If nrti.Text <> "" Then
         xHasText = True
      End If
   End If
End Function


添付ファイルの判定

添付ファイルが貼り付けを判定する関数 xHasFile はこちらです。

構造は 第 17 回 のサンプルと同じにしています。違いは添付ファイルを発見したら、戻り値に True をセットして関数を抜けている点ですね。

Function xHasFile(vnd As NotesDocument, Byval vsFldName As String) As Boolean
   Dim ni As NotesItem
   Dim nrti As NotesRichTextItem
   Dim nemb As NotesEmbeddedObject
   Dim vTmp As Variant
   Dim i As Integer

   xHasFile = False

   'フィールドを取得
   Set ni = vnd.GetFirstItem(vsFldName)
   If ni.Type = RICHTEXT Then
      'リッチテキストなので添付ファイルをチェック
      Set nrti = ni
      vTmp = nrti.EmbeddedObjects
      If Not (Isempty(vTmp)) Then
         For i = 0 To Ubound(vTmp)
            '埋め込みオブジェクトを順に取得
            Set nemb = vTmp(i)
            If nemb.Type = EMBED_ATTACHMENT Then
               xHasFile = True
               Exit Function
            End If
         Next
      End If
   End If
End Function


まとめ

今回の対応では、わかりやすさと汎用化を意識して対応しました。次のような点がポイントと考えています。

  • インターフェースである引数と戻り値をそろえる
  • 引数を文書とフィールド名にし、フィールド取得やリッチテキスト判定は関数内で実施

この効果でメインプログラムは見やすくシンプルになっています。

逆の効果としては、それぞれの関数内でフィールド取得やリッチテキスト判定を行っており、コード量の側面では非効率です。また、実行時も同じ処理が走りますので、レスポンスの側面でも効率は悪いと言えます(この程度であれば、気がつくレベルではないでしょうが...)。

この連載の主題とは違いますが、目的によってプログラムの書き方も変わるということですね。

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


2024/09/06

作ってみよう:#13)お小遣い帳 - キーワード検索機能追加

今回は、Google Map と連携した近隣検索機能を拡張し、キーワード検索機能を追加します。次のような画面を表示して、キーワードを入力しします。必要に応じて、プレイスタイプを指定し、より限定した検索を可能とします。


検索条件入力フォーム作成

ノーツではこの事例のような複雑な入力画面は、ダイアログボックスで実現します。そこで、まずは、ダイアログボックス用のフォームを作成します。

フォーム名 (dlgInpKeyword)
別名 dlgInpKeyword

項目名 フィールド名 種類 補足
キーワード dlgKeyword テキスト
プレイスタイプ dlgSchType テキスト 非表示
dlgSchTypeKj テキスト

※ プレイスタイプは作成時の計算結果で初期値は ""


画面イメージは以下の通りです。


表は 3 段階のカスケードの表で作成されています。それぞれの階層に役割がを持たせています。


1 階層目(一番外側)

この表の役割は画面の幅を有効に使うことで、2 行 1 列の表を作成し、表の幅を『ウィンドウに合わせる』としています。セルの背景は 1 行目(ヘッダ)を黄色、2 行目(明細)を白に設定。罫線の色を濃い黄色にして行間のみ表示しています。


2 階層目

この階層は左マージンの確保が目的です。

マージンに合わせる表を作成し、列の間隔を 0.5 cm に設定しています。行の間隔は、ヘッダが 0.1 cm、明細が 0.06 cm とヘッダを少し広くしています。

明細側の表は 4 行 1 列で、1 階層と同様に背景色を変え、項目名と入力欄を分けています。


3 階層目

この階層は入力項目の配置管理が目的です。


プレイスタイプの選択

キーワード検索でもプレイスタイプを選択する機能を実装します。[選択]ボタンに次の LotusScript を記述します。

(Options)

Option Declare
Use "lsGoogleMAP_UI"

Click

Sub Click(Source As Button)
   Dim nuiw As New NotesUIWorkspace
   Dim nuid As NotesUIDocument
   Dim nd As NotesDocument
   Dim s As String
   Dim v As Variant

   Set nuid = nuiw.CurrentDocument
   Set nd = nuid.Document

   'プレイスタイプの選択
   s = PicPlaceType()
   If s = "" Then Exit Sub

   v = Split(s, "|")
   nd.dlgSchType = v(1)
   nd.dlgSchTypeKj = v(0)
End Sub


エージェントの作成

これまで利用していた NearBySearch_Google エージェントをコピペして、KeywordSearch_Google エージェントを作成します。エージェントを編集し、プレイスタイプを選択していた部分を削除して、次のコードを追加します。

Sub Initialize
         ・・・
   Set oLoc = New Location(nd.Sch_Latitude(0), nd.Sch_Longitude(0))
   Set oSch = New NearBySearch(oLoc)

   '検索条件取得
   Dim ndDlg As NotesDocument
   Set ndDlg = xndb.CreateDocument()

   If xnuiw.Dialogbox("dlgInpKeyword", True, True, False, False, False, False, "検索条件", ndDlg, True) Then Else Exit Sub


   '検索条件設定
   oSch.Keyword = ndDlg.GetItemValue("dlgKeyword")(0)
   oSch.SearchType = ndDlg.GetItemValue("dlgSchType")(0)

   Call oSch.Search()

   '検索結果の確認
   If oSch.Count = 0 Then
         ・・・
End Sub


メインフォームの修正

支出フォームを編集し、[近隣]ボタンをコピペして[検索]ボタンを作成します。検索ボタンをクリックすると、先ほど作成した KeywordSearch_Google エージェントを実行するように変更します。

この作業は、Nomad 用とノーツ用の両方のフォームで行います。


動作検証

完成したら、動作検証です。

Nomad からの実行では次のようになります。


画面いっぱいに表示される予定だったのですがそうはなっておらずあまり美しくありませんね。このあたりはもう少し検証が必要なようです。改善策が見つかれば別途まとめたいと思います。


ノーツから実行すると次のように表示されました。PC だと色遣いが少々不自然ですが、デザイン的には計画通りとなっていますね。


前回 作ってみよう


2024/09/05

作ってみよう:#12)お小遣い帳 - ノーツ用フォームの修正とテスト機能追加

前回の対応で、それぞれのクライアント用の画面を作成し、Nomad - ノーツ ハイブリッド環境ができました。この機能を生かして、ノーツからでも Google Map 連係機能が利用できるよう修正します。


フォームの更新

第 4 回 以降 Nomad 用の設計ばかりを作ってきました。ノーツ用フォーム fExpence は放置状態だったので、Nomad 版と同じレベルに修正します。

場所については Nomad 用フォームからカスケードした表ごとコピペすると簡単です。

分類は 1 行 2 列の表をマージンに合わせる設定で作成し、罫線を 0 に設定します(表の上下の段落は非表示)。そこに、Budget と ItemGroup のフィールドを Nomad 用フォームからコピペします。フィールドは、ノーツクライアントに合わせてサイズを調整します(添付の画像では、幅 4.5 cm、高さ 4 cm に設定)。

座標欄は、外側の表に一行追加します。セル内のデザインは場所と同じなので、コピペした後に、フィールドを Nomad 用フォームからコピペします。ボタンは後ほど作業するので名称だけ[入力]に変更します。


マージンに合わせる設定のままだとフォームが間延びするので固定幅とします。添付の画像では、タイトルの表は 12.4 cm、入力欄の列は 10 cm 幅に設定しています。


入力ボタンの作成

ノーツクライアントには通常 GPS は付いていません。Google Map 連係機能を利用するためには座標の入力が必要です。ただ、緯度・経度を手入力するのは大変なので、入力機能を作成します。

入力を補助してくれるのも Google Map です。地図上を右クリックすると座標をクリップボードにコピーする機能があり、これを利用します。

まず、新規で LotusScript のエージェントを作成します。

エージェント名 InputLocation
トリガー イベント
エージェントリストの選択
対象 なし

エージェントのプログラムは次の通りです。

Option Declare

Sub Initialize
   Dim nuiw As New NotesUIWorkspace
   Dim nuid As NotesUIDocument
   Dim nd As NotesDocument
   Dim sTmp As String
   Dim vTmp As Variant
   Dim dLat As Double
   Dim dLng As Double

   On Error GoTo Label_Err

   '座標の入力
   sTmp = nuiw.Prompt(PROMPT_OKCANCELEDIT, "座標の入力", "検索の座標を入力してください。", "")
   vTmp = Split(sTmp, ",")
   dLat = CDbl(Trim(vTmp(0)))
   dLng = CDbl(Trim(vTmp(1)))

   '座標の設定
   Set nuid = nuiw.CurrentDocument
   Set nd = nuid.Document
   nd.Sch_Latitude = dLat
   nd.Sch_Longitude = dLng

Label_Exit:
   Exit Sub

Label_Err:
   Resume Label_Exit
End Sub


エージェントができたら[入力]ボタンで実行するように設定します。


動作検証

完成したら、動作検証です。

ノーツから新規作成し座標を入力します。そのあと[近隣]ボタンでその近辺の情報がリストアップされたら成功です。

これでいちいち Nomad で操作しなくても、検証ができるようになりました。また、その場所にいたことにして検索できるもありがたいですね。


前回 作ってみよう 次回


2024/09/04

作ってみよう:#11)お小遣い帳 - Nomad とノーツクライアントの併用

前回の対応で、Google Map のプレイスタイプを管理するマスタを作成しました。このようなマスタメンテなどは、外出先で行うことはありません。Nomad 対応は不要と言えますし、そもそもノーツクライアントで操作したほうが効率もいいですよね。

現時点の『お小遣い帳』アプリをノーツで開くと Nomad 用に作成した画面が開きます。

そこで今回は、初期画面の表示をノーツクライアントではノーツ用、Nomad では Nomad 用の画面を開くよう、ハイブリッド化を行います。そして、ノーツ用画面にマスタメンテ機能を追加します。

 

ノーツ用設計要素の作成

まず、ハイブリッド化の前にノーツ用の画面(フレームセット)を作成します。複数の設計要素が必要なので、関連が奥の設計要素から順に作成します。


アウトライン

ノーツ用画面の左メニューの構造を作成します。

アウトライン名 olMain

エントリは次の通り作成します。既存のビュー 2 つを指定し、[管理]メニューについては今後の拡張を考え階層化しておきます。

また、必要に応じてアイコンを準備し、セットしておきましょう。今回はトップレベルだけアイコンをセットしています。


ページ - メニュー

先ほど作成したアウトラインを表示するページを作成します。

ページ名 pMenu_Main

配置した埋め込みアウトライアンが体裁よく表示されるようプロパティを調整します。



フレームセット

最後に 3 フレームの一般的なフレームセットを作成します。

フレームセット名 fsMain

各フレームには、作成済みの設計要素を指定します。

ヘッダー frmHeader にセットする pTitle は 第 4 回 で作成した Nomad 用のタイトルのページを流用しています。ビューの『1.日付順』は 第 2 回 で作成したビューです。Nomad 対応では使用していないので、当時の状態のままのはずです。


フレームの設定が終わったら、フレームの高さや幅の設定を整えて完成です。


初期画面の切り替え

ノーツ用の画面が準備できたので、クライアントに応じて起動画面を切り替える設定を追加します。実現方法はいろいろありますが、今回はページを利用して作成します。

新規のページを作成します。

ページ名 pUISelector


ページの Postopen イベントに以下のスクリプトを記述します。

Sub Postopen(Source As NotesUIDocument)
   Dim ns As New NotesSession
   Dim nuiw As New NotesUIWorkspace

   '初期フレームセット切替
   If ns.Platform = "iOS" Or ns.Platform = "Android" Then
      Call nuiw.OpenFrameSet("mfsMain")
   Else
      Call nuiw.OpenFrameSet("fsMain")
   End If

   'ページを閉じる
   Call Source.Close()
End Sub

NotesSession クラスの Platform プロパティでクライアントを判別し、クライアントに応じたフレームセットを開いています。

その後このページは不要になるので閉じています。


ページができあがったら、初期画面がこのページとなるようデータベースのプロパティを変更します。


動作検証

完成したら、動作検証をしましょう。Nomad ではこれまで通り利用でき、ノーツクライアントでは、今回作成したフレームセットが開けば、成功です。


前回 作ってみよう 前回


2024/09/03

作ってみよう:#10)お小遣い帳 - プレイスタイプの日本語化

前回までの対応で、お小遣い帳としては使える状態になりました。今後は使いながら気になった点を少しずつ機能を改善して、より使いやすくしていきます。

最初の改善は、前回作成したばかりの機能です。計画性がないですね...。

プレイスタイプを指定して購入場所を検索する機能を作成しました。この機能について、一部の方から drugstore など内部的な文字列(英語)ではなく、日本語表記としてほしいというご意見をいただきました。今回は、少し予定を変更して、この対応を行います。


プレイスタイプマスタの作成

まずは、単純なマスタフォームを作成します。基本的な操作ですので、詳細な手順や設定は割愛します。ご了承ください。

フォーム名 a.PlaceType
別名 fPlaceType

項目名 フィールド名 種類 補足
並び順 SortNo 数値
プレイスタイプ PlaceType テキスト
名称 Name テキスト

[閉じる][編集][保存]などの最低限のアクションボタンもあわせて作成します。


続いて、マスタメンテ用ビューを作成し、上記 3 つのフィールドを表示する列を配置します。当たり前ですが、並び順でソートしておきます。

ビュー名 a.PlaceType
別名 vPlaceType

ビューには[閉じる]と [新規作成]のアクションボタンを作成します。


ここまで出来上がったら、ビューをプリビュー表示して、選択肢として必要なマスタデータを作成します。


選択用のビューの作成

先ほど作成したビューをコピペして、選択用のビューを作成します。

ビュー名 (vPicPlaceType)
別名 vPicPlaceType

選択画面(PickList)では不要となるので、「並び順」を非表示にし、「プレイスタイプ」を削除します。また、ビューのプロパティで列ヘッダと選択用余白を非表示にします。


ライブラリの修正

lsGoogleMAP_UI を開き、ビュー経由でプレイスタイプを選択する関数を追加します。

Public Function PicPlaceType() As String
   Dim ndc As NotesDocumentCollection
   Dim nd As NotesDocument
   Dim s As String

   s = "検索するプレイスタイプを選択してください。"

   '選択画面の表示
   Set ndc = xnuiw.PickListCollection(_
         PICKLIST_CUSTOM , False, xndb.Server, xndb.FilePath,_
         "vPicPlaceType", xndb.Title, s)

   If ndc.Count > 0 Then
      '戻り値のセット(名称|別名)
      Set nd = ndc.GetFirstDocument()
      s = nd.GetItemValue("Name")(0)
      s = s & "|" & nd.GetItemValue("PlaceType")(0)
      PicPlaceType = s
   End If
End Function

選択画面は、NotesUIWorkSpace クラスの PickListCollection メソッドを使用しています。2 つ目の引数を False に設定し複数選択できないようにしていますが、戻り値は NotesDocumentCollection で返されます。1 以上の場合、選択したことになるので最初の文書を取得して PlaceType フィールドの値を戻り値にセットしています。


メインルーチンの修正

プレイスタイプを選択する関数が変更となったので、メインルーチンを調整します。

エージェント NearBySearch_Google の Initialize を次の通り修正します。

Sub Initialize
         ・・・
   Set oLoc = New Location(nd.Sch_Latitude(0), nd.Sch_Longitude(0))
   Set oSch = New NearBySearch(oLoc)

   'プレイスタイプの選択
   Dim s As String
   s = PicPlaceType()
   If s = "" Then Exit Sub

   v = Split(s, "|")
   oSch.SearchType = v(1)
   Call oSch.Search()

   '検索結果の確認
   If oSch.Count = 0 Then
         ・・・
End Sub

なお、元の状態ではプレイスタイプの選択をキャンセルした場合でも、検索を実行しているバグがありました。今回はキャンセル(=戻り値が null)の場合、処理を終了するように修正しています。


動作確認

修正が終わったらテストします。Nomad から [近隣]ボタンをクリックします。以下のように選択肢が日本語で表示されれば成功です。


今回の対応では、PickList を利用しました。選択肢の一覧の実体はビューとなります。文字色や文字サイズや行間などの設定ができるので、利用者のニーズに合わせて調整できますね。

前回 作ってみよう 次回


2024/09/01

複数のフィールド値をセットでソート

前回 LotusScript を使ってソートする方法を紹介しました。ただ、実際にアプリを作っていると前回の関数だけではうまくいかない問題に直面することがあります。

例えば、ノーツでありがちな複数のフィールドを同じ要素数だけ値を保持、改行表示することで表っぽく見せる場合です。

今回はこのような複数のフィールド値をセットでソートする場合を考えてみましょう。


力技には限界がある

今回の例では、ソート値以外に 2 つのフィールドがあります。これら値もソート順に従い並び替える必要があります。

単純に考えると、前回の関数に引数を追加、値の入れ替えが発生したタイミングで追加した値も入れ替えれば、実現できます。

Function xSort(vvData As Variant) As Variant  '引数に行先と交通費を追加
   Dim i As Integer
   Dim j As Integer
   Dim v As Variant
   Dim vRtn As Variant

   '戻り値配列を準備
   vRtn = vvData

   '値をソート
   For j = 1 To Ubound(vRtn)
      For i = 0 To Ubound(vRtn) - j
         If vRtn(i) > vRtn(i+1) Then
            '値を入れ替え
            v = vRtn(i)
            vRtn(i) = vRtn(i+1)
            vRtn(i+1) = v

            'ここで行先と交通費も入れ替え

         End If
      Next
   Next

   'ソートした配列を返す
   xSort = vRtn
End Function

ただ、今回のようにフィールドが 3 つ程度であればいいですが、10 や 20 フィールドだったらどうでしょう? このような力技は非効率で限界があることがわかります。


配列を活用して回避

この問題は配列をうまく活用すると比較的簡単に解決できます。

まずはソート関数 xSort の改造です。赤字の部分が修正箇所です。基本的な構造はそのままですが、インデックス配列なるものが登場しており、関数の最初でソートしたいデータと同じだけの配列を用意して、添え字番号をセットしています。値の比較では、添え字の指定がカスケードされており、入れ替えは値ではなく、インデックス変数となっています。また、戻り値がインデックス配列となっていますね。

Function xSort(vvData As Variant) As Variant
   Dim i As Integer
   Dim j As Integer
   Dim v As Variant
   Dim aiIdx() As Integer

   'インデックス配列を準備
   Redim aiIdx(Ubound(vvData))
   For i = 0 To Ubound(vvData)
      aiIdx(i) = i
   Next


   'ソート開始
   'インデックス配列経由で値を参照し、インデックスを入れ替え

   For j = 1 To Ubound(vvData)
      For i = 0 To Ubound(vvData) - j
         If vvData(aiIdx(i)) > vvData(aiIdx(i+1)) Then
            'インデックスを入れ替え
            v = aiIdx(i)
            aiIdx(i) = aiIdx(i+1)
            aiIdx(i+1) = v
         End If
      Next
   Next

   'インデックス配列を返す
   xSort = aiIdx
End Function


これがどのような挙動になるのか順にみていきましょう。

まずはソート開始前の状態確認です。引数で渡される配列と準備したインデックス配列は次の通りとなります。

i vData(i) aiIdx(i)
0 2024/08/30 0
1 2024/06/13 1
2 2024/10/25 2
3 2024/09/19 3


続いてソートを開始します。

ポイントとなるのが、vvData(aiIdx(i)) の表現です。例えば、i = 1 の場合では、aiIdx(i) = 1 となり、vvData(aiIdx(i)) = vvData(1) = 2024/06/13 となります。

現時点では意味がなく、ムダに見えますが、もう少し我慢してください。

実際のソートの処理を順に確認しましょう。インデックス配列 aiIdx 、ソートしたい値である vvData をループ変数の進展に合わせて表にまとめています。

j i aiIdx() vvData() 操作
0 1 2 3 aiIdx(i) aiIdx(i+1)
1 0 0 1 2 3 2024/08/30 2024/06/13 入れ替え
1 1 0 2 3 2024/08/30 2024/10/25
2 1 0 2 3 2024/10/25 2024/09/19 入れ替え
2 0 1 0 3 2 2024/06/13 2024/08/30
1 1 0 3 2 2024/08/30 2024/09/19
3 0 1 0 3
2
2024/06/13 2024/08/30
結果 1
0
3
2
ソート完了

いかがですか?

今回の処理では、ソートしたい値 vvData の値は一切変化していません。インデックス変数を並べ替え、そこを経由してアクセスすることで、ソートを実現しています。


インデックス配列の活用

ソート完了後はこのインデックス配列を戻り値としていました。そのインデックス配列と実データを引数で渡し、並び替えた実データを戻り値で返す関数です。

Function xGetSortedVal(vvIdx As Variant, vvData As Variant) As Variant
   Dim i As Integer
   Dim v As Variant

   '戻り値配列の初期化
   v = vvData

   '戻り値生成
   For i = 0 To Ubound(v)
      v(i) = vvData(vvIdx(i))
   Next

   xGetSortedVal = v
End Function

ソートの比較で使用したインデックス配列を経由して取得する方法を使用して、並び替えられた値を取得しています。


メインルーチンでは、これら関数を利用して次のように記述します。

Sub Click(Source As Button)
   Dim nuiw As New NotesUIWorkspace
   Dim nuid As NotesUIDocument
   Dim nd As NotesDocument
   Dim v As Variant

   Set nuid = nuiw.CurrentDocument
   Set nd = nuid.Document

   'ソートされたインデックス配列を取得
   v = nd.Src_Date
   v = xSort(v)


   'ソートされた実データを取得
   nd.Sort_Date = xGetSortedVal(v, nd.Src_Date)  '日付
   nd.Sort_Text = xGetSortedVal(v, nd.Src_Text)  '行先
   nd.Sort_Cost = xGetSortedVal(v, nd.Src_Cost)  '交通費

End Sub


関数のテスト

前回と同様に、簡単なテストフォームを作成して、動作確認します。

実行すると、次のようにすべてのフィールドが日付でソートされます。


これで、日付、行先、交通費の各項目を日付順で並び替えることができました。この方法であれば、セットで操作するフィールド数が増えてもあまり気にならないですね。