2010年10月28日木曜日

メール文書を開くと、メール文書は開かずに他のアプリケーションの文書が開く仕組み

ワークフローなどのアプリケーションで依頼や通知のメールを送信することがありますが、相手にリンク先の文書を開いてもらうには、メールの本文へ文書リンクを挿入しておき、それをクリックしてもらうといったが運用が多いかと思います。

これからご紹介するのは、メールに文書リンクを挿入しますが、受信者はリンクをクリックしなくても、文書を開くだけで他のアプリケーションの文書が開くようにする仕掛けです。

まずはメールを送信するためのボタンに設定する LotusScript のサンプルです。
Sub Click(Source As Button)
    Dim ws As New NotesUIWorkspace
    Dim uidoc As NotesUIDocument
    Dim ss As New NotesSession
    Dim doc As NotesDocument
    Dim rtitem As NotesRichTextItem
    Set uidoc = ws.CurrentDocument
    Call uidoc.Save
    Set doc = New NotesDocument( ss.CurrentDatabase )
    doc.Form = "Link"
    doc.Subject = "[Request] " & uidoc.Document.Subject(0)
    Set rtitem = New NotesRichTextItem( doc, "Body" )
    Call rtitem.AppendDocLink( uidoc.Document, uidoc.Document.Subject(0) )
    Call doc.Send( True, "Hoge Hoge/ORG" )
    Set doc = Nothing
    Call uidoc.Close( True )
End Sub

このサンプルで作られる文書(doc)ではフォーム名として "Link" を指定しています。※フォーム名は"Link"以外でも結構です
この"Link"フォームをDBに作成しておき、フォーム上に2つのフィールド"Subject"と"Body"を配置します。
そして"Link"フォームのプロパティを開き、[起動]タブにある「自動起動」の値を "-最初の文書リンク-"に変更します。

作成した文書を送信するとき、Send メソッドの第一パラメータへ True を指定すると、文書にフォームの設計情報が埋め込まれます。
つまり「フォームの最初にある文書リンクを自動起動する」情報が埋め込まれるわけです。

こうして送信されたメールをビューからダブルクリックして開くと、リンク先の文書が開きます。

この仕組みの惜しいところは、プリビューペインで開いた場合はリンク先の文書は開かず、埋め込まれたフォームの内容が表示されるところです。
プリビューペインで開くことを想定して、「リンクをクリックして文書を開きなさい」的な文言を追加したり、"Link"フォームの設計を工夫するのが良いと思います。

2010年9月27日月曜日

特定のロールがついたACLエントリを宛先とするメール作成画面を開く

Notes Q&A のサイトにあったこのエントリについて、質問の意図を勘違いして回答した挙句「@関数ではできない」とさらにつまらない回答してしまったものの「Lotus Script ならできそうです」と言いにくい感じだったので、こちらにサンプルを掲載することにしました。
Sub Initialize
    Dim ss As New NotesSession
    Dim acl As NotesACL
    Dim entry As NotesACLEntry
    Dim nam As NotesName '階層名を省略形にするために利用
    Dim admin As String '宛先を格納する変数
    Dim ws As New NotesUIWorkspace
    Dim db As New NotesDatabase( "", "" )
    Dim uidoc As NotesUIDocument
    
    'ACLエントリに特定のロールが付与されている場合、宛先へ追加する
    admin = ""
    Set acl = ss.CurrentDatabase.ACL
    Set entry = acl.GetFirstEntry
    While Not ( entry Is Nothing )
        If Not Isnull( Arraygetindex( entry.Roles, "[Admin]", 0 ) ) Then
            Set nam = New NotesName( entry.Name )
            admin = admin & nam.Abbreviated & ", "
        End If
        Set entry = acl.GetNextEntry( entry )
    Wend
    
    'メール作成画面を開き、宛先をセットする
    Call db.OpenMail
    Set uidoc = ws.ComposeDocument( db.Server, db.FilePath, "Memo" )
    Call uidoc.GotoField( "EnterSendTo" )
    Call uidoc.InsertText( admin )
End Sub

これはサンプルなので手抜きですが、実際にはサーバーやサーバーグループのような不要なエントリを外したり、宛先フィールドのサイズ制限を考慮した仕組みが欲しくなるかと思います。

2010年8月19日木曜日

すべてのアプリケーションの利用状況を調べる

log.nsfではデフォルトで数日分の情報が蓄積され、それ以前の情報は破棄されます。
当方では過去ログを追うために保存用のDBを用意しています。
保存用のDBの実態は log.nsf のレプリカなのですが、log.nsf で破棄された情報が保存用へ伝わらないようにしています。
他のサーバーにある保存用DBへ毎朝、複製するよう接続文書で設定しています。

そうして蓄積された情報の中に Session文書があります。
Session文書にはユーザーやサーバーが読み書きしたアプリケーションごとの件数などがアプリケーションごとに記録されています。

当方ではSession文書を集計することで、ユーザーのアクセスが無いものをチェックしています。

以下は保存用DBから半年分のSession文書から読み込みと書き込みの件数を集計して、タブ区切りのテキストファイルへ書き出すサンプルです。
Sub Initialize
    'logArchive.nsf をサーチして半年間のSession文書を取得する
    Dim strTAB As String, strCRLF As String
    Const ARCHIVE_SERVER = "hogehoge/org"
    Const ARCHIVE_FILE = "LogArchive.nsf"
    strTAB = Chr(9)
    strCRLF = Chr(13) & Chr(10)
    Dim listExclude List As String, listCounter List As String
    listExclude("CN=Administrator/O=org") = "1" '1に意味は無い
    listExclude("CN=hogehoge/O=org") = "1"
    
    Dim logdb As NotesDatabase
    Dim dc As NotesDocumentCollection
    Dim doc As NotesDocument
    Dim ndt As New NotesDateTime( "Today" )
    Call ndt.AdjustMonth( -6 ) '半年分の情報を検索する
    
    '検索する
    Set logdb = New NotesDatabase( ARCHIVE_SERVER, ARCHIVE_FILE )
    Print "検索開始"
    Set dc = logdb.Search( | Form="Session" |, ndt, 0 )
    Print "検索終了、取得した文書数 : " & Cstr( dc.count ) & ", 集計開始"
    
    '集計する
    Set doc = dc.GetFirstDocument
    While Not ( doc Is Nothing )
        If doc.HasItem( "Body" ) Then
            UserName$ = doc.GetItemValue( "UserName" )(0)
            If Not Iselement( listExclude( UserName$ ) ) Then
                rtBody = Split( doc.GetItemValue( "Body" )(0), strCRLF )
                For i% = 0 To Ubound( rtBody )
                    varData = Split( rtBody(i%), strTAB )
                    strPath$ = Lcase$( varData( 0 ) )
                    If Right( strPath$, 4 ) <> ".ntf" Then
                        If Iselement( listCounter( strPath$ ) ) Then
                            varRW = Split( listCounter( strPath$ ), strTAB )
                            listCounter( strPath$ ) = Cstr( Cdbl( varRW(0) ) + Cdbl( varData(1) ) ) & _
                            strTAB & Cstr( Cdbl( varRW(1) ) + Cdbl( varData(2) ) )
                        Else
                            listCounter( strPath$ ) = Cstr( varData(1) ) & strTAB & Cstr( varData(2) )
                        End If
                    End If
                Next i%
            End If
        End If
        Set doc = dc.GetNextDocument( doc )
    Wend
    Print "集計終了, 出力開始"
    
    '出力する
    Dim objWshShell As Variant
    Set objWshShell = CreateObject("WScript.Shell")
    tmpPath$ = objWshShell.SpecialFolders("Desktop")
    filename$ = tmpPath$ & "\半年間の読み書き件数.txt"
    fileNum% = Freefile()
    Open filename$ For Output As fileNum%
    Print #fileNum%, "ファイルパス" & strTAB & "読み込み" & strTAB & "書き込み"
    Forall o In listCounter
        Print #fileNum%, Listtag( o ) & strTAB & o
    End Forall
    Close fileNum%
    Print "出力終了、処理完了"
End Sub

補足すると、アプリケーションごとの読み書き件数は Session文書の "Body" アイテムにあります。
アプリケーションのファイルパス、読み込み件数、書き込み件数などがタブで区切られ、
これらがアプリケーションごとに改行で区切られています。

また、ここではサーバーや管理者のSession文書を除外しています。

サンプルでは読み込み件数と書き込み件数のみを集計していますが、最終アクセス日付をSession文書の日付情報から求めることもできそうですね。

2010年7月28日水曜日

すべてのビューのサイズを調べる

Notesのアプリケーションには「ビュー」という登録されている文書をリスト表示するのに便利な設計要素がありますが、最近になってビューに関連すると思われる不具合が1週間に2度も発生しました。

ユーザーがDBを開こうとしたりすると
「メモリが足りません。インデックスプールがいっぱいです」
といったメッセージが表示されるようになったのです。※表示されないユーザーもいました

IBMの情報(下のリンク)によると、当現象は Domino 6.5.3 の不具合とのこと。
定期的に「インデックスプールがいっぱいです」というメッセージが表示される
http://www-06.ibm.com/jp/domino04/lotus/support/faqs/faqs.nsf/all/729123

