プログラム

125件の投稿

編集モードとアイドルモード 2

コレクションを使って、モードを切り替えることは簡単です。

例えば9月18日の[CtrlBindingCollection]クラスに次のメソッドを追加します。

    Sub モード設定(ByVal blnEdit As Boolean)
        For i As Integer = 0 To m_lst.Count - 1
            m_lst(i).モード設定(blnEdit)
        Next
    End Sub

m_lst(i).モード設定(blnEdit)という怪しげなコードがありますが、要は[編集モード]にしたいときはそのコントロールがButtonなら[Enable = False]、TextBoxなら[Readonly = False]にするだけです。

すこし厄介なのは、画面上のコントロールに触ったとき、直ちに[編集モード]([修正]作業)に切り替える仕組みです。

やりたいことは、当該のコントロールが[TextBox(Base)]のときは[TextChanged]イベント、[ListBox]のときは[SelectIndexChanged]イベントが発生したとき、コントロールの入力データが編集されたとみなし、[編集]作業用ルーティンをコールすることです。

ここで、引数のない[Sinki]、[Shusei]、[Sakujo]等のメソッドを用意し、[新規]、[修正]、[削除]等のボタンのクリックイベントでこれらのメソッドをコールするようにします(無理に引数のないメソッドにすつこともないのですが、説明も楽ですので…)。
すなわち、れぞれのボタンをクリックすれば、それぞれのメソッドを起動し[新規]、[修正]、[削除]等の作業に入ります。

さて、TextBox等が[編集]されればこの[修正]メソッドをコールするようにしたいのです。

次にこれを処理するクラスをご紹介します。
本当はもっと複雑ですが、本質部分はこのようなものです。

Public Class Set2Changed

    Private m_dlg修正メソッド As System.Action

    Sub New(ByRef 修正 As System.Action, ByRef ctrls() As Control)
        Me.m_dlg修正メソッド = 修正

        For Each ctrl As Control In ctrls
            SetChangedHandler(ctrl)
        Next
    End Sub

    Private Sub SetChangedHandler(ByRef ctrl As Control)
        If TypeOf (ctrl) Is TextBoxBase Then
            AddHandler ctrl.TextChanged, AddressOf MyTextChanged
        ElseIf TypeOf (ctrl) Is ListControl Then
            AddHandler CType(ctrl, ListControl).SelectedIndexChanged, AddressOf MySelectedIndexChanged
        ElseIf TypeOf (ctrl) Is ButtonBase Then
            AddHandler CType(ctrl, ButtonBase).CheckedChanged, AddressOf MyCheckedChanged
        End If
    End Sub

    Private Sub MyTextChanged(ByVal sender As Object, ByVal e As System.EventArgs)
        m_dlg修正メソッド.Invoke()
    End Sub

    '    その他、[MySelectedIndexChanged][MyCheckedChanged]等も準備します
End Class

Formからこのクラスのインスタンスを作成しますが、このときそのFormの[修正]メソッドと、監視するコントロールの集合(実際にはコレクション)を渡します。
メソッド[SetChangedHandler]では、これらのコントロールのデータが修正されると、修正デリゲートが起動するイベントハンドラを各コントロールに付加しています。

これだけで、画面の各コントロールの修正にかかると、[修正]作業に入り、[モード設定]をコールすることにより[編集モード]になります。

編集モードとアイドルモード

David Sceppa[プログラミング ADO.NET]の13章の例に倣って(Sceppaでなくてもよくあることですが)、現在私はデータ入力画面では[編集モード]と[アイドルモード]の区別を徹底しています。そうしないと、メモリ上のデータの管理が難しくなるからです。

画面で何か編集すると編集モードに移行し、必ずそのデータを保存するか作業をキャンセルするか(すなわちアイドルモードに移行)しないと、他の画面への遷移ができないようにしています。

もっと正しくいうと、画面上で[新規]、[修正]、[削除]の何れかのボタンを押すと編集モードに移行します。編集モードでは[登録]あるいは[キャンセル]ボタンを押さない限り、[その]操作すなわち[新規]作業、[修正]作業あるいは[削除]作業から抜け出すことはできません。

[新規]作業中は新規データを作成する以外の作業は許されません。[修正]作業でも修正作業以外の作業はできませんし、[削除]でも同様です。

ただし[削除]はワンクリックの操作しかなく、あとは[登録]するか[キャンセル]するかです。

また、画面であるデータを読み出して修正しようとするとき、その都度[修正]ボタンを押すのは「カッタルイ」ので、画面を修正した途端に自動的に[修正]作業に移行するようにしています。

同じ編集モードでも[新規]、[修正]あるいは[削除]作業から最終的にデータベースにデータを保存するとき、処理が異なってきますので、Form変数でその時の作業状態を保存しています。

