2010年2月24日水曜日

ビューの書き出しを無効にする

フォームの印刷は禁止にできてもビューの書き出しは禁止できませんでしたが、Notes 8.0.2 からはビューの書き出しを禁止にすることができるようになっています。

アプリケーションのプロパティから[詳細]タブを開き、「ビューのデータの書き出しを無効にする」を有効にすることで Lotus Notes のメニューに「ファイル - 書き出し」が表示されなくなります。

このプロパティ項目の設定も先日ご紹介したアイコンの絵柄と同じ Note に保存されています。

「ビューのデータの書き出しを無効にする」を有効にすると $DisableExport というアイテムができ、値に"1"がセットされます。無効にするとアイテムがなくなりました。
(Notes 8.5.1 Standard版 で確認)

2010年2月23日火曜日

アプリケーションのプロパティを変える

アプリケーション内では、データと同様に設計要素にも NoteID が割り当てられます。
通常、文書に割り振られる NoteID は、UNID とは違い、レプリカであっても同じ NoteID にはならないそうです。

しかしながら、アイコンの絵柄が保存される Note はどのアプリケーションにも同じ NoteID が割り振られ、

NotesDatabase.GetDocumentByID( "FFFF0010" )

とすることで NotesDocument オブジェクトとして取得できます。

ところで、この Note にはアイコンの図柄のほかにアプリケーションのプロパティの一部も保存されているのをご存知でしょうか。

アプリケーションのプロパティにあるラジオボタンやチェックボックスなどで選択された値の一部は $Flags というアイテムに英数字で保存されています。

もちろん、これらの値は変更できます。

下の Lotus Script では、プロパティにある5つのチェックボックスを無効にします。
Sub Initialize
    Dim ss As New NotesSession
    Dim db As NotesDatabase
    Dim doc As NotesDocument
    Dim item As NotesItem

    Set db = ss.CurrentDatabase
    Set doc = db.GetDocumentByID( "FFFF0010" )
    Set item = doc.GetFirstItem( "$Flags" )
    Print item.Text
    Dim strFind( 4 ) As String
    Dim strReplace( 0 ) As String
    strFind( 0 ) = "N" '文書データの圧縮
    strFind( 1 ) = "q" 'データベース設計の圧縮
    strFind( 2 ) = "H" '返答スレッド履歴のサポート
    strFind( 3 ) = "7" 'データベースで多くのフィールドを許可
    strFind( 4 ) = "2" '文書テーブルマップの最適化
    strReplace( 0 ) = ""
    item.Values = Replace( item.Values, strFind, strReplace )
    Call doc.Save( True, False )
End Sub

それぞれのプロパティの項目とその値は下のサイトなどを参照ください。

(参考)プログラムを使用してデータベースプロパティを変更する方法
Programmatically change database properties

2010年2月22日月曜日

ブレークポイントを削除したい

Lotus Script をデバッグするときに、特定の行で F9 を押すなどしてブレークポイントを設定することがあります。

このブレークポイントは、ユーザープリファレンス - [基本]タブ - 追加のオプションの「LotusScript デバッガのブレークポイントを保持する」を有効にすると保持することができます。

ところで、どこに「保持」されるのでしょうか?

実はアプリケーション内に "breakpoint_" という名前のプロフィール文書として保存されています。

プロフィール文書は NotesPeek などで確認できますが、たまたま見たアプリケーションに自身が作成した "breakpoint_" が残っていたりすると、削除したい衝動に駆られてしまうのです...

小さいことですが、気になってしまうのはしょうがない。

そこで "breakpoint_" を削除するエージェントを作成しましたのでご紹介します。
(Options)
%INCLUDE "LSCONST.LSS"

Sub Initialize
    Dim ws As New NotesUIWorkspace
    Dim varDB As Variant
    On Error Goto ERRORTRAP
    varDB = ws.Prompt( 13, "選択", "アプリケーションを選択してください。" )
    If Isempty( varDB ) Then Exit Sub
    Dim db As New NotesDatabase( "", "" )
    If Not db.Open( varDB( 0 ), varDB( 1 )) Then Exit Sub
    Dim nc As NotesNoteCollection
    Set nc = db.CreateNoteCollection( False )
    nc.SelectProfiles = True
    Call nc.BuildCollection
    If nc.Count = 0 Then
        Print "プロフィール文書は見つかりませんでした"
        Exit Sub
    End If
    Dim dc As NotesDocumentCollection
    Dim doc As NotesDocument
    Set dc = db.GetProfileDocCollection("hogehoge") '0件で初期化
    nid = nc.GetFirstNoteId
    For i = 1 To nc.Count
        Set doc = db.GetDocumentByID( nid )
        If doc.NameOfProfile = "breakpoints_" Then
            Call dc.AddDocument( doc )
        End If
        nid = nc.GetNextNoteId( nid )
    Next
    Dim intCount As Integer
    intCount = dc.Count
    If intCount > 0 Then
        Call dc.RemoveAll( True )
        Print Cstr( intCount ) & " 件の breakpoint_ を削除しました。"
    Else
        Print "breakpoint_ は見つかりませんでした"
        Exit Sub
    End If