どうやら NIF Pool が制限値の128MBに達すると発生するようです。

確かに、2度目に発生した時の統計情報の値 (Database.NIFPool.Used) は8MB付近から一気に制限値へ増加していました...

そもそもこの不具合がどうして突然発生するようになったのか...??
最近作成したアプリケーションでどでかいビュー索引を作った覚えもないし...??

まずはサーバー上にある全てのビューのサイズをチェックしようと考え、次のようなスクリプトを作りました。
Sub Initialize
    Dim ss As New NotesSession
    Dim logdb As NotesDatabase
    Dim logdoc As NotesDocument, doc As NotesDocument
    Dim logdc As NotesDocumentCollection
    Dim item As NotesItem
    Dim views As Variant
    dlmCRLF = Chr( 13 ) & Chr( 10 )
    
    Set logdb = New NotesDatabase( "hogehoge/org", "log.nsf" )
    If logdb Is Nothing Then Exit Sub
    Set logdc = logdb.Search( |Form = "Activity"|, Nothing, 0 )
    If logdc.Count = 0 Then Exit Sub
    Set logdoc = logdc.GetFirstDocument
    While Not ( logdoc Is Nothing )
        If logdoc.HasItem( "AllViewInfo" ) Then
            Set item = logdoc.GetFirstItem( "AllViewInfo" )
            views = Split( item.Values, dlmCRLF )
            Forall o In views
                Set doc = New NotesDocument( ss.CurrentDatabase )
                doc.Form = "view"
                doc.Title = logdoc.Title( 0 )
                doc.PathName = logdoc.PathName( 0 )
                doc.ViewName = Strleftback( o, " ", 1 )
                doc.ViewSize = Strrightback( o, " ", 2 )
                Call doc.Save( True, False )
                Set doc = Nothing
            End Forall
        End If
        Set logdoc = logdc.GetNextDocument( logdoc )
    Wend
End Sub
簡単に説明します。

log.nsf には毎朝5時に statlog タスクにて作成・更新される Activity というフォームの文書があり、ここに各アプリケーション内にあるすべてのビューやフォルダの名前とサイズが記録されています。
log.nsf にある全ての Activity 文書を参照して、改行で区切られた各ビューの情報を分解して書き出します。
こうして書き出した情報をビューで工夫するとビューの数が多いアプリケーションやサイズが大きいビューがわかります。

不具合に関しては現在のところ、アップデートするまでに再現した場合に何かの参考になるかと考え LOG_UPDATE=1 を設定して様子をみていますが、近い将来Domino を 6.5.6 へアップデートします。

2010年6月15日火曜日

全文索引が壊れた?

全文索引のあるアプリケーションでビュー上に検索バーを出して英単語で検索すると自分は3件しか表示されないのに隣の人の結果は180件表示される、と問い合わせがありました。
確認したところ、オプションの「あいまい検索」にチェックが入っている(有効にする)と件数が少なくなることがわかりました。
普通なら「あいまい検索」のオプションを"無効"にするより"有効"にするほうが件数は増えるはずです(よね?)。
そこで全文索引の異常を疑い、再作成してみました。
Dominoコンソールから次のコマンドを投入

load updall hogehoge.nsf -X

この後「あいまい検索」を"有効"にしたときの件数が"無効"のときの件数を超えました。
全文索引を再作成して件数が改善したということは、つまり全文索引が壊れていたということでしょうか...?
上のアプリケーションの全文索引はつい数日前にも更新されており、その時のログ(log.nsf)には異常を示すようなメッセージはありません。
全文索引の異常ってどうやって見つけるのだろう...
これがわからないとなると、定期的に再作成するのがよいのかも。

2010年4月21日水曜日

リッチテキストへの変更内容を反映させる

現在開いている文書のリッチテキストフィールドへバックエンドで変更を加えても、文書を開き直さないと変更内容が反映されません。

そこで内容を動的に反映するために文書を開き直すサンプルを作ってみました。

このサンプルではリッチテキストフィールドの編集例として "Body" の一番下の位置へファイルを添付しています。
このエージェントのプロパティでは実行の対象を"なし"にしてください。

Sub Initialize
    Dim ws As New NotesUIWorkspace
    Dim ss As New NotesSession
    Dim uidoc As NotesUIDocument
    Dim doc As NotesDocument
    Dim db As NotesDatabase
    Dim rtitem As NotesRichTextItem
    Dim unid$, pathname$
    Dim neo As NotesEmbeddedObject
    Dim editmode As Boolean
    
    Set uidoc = ws.CurrentDocument
    If uidoc Is Nothing Then Exit Sub
    '開き直すときのモードを現在の状態と一致させる
    editmode = uidoc.EditMode
    
    '編集モードなら変更を反映させるため保存する
    If editmode Then Call uidoc.Save
    
    'バックエンドでリッチテキストフィールドを編集して保存
    pathname = "C:\TEMP\HOGE.TXT"
    Set doc = uidoc.Document
    Set rtitem = doc.GetFirstItem( "Body" )
    Set neo = rtitem.EmbedObject( 1454, "", pathname )
    Call doc.Save( False, False )
    unid = doc.UniversalID
    
    'フロントエンド文書を閉じる。保存プロンプトは出さない
    If editmode Then doc.SaveOptions = "0"
    Call uidoc.Close
    
    'バックエンド文書を取得し直してフロントエンドで開く
    Set db = ss.CurrentDatabase
    Set doc = db.GetDocumentByUNID( unid )
    Call ws.EditDocument( editmode, doc )
End Sub
現在開いている文書が編集モードでも読み込みモードでも動作します。

2010年3月27日土曜日

フォームを自動作成する

懇談室の話題についてサンプルを作ってみました。

設計情報が書かれた XML ファイルを読み込んでフォームを作ることができました。

以下、確認したバージョンは 8.0.1 ですが 6.0.x でも動作すると思います(...たぶん)

まずはエージェントのサンプルです。
Sub Initialize
    Dim ss As New NotesSession
    Dim stream As NotesStream
    Dim db As NotesDatabase
    Dim importer As NotesDXLImporter
    
    Set stream = ss.CreateStream
    If Not stream.Open("c:\temp\template.xml") Then Exit Sub
    
    Set db = ss.CurrentDatabase
    Set importer = ss.CreateDXLImporter(stream, db)
    
    importer.DesignImportOption = DXLIMPORTOPTION_REPLACE_ELSE_CREATE
    Call importer.Process
End Sub

サンプルとして用意した XML ファイルは次のとおりです。
<?xml version="1.0" ?>
<database xmlns="http://www.lotus.com/dxl">
<form name="Memo1">
<body>
<richtext>
<par def="1"><field kind="editable" name="Subject" type="text"></field></par>
<par def="1"><field kind="editable" name="Body" type="richtext"></field></par>
</richtext>
</body>
</form>
</database>

2010年3月24日水曜日

「Designer で開く」が消えた!?

Domino Designer がインストールされていれば、ワークスペース上で設計者以上の権限を持つアプリケーションのアイコンを右クリックして表示されるリストに「Designer で開く」が出てきます。

「Designer で開く」が表示されない場合、Notesのメニューにある「表示」をクリックして「詳細メニューの表示」にチェックが付いていないかもしれません。

いつもどおりアイコンを右クリックして表示されなくなったら「ローカルの設定ファイルが壊れた?」とか 「Designerを再インストール?」などといろいろ考えてしまいますね。

2010年3月17日水曜日

文書の背景色を変更するには

懇談室のこの話題について検討してみました。

文書の背景色は動的に変更できることがIBMのWEBサイトに掲載があります。

文書の背景色を動的に変更する方法

ただ、こうやって背景色を変更することが後のバージョンでもサポートされている、ということではないことに注意してください。

早速試してみました。

次のサンプルでは、チェックボックスにチェックを付けて保存した文書を開くと背景はグレーになります。
チェックがない場合、背景は白になります。

フォームには2つのフィールドを追加します。

フィールド「Color」...チェックボックス、編集可能
キーワードには「選択肢を入力」として次の1行を設定します
グレー表示

フィールド「$PaperColor」...数値、計算結果
値として次の式を設定します
@If(Color="グレー表示"; 15; 1)

※ $PaperColor へ設定する数値については以前に投稿したエントリが参考になると思います。


R5以降では自動で作成される $PaperColorEx を削除するため、フォームのイベント Postsave へ次のスクリプトを記述します。
Sub Postsave(Source As Notesuidocument)
    Dim doc As NotesDocument
    Set doc = Source.Document
    If doc.HasItem( "$PaperColorEx" ) Then
        Call doc.RemoveItem( "$PaperColorEx" )
        Call doc.Save( True, True )
    End If
End Sub

これで完成です。なかなかいい感じですね。

もしフォームに $PaperColor フィールドを作りたくない場合、 上の Postsave イベントのコードを下の様に変更することで設定できました。
Sub Postsave(Source As Notesuidocument)
 Dim doc As NotesDocument
 Dim color%
 Set doc = Source.Document
 If doc.Color(0) = "グレー表示" Then
  color = 15
 Else
  color= 1
 End If
 Call doc.ReplaceItemValue("$PaperColor", color)
 
 If doc.HasItem( "$PaperColorEx" ) Then
  Call doc.RemoveItem( "$PaperColorEx" )
 End If
 Call doc.Save( True, True )
