Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
ラベル VBA の投稿を表示しています。 すべての投稿を表示
ラベル VBA の投稿を表示しています。 すべての投稿を表示

2011年7月30日土曜日

SKKIME で Google 日本語入力の変換を利用する

1. Google 日本語入力の辞書は、新しい言葉にも対応してくれる

Google 日本語入力は、最近使われるようになった新しい言葉でも、ちゃんと変換してくれるところが便利。

これに対して、SKKIME は、自分で登録しなければならない。できることなら、SKKIME の辞書として、Google 日本語入力で使われている辞書を使いたい。

Google IME SKK サーバー 作った - hitode909のダイアリー」によると、

Google CGI API for Japanese Input*1を使って変換するSKKサーバーを作った.

Googleのサーバーが高性能なので,通常のSKK辞書ではできないような変換ができる.

このアプリを利用すると、例えば、「さーばー」で変換すると、自分で辞書に登録してないにも関わらず、

「サーバー、Server、server、SERVER」

と変換してくれる。

 

2. google-ime-skk のインストールと起動

予め RubyInstaller for Windows をインストールしておくか、RubyGems がインストールされていること。

上記サイトのインストール方法に従い、

gem install google-ime-skk

起動はコマンドラインより、

google-ime-skk

 

3. SKKIME のサーバ辞書の設定

SKKIME の設定において、

  1. 「辞書設定」タブを選択。
  2. 「検索辞書」において「追加」ボタンを押す。
  3. サーバー辞書の追加ダイアログにおいて、追加ボタンを押し、以下のように設定。
    • ホスト名: localhost
    • ポート番号: 55100

CropperCapture[263][3]

 

4. コンピュータの起動時に google-ime-skk を起動

google-ime-skk を、毎回コマンドラインから起動するのではなく、コンピュータの起動時に同時に起動するようにしたい。

自分の場合は、Dropbox 内に、以下の内容の googleime.bat ファイルを作成。これにより、google-ime-skk を起動する。

google-ime-skk

「スタートアップ」フォルダに、以下の内容の googleime.vbs ファイルを作成。上記の googleime.bat を起動するようにした。これにより、google-ime-skk を起動する際、コマンドラインのウィンドウが開かれないようになる。

CreateObject("WScript.Shell").Run "%DROPBOX%\program\skk\googleime.bat",0

ただし、環境変数として DROPBOX 変数として、Dropbox までのパスを設定している。

( cf. Windowsのバッチプログラム(.bat)を実行する際、ウィンドウを表.. - 人力検索はてな )

追記(2014/3/16): googleime.vbs ファイルのパスに「空白を含む」場合、上記のコードの二重引用符を 2 つ続けて記述することに注意。

2010年1月23日土曜日

Access の VBA でスタックトレースを表示

メニューより 「表示 > ツールバー > デバッグ」にチェックを入れる。

img01-21-2010[7].png

VBA のコードを実行する前にブレークポイントを設置。実行して停止したところで、ツールバーの「呼び出し履歴」のアイコンをクリック。

img01-21-2010[8].png

ショートカットキーは、Ctrl + L 。表示メニューにある。

Access の VBA で複数行をコメントアウト

メニューより 「表示 > ツールバー > 編集」にチェックを入れる。

img01-21-2010[7] - コピー.png

コードを複数行選択し、ツールバーのコメントブロックのアイコンをクリック。

img01-21-2010[9].png

2009年5月19日火曜日

Excel でリンクを含んだセルから HTML の A 要素を作成

Excel の A 列に特定のサイトへのリンク付きのテキストがある。ここから HTML の

<li><a>サイト名</a></li>

というような文字列を作成したい。

 

方法

単純にハイパーリンクを記入したセルを別のセルから参照すればいいのかと思いきや、参照するとリンクがなくなりテキストだけになってしまう。

090519-001.png

リンクを抽出したい場合、どうすればいいのだろう?

090519-002エクセルのハイパーリンクについて教えて下さい –OKWave を見ると、Range オブジェクトから Hyperlinks コレクションを取得する方法が書かれていた。特定の範囲からリンクのアドレスを抽出するということのようだ。(via  エクセルのハイパーリンクのURLを抽出するため、... - 人力検索はてな)

 

標準モジュールに以下の関数を定義。

Function createHtmlLink(objRange As range) As String
    createHtmlLink = objRange.Hyperlinks(1).Address
End Function

B1 セルに以下を記述して、下方向へフィル。