FINAL:
    Exit Sub
ERRORTRAP:
    Print Getthreadinfo( LSI_THREAD_PROC ) & ", " & Erl & ", " & Err & ", " & Error
    Resume FINAL
End Sub

ところで、このエージェントをテストしようと以前のエントリの関係で作ったアプリケーションを選択したところ nc.BuildCollection で「フィールドが大きすぎる(32K)、またはビューの列と選択式が大きすぎます。」というエラーになりました。
(ODSバージョンが 43 (R6)で Local にあるアプリケーション、クライアントは 8.0.1 のBasic版)

まあ 32K制限に引っかかる文書がアプリケーション内にある場合、関係のないNoteCollectionのビルドでもエラーになることもあるのね、ということで深入りしないことにします...

そんなアプリケーションでもどうしても削除した場合は NotesPeek 等で noteID を調べて NotesDatabase.GetDocumentByID( "noteID" ) で取得した文書を削除してください。

2010年2月19日金曜日

MailAddress @コマンドって便利

メールアドレスを選択するようなフォームで、ドミノディレクトリからユーザーを選択させたい場合 MailAddress @コマンドが便利です。

フォーム上に SendTo、CopyTo、BlindCopyTo という名前のフィールドがある場合

@Command([MailAddress])

で表示されるダイアログで各フィールドへ設定するNotesIDを選択できます。

このとき、フォーム上に BlindCopyTo がなければ、宛先とccだけが選択できるダイアログが表示されます。

宛先がなく、ccとbccだけが選択できるダイアログを表示したい場合、SendToフィールドを作らなければいいようです。

このダイアログでは、個人のアドレス帳やディレクトリアシスタンスで指定されているディレクトリを選択できるし、
フラットな「名前によるリスト」、組織階層別に表示できる「組織の階層」など、ビューが選択できるところが気に入っています。

このように便利なコマンドなのですが、さらに便利なことに宛先、cc、bccのフィールド名をパラメータで指定できるのです。

@Command([MailAddress];"EnterSendTo";"EnterCopyTo";"EnterBlindCopyTo")

1つ目のパラメータが宛先、2つ目がcc、3つ目がbccになります。

パラメータを指定する場合、フィールド名は任意の名前でいいようです。

フィールド名にはヌル "" を指定できます。

例えば1つ目のパラメータをヌルにした場合、SendTo という名前のフィールドがあれば宛先は設定できますが、なければ設定できなくなります。

メールのテンプレートを覗くとパラメータが設定されているのですが、パラメータを指定できることはデザイナーヘルプに記載がありません...orz

よくあることですね、はい。

2010年2月18日木曜日

親文書を削除をさせない

前回のエントリでは、親文書が削除されてしまった子文書の数を調べるLotus Scriptをご紹介しましたが...

そもそも子文書があるのに親文書が削除できてしまう仕様が問題なんだよ、なんて言われちゃう場合もあります。

そこで、削除しようとしている文書に関連する子文書があるとメッセージを表示して削除できなくする方法をご紹介します。

文書の削除を認識するにはデータベーススクリプトの Querydocumentdelete というイベントを利用します。

このイベントに書かれた処理は、文書を削除する前に実行されます。

例えば、ビュー上で複数の文書にチェックを付け、Delete キーを押した直後に実行されます。

選択した削除対象の文書は NotesUIDatabase クラスの Documents プロパティで取得できます。

削除対象の文書( NotesDocument ) を親とする返答文書(子文書)は NotesDocument クラスの Responses プロパティで取得できます。

返答文書があれば、Responses の件数( Count )が0より大きくなりますが、その場合にメッセージを表示します。

この時 Continue に False を設定することで削除をキャンセルできます。
Sub Querydocumentdelete(Source As Notesuidatabase, Continue As Variant)
    Dim dc As NotesDocumentCollection
    Set dc = Source.Documents

    Continue = True
    Dim doc As NotesDocument
    Set doc = dc.GetFirstDocument
    While Not ( doc Is Nothing )
        If doc.Responses.Count > 0 Then
            Messagebox "選択文書の下位に文書があります。", 0 +48 , "警告"
            Continue = False
            Exit Sub
        End If
        Set doc = dc.GetNextDocument( doc )
    Wend