End Sub
※QuerySave で$PaperColor アイテムの値を置換しても、保存したときに文書に設定されていた背景色の値に置き換わってしまうようです...orz

2010年3月16日火曜日

選択式アンケートをビューで集計する

アンケートの様なアプリケーションで、ダイアログリストやチェックボックスといったキーワードから選択する方式のフィールドが複数あるとき、どのキーワードが何個選択されたかを知るためのビューをフィールド毎に1つ作っていませんか?

これら複数のフィールドの値をまとめて一つのビューへ表示する方法があります。

以下、アンケートのアプリケーションを作成したときの事例です。

フォームには選択式の回答欄として、フィールドを9つ(フィールド名:A1~A9)設置しています。

ビューは1つ。「簡易集計」
ビューのプロパティで"データベースを最初に開くときすべてを省略する"を有効にします。

1列目:選択された値の数を表示します
ソート:なし、カテゴリなし
式:@IsCategory(@DocDescendants("%"; "%"; "%"); "")

2列目:選択式の項目の名前と値を階層表示します
展開できる行に三角アイコンを表示する:有効
ソート:昇順、種類:カテゴリ別、複数値を別のエントリで表示:有効
式:
empty := " (未回答)";
( "Q1\\" + @If( A1 = ""; empty; A1) ):
( "Q2\\" + @If( A2 = ""; empty; A2) ):
...省略...
( "Q8\\" + @If( A8 = ""; empty; A8) ):
( "Q9\\" + @If( A9 = ""; empty; A9) )


ここで2列目の式について簡単に説明します。

A1フィールドで何も選択しなかった場合、"Q1\\ (未回答)"となります。
A1フィールドで複数の値("みかん"と"りんご")を選択した場合は"Q1\\みかん":"Q1\\りんご"となります。
2つの円マーク(またはバックスラッシュ)で繋げた文字列を上のビューで表示すると2列目は

▼Q1
 ▼ (未回答)
 ▼みかん
 ▼りんご

のように表示されます。

2010年3月15日月曜日

添付のExcelファイルから値を取得して文書へ反映する

懇談室の話題からサンプルを作ってみました。

特定の文書にある添付ファイルをExcelで開き、現在開いている文書へ計算結果を書き込みます。
計算結果は1つ目のシートの2列目にある数値の合計としています。

Notesクライアントで開いた文書(フォーム)にあるアクションボタンを押したときに実行することを想定しています。

前提条件を設定してコードを簡単にしています。
・「特定の文書」はユニバーサルIDが変わらない
・「添付ファイル」のファイル名が変わらない
・保存先のパスには書き込み権限がある
・クライアントPCにはExcelがインストールされている
Sub Click(Source As Button)
    Dim ws As New NotesUIWorkspace
    Dim uidoc As NotesUIDocument
    Dim doc As NotesDocument
    Dim filename$, filepath$, unid$
    Dim obj As NotesEmbeddedObject
    Dim xlApp As Variant
    Dim xlbook As Variant
    Dim xlsheet As Variant
    Dim maxrows As Long
    Dim sum%
    filepath = "C:\TEMP\"
    filename = "Book1.xls"
    unid = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" '32桁のユニバーサルID
    sum = 0
    On Error Goto ERRORTRAP
    Set doc = ws.CurrentDatabase.Database.GetDocumentByUNID(unid)
    If doc Is Nothing Then Exit Sub
    If Not doc.HasEmbedded Then Exit Sub
    Set obj = doc.GetAttachment(filename)
    Call obj.ExtractFile(filepath & filename)
    Set xlApp = CreateObject("Excel.Application")
    xlApp.Visible = False
    Set xlbook = xlApp.Workbooks.Open(filepath & filename)
    Set xlsheet = xlbook.Worksheets(1)
    With xlsheet.UsedRange
        maxrows = .Rows(.Rows.Count).Row
    End With
    For rows = 1 To maxrows
        sum = sum + xlsheet.Cells(rows, 2).Value
    Next
    Set uidoc = ws.CurrentDocument
    Call uidoc.Document.ReplaceItemValue("Sum", Cstr(sum))
FINAL:
    If Not xlApp Is Nothing Then
    xlApp.quit
    Set xlApp = Nothing
    End If
    Kill filepath & filename
    Exit Sub
ERRORTRAP:
    Msgbox Cstr(Err) & ": " & Error
    Resume FINAL
End Sub

2010年3月10日水曜日

ビューでチェックを付けた順に番号を付けたい(2)

昨日のサンプルは中途半端でした。反省...
ビューのイベント Onselect を使ったサンプルの差し替え版です。

下の Lotus Script では、ビューの選択マージンに1文書ずつチェックマークを付けた場合はチェックマークを付けた順に番号を付けますが、選択マージンをマウスでなぞったり、Ctrl + A 等で一度にチェックマークを付けた場合は作成日順に番号が付きます。

チェックマークを消した場合は番号が付きません。チェックマークを消した後また同じ文書に付けると、最後にチェックマークを付けた時の番号になります。

文書のチェックマークが解除されたことを調べるのに NotesDocumentCollection クラスの Contains を使っていますが、これも 8 から利用できるメソッドです。文書コレクションをループしなくても探せるところがステキです。

ヘルプによると Contains の引数には NotesDocument, NotesDocumentCollection のオブジェクトの他、 String値の NoteID が使えるとのこと。ただし NoteID を配列として指定できないようです。
'ビューの(Globals) (Declarations)
Dim ws As NotesUIWorkspace
Dim ss As NotesSession
Dim idlist List As Long

'ビューのイベント Initialize
Sub Initialize
 Set ws = New NotesUIWorkspace
 Set ss = New NotesSession
End Sub

'ビューのイベント Onselect
Sub Onselect(Source As Notesuiview)
    Dim doc As NotesDocument
    Dim dc As NotesDocumentCollection

    Set dc = Source.Documents
    Set doc = dc.GetFirstDocument
    While Not ( doc Is Nothing )
        If Not Iselement( idlist( doc.NoteID ) ) Then
            idlist( doc.NoteID ) = "1"
        End If
        Set doc = dc.GetNextDocument( doc )
    Wend

    Forall o In idlist
        If Not dc.Contains( Listtag( o ) ) Then
            Erase idlist( Listtag( o ) )
        End If
    End Forall
End Sub

'ビューのアクション「番号を付ける」 
Sub Click(Source As Button)
    Dim doc As NotesDocument
    Dim db As NotesDatabase
    Dim lngNum%

    Set db = ws.CurrentDatabase.Database
    lngNum = 0
    Forall o In idlist
        Set doc = db.GetDocumentByID( Listtag( o ) )
        lngNum = lngNum + 1
        doc.num = lngNum
        Call doc.Save( True, False )
    End Forall

    Erase idlist
    Call ws.ViewRefresh
    Call ws.CurrentView.DeselectAll
End Sub

2010年3月9日火曜日

ビューでチェックを付けた順に番号を付けたい

懇談室のこのエントリについて考えてみました。

質問者のバージョンは6.5なので「できない」と回答しています(いじわるじゃないですよ)が、8 では OnSelect というビューのイベントが追加されています。

ヘルプによると、このイベントが発生するのは
・ビューが開くとき
・選択マージンでチェックマークを使用して文書を選択/選択解除したとき
・選択された行をクリックするとき
とのこと。

ビューで文書を1つ選択するたびにこのイベントが発生するなんて...なんだかパフォーマンス悪そうな予感....

ビューで「選択された」文書をこのイベントで取得したい場合、ハイライト表示されている文書は CaretNoteID を利用して簡単に取得することができますが、(ハイライトされていないけど)チェックマークをつけた1文書を取得するには工夫が必要です。

次のLotusScriptのサンプルでは、選択マージンでチェックマークを付けた時にその文書の NoteID をリスト変数へ追加します(NoteID が既にリストにある場合は追加しません)。

その後、ビューのアクションボタンを押すとリスト変数へ追加した順番に番号付けします。
'ビューの(Globals) (Declarations)
Dim ws As NotesUIWorkspace
Dim ss As NotesSession
Dim num%
Dim idlist List As Long

'ビューの(Globals) Initialize
Sub Initialize
    Set ws = New NotesUIWorkspace
    Set ss = New NotesSession
End Sub

'ビューのイベント Onselect
Sub Onselect(Source As Notesuiview)
    Dim dc As NotesDocumentCollection
    Dim doc As NotesDocument
    Set dc = Source.Documents
    Set doc = dc.GetFirstDocument
    While Not ( doc Is Nothing )
        If Not Iselement( idlist( doc.NoteID ) ) Then
            num = num + 1
            idlist( doc.NoteID ) = num
            Exit Sub
        End If
        Set doc = dc.GetNextDocument( doc )
    Wend
End Sub