編集の途中で他の画面に移行したり、[削除]に続いて[新規]作業しデータベースへの保存を試みると[同時実行違反]の原因になります。
ですからプログラムでそれらの操作をさせないようにする必要があります。
[編集モード]になったら、まず[編集]用ボタン[新規]、[修正]および[削除]ボタンはEnabel = Flaseにしなければいけませんし、これだけでなく触らせたくない画面上のコントロールはすべてEnable = Falseにしなければいけません。

そして、[アイドルモード]に移るとこれらのコントロールを開放してやらなければなりません。

たくさんのコントロールの状態制御に以前(昨2009年9月)ご紹介しましたCollectionを使っています。

Spreadの工夫 2

前回の「clsShtData」の主要な利用価値は、[コード]で[デザイン]を決めることと、次の二つの機能です。

一つ目は、列名に対して列番号(Index)を返す仕組みです。

    SpdIdx賃料 = .画面設定(multiHeader, 非表示, True)

では、SpdIdx賃料は実は次のように定義されています。

    Private SpdIdx賃料 As Dictionary(Of String, Integer)

例えば、 SpdIdx賃料(“金額”)の値は2、SpdIdx賃料(“税区分”)の値は3を返します。
同じ列名があるときは、2番目以降は[金額_2]等のように出現番号を付けて返します。
従って、SpdIdx賃料(“金額_2”)の値は8、SpdIdx賃料(“税区分_2”)の値は9を返します。
Spreadでは列や行の指定にはIndex(整数)を使いますが、これをハードコードにすることの煩わしさはプログラムを書く人なら分かると思います。

重宝しているもう一つは、Spreadの幅を計算するものです。まず、[行見出し]の幅で、シートの[RowHeader.Columns](一般に複数列)の幅です。次はシートの表示列(非表示ではなく)の幅の全合計で、前回のコードでは次式で設定しています。

    New clsSpdHeader("コード", 55, SpdIntCellType, False)

さらにバーティカルBarがあればその幅で、Spread の[VerticalScrollBarWidth]プロパティで得られます。
最後はSpreadの両サイドの[BorderStyle]です。
これらをすべて足し合わせてものが必要なSpread幅です。
これによってピタッとしたSpreadを作成できます。

このクラスを継承したクラスもいくつか作っています。
例えば、消費税や管理料の計算をするクラスもその一つです。

Spreadの工夫

私は米FarPoint社のSpreadを多用しています(Excelに似たプログラム開発用シート)。

予断ですが…
このSpreadは日本ではGrapeCity社が日本語版を販売しています。
原版に比べて日本語版は倍近く高価です。
原版はマニュアルやデザイン画面のメニューが英語であることを我慢すれば、使用には何も問題はありません。
ところが、最近このFarPoint社はGrapeCity社に買収されたようです(多分)。

このSpreadでは沢山の工夫をしています。

まず、
Spreadの列幅やCellTypeの設定等々様々な設定をデザイン画面で設定すると、結構手間がかかります。
しかも開発の途中でシートのデザインの変更が多々ありますので、その都度デザイン画面でやり直しをやっていては大変非効率です。
そこで私は、クラス[clsShtData]を作ってこれらの処理を全部このクラスに任せています。
パフォーマンスの検証はしていません。
少なくとも最終版の手前まではこれらの設定をコードで書き、最終的にパフォーマンスを考慮した方法に置き換えるのも一つだと思います(私の経験では特にパフォーマンスが悪いとは思いません)。

Spread2
        Sht賃料 = New clsShtData(FpSpd賃料, 0, True, True, 賃料表示行数)
        With Sht賃料
            With .P_Heading
                .Add(New clsSpdHeader("コード", 55, SpdIntCellType, False))
                .Add(New clsSpdHeader("科目名", 100, SpdStrCellType, False))
                .Add(New clsSpdHeader("金額", 80, SpdComCellType, True))
                .Add(New clsSpdHeader("税区分", 55, cmbcell税区分, False))
                .Add(New clsSpdHeader("消費税", 60, SpdComCellType, True))
                .Add(New clsSpdHeader("合計", 80, SpdComCellType, True))
                .Add(New clsSpdHeader("送金", 40, Spdchkcell, False))
                .Add(New clsSpdHeader("料率", 40, SpdDblCellType, False))
                .Add(New clsSpdHeader("金額", 60, SpdComCellType, True))
                .Add(New clsSpdHeader("税区分", 55, cmbcell税区分, False))
                .Add(New clsSpdHeader("消費税", 60, SpdComCellType, True))
                .Add(New clsSpdHeader("合計", 60, SpdComCellType, True))
                .Add(New clsSpdHeader("賃料明細ID", 60, SpdIntCellType, False))
            End With

            ' VB2008仕様
              Dim multiHeader(,) = New Object(,) {{"金額", 1, 4, "金額"}, {"送金", 1, 6, "管理料"}}
            Dim 非表示() = {"賃料明細ID"}
            SpdIdx賃料 = .画面設定(multiHeader, 非表示, True)
        End With