End Sub

2010年2月17日水曜日

迷子の文書を捜す

文書をディスカッション形式で表示するビューではフォームの種類として「文書」、「返答」、「返答への返答」の3つがよく使われます。

「文書」で作成した文書を"親"とすると、
「返答」で作成した文書は、「文書」フォームで作成した文書の"子"になり、
「返答への返答」で作成すると、現在開いている文書の"子"になります。

ややこしいですね。

"親"への返答は"孫"を開いている時には作成しない、といった要件の場合、「返答」を使わず「文書」と「返答への返答」の2つでディスカッションを実現することがあります。

と、前置きはここまでにして、本題です。

ディスカッション形式のビューでは、階層の上にある文書が削除されてしまうと、削除された文書の下の階層にあった文書が表示されなくなります。

このように表示されなくなった文書の数を調べるエージェントを作ってみました。
(Declarations)
Dim db As NotesDatabase

Sub Initialize
    Dim ss As New NotesSession
    Dim dc As NotesDocumentCollection
    Dim doc As NotesDocument
    Dim cnt As Long
    Set db = ss.CurrentDatabase
    Set dc = db.Search( |@IsAvailable($Ref)|, Nothing, 0 )
    If dc.Count = 0 Then Exit Sub
    Set doc = dc.GetFirstDocument
    cnt = 0
    While Not ( doc Is Nothing )
        If Not isActiveRef( doc ) Then cnt = cnt + 1
        Set doc = dc.GetNextDocument( doc )
    Wend
    Print "「" & db.Title & "」の迷子文書は " & Cstr( cnt ) & " 件でした。"
End Sub

Function isActiveRef( doc As NotesDocument ) As Boolean
    Dim parent As NotesDocument
    Dim unid As String
    isActiveRef = True
    unid = doc.GetItemValue( "$Ref" )( 0 )
    Set parent = db.GetDocumentByUNID( unid )
    If parent Is Nothing Then '親が存在しない
        isActiveRef = False
    Else
        If Not parent.IsValid Then '親が削除スタブで存在
            isActiveRef = False
        Else
            If parent.HasItem( "$Ref" ) Then '親の親が存在
                If Not isActiveRef( parent ) Then
                    isActiveRef = False
                End If
            End If
        End If
    End If
End Function

簡単にロジックを説明します。

「返答」「返答への返答」で作成した文書には $Ref というアイテムが自動で作成され、値として返答元文書の UNID が設定されます。

つまり $Ref というアイテムがある文書は子文書というわけです。

$Ref の UNID から親文書を探す、つまり階層をさかのぼっていくわけですが、この時
・文書が見つからない
・文書が削除スタブ
の場合、迷子と認定します。

逆に削除スタブでなく $Ref がない文書に辿りつくことができれば、それは迷子ではありません。

2010年2月15日月曜日

UNID を復活させるには

※UNIDはデザイナーヘルプで DocumentUniqueID, UniversalIDなどと呼ばれているものを指します。

ユーザーから「文書を削除しちゃったから復活してよ~」と泣き付かれると、nsfファイルをバックアップのテープからローカルへ落とし、削除された文書をコピーして本番DBへペーストします。

この場合、手動でコピーペーストするので文書の UNID はもとの文書の UNID とは違います。

もし削除された文書へのリンクが他の文書にある場合、それをクリックした人は「文書が削除されました」と Notes に怒られてしまいます。

そんなことがないよう、コピー後した文書の UNID をコピー元文書と一致させてあげます。
すると文書リンクが機能するようになります。

UNID は文書のプロパティで[ヘッダー情報]タブの"識別子"等で確認できます。
"識別子"の最後の32桁が UNID になります。

ただ、32桁などとても覚えられるわけもなく、コピーする文書が多い場合など、とても大変です。

そんな面倒くさがりな私のために作ったのが下の Lotus Script です。

ビューで文書を選択して Ctrl + C 等で文書をクリップボードにコピーした後、コピー先のアプリケーションへ仕込んだ次のエージェントを実行します。