'ビューのアクション「選択順に番号を付ける」
Sub Click(Source As Button)
    Dim doc As NotesDocument
    Dim db As NotesDatabase
    Set db = ws.CurrentDatabase.Database
    Forall o In idlist
        Set doc = db.GetDocumentByID( Listtag( o ) )
        doc.num = o
        Call doc.Save( True, False )
    End Forall
    num = 0
    Erase idlist
    Call ws.ViewRefresh
    Call ws.CurrentView.DeselectAll
End Sub
とっても手抜きなので、選択マージンをマウスでなぞったり、チェックマークをを消したり、Ctrl + A などで一度にチェックを付けたり、などなどには対応していません...orz

2010年3月5日金曜日

NotesColor の色見本表を作る

客先と認識を一致させるには具体的なイメージが必要な場合もありますよね。

色もそうですが「もうちょっと明るい感じ」と言われて変更しても一発OKになることは稀かも。

そこで NotesColorObject の NotesColor プロパティを使って色の見本表を作ってみました。

NotesColor は 0 から 240 の範囲の数値に色が割り当てられています。

最初の0から15には COLOR_BLACK といった定数もあります。

NotesColor へ数値をセットすると、Red, Green, Blue, Hue(色相), Saturation(彩度), Luminance(明度)の値が変わります。
Sub Initialize
    Dim ss As New NotesSession
    Dim doc As NotesDocument
    Dim color As NotesColorObject
    Dim body As NotesRichTextItem
    Dim nav As NotesRichTextNavigator
    Dim range As NotesRichTextRange
    Dim table As NotesRichTextTable
    Dim style1 As NotesRichTextStyle
    Dim style2 As NotesRichTextStyle
    Dim style3 As NotesRichTextStyle
    Dim styles() As NotesRichTextParagraphStyle
    Dim rows%, columns%, i%
    Dim rgb$, hsl$
    
    Set doc = ss.CurrentDatabase.CreateDocument
    Set body = New NotesRichTextItem( doc, "Body" )
    Set nav = body.CreateNavigator
    Set range = body.CreateRange
    Set color = ss.CreateColorObject
    color.NotesColor = 0
    Set style1 = ss.CreateRichTextStyle
    style1.FontSize = 1
    Set style2 = ss.CreateRichTextStyle
    style2.NotesColor = COLOR_BLACK
    style2.FontSize = 8
    Set style3 = ss.CreateRichTextStyle
    style3.NotesFont = FONT_ROMAN
    style3.FontSize = 7
    rows = 16
    columns = 16
    Redim styles( columns - 1 )
    For i = 0 To columns - 1
        Set styles( i ) = ss.CreateRichTextParagraphStyle
        styles( i ).LeftMargin = 0
        styles( i ).FirstLineLeftMargin = 0
        styles( i ).RightMargin = RULER_ONE_INCH * 0.75
    Next
    body.AppendTable rows, columns, , ,styles
    
    nav.FindFirstElement RTELEM_TYPE_TABLECELL
    range.SetBegin nav
    range.SetStyle style1
    range.SetEnd nav
    Do
        With color
            rgb = .Red & " : " & .Green & " : " & .Blue
            hsl = .Hue & " : " & .Saturation & " : " & .Luminance
        End With
        body.BeginInsert nav
'%REM 'NotesColorが不要ならコメントアウト--ここから
        body.AppendStyle style2
        body.AppendText color.NotesColor
'%END REM 'NotesColorが不要ならコメントアウト--ここまで
        body.AppendStyle style1
        body.AppendText( " " )
        If color.Luminance < 120 Then
            style3.NotesColor = COLOR_WHITE
        Else
            style3.NotesColor = COLOR_BLACK
        End If
        body.AppendStyle style3
        body.AppendTable 1, 1
        body.EndInsert
        nav.FindNextElement RTELEM_TYPE_TABLE
        Set table = nav.GetElement
        table.Style = TABLESTYLE_SOLID
        table.SetColor color
        nav.FindNextElement RTELEM_TYPE_TABLECELL
'%REM 'RGBとHSLが不要ならコメントアウト--ここから
        body.BeginInsert nav
        body.AppendText rgb
        body.AddNewline 1, False
        body.AppendText hsl
        body.EndInsert
'%END REM 'RGBとHSLが不要ならコメントアウト--ここまで
        color.NotesColor = color.NotesColor + 1
        If color.NotesColor > 240 Then Exit Do
    Loop While nav.FindNextElement( RTELEM_TYPE_TABLECELL )
'%REM '説明が不要ならコメントアウト--ここから
    body.AppendStyle style2
    body.AppendText "Line 1  NotesColor"
    body.AddNewline 1, False
    body.AppendText "Line 2  Red : Green : Blue"
    body.AddNewline 1, False
    body.AppendText "Line 3  Hue : Saturation : Luminance"
'%END REM '説明が不要ならコメントアウト--ここまで
    doc.Form = "Memo"
    doc.Subject = "Lotus Notes 色見本"
    doc.SendTo = ss.UserName
    doc.Send False
End Sub
色付けはセルごとに設定できません。そのため外側の表にあるセルの中に入れ子の表を作成して、その表へ色を付けています。
おおまかな処理の流れは次のとおりです。

1. 外側の表(16行x16列)を作成します
2. 最初のセルへ移動します
3. 内側の表(1行x1列)を作成します
4. 内側の表へ移動します
5. 内側の表全体に色を設定します
6. 次のセル(内側の表のセル)へ移動します
7. RGB, HSL の値を記入します
8. NotesColor に 1 追加します
9. 次のセルへ移動できる間、3~8を繰り返します

NotesColor の数値、RBGおよびHSLの値、値の説明が不要ならコメントアウトしてください

NotesColor の数値を表示しない場合でも表がつぶれず色が見やすくなるようスペース文字を追加してフォントサイズで高さを調整しています。

R,G,BとH,S,Lのそれぞれの値は0から256までの数値です。これを16進表現にしたい場合は Hex 関数で変換できます。
rgb = Hex$(.Red) & " : " & Hex$(.Green) & " : " & Hex$(.Blue)
hsl = Hex$(.Hue) & " : " & Hex$(.Saturation) & " : " & Hex$(.Luminance)


AppendTable で styles を省略して16行16列の表を作成しようとすると「列の幅が不正です - 左余白と右余白を確認してください」が表示され終了してしまいました。
(Notes 8.0.1 Basic版で確認)

2010年3月3日水曜日

新規文書にリッチな初期値を設定する

新規文書を作成するときに、リッチテキスト・フィールドへ初期値を設定しておきたい場合があります。

もちろんリッチテキスト・フィールドにもデフォルト値を設定できますが、値は文字列でなければなりません。

もし初期値として色つきの文字や特定のフォントにしたり、表やリンク、あるいは添付ファイルを張り付けておきたい、などといった場合は作りこみが必要です。

そこで、初期値としてのひな型を作成する機能と、作成したひな型のリッチテキストフィールドの内容をコピーして編集モードで開いている文書へペーストする機能を作ってみました。

テンプレートを編集するフォーム "Template" を作ります。
フィールドは2つ、テキストの"Subject"とリッチテキストの"Body"を追加します。
"Subject"は読み込みモードの時とクリップボードへコピーする時に非表示になるよう設定します。

テンプレートを選択するためのビュー"Templates"を作ります。
ビューの1列目に"Subject"を表示します。

次は新規文書を作成するアクションボタンの Lotus Script です。
ここではひな型の名前等を固定で指定しちゃっています。
Sub Click(Source As Button)
    Dim ws As New NotesUIWorkspace
    Dim tdoc As NotesDocument
    Dim ss As New NotesSession
    Dim db As NotesDatabase
    Dim vw As NotesView
    Dim templateName$
    Set db = ss.CurrentDatabase
    Set vw = db.GetView( "Templates" )
    Set tdoc = vw.GetDocumentByKey( "Default Template", True )
    If tdoc Is Nothing Then Exit Sub
    Dim uidoc As NotesUIDocument
    Set uidoc = ws.EditDocument( False, tdoc, True )
    Call uidoc.SelectAll
    Call uidoc.Copy
    Call uidoc.Close( True )
    Dim cuidoc As NotesUIDocument
    Set cuidoc = ws.ComposeDocument( db.Server, db.FilePath, "MainTopic")
    Call cuidoc.GotoField( "Body" )
    Call cuidoc.Paste
End Sub
これを実行すると、画面が一瞬フラッシュしたように見えることがありますが、それは気にしちゃいけません。
もし NotesUIDocument.Visible といったプロパティがあって、False をセットすると非表示にできちゃったりなんかすると解決できそうなんですが...