このコードで、Spread[FpSpd賃料]のインデックス[0]のシートで、行数は[賃料表示行数]に設定、合計行を作成し、指定する列で合計を計算表示します。

[P_Heading]では次の内容を設定しています。
列名は、[コード]、[科目名]、[金額]等で、列幅はそれぞれ[55]、[100]、[80]で、データ型はそれぞれ[SpdIntCellType]、[SpdStrCellType]、[SpdComCellType]であり、[金額]列は合計を計算します。

[画面設定]メソッドでは、[P_Heading]の内容で各シートのスタイルを設定しますが、このとき列名を表示するヘッドの部分は2行にし、列名[金額]から4列分に分類名[金額]、同じく[送金]から6列に分類名[管理料]を付けています。
また、[賃料明細ID]の列は非表示にしています。

[clsShtData]ではそのほか様々な処理をしています。
一般的に参考になることを次回紹介します。

複数コントロールのセットを何度も使う

今、郵便番号を複数の画面で検索する場合を想定します。
郵便番号を入力して[住所検索]ボタンをクリックすると住所を表示し、住所を入力して[郵便番号検索]ボタンをクリックすると郵便番号を表示するというものです。

YubinBango

ここには、二つのTextBoxと二つのボタン、すなわち郵便番号を入出力するTextBox(あるいはMaskedTextBox)、住所を入出力するTextBox 、入力した郵便番号から住所を検索出力するためのボタン、逆に住所から郵便番号を検索出力するためのボタンがあります。

いまここで、ボタンにはそれぞれ郵便番号から住所を検索出力する、逆に住所から郵便番号を検索出力するプログラムが対応しているものとします。

あちこちの郵便番号検索画面で、これらのコントロールを配置しボタンのイベントプロシージャをその都度書くのは嫌です。

一つの解決策は、上のすべてのコントロールを一つにカスタムコントロールにする手があります。
が、少し大げさなので、次のようなクラスを一つ作っています。
主眼は、このクラスのインスタンスを作るとき、クライアントのコントロールをByRefで渡すこと、渡されたコントロールのイベントをこのクラスの中に定義することです。

Public Class cls住所検索
    Friend WithEvents m_btn住所検索 As Button
    Friend WithEvents m_btn郵便番号検索 As Button

    Private m_msk郵便番号 As MaskedTextBox
    Private m_txt住所 As TextBox

    Sub New(ByRef btn住所検索 As Button, ByRef btn郵便番号検索 As Button, _
        ByRef msk郵便番号 As MaskedTextBox, ByRef txt住所 As TextBox)

        m_btn住所検索 = btn住所検索
        m_btn郵便番号検索 = btn郵便番号検索
        m_msk郵便番号 = msk郵便番号
        m_txt住所 = txt住所
    End Sub

    Private Sub btn住所検索_Click(ByVal sender As Object, ByVal e As System.EventArgs) _
        Handles m_btn住所検索.Click

        Using frm As New frm住所取得
             frm.Called郵便番号2住所(m_msk郵便番号.Text)
            If frm.DialogResult = Windows.Forms.DialogResult.OK Then
                m_txt住所.Text = frm.P_住所
             Else
                MsgBox("この郵便番号は、有効ではありません。郵便番号を変更してください。", _
                    MsgBoxStyle.Exclamation, "住所検索")
            End If
        End Using
    End Sub

    Private Sub btn郵便番号検索_Click(ByVal sender As Object, ByVal e As System.EventArgs) _
        Handles m_btn郵便番号検索.Click
        Using frm As New frm郵便番号取得
            frm.Called住所2郵便番号(m_txt住所.Text)
            If frm.DialogResult = Windows.Forms.DialogResult.OK Then
                m_msk郵便番号.Text = frm.P_郵便番号
            Else
                MsgBox("この住所は、有効ではありません。住所を変更してください。", _
                    MsgBoxStyle.Exclamation, "郵便番号検索")
            End If
        End Using
    End Sub
End Class

これを使う画面のコードは、次のようになります。

    Private inst住所検索 As cls住所検索

   Private Sub frm_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
        inst住所検索 = New cls住所検索(btn契約者住所検索, btn契約者郵便番号検索, _
                msk契約者郵便番号, txt契約者住所1)
    End Sub

すなわち、各画面ではこのクラスのインスタンスを作るだけで、処理はすべてこのクラスが行います。
私はこのような仕組みをよく使います。
たとえば、氏名を漢字で入力するとそのふりがなを別のTextBoxに表示する場合も同様です。