するとクリップボードへコピーした文書が、現在開いているアプリケーションへコピーされます。
もちろんコピー元の UNID の値と同じになります。
Sub Initialize
    Dim ss As New NotesSession
    Dim db As NotesDatabase, clipbrd As NotesDatabase
    Dim dc As NotesDocumentCollection
    Dim doc As NotesDocument, cdoc As NotesDocument

    Set db = ss.CurrentDatabase
    Set clipbrd = ss.GetDatabase( "", "~clipbrd.ncf" )
    If clipbrd Is Nothing Then Exit Sub
    If Not clipbrd.IsOpen Then Exit Sub

    Set dc = clipbrd.AllDocuments
    If dc.Count = 0 Then Exit Sub

    On Error Goto ERRORHANDLER

    Set cdoc = dc.GetFirstDocument
    While Not ( cdoc Is Nothing )
        Set doc = Nothing
        Set doc = db.GetDocumentByUNID( cdoc.UniversalID )
        If Not (doc Is Nothing) Then
            res = doc.RemovePermanently( True )
        End If
        Set doc = db.CreateDocument
        Call cdoc.CopyAllItems( doc )
        doc.UniversalID = cdoc.UniversalID
        Call doc.Save( True, False )
        Set cdoc = dc.GetNextDocument( cdoc )
    Wend
    Exit Sub
ERRORHANDLER:
    Print Err & ", " & Error 'GetDocumentByUNID で 「4091 ユニバーサル ID が無効です。」がでる
    Resume Next
End Sub
上記の "~clipbrd.ncf" は Windows のクリップボードで処理できないものを一時的にコピーしておく場所です。

"~clipbrd.ncf" については懇談室で紹介したことがありますが、本当はあまり使いたくありません。

ですので上のエージェントを実行した後には、念のためひとつひとつUNIDを確認しています...orz

2010年2月12日金曜日

NotesACL.GetEntry は大文字小文字を区別しない

アプリケーションのACLとして管理者グループ "Administrators" を登録していますが、稀にエントリの名前が "administrators" のようにすべて小文字だったりして気持ち悪い思いをしています。

そこでこれを機械的に変更してやろうかと思い立ち、エージェントを作っていたのですが、思いのほか時間がかかってしまいました。

その原因が標記の件です。

デザイナーヘルプでは NotesACL.GetEntry のパラメータについて「大文字と小文字を区別して、検索対象の名前と完全に一致する名前を指定しなければなりません。」と書かれています。

そこで試しました。

ACL に "Administrators" と頭文字だけ大文字で登録されているエントリに対して NotesACL.GetEntry( "aDMINISTRATORS" ) と大文字小文字を逆にすると、きっとエントリは取得できないのだろう思っていました...

が、見事エントリは取得できるのです。

"Option Compare Binary" を指定しても変わらず。(Case, Pitchも同様)

デザイナーヘルプにはいつも裏切られっぱなし...orz

しかも取得したエントリの Name プロパティの値は、パラメータで指定した "aDMINISTRATORS" です。

結局のところ、大文字/小文字は区別しないことが判明しました。

ACLエントリの大文字小文字なんて気にするな、ということでしょうか...

わかったよ...もう気にしないさ...

ちなみに、全角で "ADMINISTRATORS" としてみたところ、こちらは取得できず、全角半角は区別するようでした。
(Windows Vista + Notes 8.0.1 の環境で確認)

2010年2月10日水曜日

フィールドが大きすぎる(32K)、またはビューの列と選択式が大きすぎます。

ある掲示板のエントリについてコメントしましたが手抜きになりましたので、ここに書き直します。

標記のメッセージが表示される文書を作ってみました。
Set doc = ss.CurrentDatabase.CreateDocument
doc.Form = "Main"
Set rtitem = New NotesRichTextItem(doc, "Body")
For i = 1 To 3277
    Call rtitem.AppendText("AAAAAAAAAA")
Next
Call doc.Save(True, True)
v = doc.GetItemValue("Body")
doc.Body1 = v
Call doc.Save(True, True)

作成した文書はビューに表示できますが、ダブルクリックなどアクションをすると「フィールドが大きすぎる(32K)、またはビューの列と選択式が多きすぎます。」と表示されます。
以下、この文書からアイテム Body1 を削除して復活させるべく、試した内容と結果です。


次の式で選択文書からのフィールドの削除を試みたが「フィールドが大きすぎる(32K)、またはビューの列と選択式が多きすぎます。」が表示される。
FIELD Body1 := @DeleteField

Lotus Script では NotesDocument オブジェクトは取得できるものの、そのプロパティやアイテムにはアクセスできません。
デバッグモードで見ると NotesDocument.IsValid は False (削除スタブと同じ)、作成日等はゼロになっています。
Set dc = ss.CurrentDatabase.Search(|@Length(Body1) > 32767|, Nothing, 0)
If dc.Count > 0 Then
    Set doc = dc.GetFirstDocument
    While Not doc Is Nothing
        Forall item In doc.Items '<<- エラー「T003 型が一致しません」
            If item.Name = "Body1" Then
                Call item.Remove
                Exit Forall
            End If
        End Forall
        Call doc.Save(True, True)
        Set doc = dc.GetNextDocument(doc)
    Wend