次は編集モードで開いている文書へひな型を挿入したい場合にアクションボタンへ記述する Lotus Script です。
このボタンは読み込みモードのときに非表示にします。
Sub Click(Source As Button)
    Dim ws As New NotesUIWorkspace
    Dim cuidoc As NotesUIDocument
    'リッチテキスト"Body"に挿入点がない場合はエラー
    Set cuidoc = ws.CurrentDocument
    If cuidoc.CurrentField <> "Body" Then
        Messagebox "現在の項目へは挿入できません。",, "エラー"
        Exit Sub
    End If
    'ひな型を選択する
    Dim ss As New NotesSession
    Dim db As NotesDatabase
    Dim dc As NotesDocumentCollection
    Dim tdoc As NotesDocument
    Set db = ss.CurrentDatabase
    Set dc = ws.PickListCollection( 3, False, 
    db.Server, db.FilePath, "Templates", _
    "選択", "ひな型を選択してください。" )
    If dc.Count = 0 Then Exit Sub
    Set tdoc = dc.GetFirstDocument
    'ひな型をクリックボードへコピーする
    Dim uidoc As NotesUIDocument
    Set uidoc = ws.EditDocument( False, tdoc, True )
    Call uidoc.SelectAll
    Call uidoc.Copy
    Call uidoc.Close( True )
    'クリップボードからペーストする
    Call cuidoc.Paste
End Sub

2010年3月2日火曜日

PANIC: pool free chain が無効です

サーバーにあるアプリケーションの既存文書をビュー上でダブルクリックして開いたところ、NSD が起動され Lotus Notesが終了する現象が発生しました。
(Notes 8.0.1 Basic版にて、特定の文書だけで再現性あり)

NSD で出力されたログの FATAL THREAD には次の記載がありました。
[ 1] 0x76fe9a94 ntdll.KiFastSystemCallRet+0 (600,493e0,0,d8fe854)
[ 2] 0x7601c1b2 kernel32.WaitForSingleObject+18 (600,493e0,3,d8fea70)
@[ 3] 0x601a5754 nnotes.OSRunExternalScript@8+1284 (12c,1)
@[ 4] 0x601a5bea nnotes.FRTerminateWindowsResources+986 (1,0,1010,1)
@[ 5] 0x601a5faf nnotes.OSFaultCleanupExt@24+895 (22a4dd8,1010,0,0,0,d8feda0)
@[ 6] 0x601a603a nnotes.OSFaultCleanup@12+26 (0,1010,0)
@[ 7] 0x601b12a4 nnotes.OSNTUnhandledExceptionFilter@4+276 (d8ffdd8)
@[ 8] 0x6017aca8 nnotes.Panic@4+520 (60bb0f62)
@[ 9] 0x60656400 nnotes.HandleBadFreeChain@4+640 (d342c4c)
@[10] 0x6000306d nnotes.FreeDBlock@12+429 (d34008a,373196d9,d342c4c)
@[11] 0x6065662e nnotes.OSFreeDBlockWithSize@16+78 (60e9cb7a,1466c,1314,1)
@[12] 0x60656662 nnotes.OSFreeDBlockExt@12+34 (60e9cb7a,1466c,1)
@[13] 0x601a98dc nnotes.DPoolFree@8+76 (f9ca8de,1)
@[14] 0x600067fc nnotes.Discard@4+364 (f9ca8d0)
@[15] 0x60008063 nnotes.OSMemoryFree@4+179 (f010a656)

上記のいくつかのキーワードでIBMのサイトを検索したところ、Standard版で似たような状況が発生していることがわかりました。
Lotus Notes 8 (Standard Configuration) crashes when user accepts meeting invitation or update

この情報によると、ローカルにあるアドレス帳(names.nsf) のODSバージョンが古い(20 or 41)場合、圧縮すると解決するようです。

そこでコマンドプロンプトから ncompact を実行しました。
ODSバージョンを変換するため、-C オプションを付けます。

ncompact -c names.nsf

圧縮前のODSバージョンは 41 でしたが、実行後は 43 になりました。

これでもまだ再現するようです。

そこで 8 のODSバージョンになるよう notes.ini ファイルへ次の1行を追加して、再度 -C 付きで ncompact を実行しました。

Create_R8_Databases=1

これでODSバージョンが 48 になり、当該文書は無事開くようになりました。

2010年3月1日月曜日

True と False を数値で判定する

If Then Else ステートメントを使って条件が True か False のどちらになるかを調べたいとき、条件式の結果が 0 なら False, それ以外は True となることを知っているとシンプルに記述できる場合があります。

では0以外の数値が True として扱われていることを次のエージェントで確かめてみます。
Sub Initialize
    Dim intValue As Integer

    intValue = 6
    If intValue Then
        Print Cstr( intValue ) & " は True です。"
    Else
        Print Cstr( intValue ) & " は False です。"
    End If

    intValue = 0
    If intValue Then
        Print Cstr( intValue ) & " は True です。"
    Else
        Print Cstr( intValue ) & " は False です。"
    End If

    intValue = -1
    If intValue Then
        Print Cstr( intValue ) & " は True です。"
    Else
        Print Cstr( intValue ) & " は False です。"
    End If
End Sub
上記の結果は

6 は True です。
0 は False です。
-1 は True です。


となりました。


ところで True か False かを表すデータ型 Boolean の値は計算することができるのでしょうか。

下のエージェントで確認してみます。
Sub Initialize
    Dim bolValue As Boolean
    bolValue = True
    Print Cstr( bolValue ) & " に 1 を掛けると " & Cstr( bolValue * 1 ) & " です。"

    bolValue = False
    Print Cstr( bolValue ) & " に 1 を掛けると " & Cstr( bolValue * 1 ) & " です。"
End Sub
上の結果は

True に 1 を掛けると -1 です。
False に 1 を掛けると 0 です。


となりました。

きちんと計算できました。

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

2010年1月30日土曜日

@DbLookup 式を LotusScript へ変換する

既存アプリケーションを変更する際、式言語ではできないことを実現するため LotusScript へ変換することがあります。

今回は思い出したくない過去の失敗事例を思い出してしまったので、ここにご紹介します。

変換前の式には @DbLookup は下のような式です。

Category2 := @DbLookup("": "NoCache"; @DbName; "test"; Category1; "Category2")

これを Lotus Script へ変換するとします。Category1 はフィールド名です。
Dim ws As New NotesUIWorkspace
Dim doc As NotesDocument
Dim vw As NotesView
Dim dc As NotesDocumentCollection
Dim category2 As Variant
Dim tmpKey As String, init(0) As String

Set vw = ws.CurrentDatabase.Database.GetView( "test" )
set tmpKey = ws.CurrentDocument.Document.Category1(0)
Set dc = vw.GetAllDocumentsByKey( tmpKey, True )
If dc.Count > 0 Then
  category2 = init
  Set doc = dc.GetFirstDocument
  While Not ( doc Is Nothing )
    Category2 = Arrayappend( Category2, doc.Category2 )
    Set doc = dc.GetNextDocument( doc )
  Wend
  category2 = Fulltrim( Category2 )
End If
一見何の問題ないように見えるこのプログラム...実は大きな落とし穴があったんです。

見落としだったのですが...フォーム上の Category1 は"複数値も可"が有効なフィールドでした。

@DbLookup のキーには複数値を設定できます。
複数値で検索した場合、指定した複数の値に一致するすべての値を一度に取り出します。

そのため上記の Category(0) といったように配列の0番目だけを取り出すのではなく、
配列のすべての要素を検索のキーとして指定する必要があります。

ここで注意したいのは、GetAllDocumentsByKey で指定するキーです。

このキーは配列でもよいのですが、@DbLookup では最初のソートされた列だけを対象に検索してくれるのに対して、GetAllDocumentsByKey では複数のソート列を対象にして検索します。

同じように複数値を指定しても検索結果が違うのです。

つまり、次のように変換するのが正しかったのです。
<...宣言は省略...>
Set vw = ws.CurrentDatabase.Database.GetView( "test" )
Category2 = init
Forall o In ws.CurrentDocument.Document.Category1
  Set dc = vw.GetAllDocumentsByKey( o, True )
  If dc.Count > 0 Then
    Set doc = dc.GetFirstDocument
    While Not ( doc Is Nothing )
      Category2 = Arrayappend( Category2, doc.Category2 )
      Set doc = dc.GetNextDocument( doc )
    Wend
  End If
End Forall
Category2 = Fulltrim( Category2 )

2010年1月28日木曜日

文書を印刷できなくする

アプリケーション内の文書をNotesクライアントから印刷させたくない場合、印刷機能を無効にすることができます。

WEBブラウザでアクセスさせる場合、Dominoでは制御できません。ブラウザの種類によって制御方法を変えたり、そもそも制御できないブラウザもある(?)ようで、いろいろ面倒なようです。

ここは専用クライアントを使うことのメリットがある好例でしょう。

Notesの印刷機能を無効にしたからといっても、画面をキャプチャしたりデジタルカメラで撮影することまでは制御できませんから、そういった意味では印刷を完全に防ぐことは不可能です。

でも画面をキャプチャしたものはコピーであり原本ではありません。
重要なのは、原本(と同じもの)を他の媒体へコピーできないことです。

ここではNotesの文書を原本と考え、それを印刷やコピーができないよう制御する方法をご紹介します。


A. アプリケーション内のすべての文書を印刷不可にする

アクセス制御リスト(ACL)で各エントリに対して "文書を複製またはコピー" のチェックを外します。

この権限をもたない(チェックを外した)ユーザーが文書を作成することによる問題が報告されていますので、ご注意ください。


B. 特定のフォームで作成した文書を印刷不可にする