="<li><a href="""&createHtmlLink(A1)&""">"& A1 & "</a></li>"

結果

  • すぐに忘れる脳みそのためのメモ
  • Home ‎(すぐに忘れる脳みそのための Wiki)‎
  •  

    参考サイト

    2009年5月14日木曜日

    VBA のバリアント型

    配列の走査

    VBA で文字列の配列の初期化」のついでに配列の走査。変数 strAry が文字列の配列だとすると、

        Dim idx As Integer
        For idx = LBound(strAry) To UBound(strAry)
            Debug.Print strAry(idx)
        Next

    LBound, UBound 関数が記憶の彼方にあって全く思い出せなかった。 (o_ _)o~†

     

    For Each … Next

    For Each を使う場合は、

        For Each elem In strAry
            Debug.Print elem
        Next

    もし、上記に先立ち、

        Dim elem As String

    と宣言すると、

    090513-005

    For Each の場合、要素を指す変数はバリアント型でないといけなかったかぁ。。 (@_@;)

    ヘルプ > Microsoft Visual Basic Documentation > Visual Basic プログラミングのヒント の「For Each...Next ステートメントの使い方」によると、

    For Each...Next ステートメントは、コレクションの各オブジェクトまたは配列の各要素に対して、一連のステートメント ブロックを繰り返し実行します。

    また、「For Each...Next ステートメント」 には、要素が代入される変数について、

    コレクションや配列の各要素を繰り返す変数を指定します。コレクションの場合、引数 element にはバリアント型 (Variant) 変数、総称オブジェクト型変数、または任意の固有オブジェクト型のオブジェクトの変数を指定できます。また、配列の場合は、引数 element にはバリアント型のみ指定できます。

     

    クラスモジュール

    For Each … Next と言えば、オブジェクトのコレクションを走査するときによく使った。例えば、プロパティに「名前」を持つ Person クラスを定義し、

    Dim mstrName As String
    
    Public Property Let Name(strName As String)
        mstrName = strName
    End Property
    
    Public Property Get Name() As String
        Name = mstrName
    End Property

    このクラスのオブジェクトをコレクションに追加し、For Each … Next で走査するには、

        Dim col As New Collection
        
        Dim p1 As New person
        p1.Name = "Tarou"
        Dim p2 As New person
        p2.Name = "Jirou"
        
        col.Add p1
        col.Add p2
        
        Dim person As person
        For Each person In col
            Debug.Print person.Name
        Next

     

    バリアント型

    ところで、バリアント型はあまり使ってなかったので、For Each で使えることを知らなかった。上記でも For Each で使っている要素を代入する変数 person の型宣言をせずにバリアント型としても使用できる。

    ヘルプの「バリアント型 (Variant)」を見ると、

    バリアント型は特殊なデータ型で、固定長の文字列型 (String) データとユーザー定義型を除く、あらゆる種類のデータを格納することができます。

    (太字は引用者による)

    バリアント型というと、文脈に応じて適用する演算子の意味が変化し、メモリを結構使うということしか頭に残っていない。 ^^;

    バリアント型には、特殊な値 Empty 値Null 値、およびエラー値や Nothing なども格納することができます。

    (同上より)

    あぁ~、なんかややこしいなぁ。。 (+_+)

    Empty 値 とはヘルプによると、

    その変数が初期化されていないことを示し

    Null 値とは、

    その変数に有効なデータが格納されていないことを示します。

    確認しておく。

        Dim hoge As Variant
        
        Debug.Print IsNull(hoge)   ' False
        Debug.Print IsEmpty(hoge)  ' True
        
        hoge = Null
        
        Debug.Print IsNull(hoge)   ' True
        Debug.Print IsEmpty(hoge)  ' False
        
        hoge = 0
        
        Debug.Print IsEmpty(hoge)  ' False
        
        Set hoge = Nothing
        
        Debug.Print IsNull(hoge)   ' False
        Debug.Print IsEmpty(hoge)  ' False

    2009年5月13日水曜日

    VBA で文字列の配列の初期化

    1. 文字列の配列

    VBA で文字列の配列を初期化しようと思い、次のコードを実行した。

    dim strAry as String = {"hoge", "piyo", "fuga"}

    しかし、エラーが表示された。(@_@;) VB6 ってこういうのできなかったかな?

    VB 配列の初期化 によると、

    VB6では配列を初期化することはできません。要素ごとに値をセットする必要があります。ただし、固定長の配列は既定値で初期化されています。

    ちなみに .NET では上記のように書けるようだ。

    仕方がないので、

        Dim strAry(2) As String
        strAry(0) = "hoge"
        strAry(1) = "piyo"
        strAry(2) = "fuga"

    しかし、これは面倒だなぁ。。 (+_+)

     

    2. split 関数で初期化

    VB 文字列操作 に split 関数を使う方法が書かれていた。これを参考にして、

        Dim strAry() As String
        strAry = Split("hoge,piyo,fuga", ",")

    こちらの方が楽。 ^^

    VBA で文法のチェックをやめさせる

    Visual Basic Editor で VBA を書いているとき、文法を間違えるとすぐにエラーメッセージが表示される。if 文で条件をキチンと書く前に中の処理を書きたいときなど、一々注意されるのでうざい。 (+_+)

    そこで、メニューより 「ツール > オプション」より「編集 > コードの設定」の「自動構文チェック」のチェックをはずした。

    090513-003.png

     

    これで意図的に文法に不備があった場合でも、エラー箇所が赤く表示されるだけでエラーメッセージが表示されなくなったので、ちょっと書きやすくなった。

    090513-004.png

    2009年5月7日木曜日

    Excel の VBA でグラフの対象範囲を更新する

    1. グラフの内容を、データ入力に応じて変化させたい

    Excel でデータから、グラフを作成。グラフをワークシートに埋め込んだとする。

    090506-019

    このグラフをデータの入力応じて変化するようにしたい。

    このために、グラフの範囲設定に OFFSET 関数を入力してみたけれど、できなかった。

    OFFSET の具体的な結果が設定され、値が固定されてしまう。 (+_+)

     

    2. マクロで記録したものを改造

    そこで、マクロで、グラフの対象範囲を変更する操作を記録してみた。

    Sub Macro1()
        ActiveSheet.ChartObjects("グラフ 1").Activate
        ActiveChart.ChartArea.Select
        ActiveChart.SetSourceData Source:=Sheets("Sheet1").Range("A1:C10")
    End Sub

    これを元にして、ワークシートに埋め込まれたグラフを更新する関数を作成。

    ' ワークシートに埋め込まれたグラフを更新する
    Sub SetChartObjectRange(strWorksheet As String, strChart As String, strRange As String)
        Worksheets(strWorksheet).ChartObjects(strChart).Activate
        ActiveChart.SetSourceData Source:=Sheets(strWorksheet).Range(strRange)
    End Sub

    呼出すときは、グラフが埋め込まれたシート名、グラフ名、範囲を文字列を引数に指定する。

    SetChartObjectRange "Sheet1", "グラフ 1", "A1:c10"

    ところで、グラフをアクティブにしてないと上記はエラーになる。アクティブにしないで更新する方法はないのかなぁ?

     

    3. Excel におけるオブジェクト

    上記 ChartObject オブジェクト とは、

    ワークシートにある埋め込みグラフを表します。ChartObject オブジェクトは、Chart オブジェクトのコンテナとして機能します。(ヘルプより)

    これに対して Chart オブジェクト というのもあったが、これは、

    ブック内のグラフを表します (ヘルプより)

    つまり、Chart オブジェクトは埋め込みではなく、一つのシートで一面グラフのもの (グラフシート) を表わすようだ。

    ちなみによく使う Worksheet オブジェクト は、

    ワークシートを表します。(ヘルプより)

    当り前か。 ^^;

    Excel のオブジェクトモデルを見ていたら、名前がよく似た Sheets コレクション オブジェクト というのがあった。

    指定されたブックまたは作業中のブックにあるすべてのシートのコレクションです。Sheets コレクションには、Chart オブジェクトまたは Worksheet オブジェクトを含めることができます。(ヘルプより)

    Workbook オブジェクト は、

    Excel ブックを表します。Workbook オブジェクトは Workbooks コレクションのメンバーです。Workbooks コレクションには、現在開かれているすべての Workbook オブジェクトが含まれています。(ヘルプより)

    そして、最上位には Application オブジェクトがある。これをまとめて図にすると、

     

    4. グラフシートの範囲を設定

    Chart オブジェクトを更新するには、

    ' グラフシートを更新する
    Sub SetChartRange(strChart As String, strWorksheet As String, strRange As String)
        Charts(strChart).Activate
        ActiveChart.SetSourceData Source:=Sheets(strWorksheet).Range(strRange)
    End Sub

    呼出すときは、グラフシート名, グラフの対象のシート名, 範囲を文字列で指定する。

    SetChartRange "Graph1", "Sheet1", "A1:c10"

    上記 Charts コレクション とは、

    指定されたブックまたは作業中のブックにあるすべてのグラフ シートのコレクションです。各グラフ シートは、Chart オブジェクトによって表されます。ワークシートまたはダイアログ シートにある埋め込みグラフは含まれません。

    Charts(“グラフチャート名”), Worksheets(“ワークシート名”) というように、コレクションに対して文字列でその要素を指定するというのがパターンのようだ。これは Access で Forms(“フォーム名”) と指定するのと同じ。

     

    5. グラフの対象となるワークシートの最後の行番号を知りたい

    No.8 ワークシートの最終行、最終列を取得する」によると、

    UsedRangeプロパティは指定されたワークシートで使われたセル範囲を返します

    これを利用して、ワークシートで使われている範囲のアドレスを文字列で取得。それを先ほど定義した埋め込み用のグラフの範囲を設定するプロシージャに渡す。

    SetChartObjectRange "Sheet1", "グラフ 1", Worksheets("Sheet1").UsedRange.Address

    しかし、これだと離れた列を対象とした、以下のグラフを作成できない。

    090507-023

    最後の行番号を取得できた方が、柔軟に対応できそう。Address プロパティで取得した文字列から行番号を抽出することにした。

     

    6. 正規表現を利用

    Office TANAKA - Excel VBA(正規表現によるマッチング) によると、

    VBAから正規表現を使うには、VBScriptが便利です。ただし、正規表現をサポートしているVBScriptはVer5.0からですから、IE5.0がインストールされているパソコンでないと使えません。

    また、正規表現における後方参照は、VBAで正規表現 - へたれプログラマな日々 を参考にした。

    ワークシート名を渡されると、使われている最後の行番号を返す関数を定義する。

    Function LastRowNum(strWorksheet As String)
        Set re = CreateObject("VBScript.RegExp")
        strCellAddress = Worksheets(strWorksheet).UsedRange.Address(RowAbsolute:=False, ColumnAbsolute:=False)
        re.Pattern = "[A-Z]+[0-9]+:[A-Z]+([0-9]+)"
        LastRowNum = re.Replace(strCellAddress, "$1")
    End Function

    これで例えば、上記のグラフの範囲を設定するなら、

        rownum = LastRowNum("Sheet1")
        SetChartObjectRange "Sheet1", "グラフ 1", "A1:A" & rownum & ",C1:C" & rownum

     

    7. 全体

    ' ワークシートに埋め込まれたグラフの範囲を更新する
    Sub SetChartObjectRange(strWorksheet As String, strChart As String, strRange As String)
        Worksheets(strWorksheet).ChartObjects(strChart).Activate
        ActiveChart.SetSourceData Source:=Sheets(strWorksheet).Range(strRange)
    End Sub
    
    ' グラフシートの範囲を更新する
    Sub SetChartRange(strChart As String, strWorksheet As String, strRange As String)
        Charts(strChart).Activate
        ActiveChart.SetSourceData Source:=Sheets(strWorksheet).Range(strRange)
    End Sub
    
    ' ワークシートの使われている最後の行番号を返す
    Function LastRowNum(strWorksheet As String)
        Set re = CreateObject("VBScript.RegExp")
        strCellAddress = Worksheets(strWorksheet).UsedRange.Address(RowAbsolute:=False, ColumnAbsolute:=False)
        re.Pattern = "[A-Z]+[0-9]+:[A-Z]+([0-9]+)"
        LastRowNum = re.Replace(strCellAddress, "$1")
    End Function
    
    Sub UpdateGraph()
        rownum = LastRowNum("Sheet1")
        SetChartObjectRange "Sheet1", "グラフ 1", "A1:A" & rownum & ",C1:C" & rownum
    End Sub

    2009年5月5日火曜日

    Excel の VBA で名前を付けたセルの内容を参照

    特定の範囲に名前を付ける

    一つのセル、または、特定の範囲に名前を付けるには、セルを選択した後にワークシートの左上をクリックして名前を入力する。

    例えば、セルA1 に “hoge” と名前を付けてみた。

    090505-005

     

    付けた名前を管理しているのは、メニューより「挿入 > 名前 > 定義」。ここで定義した名前を追加・削除できる。

    先ほど定義した “hoge” が表示されている。

    090505-006

    ちなみに、ワークシートごとに名前空間があるのではなく、一つの Excel ファイルにおいて一意な名前を付けることができるようだ。

     

    VBA で名前を付けたセルの値を参照

    メニューより「ツール > マクロ > Visual Basic Editor」を選択し、標準モジュールを作成。(「挿入 > 標準モジュール」)

    さて、セルの値を参照するにはどうすればいいのだろう? Visual Basic Editor 内において F1 でヘルプを表示し、「Microsoft Office Excel オブジェクト モデル」を見ると、Cell というのは見当たらず。

    ところで、上記のように名前を付けるとき、範囲を指定して名前を付けることができた。ということは Range で操作できそうだ。

    090505-007

    ヘルプの Range コレクションによると、

    セル、行、列、1 つ以上のセル範囲を含む選択範囲、または 3-D 範囲を表します。

    標準モジュールにプロシージャを定義した。

    Sub test()
        MsgBox Range("hoge").Value
    End Sub

    プロシージャ内で、F5 または F8 を押して動作を確認。

     

    ちなみにセルを表わすものは、Worksheets オブジェクトのプロパティとして表現されていた。

    2009年4月22日水曜日

    Access のフォームにおいて、イベントが呼出されるタイミングを知りたい

    1. イベントが発生するタイミングを把握する必要がある

    Access が分かりにくい理由は、フォームがどういう仕組みの上で動いているかをちゃんと理解せず、場当たりに作ってしまうことにある。フォームに配置できる部品のプロパティを把握しておくことは重要。

    フォームのデザインにおいて、

    1. フォームの左上隅をクリックし、
    2. プロパティを表示させると、

    色々なイベント処理に対応していることがわかる。

    090422-006

    しかし、その数があまりにも多く、どういうタイミングで何が呼出されているか理解していないと、簡単にイベント処理の森に迷いこんでしまう。 (@_@;)

    イベントの意味、発生する順序について、以下の説明がわかりやすかった。

     

    2. Debug.Print で力技

    どういうタイミングでイベントが呼出されているのか知りたい。

    イベント処理に対して、横からキャッチできないか調べてけれど、分からなかった… (+_+)

    仕方がないので、フォームのイベント全部に Debug.Print 書いて、イミディエイト ウィンドウに出力させた。

    Private Sub Form_Activate()
        Debug.Print "Activate"
    End Sub
    
    Private Sub Form_AfterDelConfirm(Status As Integer)
        Debug.Print "AfterDelConfirm"
    End Sub
    
    Private Sub Form_AfterFinalRender(ByVal drawObject As Object)
        Debug.Print "AfterFinalRender"
    End Sub
    
    Private Sub Form_AfterInsert()
        Debug.Print "AfterInsert"
    End Sub
    
    Private Sub Form_AfterLayout(ByVal drawObject As Object)
        Debug.Print "AfterLayout"
    End Sub
    
    Private Sub Form_AfterRender(ByVal drawObject As Object, ByVal chartObject As Object)
        Debug.Print "AfterRender"
    End Sub
    
    Private Sub Form_AfterUpdate()
        Debug.Print "AfterUpdate"
    End Sub
    
    Private Sub Form_ApplyFilter(Cancel As Integer, ApplyType As Integer)
        Debug.Print "ApplyFilter"
    End Sub
    
    Private Sub Form_BeforeDelConfirm(Cancel As Integer, Response As Integer)
        Debug.Print "BeforeDelConfirm"
    End Sub
    
    Private Sub Form_BeforeInsert(Cancel As Integer)
        Debug.Print "BeforeInsert"
    End Sub
    
    Private Sub Form_BeforeQuery()
        Debug.Print "BeforeQuery"
    End Sub
    
    Private Sub Form_BeforeRender(ByVal drawObject As Object, ByVal chartObject As Object, ByVal Cancel As Object)
        Debug.Print "BeforeRender"
    End Sub
    
    Private Sub Form_BeforeScreenTip(ByVal ScreenTipText As Object, ByVal SourceObject As Object)
        Debug.Print "BeforeScreenTip"
    End Sub
    
    Private Sub Form_BeforeUpdate(Cancel As Integer)
        Debug.Print "BeforeUpdate"
    End Sub
    
    Private Sub Form_Click()
        Debug.Print "Click"
    End Sub
    
    Private Sub Form_Close()
        Debug.Print "Close"
    End Sub
    
    Private Sub Form_CommandBeforeExecute(ByVal Command As Variant, ByVal Cancel As Object)
        Debug.Print "CommandBeforeExecute"
    End Sub
    
    Private Sub Form_CommandChecked(ByVal Command As Variant, ByVal Checked As Object)
        Debug.Print "CommandChecked"
    End Sub
    
    Private Sub Form_CommandEnabled(ByVal Command As Variant, ByVal Enabled As Object)
        Debug.Print "CommandEnabled"
    End Sub
    
    Private Sub Form_CommandExecute(ByVal Command As Variant)
        Debug.Print "CommandExecute"
    End Sub
    
    Private Sub Form_Current()
        Debug.Print "Current"
    End Sub
    
    Private Sub Form_DataChange(ByVal Reason As Long)
        Debug.Print "DataChange"
    End Sub
    
    Private Sub Form_DataSetChange()
        Debug.Print "DataSetChange"
    End Sub
    
    Private Sub Form_DblClick(Cancel As Integer)
        Debug.Print "DblClick"
    End Sub
    
    Private Sub Form_Deactivate()
        Debug.Print "Deactivate"
    End Sub
    
    Private Sub Form_Delete(Cancel As Integer)
        Debug.Print "Delete"
    End Sub
    
    Private Sub Form_Dirty(Cancel As Integer)
        Debug.Print "Dirty"
    End Sub
    
    Private Sub Form_Error(DataErr As Integer, Response As Integer)
        Debug.Print "Error"
    End Sub
    
    Private Sub Form_Filter(Cancel As Integer, FilterType As Integer)
        Debug.Print "Filter"
    End Sub
    
    Private Sub Form_GotFocus()
        Debug.Print "GotFocus"
    End Sub
    
    Private Sub Form_KeyDown(KeyCode As Integer, Shift As Integer)
        Debug.Print "KeyDown"
    End Sub
    
    Private Sub Form_KeyPress(KeyAscii As Integer)
        Debug.Print "KeyPress"
    End Sub
    
    Private Sub Form_KeyUp(KeyCode As Integer, Shift As Integer)
        Debug.Print "KeyUp"
    End Sub
    
    Private Sub Form_Load()
        Debug.Print "Load"
    End Sub
    
    Private Sub Form_LostFocus()
        Debug.Print "LostFocus"
    End Sub
    
    Private Sub Form_MouseDown(Button As Integer, Shift As Integer, X As Single, Y As Single)
        Debug.Print "MouseDown"
    End Sub
    
    Private Sub Form_MouseMove(Button As Integer, Shift As Integer, X As Single, Y As Single)
        Debug.Print "MouseMove"
    End Sub
    
    Private Sub Form_MouseUp(Button As Integer, Shift As Integer, X As Single, Y As Single)
        Debug.Print "MouseUp"
    End Sub
    
    Private Sub Form_MouseWheel(ByVal Page As Boolean, ByVal Count As Long)
        Debug.Print "MouseWheel"
    End Sub
    
    Private Sub Form_OnConnect()
        Debug.Print "OnConnect"
    End Sub
    
    Private Sub Form_OnDisconnect()
        Debug.Print "OnDisconnect"
    End Sub
    
    Private Sub Form_Open(Cancel As Integer)
        Debug.Print "Open"
    End Sub
    
    Private Sub Form_PivotTableChange(ByVal Reason As Long)
        Debug.Print "PivotTableChange"
    End Sub
    
    Private Sub Form_Query()
        Debug.Print "Query"
    End Sub
    
    Private Sub Form_Resize()
        Debug.Print "Resize"
    End Sub
    
    Private Sub Form_SelectionChange()
        Debug.Print "SelectionChange"
    End Sub
    
    Private Sub Form_Timer()
        Debug.Print "Timer"
    End Sub
    
    Private Sub Form_Undo(Cancel As Integer)
        Debug.Print "Undo"
    End Sub
    
    Private Sub Form_Unload(Cancel As Integer)
        Debug.Print "Unload"
    End Sub
    
    Private Sub Form_ViewChange(ByVal Reason As Long)
        Debug.Print "ViewChange"
    End Sub

    あぁ~面倒だった。パタッ(o_ _)o~†

     

    3. 試してみる

    以下の処理をしたときに、どのようなイベントが発生するか試してみた。

    1. フォームを開く
    2. フィールドを修正
    3. フォームを閉じる

    イミディエイト ウィンドウに、以下のように表示された。

    Open
    Load
    Resize
    Activate
    Current
    Dirty
    BeforeUpdate
    AfterUpdate
    Unload
    Deactivate
    Close

    close イベントの前に Before/AfterUpdate イベントが処理されている。Close イベントのときに、Me.Dirty が True でなかったのはこれが理由だったのか。

     

    関連記事

    Access の VBA でエラー処理

    例えば、フォームにレコード を削除するためのボタンがあり、クリックされたときの処理を次のように記述したとする。

    Private Sub btnDelete_Click()
        DoCmd.RunCommand acCmdDeleteRecord
    End Sub

    エラー処理を記述してないので、削除ボタンを押したときに「いいえ」を選択すると、

    090422-003

    実行時エラーのエラーのダイアログが表示される。

    090422-002

     

    エラー処理

    エラーをキャッチして、エラー番号とエラー内容を表示するように変更するなら、

    Private Sub btnDelete_Click()
    On Error GoTo Err_Handler
        DoCmd.RunCommand acCmdDeleteRecord
        Exit Sub
        
    Err_Handler:
        MsgBox Err.Number & " : " & Err.Description
    End Sub

    正常に処理されたとき、エラー処理に突入しないように Exit Sub の記述を忘れずに。

    先ほどと同じ操作をすると、同じ内容がメッセージボックスに表示される。

    090422-004

     

    エラー処理の分岐

    ところで、ユーザに上記のメッセージを見せる必要はないので、エラーの内容によってエラー処理を変えたい。この場合、先ほどのエラー番号で処理を分ける。

    Private Sub btnDelete_Click()
    On Error GoTo Err_Hander
        DoCmd.RunCommand acCmdDeleteRecord
        Exit Sub
        
    Err_Handler:
        If Err.Number = 2501 Then
            Exit Sub
        Else
            MsgBox Err.Number & "/" & Err.Description
        End If
    End Sub

    Java ばかりいじっていたとき、エラー番号でトラップするのを見たときびびったけど ^^;

    ちなみに Chapter 09 例外処理 - @IT によると、

    VB.NETでは、On Error Gotoは「非構造化例外処理」という名前で呼ばれる

     

    後処理がある場合

    もし、次のような要件がある場合は、

    • エラーの内容に応じて処理をしたい
    • 正常、エラーを問わず後処理が必要
    Private Sub btnDelete_Click()
    On Error GoTo Err_Handler
        DoCmd.RunCommand acCmdDeleteRecord
    
    Finally:
        ' 後処理
        Exit Sub
    
    Err_Handler:
        If Err.Number = 2501 Then
            ' このエラー独自の処理
        Else
            MsgBox Err.Number & "/" & Err.Description
        End If
        Resume Finally
    End Sub

    (cf. Access の VBA でトランザクション処理 – CSV ファイルからデータの登録)

    処理がシンプルなときは Resume Next でもいいけれど、バグを誘発しそうなので Resume ラベル名 を使うことにしようかな。

    2009年4月16日木曜日

    Access の VBA でトランザクション処理 – CSV ファイルからデータの登録

    トランザクションと言えば、やはりこの本。

    未だ買ってすらいない。 パタッ(o_ _)o~†

    昔からデータベースのこの辺がチンプンカンプン。入門レベルを読んでもトランザクション、排他制御になるとついていけない。いつかはちゃんと頭の中を整理したいのだけれど… (+_+)

     

    クライアントから見たトランザクション

    トランザクションの大枠については以下の図を参照。

    異常なければコミット、異常があればロールバック。これだけ聞くとシンプルなのだけれど…。とりあえず、クライアントアプリがトランザクションを利用するときのテンプレートを書いておくことに。参考にしたのは以下の二つの記事。

    Access の ADO については、「ActiveX Data Object : ADO入門講座」を。

     

    テンプレート

    以下、ADO でテーブルにレコードを追加するときのテンプレート。Java のように try ~ catch がないのでエラーハンドラと Resume を利用。

    ポイントは、トランザクションが開始された後に異常が置きたらロールバック。正常異常を問わず、最後にデータベースへのコネクションを閉じる。ただし、その際コネクションが存在していることを確認すること。

    Sub tmplADOTran()
    On Error GoTo Err_Handler
        Dim cn As ADODB.Connection
        Dim rs As ADODB.Recordset
        Dim blnTran As Boolean
        
        blnTran = False
        
        Set cn = CurrentProject.Connection
        Set rs = New ADODB.Recordset
        
        ' トランザクションの開始
        cn.BeginTrans                   '
        blnTran = True
    
        rs.Open "テーブル名", cn, adOpenKeyset, adLockOptimistic
        
        ' データを列ごとに追加する
        rs.addnew
        rs!列名 = "値"
        rs.Update
        
        ' コミット
        cn.CommitTrans
        blnTran = False
        
    Finally:
        If Not rs Is Nothing Then rs.Close
        If Not rs Is Nothing Then cn.Close
        
        Exit Sub
        
    Err_Handler:
        ' ロールバック
        If blnTran Then cn.RollbackTrans
        MsgBox "エラーが発生しました: " & Err.Description, vbOKOnly
        
        Resume Finally
    End Sub

    あぁ~、何かややこしいなぁ。 (+_+)

     

    例. CSV ファイルのデータを登録

    上記のテンプレートを元に、CSV ファイル (test.csv) のデータをテーブルに登録するコードを書いてみる。ただし、対象のテーブルは一つ。追加する先のテーブルの列は文字列型とする。

    まず、データを追加するテーブルの名称と列の名前を保持する Table クラスを作成。(挿入 > クラスモジュール) 値を保持するだけなのでインスタンス変数は Public に。

    Public name As String               ' テーブルの名称
    Public cols As New Collection       ' 列の名前のコレクション

    上記のテーブル情報を持ったオブジェクトと、データ元となるファイルへのパスを渡すとレコードを追加する関数は以下のようになる。

    Sub insert(objTbl As table, strFilePath As String)
    On Error GoTo Err_Handler
        Dim cn As ADODB.Connection
        Dim rs As ADODB.Recordset
        Dim blnTran As Boolean
        
        Dim fileNo As Integer
        Dim idx As Integer
        
        blnTran = False
        
        Set cn = CurrentProject.Connection
        Set rs = New ADODB.Recordset
        
        ' トランザクションの開始
        cn.BeginTrans                   '
        blnTran = True
    
        rs.Open objTbl.name, cn, adOpenKeyset, adLockOptimistic
        
        ' CSV ファイルからデータを一行ずつ読み込む
        fileNo = FreeFile()
        Open strFilePath For Input As #fileNo
        Do Until EOF(fileNo)
            Line Input #fileNo, strLine
            strAry = Split(strLine, ",")
            
            ' データを列ごとに追加する
            rs.addnew
            
            idx = 0
            For Each col In objTbl.cols
                rs(col) = strAry(idx)
                idx = idx + 1
            Next
            
            rs.Update
        Loop
        Close #intFileNo
        
        ' コミット
        cn.CommitTrans
        blnTran = False
        
    Finally:
        If Not rs Is Nothing Then rs.Close
        If Not rs Is Nothing Then cn.Close
        
        Exit Sub
        
    Err_Handler:
        ' ロールバック
        If blnTran Then cn.RollbackTrans
        MsgBox "エラーが発生しました: " & Err.Description, vbOKOnly
        
        Resume Finally
    End Sub

    上記を例えば次のように呼ぶ。

    Sub test()
        Dim objTbl As New table
        Dim filePath As String
        
        ' CSV ファイルのパス
        filePath = Application.CurrentProject.Path & "\test.csv"
        
        objTbl.name = "テーブル名"        ' データを追加するテーブルの名前
        objTbl.cols.Add "列名"      ' データを追加するテーブルの列の名前
        
        insert objTbl, filePath
    End Sub

     

    参考

    2009年4月14日火曜日

    ACCESS で生年月日から年齢を求める

    1. 「人」テーブルの定義

    以下のような、「人」テーブルがあるとする。

    1. id : 主キー
    2. 名前
    3. 性別id : 01 男、02 女
    4. 生年月日

    090413-001

    各々の「人」に対して、次のことを知りたい。

    • 現在の年齢
    • 今年度末に何歳になるか?

     

    2. 年齢を求めるための関数を定義

    現在の年齢を求める関数

    生年月日から年齢を求めるには、

    「今年の誕生日が来ているかどうか?」

    がわかればよい。

    そのため、

    1. 「指定された日付に、何歳になっているか求める」関数を定義し、
    2. 現在の日付を渡すことによって、
    3. 今の年齢を求める。

    関数の定義は、

    • 「データベース > モジュール」

    において、新規モジュールを作成する。

    日付の関数については

    を参考にした。

    ' ある誕生日の人が、指定した日付に何歳になっているか返す。
    Function Age(birthday As Date, theDate As Date) As Integer
        If AfterThisYearsBirthday(birthday, theDate) Then
            Age = Year(theDate) - Year(birthday)
        Else
            Age = Year(theDate) - Year(birthday) - 1
        End If
    End Function
    
    ' 指定された日付に今年の誕生日が来ているか?
    Function AfterThisYearsBirthday(birthday As Date, theDate As Date) As Boolean
        If Month(birthday) < Month(theDate) Or _
            (Month(birthday) = Month(theDate) And Day(birthday) <= Day(theDate)) Then
            AfterThisYearsBirthday = True
        Else
            AfterThisYearsBirthday = False
        End If
    End Function

     

    年度末における年齢を求める関数

    次に、年度末に何歳になっているか知りたい。

    上記と同様に「指定された日付の年度末を返す」関数を書く。

    ' 指定された日付の年度末の日付を返す
    Function Nendomatu(theDate As Date) As Date
        Const DATE_331 = "/3/31"
        If 4 <= Month(theDate) And Month(theDate) <= 12 Then
            Nendomatu = CDate(Year(theDate) + 1 & DATE_331)
        Else
            Nendomatu = CDate(Year(theDate) & DATE_331)
        End If
    End Function

     

    3. クエリから関数を呼び出す

    上記の関数を、クエリから呼出すようにする。

    1. 「人」テーブルを対象にしたクエリを作成し、
    2. 「年齢、年度末の年齢」を求めたい列で、右クリック > ビルド... を選択し、
    3. 式ビルダを呼出す。

    左下を見ると、「関数」フォルダに、先ほど作成したモジュール名が表示され、定義した関数が属しているのがわかる。

    090413-003

    それぞれ以下のように定義する。

    年齢: Age([生年月日],Now())
    年度末の年齢: Age([生年月日],Nendomatu(Now()))

    クエリの結果。今日の日付は 2009.4.13 。

    090413-002

     追記(2009.4.25) : 学校の学年においては、 4/1 生まれは早生まれになるので、年度末の翌日における年齢を求める。

    年度末の年齢: Age([生年月日],nendomatu(Now())+1)

    (参考: 法制執務コラム集「4月1日生まれの子どもは早生まれ?」)

     

    4. クエリの中だけで計算するには

    生年月日から年齢を計算する方法」には、次のようなクエリが書かれていた

    年齢: IIf(Right(Format([誕生日],"yyyy/mm/dd"),5)>Right(Format(Now(),
    "yyyy/mm/dd"),5),DateDiff("yyyy",[誕生日],Now())-1,DateDiff("yyyy",
    [誕生日],Now()))

    (via 生年月日の入力によって年齢を出したい。 --Access Club 超初心者 FORUM—)

    うへぇ~、一行だと読みにくい… (+_+)

    そもそも、これは何だろう?

    Right(Format([誕生日],"yyyy/mm/dd"),5)

    関数をテストしてみる。

        Debug.Print CDate("1/1")    ‘ 2009/01/01  
        Debug.Print CDate("1")      ‘ 1899/12/31 
        Debug.Print CDate("366")    ‘ 1900/12/31 
        Debug.Print CDate("0")      ‘ 0:00:00 

    CDate のヘルプを見ると、

    CDate    日付型 (Date)    任意の有効な日付式

    「日付式」とは、

    日付として解釈可能な式。… 日付と解釈できる文字列、

    結果を見ると、そこまで無理に解釈しなくても…と言いたくなるような仕様。 ^^;

    日付の内、「年」を省略すると、「現在の年」と解釈されるようだ。

     

    5. シンプルな計算方法

    生年月日から年齢を計算する簡単な計算式:ITpro」には、次のような計算式が書かれている。

    (今日の日付-誕生日)/10000の小数点以下切捨て。

    なるほど、「年月日」をわざと一連の数値として扱う方法のようだ。確かに、年月だけ見れば、年月を各々二桁で表現しているなら、誕生日が来ていれば、今日の日付よりも誕生日の数値が小さくなる。

    先ほどの方法でパフォーマンスが悪ければ、こちらに変更しようかな。

    ただし、次の注意点が述べられている。

    「誕生日の前日が終了する瞬間(すなわち誕生日をむかえる午前0時00分の直前)に1歳を加えることになる」の部分の解釈には前日に1日加えると解釈される場合と当日に1日加えると解釈される場合の2種類があるようです。特に自治体においては前者で解釈される場合が多いとのことです。

    (同上より)

    ん?これは自治体の特殊ルールで、「年齢」と「年齢をどう解釈するか」とは別の話だよねぇ。

    2009年3月31日火曜日

    Access でコンボボックスの「リスト外入力時」のメッセージを変更

    テーブルの構造とルックアップの設定

    「人」テーブルと「都道府県」テーブルがあるとする。

    「人」テーブルは「都道府県」を参照。 id は主キーで、CD は候補キー。 id はシステムがタプルを一意に識別するためのもので、CD も一意に識別することができるが人間の都合で変更の可能性がある。

    090331-003

     

    「都道府県」を参照しているフィールドは、「ルックアップ」で「コンボボックス」が表示されるようにする。

    090331-004

     

    クエリからフォームを作成

    上記のテーブルから下記のクエリを作成。

    090331-005

     

    これを元にフォームを作る。

    このとき、フォームにおける「都道府県」の入力は CD (コード) でしようと考えていたので、「あれ? id を入力しなくていけないかな?どうしよう… (@_@;) 」と思ったけれど、上記の通り「人」テーブルの「都道府県id」フィールドには「ルックアップ」が設定されているので、id の入力をあたかも CD を入力してるかのようにフォームのコンボボックスで入力できる。

     

    リスト外入力時のイベント

    さて、ここで「都道府県」テーブルにない CD を入力しようとすると、

    指定した項目はリストにありません。

    とメッセージが表示される。

    090331-007

     

    このメッセージではユーザにとってわかりにくいので変更したい。

    コンボボックスのプロパティより「イベント > リスト外入力時」の VBA を記述する。

    Private Sub 都道府県id_NotInList(NewData As String, Response As Integer)
        Response = acDataErrContinue
        MsgBox "存在しない都道府県コードです"
    End Sub

    引数 Response の設定で先ほどのメッセージを抑制して、新たにメッセージを表示する。

     

    参考

    2009年3月24日火曜日

    Access の VBA で関数をちょっと試すには

    1. VBA で関数の動作を調べたい

    試したい関数があるとき、

    1. フォームでコマンドボタン作り
    2. 関数をボタンを押したのイベントに記述し、
    3. MsgBox で出力する

    という方法は手間がかかる。

     

    2. イミディエイトウィンドウを使う

    データベースウィンドウで右クリック > Visual Basic Editor を選択。

    090324-007

    表示 > イミディエイトウィンドゥ」 を選択しておく。

    標準モジュール」の中でプロシージャを作成し、

    • F5 で実行。
    • F8 でデバッグ。

    これにより、イミディエイトウィンドウに結果が表示される。

    090324-005

    関数の場合は「表示 > ローカルウィンドウ」 を表示しておくと良い。

    2008年3月10日月曜日

    Access で複数のサブフォーム間でのフォーカスの移動

    サブフォーム間を移動するためのショートカットキー

    サブフォーム間を移動するためのショートカットは、 Ctrl + Tab 。サブフォーム内で Tab キーを押し続けると、サブフォーム内に限定されたレコード間のフォーカスの移動が行われる。帳表の形式である場合、通常の入力において、サブフォーム内の新しいレコードを追加するための動作が生じる。

     

    1 対 1 のサブフォーム

    あるフォームに、複数のサブフォームを存在させることがある。その中に、親となるフォームに対して、 1 対 1 の関係にあるサブフォームがあるとする。この場合、データの入力において、タブキーを押し続けると、上記と同様に、サブフォーム内で新しいレコードを追加しようとする動作が生じる。 親フォームに対して 1 対 1 の関係のサブフォームなので、そのまま追加すると、主キーの制約違反の通知がされる。

    1 対 1 の関係である場合、最後のフィールドの入力が終ったら、レコード追加の動作が生じるのではなくて、できれば、次の入力フィールドへ移ってほしい。例えば、入力中のサブフォームから、別のサブフォームのフィールドへフォーカスが移ってほしい場合、入力が終ったフィールドの「フォーカス喪失時」のイベントにおいて、以下のようなコードを記述することによって、タブキーによるスムーズなデータの入力を実現できる。

    Forms!フォーム名!サブフォーム名.SetFocus

    フォーカスされた方のサブフォームでは、「フォーカス取得時」のイベントで対応する。

    2008年3月9日日曜日

    Access でダイアログボックスを表示して、ユーザに Yes, No を選択させる

    MsgBox 関数 を利用し、引数に vbOKCancel をとる。

    MsgBox(prompt [, buttons ] [, title ] [, helpfile ] [, context ] )

    prompt 必ず指定します。ダイアログ ボックス内にメッセージとして表示する文字列式を指定します。引数 prompt に指定できる最大文字数は、1 バイト文字で約 1,024 文字です。

    vbOKCancel [OK] ボタンと [キャンセル] ボタンを表示します。

    (MsgBox 関数 - Access - Microsoft Office Online より)

    . レコードを削除する前に、ユーザに確認をとるためのダイアログを表示する。

    If (MsgBox("本当に削除してもよろしいですか?", vbOKCancel) = vbCancel) Then
       Exit Sub
    End If
    
       ' 処理を続ける...

    080309-001

    関連サイト

    2008年2月29日金曜日

    Access の VBA でフォームのレコードをフィルタしてソートする

    フィルタの適用

    DoCmd オブジェクトの ApplyFilter メソッドを使う。

    DoCmd.ApplyFilter , 条件式

    条件式で Like 演算子を使うときは、

    • % ではなくて、*
    • _ ではなくて、?

     

    フィルタの解除

    フォームオブジェクトの FilterOn プロパティを使う。レポートの方でも使えるようだ。

    Me.FilterOn = False

     

    ソート

    フォームオブジェクトの OrderBy プロパティを使う。

    Me.OrderBy = "フィールド名"

    • 複数のフィールドを指定するときは、コンマで区切る。
    • 降順にするには、列名の後に DESC をつける。

    2008年1月29日火曜日

    Excel VBA で選択範囲を値に応じたセルの色に変更する

    以下の表は、各男性の各女性に対する好みを 5 段階評価で表わしている。

    080129-002

    上記の表を次のように値に応じてセルに色を付けたい。

    080129-003

    方法

    予めセルには値が入力されていて、色をつけたい範囲を選択してから、ボタンを押して色を付けるという実装にする。

    VBA で以下のコードをサブルーチンとして定義する。

    With Selection
    For i = .Row To .Row + .EntireRow.Count - 1
        For j = .Column To .Column + .EntireColumn.Count - 1
            If IsNumeric(ActiveSheet.Cells(i, j).Value) Then
                ActiveSheet.Cells(i, j).Interior.ColorIndex = _
                    ActiveSheet.Cells(i, j).Value + 41
            End If
        Next
    Next
    End With
    ボタンを作成し、上記サブルーチンを呼出すようにする。

    セルの色は、Cells の Interior.ColorIndex で設定する。この値は以下の数値と対応している。

    080129-001

    参考

    疑問

    最初、Worksheet の Change イベントで値が入力されたときに、セルの色を変更しようと考えた。一つ一つの値を入力するときは問題なかったが、複数の値をコピペして値を変更しようとするとエラーがでてしまった。 QQQ