End If
Set dc = ss.CurrentDatabase.Search(|@Length(Body1) > 32767|, Nothing, 0)
If dc.Count > 0 Then
    Set doc = dc.GetFirstDocument
    While Not doc Is Nothing
        Set item = doc.GetFirstItem("Body1") '<<- ここで item は Nothing になる
        If Not item Is Nothing Then
            Call item.Remove
            Call doc.Save(True, True)
        End If
    Set doc = dc.GetNextDocument(doc)
    Wend
End If
文書だけのnoteコレクションを作ろうにもエラーになります。
Set nc = db.CreateNoteCollection( False )
nc.SelectDocuments = True
Call nc.BuildCollection '<<- ここで「フィールドが大きすぎる(32K)、またはビューの列と選択式が多きすぎます。」
XMLへ書き出ししようにもエラーになります。
Set dc = ss.CurrentDatabase.Search(strFormula$, Nothing, 0)
Set nc = db.CreateNoteCollection( False )
Call nc.BuildCollection
Call nc.Add( dc )
Set stream = ss.CreateStream
If Not stream.Open( filename$ ) Then Exit Sub
Call stream.Truncate
Set exporter = ss.CreateDXLExporter( nc, stream )
Call exporter.Process '<<- ここでエラー「DXL exporter operation failed」
フルアクセスアドミニストレーションで実行しても変わらずエラーが表示されます。 以上が試したことです。 現状「お手上げ」です。 復活は無理かも。 ちなみに、テキストフィールドの値が32KBを超える場合でも NotesItem.IsSummary へ False を設定すると、ビューから「異常な」文書を開こうとしてもエラーになりません。ただしビューには表示できませんが...orz
Set item = doc.GetFirstItem( "Body1" )
If item.ValueLength > ( 32 * 1024 ) Then
    item.IsSummary = False
End If

それから、アプリケーションのプロパティで「一時的削除を許可」を有効にすると、[Delete] -> [F9]でビューから「異常な」文書を削除できなくなります。[F9]を押したとき「フィールドが大きすぎる(32K)、またはビューの列と選択式が多きすぎます。」が表示されます。

次のコマンドをクライアントのコマンドプロンプトから投入したところ「異常な」文書だけが全件削除されました。
ncompact -c -i -D hogehoge.nsf

2010年2月8日月曜日

COMで GetAllUnreadEntries は使えなかった...

懇談室のこのエントリについて Notes 8 で追加された未読文書に関するメソッドは使えないものかと試してみました。

「メモ帳」等のテキストエディタを開き、次のコードを .vbs という拡張子で適当なフォルダに保存します。
Dim ss
Set ss =CreateObject("Lotus.NotesSession")
Call ss.Initialize

Dim dir
Set dir = ss.GetDbDirectory("")

Dim db
Set db = dir.OpenMailDatabase

Dim vw
Set vw = db.GetView("($Inbox)")

'Dim vc
'Set vc = vw.GetAllUnreadEntries(ss.UserName)
'MsgBox "未読件数 : " & Cstr(vc.Count), , db.Title

Dim nav
Set nav = vw.CreateViewNav

Dim ent
Set ent = nav.GetFirst

Dim v
v = ent.ColumnValues
MsgBox v(6), , ""

Set v = Nothing
Set ent = Nothing
Set nav = Nothing
'Set vc = Nothing
Set vw = Nothing
Set db = Nothing
Set dir = Nothing
Set ss = Nothing
.vbs ファイルをダブルクリックして実行すると、自身のメールアプリケーションにある受信ボックスにある最初のエントリのタイトルを表示します。

実行するとき、Lotus Notes は起動していなくてもいいのですが、実行するPCには Lotus Notes がインストールされ、セットアップが完了していなければなりません。

上記コードから Set vc = vw.GetAllUnreadEntries(ss.UserName) の1文字目についているコーテーションをとって実行すると次のエラーが表示されます。

オブジェクトでサポートされていないプロパティまたはメソッドです。: 'GetAllUnreadEntries'
コード: 800A01B6
ソース: Microsoft VBScript 実行時エラー

Notes 8.0.1 が導入されているクライアントPCでは COM で GetAllUnreadEntries は使えませんでした。
ちなみに NotesDatabase.GetAllReadDocuments も同様のエラーになりました。