フォームのプロパティ - [セキュリティ]タブ - "印刷/転送/クリップボードへのコピーを不可" のチェックを付けます。


C. 特定の文書を印刷不可にしたい場合

Bのフォームで作成した文書には $KeepPrivate というアイテムができ、値に "1" が設定されますが、実はBのプロパティを有効にしなくても同様の機能を実現できます。

FIELD $KeepPrivate := "1"

といった式を設定したエージェントで文書に $KeepPrivate アイテムと、その値として "1" を設定するのです。

フォーム上に "印刷不可にする" といった表示のチェックボックスを出しておいて、作成者が当該文書の印刷可否を決められるような仕様も実現できます。



なお、一度 $KeepPrivate を設定した文書は、フォームのプロパティで "印刷/転送/クリップボードへのコピーを付加" のチェックを外した後、文書を保存しなおしても $KeepPrivate アイテムと値はそのまま残ります。

ときどき“このアプリケーションは印刷制御しているからリッチテキストフィールドに貼られた添付ファイルの内容も印刷できない”と勘違いされている方がいらっしゃいますが、添付ファイルを開いた後の印刷機能はファイルに関連付けられたアプリケーション側の制御になります。

2010年1月27日水曜日

フレームにビューが表示されなくなる

フレームセットで画面を分割したアプリケーションで、データベースを開いたときにフレーム内に最初に表示するよう設定しているビューが開かず、そのフレームだけ別のビューが開くようになることがあります。

こうなった場合、Bookmark.nsfのエントリを削除すると解決するのですが、面倒だし一般ユーザーには敷居が高すぎます。

そもそもこんなことにならないよう作っておくべきなのですが、Notesのお作法はよくわからん...
ということで失敗から学んだ事例です。

フレームに別のビューが表示されるようになったDBのフレームセットでは、
横に2分割して、そのうちの右のフレームをさらに縦に2分割した、計3分割の構成でした。
右の上にあるフレームへ最初にで "WhatsNew" というビューを開くよう設定していました。

また、DBのプロパティでは[起動]タブにある"最後に表示していたものを表示する"のチェックを外していました。

このような設定でしばらく運用していたところ、フレームに "WhatsNew" 以外のビューが最初に表示されるようになったという声がちらほらではじめました。

懇談室で相談したところ、フレームの枠をドラッグして幅を調整した後、アプリケーションを再度起動した時から、フレームの内容が表示されなくなることがわかりました。

どうやらフレームセットの構成が影響している...との助言があり下図のように作り変えたところ、枠の幅を変更してもビューが置き換わらなくなりました。

クリックすると大きく表示されます

2010年1月23日土曜日

NotesDirectory クラスをさわってみた

先日の投稿に関連して、ディレクトリアシスタントを使った(複数のアドレス帳で認証する)環境でもグループ検索を可能にするものはないかとDesigner ヘルプを開いたところ、8 から追加された Lotus Script のクラスに NotesDirectory なるものがありました。

これについて学ぼうと記述例なんぞを探してみたのですが、関連するプロパティやメソッドの文書の例の多くが、本文に TBD (= to be determined:将来決定する、未使用)と書かれているだけでした...orz

もっとひどいのは NotesDirectoryNavigator クラスで、プロパティやメソッドの記述例の文書がひとつもありゃしない...

ここはテストして学ぶしかないかぁ~

というわけで、以下、テストの結果です。Notes(Basic)8.0.1で実験してます。

テスト用に作成したエージェント
Dim ss As New NotesSession
Dim nd As NotesDirectory
Dim ndnav As NotesDirectoryNavigator
Dim strServer$, strView$
Dim strName as String
Dim strItem as String

strServer = "server1/org"
strView = "$NamesFieldLookup"
strName = "hoge hoge1/org"
strItem = "FullName"

Set nd = ss.GetDirectory(strServer)
Set ndnav = nd.LookupNames(strView, strName, strItem, False)

If ndnav.NameLocated Then
 If ndnav.MatchLocated Then
  Print "登録済み"
 Else
  Print "未登録"
 End If
End If

NotesSession.GetDirectory
指定したサーバーが存在しなくても NotesDirectory オブジェクトはできますが、その後
NotesDirectory.LookupNames を実行すると、サーバーを検索した後、次のように最後が文字化けしたようなメッセージが表示されました...orz

----------------------
LookupNames が失敗しました:
サーバーへのパスが見つかりません。ネットワーク接続を確認してください。
接続に問題がなければ、[プリファレンス] - [Notes のポート] - [トレース]をクリックして、問題を特定してく■
----------------------

NotesDirectory.LookupNames("ビュー名", 検索する名前, アイテム名, 部分一致)
"検索する名前"と"アイテム名"は単一の文字列、または配列です。一度に複数の名前を検索することができ、複数のアイテム値を戻すことができます。

指定したビューが存在しない場合も NotesDirectoryNavigator オブジェクトが作成されました。
隠しビュー(ビュー名が括弧でかこまれたビュー)は括弧をつけなくても機能しました。

一致する名前が見つかると次のようになりました
MatchLocated が True (みつからないと False)
CurrentMatches が 1以上の値 (みつからないと 0)
CurrentMatch は見つかるかどうかにかかわらず 1 (よくわからん...)

当方ではディレクトリアシスタントを利用して2つのドミノディレクトリで運用していますが、どちらのドミノディレクトリからも検索できました。
どちらのドミノディレクトリにもある名前で検索してみたところ、検索結果が複数になり、NotesDirectoryNavigator のメソッドで読み出すことができました。

詳しくは後日、今日はココでご勘弁ください m(_ _)m

2010年1月21日木曜日

暗号化の操作はこのバージョンの Lotus Notes ではサポートされていません。

アプリケーションリンクをクリックすると、標記のメッセージが表示され、開かないユーザーがいらっしゃいました。

アプリケーションリンクのサーバーヒントには確かに当方のサーバーが設定されていましたが、この方は当該データベースのレプリカを Local に持っていて、そのアイコンがワークスペース上にありました。

ユーザーに聞いたところ、Localのレプリカは出張先にて作成したもので、レプリカを作成した時に利用していたNotes IDは出張先のドメインで発行されたものだったとのこと。
作成時には(デフォルトで)暗号化のオプションが有効だったのを、おそらく意識しなかったのでしょう。

そこで、「データベースを開く」の画面を表示させ、当方のサーバー上の当該データベースを指定したところ、無事に開きました。

表記のメッセージじゃ見当もつかないですよね?今回に限らずですが、もっとわかりやすい文言にならないものでしょうか?>IBMさん


実はアプリケーションリンクをクリックしてもアプリケーションが開かないといった問い合わせは非常に多いのです。

多くは海外(の出張先)から帰国して間もないユーザーで、ロケーションに絡むものです。

今回のように自分がアクセスしようとしても開かないが他のメンバーはアクセスできる、他のアプリケーションは開く、といったユーザー固有の問い合わせがあると、なんだかワクワクしてしまう自分がいます。

2010年1月20日水曜日

グループの入れ子を洗い出す

ドミノディレクトリのグループ文書では、メンバーの値としてユーザーのほか、グループを設定できます。

このように入れ子(ネストとも呼ばれます)になったグループを Domino は(アクセス認証の場合) 6 つのレベルまでたどってくれます。
※メール配信の場合は20のレベルまで
※8のAdministratorヘルプには「メール配信は5つのレベル」といった記載がいくつか残っています。R4.xの頃の名残りと思われ...orz

でもグループのメンバーにさらにグループが設定されていると、実際のアクセス権限がどうなっているのかがわかりづらいですよね。

そこで入れ子のグループを階層表示するアプリケーションを作ってみました。

選択したアプリケーションのACLからグループのエントリだけ取り出し、そのメンバーに設定されたグループを掘り出していきます。
そして掘り出したグループをビューで階層表示します。

下記は "グループの入れ子を調べる" エージェントの Lotus Script です。

(Options)
Option Base 1
(Declarations)
Dim ss As NotesSession
Dim vw As notesview
Type aclentrytype
Level As Integer
Name As String
End Type
Dim entrytype() As aclentrytype
Sub Initialize
Set ss = New NotesSession
Dim db As NotesDatabase

'アプリケーションのACLからグループのエントリだけ抽出する
Dim ws As New NotesUIWorkspace
Dim notesacl As NotesACL
Dim aclentry As NotesACLEntry
Dim numEntry As Integer

varDB = ws.Prompt( 13, "選択", "サーバー、アプリケーションを選択します" )
If Isempty( varDB ) Then Exit Sub
Set db = ss.GetDatabase( varDB( 0 ), varDB( 1 ) )
If db Is Nothing Then Exit Sub

If Not db.IsOpen Then flag = db.Open( "", "")
Set notesacl = db.ACL
Set aclentry = notesacl.GetFirstEntry
While Not (aclentry Is Nothing)
If aclentry.IsGroup Then
numEntry = numEntry + 1
Redim Preserve entrytype( numEntry )
entrytype( numEntry ).Level = aclentry.Level
entrytype( numEntry ).Name = aclentry.Name
End If
Set aclentry = notesacl.GetNextEntry( aclentry )
Wend

'レベルとグループ名を書き出す
Dim ndb As NotesDatabase
Dim parentdoc As NotesDocument
Dim i As Integer

Set ndb = ss.GetDatabase( db.Server, "names.nsf")
Set vw = ndb.GetView( "Groups" )
Set parentdoc = maketreedoc( db.Title, 99, Nothing )

For i = 1 To Ubound( entrytype )
Call grpmember( entrytype( i ).Name, i, 1, parentdoc )
Next
End Sub
Sub grpmember(grpname As String, idx As Integer, nest As Integer, pdoc As NotesDocument)
If nest > 6 Then Exit Sub

Dim nam As New NotesName( grpname )
Dim entry As NotesViewEntry
Set entry = vw.GetEntryByKey( nam.Abbreviated, True )
If entry Is Nothing Then Exit Sub

Dim treedoc As NotesDocument
Set treedoc = maketreedoc( grpname, entrytype( idx ).Level, pdoc )

Dim doc As NotesDocument
Set doc = entry.Document
Forall o In doc.GetItemValue( "Members" )
Call grpmember( Cstr( o ), idx, nest + 1, treedoc )
End Forall
End Sub
Function maketreedoc(title As String, idx As Integer, parent As NotesDocument) As NotesDocument
Set maketreedoc = New NotesDocument(ss.CurrentDatabase)
With maketreedoc
If Not (parent Is Nothing) Then Call .MakeResponse(parent)
.Form = "GroupTree"
.Group = title
.Level = idx
Call .Save(True, True)  
End With
End Function

上記エージェントを実行すると、アプリケーション内に文書を書き出します。

これらの文書は親子関係になっていて、以下のような3列のビューを作成すると階層構造で表示できます。

1列目の値として次の式を記述します。
@Replace(@Text(LEVEL); "0":"1":"2":"3":"4":"5":"6":"99"; "なし":"投稿者":"読者":"作成者":"編集者":"設計者":"管理者":"")

2列目は列のプロパティで「返答文書のみ表示」と「展開できる行に三角アイコンを表示する」にチェックを付けます。列の値は Group とします。

3列目は列のプロパティで「展開できる行に三角アイコンを表示する」にチェックを付けます。列の値は Group とします。
ビューのプロパティで「返答文書を階層表示する」にチェックを付けます。

以上でできあがりです。

ディレクトリアシスタントを使って別のアドレス帳にあるグループで認証させている環境では物足りないかもしれませんね。
なんとか対応できるようにいろいろ調べてみたいと思います。

それから、ビュー上で文書をダブルクリックしてもフォームが無いのでエラーが表示されますが、気にしないでください。

2010年1月19日火曜日

Lotus Script でキャッシュをクリアする

アプリケーションを開発中に Notes や Designer が何かおかしい...と感じるときがあります。

そんなときには迷わず再起動して、ついでにキャッシュもクリアします。

キャッシュは Notes のデータディレクトリに cache.ndk というファイル名で存在します。

こいつは Notes アプリケーションの拡張子である nsf とは違いますが、NotesDatabase クラスでアクセスできます。

中にある文書を片っ端から削除してあげましょう。

Sub Initialize
Dim ws As New NotesUIWorkspace
Dim cache As NotesDatabase
Dim dc As NotesDocumentCollection
Dim doc As NotesDocument, ndoc As NotesDocument
Dim intNum As Integer

On Error GoTo ERRORTRAP

Set cache = New NotesDatabase("", "cache.ndk")
Set dc = cache.Alldocuments
If dc.Count > 0 Then
Set doc = dc.Getfirstdocument
While Not doc Is Nothing
intNum = intNum + 1
Set ndoc = dc.Getnextdocument(doc)
Call doc.Removepermanently(True)
Set doc = ndoc
Wend
End If
Call cache.Compact
Call ws.Prompt(1, "情報", "キャッシュをクリアしました。再起動してください。")
Exit Sub

ERRORTRAP:
Resume
Call ws.Prompt(1, "エラー", "キャッシュはクリアできませんでした。")
End Sub

文書の削除が済んだら、Lotus Notes を再起動しましょう。

って再起動するくらいなら Cache.ndk をOSレベルで削除すればいいじゃない....orz

キャッシュファイルがなくなっても、Lotus Notes が勝手に作成しますのでご心配なく。

2010年1月18日月曜日

セクションを制限つきアクセスにする

文書を編集モードで開いた時、一部分は特定の人にだけ編集させたい場合があります。

ドラフトの時だけ特定の人にコメントを書かせたい場合、制限つきアクセスセクションの中にコメントフィールドを設置します。

セクションを編集できる人を導き出す式を「フォームのセクション」プロパティの「式」タブにある「アクセス式」に書き込み、この式の実行結果として、ユーザー、グループ、ロールが導き出されるようにします。

結果としてヌル "" を設定すると、編集モードにできる全員がこのセクション内を編集できます。要するに「制限なし」ですね。

たとえば....

Statusフィールドが"Draft"の場合、チェッカーだけ編集可能、"Draft"でなくなったら管理者だけ編集可能
チェッカーと管理者にはそれぞれ[CHECKER]と[ADMIN]というロールを割り当てている

とすると、式は

@If(Status="Draft"; "[CHECKER]"; "[ADMIN]")

となります。

誰にも編集できないようにすることはお勧めしませんが、したい場合はいいかげんな値("DUMMY"等)を設定するといいのではないかと...


ちなみに「式」タブの「種類」に"表示用の計算結果"以外の値を選択した場合、そのフォームを使って文書保存した後の文書のプロパティを見ると、「セクションタイトルと境界線」タブで「セクションフィールド名(R3 互換)」なる項目で設定した名前でアイテムができます。

セクションを誰が編集できるかは、そのアイテムの値を見るとわかります。

また「式」タブの「種類」を"編集可能"としたセクションを編集モードでダブルクリックした時、編集させる人を選択できる「セクションの編集者を指定」画面を表示します。

競合文書のトラウマがあるのでひとつの文書をつつきあう仕様はあまり好きではありません...ボソボソ

2010年1月15日金曜日

リンク付きメールには署名が邪魔なんです

LotusScript でフロントエンドに文書リンクが付いたメール作成画面を表示する場合、バックエンドで作成した NotesDocument に RichtextItem の "Body" を作り AppendDocLink で文書リンクを付けた後、NotesUIWorkspace.EditDocument でフロントエンドに登場させる、といった手法をとることもできます。

しかし"署名を自動的に追加する"が有効の場合、署名の後に文書リンクが付いてしまいます。

(個人的には署名なんて手動でも使いたくないが...orz)こいつが非常に気になります。

Memoフォームの設計を見るとPostOpenで署名を加えているようですが、まさか設計を変えるわけにもいかない...

どうにか署名の前に文書リンクをつけられないか、フロントエンドの文書へ直接文書リンクを付ける方法がなかろうかと試行錯誤を繰り返した末、実現できたのが次のような仕組みです。


アクションボタンの式(ビュー、またはフォームに設置できます。新規文書の場合もうひと手間かけること)

@Command([EditMakeDocLink]);
@Command([RunAgent]; "(MakeDocument)")


エージェント"MakeDocument"のスクリプト

Sub Initialize
Dim ss As New NotesSession
Dim doc As NotesDocument
Dim strSubject As String, strSendTo As String
Dim varSendTo As Variant
Dim nam As NotesName

'メモリ内文書を取得
Set doc = ss.DocumentContext

'件名を取得
strSubject = doc.GetItemValue("Subject")(0)

'宛先を取得
varSendTo = doc.GetItemValue("Attention")

'階層なしの値に置換 (複数値に対応)
For i = 0 To Ubound(varSendTo)
Set nam = New NotesName(varSendTo( i ))
varSendTo( i ) = nam.Abbreviated
Next

'単一の文字列に変換。値はカンマ区切り
strSendTo = Join(varSendTo, ", ")
Dim db As New NotesDatabase("", "")

'現在のユーザーのメールアプリケーションを開く
Call db.OpenMail
If db Is Nothing Then
Messagebox "メールが開きません。",,"警告"
Exit Sub
End If

Dim ws As New NotesUIWorkspace
Dim uidoc As NotesUIDocument

'フロントエンドでMemoフォームを開く
Set uidoc = ws.ComposeDocument(db.Server, db.FilePath, "Memo")
With uidoc
Call .FieldAppendText("EnterSendTo", strSendTo)
Call .FieldAppendText("Subject", strSubject)
Call .GotoField("Body")
Call .InsertText("下のリンクをクリックして確認してください。")
Call .InsertText(Chr(10))
Call .Paste 'クリップボードの文書リンクを張付
End With
End Sub


でもこれでは本文で文字のスタイルを変更したり整った表を作るといったことがやりづらいのです。やっぱりバックエンドでメール文書を作りたいのですよ...

もう、署名なんてなければいいのに!

ところでメールアプリケーションにはリンク付きメールを作る際に便利な Bookmark フォームがあります。

Bookmark では現在開いている(あるいはビューでフォーカスが当たっている)文書のリンクがついたメールを作成できますが、フロントエンドで使うにはタイトルが勝手に設定されたりとちょっと曲者なのです。