COM でサポートされていれば、上記のスレ主さんも「将来は実現できる」と希望がもてたでしょうに...orz

2010年2月5日金曜日

この紙はNotesで印刷したもの?

以前のエントリでNotes文書を印刷できないよう制御する方法をご紹介しました。

今回はNotesで印刷したものか、画面のハードコピーを印刷したものかを判別できるようにする仕組みをご紹介します。

ちなみに、印刷物を判別可能にするといった案件にはお目にかかったことはありません...駄文になりそうな予感...orz

文書を開いたときと印刷したときで見える内容が異なれば「画面キャプチャの印刷物」か「Notes機能による印刷物」かを区別できます。

そこで印刷時に表示/非表示する設定についてご紹介します。

A. ヘッダー、フッターを利用する

フォームのプロパティにある[印刷]タブでは、印刷時に表示するヘッダーやフッターの内容を設定できます。


B. 段落非表示を利用する

文字やフィールド等のプロパティにある[段落非表示]タブで"印刷"にチェックを付けておくと、印刷したときに文字やフィールドが表示されなくなります。

これを利用すると、印刷物に特定の文字や画像があれば、それは「画面のキャプチャ」であり「原本ではない」といったような使い方ができます。


C. セクションを利用する

セクションのプロパティで[展開/省略]タブでは"文書の展開省略の規則"で印刷時に「自動展開する」や「自動省略する」が選択できます。

「自動展開する」に設定することでNotesで印刷したときに展開して表示したり、コンテンツをセクションに含めて「自動省略する」に設定することで印刷時にはコンテンツが表示されないよう制御することができます。

2010年2月4日木曜日

リストから目的の値を探すには

今回はある文字列(または文字列リスト)が別の文字列リストに含まれているかどうかを調べる方法をご紹介します。

調べるパターンは次のとおり。

A. 特定の値だけ(を含む)
B. すべての値を含む
C. いずれかの値を含む

ここではロールを使って権限をチェックする@関数式を例にします。

ロールとして次の3種類がアクセス制御リスト(ACL)に定義されているものとします。

[Admin].....管理者
[Approver]..承認者
[Checker]...点検者

現在のユーザーに付与されたロールを調べるには @UserRoles を使います。
ユーザーに複数のロールが付与されている場合、@UserRoles の戻り値はリストになります。

[Admin] を含む時、"管理者" を表示する
@If(@IsMember("[Admin]"; @UserRoles); "管理者"; "")

[Admin] を含まない時、"非管理者" を表示する
@If(@IsNotMember("[Admin]"; @UserRoles); "非管理者"; "")

[Admin] [Approver] の両方を含む時、"管理者兼承認者" を表示する
@If(@IsMember("[Admin]":"[Approver]"; @UserRoles); "管理者兼承認者"; "")

[Admin] [Approver] [Checker] の3つを含む時、"管理者兼承認者兼確認者" を表示する
@If(@IsMember("[Admin]": "[Approver]": "[Checker]"; @UserRoles); "管理者兼承認者兼確認者"; "")

[Admin] [Approver] のどちらかを含む時、"管理者または承認者" を表示する
@If(@Elements(@Keywords("[Admin]": "[Approver]"; @UserRoles; ""))>0; "管理者または承認者"; "")

[Admin] [Approver] [Checker] の3つのうちどれかを含む時、"ロール保有者" を表示する
@If(@Elements(@Keywords("[Admin]": "[Approver]": "[Checker]"; @UserRoles; ""))>0; "ロール保有者"; "")

@関数式では変数の値や関数からの戻り値が複数値かどうかを見極める機会が Lotus Script で組む場合よりも少ないのがいいな、と思います。

2010年2月3日水曜日

NotesDirectory クラスをさわってみた(2)

Notes 8 で追加されたクラス NotesDirectory のメソッド LookupNames を使い、ディレクトリ・アシスタンスで定義されている複数のドミノディレクトリからユーザー文書の短縮名を取得する処理を作成しています。
※とりいそぎ業務で使う予定はありませんが今後の参考のため...

今回の目標としては、次の式と同じ結果が戻ってくれば成功としています。

@StatusBar(@Implode(@NameLookup([Exhaustive]; "ichiro suzuki"; "ShortName"); ", "));
@StatusBar(@Implode(@NameLookup([Exhaustive]; "hogehoge"; "ShortName"); ", "));
@StatusBar(@Implode(@NameLookup([Exhaustive]; "sato"; "ShortName"); ", "));

名前を正常に検索できた場合、下表の ShortName が戻ります。
Name ShortName 1 ShortName 2 ShortName 3
ichiro suzuki sichiro si42
hogehoge
sato staro sjiro psato90

デザイナーヘルプによると LookupNames の2番目のパラメータ(以下 names) は Variant 型で指定できるのでスカラー値でも配列でも動作するようです。

そこで、検索したい名前がいくつもあるという前提で、配列で定義した names へ値を格納して実行するロジックを組み、検索する名前の順序を変化させてテストを繰り返してみたのですが、とても納得できる挙動にはならず目標が達成できませんでした。

結局 names を配列ではなくスカラー値とし、また名前を検索するたびに NotesDirectory オブジェクトを取得するロジックとしたところ、ようやく上の式と同じ結果が得られる様になりました。
Sub Initialize
    Dim strName(2) As String
    strName( 0 ) = "ichiro suzuki" '2件存在する
    strName( 1 ) = "hogehoge" '名前が存在しない
    strName( 2 ) = "sato" '3件存在する
    Forall o In strName
        Call LookupName( o )
    End Forall
End Sub

Sub LookupName( nam As String )
    Dim ss As New NotesSession
    Dim nd As NotesDirectory
    Dim ndnav As NotesDirectoryNavigator
    Dim var As Variant
    Dim init( 0 ) As String
    var = init
    Set nd = ss.GetDirectory( "hogehoge/org" )
    Set ndnav = nd.LookupNames( "$Users", nam, "ShortName", False )
    While ndnav.MatchLocated
        var = Arrayappend( var, ndnav.GetFirstItemValue )
        ndnav.FindNextMatch
    Wend
    Print Join( Fulltrim( var ), ", " )
End Sub
以下に NotesDirectoryNavigator について「理解した」ことをご紹介します。

LookupNames の検索結果へアクセスするには NotesDirectoryNavigator クラスを使います。

LookupNames を実行した直後の NotesDirectoryNavigator クラスには names の最初の名前の検索結果に位置付けられます。
names へ設定した「検索する名前」へ位置づけるのに
FindFirstName, FindNextName, FindNthName といったメソッドを使います。
LookupNames メソッドを実行したときには names の最初の名前に位置付けられます。

「検索する名前」へ位置づけた時、一致(Match)した名前の数が CurrentMatches プロパティにセットされます。
※CurrentMatches は8のデザイナーヘルプに掲載されていません...orz

例えば "suzuki" をディレクトリで検索すると一致する名前はたくさん見つかるかもしれません。
上の例の場合、"ichiro suzuki" は2エントリ(ユーザー文書の数)ですから CurrentMatches は 2 がセットされます。
CurrentMatches が 0 ではない時、一致する名前があることを示しています。

FindFirstMatch, FindNextMatch, FindNthMatch といったメソッドを使い、検索結果に一致する名前に位置決めをします。
位置づけられている名前の位置を示すのは CurrentMatch プロパティです。(名前がややこしい...)

以上、事実関係を並べましたがこういった説明がデザイナーヘルプに欲しかったよ...


未だによくわからないのが、NotesDirectory オブジェクトを使いまわす場合と、名前ごとに NotesDirectory オブジェクトを取得する場合で結果が異なることです。

「NotesDirectory キャッシュ」や「検索バッファ(namelookupバッファ?)」がどう影響して、FreeLookupBuffer メソッドはどのように効果を発揮するのか....理解するには時間がかかりそうです。


そして、とても気になっていることは、

・検索結果が3つになるはずの名前を FindNextName で位置づけたとき CurrentMatch が 2 になる...なぜ1番目に位置づけされないのか?

・FindNthMatch( i ) で i を1から順に変化させたときの CurrentMatch プロパティの値をデバッガで観察すると、どうしたわけか値は 1 -> 2 -> 3 と推移せず 2 -> 2 -> 3 と推移する

・FindNthMatch( 1 ) で CurrentMatch が 2 なのに GetFirstItemValue で 1 番目の値がとれた...orz

以下、おかしな挙動をするロジックです。ご参考まで
Dim ss As New NotesSession
Dim nd As NotesDirectory
Dim ndnav As NotesDirectoryNavigator
Dim strName(2) As String
Dim var As Variant
Dim init(0) As String
Dim cnt As Long
strName(0) = "ichiro suzuki" '2文書存在する
strName(1) = "hogehoge"      '存在しない名前
strName(2) = "sato"        '3文書存在する
Set nd = ss.GetDirectory( "hogehoge/org" )
Set ndnav = nd.LookupNames( "$Users", strName, "ShortName", False )
While cnt < Ubound( nd.AvailableNames ) + 1
    cnt = cnt + 1
    If ndnav.NameLocated Then
        For i = 1 To ndnav.CurrentMatches
            ndnav.FindNthMatch( i )
            var = Arrayappend( var, ndnav.GetFirstItemValue )
        Next
        Print Print Join( Fulltrim( var ), ", " )
    End If
    ndnav.FindNextName