でも署名が付かないところはステキです

2010年1月13日水曜日

別ドメインに不正アクセス

外部のNotesドメインのユーザーが当方のサーバーアクセスしようとして権限なしではじかれた旨、ログ(log.nsf)に記録された。

2010/01/13 10:00:17 ATTEMPT TO ACCESS SERVER by hogehoge/org was denied

気になったのは夜間のアクセスはなく、日中に限っていること。ほぼ1時間おきに10行程度記録されていること。

hogehoge/org はもともと当社の社員で関連会社へパソコンを持ち出している。

本人へ直接問い合わせたが、まったく認識がないという。

そこでNotesクライアントの設定をいくつか変更してもらった。

ロケーションの設定で複製を無効に変更 .... NG
ユーザープリファレンスで「定期ローカルエージェントの有効化」を無効に変更 .... NG
ユーザープリファレンスで「購読の確認」を無効に変更 .... OK!

ヘルプによると、購読が有効の場合、購読するDBに対して定期的にチェックを行うが、その間隔はユーザープリファレンスに設定された「新規メールの通知」の間隔の2倍の頻度でチェックされるとのこと。

本人によると、そもそも購読に登録したことすら覚えていなかったとさ...orz

2010年1月12日火曜日

未知のドメインのサーバーへの初回アクセスで「検索するサーバーの選択」画面を表示させない方法

クライアント上でアプリケーション・リンクをクリックするなどして、これまでに接続したことのないドメインのサーバーにあるアプリケーションへアクセスさせようとした時、「検索するサーバーの選択」というタイトルの画面が表示される場合があります。

当然その画面にあるサーバーリストには未知のドメインのサーバーなどあるはずもない。

Notesの名前解決では、DNSやhostsファイルと連携した名前解決はしないのです...orz

※ここではIPネットワーク的な接続はできていて、相互認証があり、サーバーやアプリケーションへのアクセス権もあります

こんなとき、Notes上で Ctrl + O をタイプして「データベースを開く」画面を開き、サーバー入力欄へIPアドレスをタイプしてもらうのも手です。
しかしながら IPアドレスのような数字の羅列は間違えやすいし、将来変わる可能性もあるので、なるべくなら入力させたくありませんね。

実はローカルの names.nsf にあるロケーション文書には、これまで接続できたサーバーの情報がキャッシュされていまして、ここへ未知のサーバーの情報を書き込んであげると...アプリケーションがすんなり開くのです。

サーバー $SavedServers
ポート  $SavedPorts
アドレス $SavedAddresses

下記にLotus Scriptのサンプルを示します。
メールのホットスポットボタンに組み込んで(Notes R6.5.3にて)利用したことがあります。

Sub Click(Source As Button)
Dim ss As New NotesSession
Dim db As New NotesDatabase("", "")
Dim dc As NotesDocumentCollection
Dim doc As NotesDocument
Dim item1 As NotesItem, item2 As NotesItem, item3 As NotesItem
Const savedServers = "CN=hogehoge/O=org"
Const savedPorts = "TCPIP"
Const savedAddresses = "hogehoge"

'現在のロケーション名を取得する
vLocation$ = ss.GetEnvironmentString("Location", True)
vLocation$ = Strtoken(vLocation$, ",", 1)

'ロケーション文書を取得する
Call db.Open("", "names.nsf")
sFormula$ = |Form="Location" & Name="| & vLocation$ & |"|
Set dc = db.Search(sFormula$, Nothing, 0)
If dc.Count = 0 Then
Messagebox "ロケーション「" & vLocation$ & "」が存在しません。",16,"失敗"
Exit Sub
End If

'ロケーション文書を変更する
Set doc = dc.GetFirstDocument
If doc.HasItem("$SavedServers") Then
Set item1 = doc.GetFirstItem("$SavedServers")
If Not Isnull( Arraygetindex(item1.Values, savedServers, 1) ) Then
Call item1.AppendToTextList(savedServers)

If doc.HasItem("$SavedPorts") Then
Set item2 = doc.GetFirstItem("$SavedPorts")
Call item2.AppendToTextList(savedPorts)
Else
Call doc.ReplaceItemValue("$SavedPorts", savedPorts)
End If

If doc.HasItem("$SavedAddresses") Then
Set item3 = doc.GetFirstItem("$SavedAddresses")
Call item3.AppendToTextList(savedAddresses)
Else
Call doc.ReplaceItemValue("$SavedAddresses", savedAddresses)
End If
End If
Else
Call doc.ReplaceItemValue("$SavedServers", savedServers)
End If
Call doc.Save(True, True)

Dim ws As New NotesUIWorkspace
Call ws.OpenDatabase("hogehoge/org", "hoge.NSF")
End Sub

上記でポート名を "TCPIP" としていますが、各クライアントの環境により異なることがある場合にはロケーション文書のアイテム "EnabledPorts" や @GetPortsList([Enabled]) コマンドで取得するといいと思います。

なお $SavedAddresses へ設定する値はIPアドレスでも良いようです。

2010年1月8日金曜日

@Commandでカレンダーを開く

アクションボタンから自身のメールアプリケーションにあるカレンダービューを開きたいと考え、試してみました。

R8.0.1 では次のどれでもカレンダービューが開きましたが、既にメールアプリケーションが開いているか否かで表示結果が違ったり、フレームセットが違ったりします。

@Command([FileOpenDatabase]; @MailDbName; "Calendar")
@Command([OpenCalendar]; @UserName)
@Command([OpenCalendar]; @UserName; [UseFrameset])
@Command([OpenCalendar]; @UserName; [UseMailFrameset])
@Command([OpenCalendar]; @UserName; [UseToDoFrameset])
@Command([OpenCalendar]; @UserName; [UseContactsFrameset])

もしかするとNotesのバージョンにより動かないかもしれませんので、ご自身の環境で確認してみてください。

ちなみに @Command([OpenCalendar]) の2番目のパラメータはヘルプには記載されていません。使わないほうが無難です。

2010年1月7日木曜日

Notes を強制終了する

Windows 上の Notes で手動で実行したエージェントを強制終了する場合 Break キーを押しますが、
Notes が出したエラーダイアログに[OK]ボタンで答えても繰り返し同じエラーが表示される等、Break キーが効かないようなどうしようもないループ状態に陥ったときに Notes を強制終了するコマンドがあります。

コマンドプロンプトを開き、次のコマンドを投入します。

cd \Program Files\lotus\notes
nsd -kill


nsd は Notes のプログラムディレクトリにあるので、コマンド投入前にチェンジディレクトリ(cd)します。

2010年1月6日水曜日

ブックマークのSubjectを変更する(デフォルト値を任意の値で置換する)

メールDBには、文書リンクを送信する専用のフォーム「ブックマーク」があります。

メールDB以外のデータベースからこのフォームを利用して誰かに通知したい、といった場合にも使うことができます。

ブックマークの件名はデフォルト値として現在選択されている文書の Subject フィールドの値に"リンクメッセージ(TM): "を自動的に付加されるよう設計されています。

その件名を変えたい場合は、フォームを開いた後に Subject フィールドに設定された値を置換するよう仕込まなければなりません。

次の例では、文書のタイトルに"[Information]:"を付けた文字列をブックマークの件名へセットします。

tmp := Subject;
@Command([Compose]; @MailDbName; "Bookmark");
@UpdateFormulaContext;
@Command([EditGotoField]; "Subject");
@Command([EditSelectAll]);
@Command([EditInsertText]; "[Information]: " + tmp)

ちなみに、上の @Command([EditSelectAll]) の直後に @Command([EditClear]) を実行すると変なメッセージが表示されました。
ただ、すべての文字列を選択した後に値を挿入することは、結果的に選択した値を消去され挿入した文字列で置換することと同意なので EditClear は不要なのでした。

2010年1月5日火曜日

ビューで選択した文書を、ビューのソート順に従って処理する

このトピックについて興味があり、遊んでみました。

ここでの問題は、ビューで選択したいくつかの文書を NotesUIView.Documents や NotesDatabase.UnprocessedDocuments で文書コレクションを取得しても、ビューの表示順(ソート順、並び順)のとおりに取り出すことができないことです。

そこで、ビューのエントリを最初から順になめていき、文書コレクションに一致する文書があれば処理する、といった手法で解決する例が紹介されていて感心したのですが、少々ややこしかったので簡単に書きなおしてみました。

下の例はビューのアクションボタンに記述しています。

Dim ws As New NotesUIWorkspace
Dim dc As NotesDocumentCollection
Dim doc As NotesDocument
Dim entry As NotesViewEntry
Dim nav As NotesViewNavigator

Set dc = ws.CurrentView.Documents '選択文書を取得する(ここは作成日の昇順になる)

Set nav = ws.CurrentView.View.CreateViewNav
Set entry = nav.GetFirstDocument
While Not entry Is Nothing
    Set doc = dc.GetDocument(entry.Document)
    If Not doc Is Nothing Then
        'ここへ処理を記述します。例) Print Format$(doc.Created, "yyyy/mm/dd")
    End If
    Set entry = nav.GetNextDocument(entry)
Wend