Wend

2010年2月2日火曜日

非表示式とフィールドヘルプで文字がグレーになる

入力項目が多いフォームを使っているアプリケーションでは、入力が面倒なので既存文書の内容をコピーした新規文書を開く機能を作成することがあります。

先日、テスト中のアプリケーションで上記機能に関するちょっとした不具合がありました。

その内容とは、上記の機能で開いた文書では、コピーした値の一部がグレー表示になる、というものです。

では、設計をみてみます。

フォームは次のとおりです(説明の都合上、入力項目を最小限にしています)

・ダイアログリスト(Category)とテキスト(Date, Subject)のフィールドがあります。
・テキストフィールドには非表示式を設定しており、ダイアログリストで選択した値により表示されません。
・テキストフィールドにはフィールドヘルプが設定されています。新規文書のとき、"必須項目:"という文言を薄い色で表示します。

次は開いている文書の値をコピーして新規文書を開くアクションボタンの式です。

tmpCategory := Category;
tmpDate := Date;
tmpSubject := Subject;
@Command([Compose]; "MainTopic");
@UpdateFormulaContext;
@SetField("Category"; tmpCategory);
@SetField("Date"; tmpDate);
@SetField("Subject"; tmpSubject);
@Command([ViewRefreshFields])

上の式をNotes 8.0.1 で実行すると、コピーされた Date フィールドの値がグレー表示されます。
このとき Subject フィールドはグレー表示にはなりません。

設計を少し変えてテストを行い次の現象を確認しました。

・Date フィールドのフィールドヘルプの設定(文言のテキスト)を削除.....グレー表示にならない
・Date フィールドの非表示式を削除.....グレー表示にならない

どうやらフィールドヘルプを設定したフィールドに非表示式があるとグレー表示になるようです。

ここでフォームの設計を基にもどして、非表示式だけを更新する @Command([RefreshHideFormulas]) を試してみることにしました。

<一部省略>
@SetField("Category"; tmpCategory);
@SetField("Date"; tmpDate);
@Command([RefreshHideFormulas]);
.....グレー表示になる

<一部省略>
@SetField("Category"; tmpCategory);
@Command([RefreshHideFormulas]);
@SetField("Date"; tmpDate);
.....グレー表示にならない

つまり、非表示式に関連する項目をセットした後に非表示式を更新するといいようです。
結果、次ように式を変更することで不具合を解消することができました。

tmpCategory := Category;
tmpDate := Date;
tmpSubject := Subject;
@Command([Compose]; "MainTopic");
@UpdateFormulaContext;
@SetField("Category"; tmpCategory);
@Command([RefreshHideFormulas]);
@SetField("Date"; tmpDate);
@SetField("Subject"; tmpSubject)

※@Command([ViewRefreshFields])は非表示式を更新するためだけに利用していましたが@Command([RefreshHideFormulas])の追加により不要になったので削除しました。

2010年2月1日月曜日

Lotus Script のリストってどう使うの?

Lotus Script で複数の値を整理可能な入れ物に保持したい時「リスト」あるいは「配列」を使います。

リストの最大の特徴は、値を入れる「部屋」(デザイナーヘルプにあわせて以降は"要素"と呼びます)の整理記号(キー)としてテキストが使えることだと思います。

各要素の値にアクセスするとき、配列では数値を指定するのに対して、リストでは名前を指定します。

今回はリストで使える関数などをご紹介します。

リスト変数を宣言するには List を指定します。

Dim vList List As Integer


変数がリストかどうかは IsList で調べます

If IsList( vList ) Then Print ""


各要素には名前を付けます。

vList( "Suzuki" ) = 1
vList( "Sato" ) = vList( "Sato" ) + 1
vList( "Yamada" ) = 2


個々の要素の値を呼び出すには名前を指定します。

Print vList( "Suzuki" ) '1が表示されます


すべての要素の値を呼び出すには Forall を使います。

Forall o in vList
Print o
End Forall


Forall の中では Listtag を使って要素の名前を調べることができます。

Forall o in vList
Print Listtag( o )
End Forall


リストに要素の名前があるかどうかを調べるには IsElement を使います。

If Iselement( vList( "Sato" ) ) Then
Print vList( "Sato" )
Else
Print "Not member"
End If