SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

現役エンジニア直伝! 「現場」で使えるコンポーネント活用術(MultiRow)

一覧更新画面を持つWebアプリの作り方~MultiRow for ASP.NETの編集機能を使いこなす!

1レコード複数行表示を実現するMultiRow for ASP.NETの実力(後編)

  • このエントリーをはてなブックマークに追加

ダウンロード サンプルソース (83.3 KB)

 MultiRow for ASP.NETには、データソースと連携したバウンドモードと連携しないアンバウンドモードがあります。本稿では、一覧系Webアプリケーションの更新機能の実装方法を、より細かな制御が可能なアンバウンドモードを中心に紹介していきます。

  • このエントリーをはてなブックマークに追加

標準Webコントロールではなかなか難しい機能をMultiRowで実現

 一覧系のWebアプリケーションを作成する際、悩むことの一つに、編集機能をどのように実装するかという点があります。

 一般的な実装方法としては、一覧の各列に[編集]ボタンを用意して、それをクリックしたら編集用ページが開き編集できる、という流れが考えられます。この流れがなぜ一般的かと言えば、参照と編集で適切なデザインを使い分けられるからです。それ以外にも、編集画面を閉じるタイミングでサーバー側にデータを送信して保存することで、ブラウザを閉じてしまったとしても最後に編集中だったデータ以外は確実にサーバー側に保存できるという点も考慮してあるためだと思います。

 しかし、一覧画面でどんどんとデータを更新していき、任意のタイミングでサーバー側に保存するような操作性を、Webアプリケーションで実現したい場合もあります。Visual Studioに添付されている標準Webコントロールでは実現が難しい、このような一覧更新画面も、MulitRow for ASP.NET 1.0Jを使えば簡単に実現可能です。

 MultiRow for ASP.NETには、データソースと連携したバウンドモードと連携しないアンバウンドモードがあります。本稿では、より細かな制御が可能なアンバウンドモードを使って更新機能を実装する方法を中心に説明し、その後に、ObjectDataSourceを使ったバウンドモードの設定方法を紹介していきます。

 なお、ヘルプファイルと製品サンプルコードには、AccessDataSourceと連携したバウンドモードの例があります。そちらをご覧いただくと、バウンドモードの手軽さがより理解できると思います。

アンバウンドモードとバウンドモード

 前述のとおり、MultiRow for ASP.NETにデータを表示する方法には、アンバウンドモードとバウンドモードがあります。

 アンバウンドモードは、データソースと連結せずにMultiRowに1行づつデータを格納していく方法です。この方法の利点は、格納時にデータを加工しながら設定できたり、データソースとして連結できないようなデータをMultiRowに設定できる点です。その分、コードの記述量は多くなります。

 一方、バウンドモードはデータソースと連結してデータソースの内容を自動的にMultiRowに表示したり、データの追加・更新・削除に対してデータソースの関連メソッドをMultiRowが自動的に呼び出したりすることができます。そのため、データソースの作り方が重要になってきますが、データ表示などMultiRowに関連するコードの記述量は少なくなります。

アンバウンドモードを使って更新系画面を作成しよう

 アンバウンドモードでは、まず必要なデータをすべて取得してMultiRowに設定します。ページングなどを行う際も、ページ単位ではなく全件取得し、ブラウザの中でMultiRowが必要なレコードのみを表示するようにページ制御を行います。このとき、サーバー側にはPostBackもCallBackも発生しません。

 表示したデータの変更には、次の2つのパターンがあります。

(1)削除

 行を選択してツールバーの[削除]ボタンをクリックするか、ショートカットキーとして既定で定義されている「Ctrl+Delete」を押下することで、選択した行の情報を添えてサーバー側でRowDeletingイベントが発生します。

(2)追加、更新

 追加行にデータを入力したり、表示されているデータを更新しただけでは、サーバー側でイベントは発生しません。また、編集後に別の行を選択したときにも、イベントは発生しません。

 追加、または更新した後にツールバーの[データの更新]ボタンをクリックするか、ショートカットキーとして既定で定義されている「Ctrl+Enter」を押下することで、追加行と編集行ごとにサーバー側でRowInsertingイベントやRowUpdatingイベントが発生します。

図1 MultiRowのツールバー
図1 MultiRowのツールバー

 イベントは該当する行ごとに発生するため、サーバー側の記述は、1行を対象にすればよいのでシンプルなコードになります。

アンバウンドモードのサンプルの画面構成

 今回のサンプルでは、MultiRowテンプレートデザイナで図2のように設定したデザインとしました。

図2 アンバウンドモードのサンプル画面構成
図2 アンバウンドモードのサンプル画面構成

 行を特定するための主キーとなるID列の値を設定するため、図2のデザインではID列を設定するIDセルは非表示の状態で列ヘッダと重ね合わせています。

 UnitPriceセル、Numberセル、Priceセルには初期値として「0」を設定するため、NullValueプロパティに「0」を設定し、UnitPriceセルとPriceセルにはCurrencySymbolプロパティに「\」を指定しています。

 CategoryセルはDropDownListになっており、区分マスタに設定された値を選択できるようになっています。

 MultiRow for ASP.NETのテンプレートデザイナも、MultiRow for Windows Formsのテンプレートデザイナと同様に、セルとセルをきっちりと寄せ合う事ができ、その操作性もブロックがカチッとはまるようで快適です。なお、MultiRowでは、テンプレートデザイナでデザインした内容が1行分のデータ表示域となります。

図3 アンバウンドモードのサンプル実行例
図3 アンバウンドモードのサンプル実行例

アンバウンドモードでのデータ表示

 アンバウンドモードでデータを表示するには、リスト1のようなコードを記述します。

リスト1 アンバウンドモードでデータ表示
Protected Sub Me_Load(ByVal sender As Object,
                      ByVal e As System.EventArgs) _
                  Handles MultiRowSample_Form.Load
    If Not Me.IsPostBack Then
        Call GetRecords()
    End If
End Sub

Private Function GetRecords() As Boolean
    Dim isOK As Boolean = False

    Using _proc As New CZ1008Bound
        Dim ds As Data.DataSet = _proc.GetRecords("admin", "", "WS-DF502")
        'カテゴリドロップダウン設定
        With block
            Dim cell As GrapeCity.Web.MultiRow.DropDownListCell =
                CType(Me.MultiRow1.Template.Row.Cells("Category"), DropDownListCell)
            For Each row As Data.DataRow In ds.Tables("Category").Rows
                cell.Items.Add(New GrapeCity.Web.MultiRow.ListItem(row(0).ToString))
            Next
            Me.MultiRow1.Template = Me.MultiRow1.Template
        End With
        'データ設定
        For index As Integer = 0 To ds.Tables("Bill").Rows.Count - 1
            MultiRow1.Rows.Add()
            With MultiRow1.Rows(index)
                Me.ViewState.Add("CustomerID", ds.Tables("Bill").Rows(index).Item("CustomerID").ToString)
                .Cells("ID").Value = ds.Tables("Bill").Rows(index).Item("ID")
                .Cells("Date").Value = ds.Tables("Bill").Rows(index).Item("Date")
                .Cells("Products").Value = ds.Tables("Bill").Rows(index).Item("Products")
                .Cells("Number").Value = ds.Tables("Bill").Rows(index).Item("Number")
                .Cells("UnitPrice").Value = ds.Tables("Bill").Rows(index).Item("UnitPrice")
                .Cells("Price").Value = .Cells("Number").Value * .Cells("UnitPrice").Value
                .Cells("SlipNo").Value = ds.Tables("Bill").Rows(index).Item("SlipNo")
                .Cells("Category").Value = ds.Tables("Bill").Rows(index).Item("Category")
            End With
        Next
        '
        isOK = True
    End Using
    Return isOK
End Function

DropDownListに選択候補を設定する方法

 リスト1の「カテゴリドロップダウン設定」のコメント行の次からが、Category選択用のDropDownListの選択候補を設定するコードです。

 選択候補は行データではないので、テンプレートに値を設定します。「CType(Me.MultiRow1.Template.Row.Cells("Category"), DropDownListCell)」でテンプレートの該当セルへのパスを取得したら、Items.Addメソッドで選択候補を追加して、最後に「Me.MultiRow1.Template = Me.MultiRow1.Template」の呪文行を実行して、テンプレートに反映します。

MultiRowにデータを設定する方法

 MultiRowへのデータ設定は、「MultiRow1.Rows.Add()」にてテンプレートのデザインに応じた行を追加し、その行に対して「.Cells("ID").Value」のようにセルを指定して値を設定することで行います。

アンバウンドモードでのデータ更新

 アンバウンドモードでのデータ更新は、ツールバーやショートカットキーの操作によって発生します。サンプル(CZ1008UpdateSample)では、RowInsertingイベント、RowUpdatingイベント、RowDeletingイベントでデータベースに変更点を反映しています。

データベースにデータを追加する方法

リスト2 アンバウンドモードでデータを追加
Protected Sub MultiRow1_RowInserting(ByVal sender As Object,
                                     ByVal e As GrapeCity.Web.MultiRow.RowInsertingEventArgs) _
                                 Handles MultiRow1.RowInserting
    If e.Values("Number") Is Nothing Then
        e.Values("Number") = Me.MultiRow1.Template.Row.Cells("Number").NullValue
    End If
    If e.Values("UnitPrice") Is Nothing Then
        e.Values("UnitPrice") = Me.MultiRow1.Template.Row.Cells("UnitPrice").NullValue
    End If
    e.Values("Price") = e.Values("Number") * e.Values("UnitPrice")
    Try
        Using _proc As New CZ1008Bound
            Dim ds As Data.DataSet = _proc.GetSchema("admin", "", "WS-DF502")
            Dim row As Data.DataRow = ds.Tables("Bill").NewRow()
            row("CustomerID") = Me.ViewState("CustomerID").ToString
            row("Date") = e.Values("Date")
            row("Products") = e.Values("Products")
            row("Number") = e.Values("Number")
            row("UnitPrice") = e.Values("UnitPrice")
            row("Price") = e.Values("Price")
            row("SlipNo") = e.Values("SlipNo")
            row("Category") = e.Values("Category")
            ds.Tables("Bill").Rows.Add(row)
            If _proc.InsertRecord("admin", "", "WS-DF502", ds) Then
                e.Values("ID") = ds.Tables("Bill").Rows(0).Item("ID")
            Else
                e.Cancel = True
            End If
        End Using
    Catch ex As Exception
        e.Cancel = True
    End Try
End Sub

 リスト2は、「追加した行の整形」「追加した行をデータセットに反映」「データセットを渡してデータベースに追加」という、3つの部分からできています。

 なお、e.Valuesの各要素が追加したMulitRowの1行分のデータになります。複数行編集したときには複数回イベントが発生します。

(1)追加した行の整形

 UnitPriceとNumberはNullValueプロパティを設定していますが、NullValueプロパティの値はあくまでも表示のためのもので、未入力の状態で追加操作が完了したときはNothing値になっています。そのため、該当するNullValue値(Me.MultiRow1.Template.Row.Cells("Number").NullValue)をコードで設定します。

 Price値は、参照専用セルで「UnitPrice × Number」から自動計算します。

(2)追加した行をデータセットに反映

 追加した行を反映するデータセット(具体的にはデータテーブル)には、構造を定義してからDataRowを追加することになります。

 サンプルでは、データアクセス用のCZ1008クラスにデータテーブルの構造を定義するGetSchemaメソッドを実装しているので、GetSchemaメソッドで取得したデータテーブルに対して「ds.Tables("Bill").NewRow()」を実行して編集用DataRow領域を確保し、そこにe.Valuesの値を転記しています。

(3)データセットを渡してデータベースに追加

 データベースへの追加は、データアクセス用のCZ1008クラスを使って行っています。

 CZ1008のInsertRecordメソッドの戻り値が「True」ならID値が返却されてくるので、「e.Values("ID")」に値を設定します。メソッドの戻り値が「False」ならば、データベースへの追加が行われなかったということなので「e.Cancel」に「True」を設定して、データの追加処理をキャンセルします。

データベースにデータ変更を反映する方法

リスト3 アンバウンドモードでデータ変更
Protected Sub MultiRow1_RowUpdating(ByVal sender As Object,
                                    ByVal e As GrapeCity.Web.MultiRow.RowUpdatingEventArgs) _
                                Handles MultiRow1.RowUpdating
    e.NewValues("Price") = e.NewValues("Number") * e.NewValues("UnitPrice")
    Try
        Using _proc As New CZ1008Bound
            Dim ds As Data.DataSet = _proc.GetSchema("admin", "", "WS-DF502")
            Dim row As Data.DataRow = ds.Tables("Bill").NewRow()
            row("ID") = e.NewValues("ID")
            row("CustomerID") = Me.ViewState("CustomerID").ToString
            row("Date") = e.NewValues("Date")
            row("Products") = e.NewValues("Products")
            row("Number") = e.NewValues("Number")
            row("UnitPrice") = e.NewValues("UnitPrice")
            row("Price") = e.NewValues("Price")
            row("SlipNo") = e.NewValues("SlipNo")
            row("Category") = e.NewValues("Category")
            ds.Tables("Bill").Rows.Add(row)
            If _proc.UpdateRecord("admin", "", "WS-DF502", ds) Then
            Else
                e.Cancel = True
            End If
        End Using
    Catch ex As Exception
        e.Tag = ex.Message
        e.Cancel = True
    End Try
End Sub

 リスト3は、「編集した行の整形」「編集した行をデータセットに反映」「データセットを渡してデータベースを更新」という3つの部分からできています。

(1)編集した行の整形

 Price値は、参照専用セルで「UnitPrice × Number」から自動計算します。

(2)編集した行をデータセットに反映

 編集した行を反映するデータセット(具体的にはデータテーブル)には、構造を定義してからDataRowを追加することになります。

 サンプルでは、データアクセス用のCZ1008クラスにデータテーブルの構造を定義するGetSchemaメソッドを実装しているので、GetSchemaメソッドで取得したデータテーブルに対して「ds.Tables("Bill").NewRow()」を実行して編集用DataRow領域を確保し、そこにe.Valuesの値を転記しています。

(3)データセットを渡してデータベースを更新

 データベースへの更新は、データアクセス用のCZ1008クラスを使って行っています。

 CZ1008のUpdateRecordメソッドの戻り値が「True」なら、更新成功です。メソッドの戻り値が「False」ならば、データベースへの更新が行われなかったということなので、e.Cancelに「True」を設定して、データの更新処理をキャンセルします。

データベースからデータを削除する方法

リスト4 アンバウンドモードでデータ削除
Protected Sub MultiRow1_RowDeleting(ByVal sender As Object, _
                                    ByVal e As GrapeCity.Web.MultiRow.RowDeletingEventArgs) _
                                Handles MultiRow1.RowDeleting
    If Not e.Values("ID") Is Nothing Then
        Using _proc As New CZ1008Bound
            If _proc.DeleteRecord("admin", "", "WS-DF502", e.Values("ID").ToString) Then
                '削除成功
            Else
                e.Tag = "削除失敗"
                e.Cancel = True
            End If
        End Using
    Else
        'まだ保存していない追加行はDBへの反映不要
    End If
End Sub

 データベースからデータを削除するのに必要なのは、追加や編集と違い、削除行が特定できる情報だけです。RowDeletingイベントでは、e.Valuesに削除する行のデータが格納されているので、主キーに相当する「e.Values("ID").ToString」をデータアクセス用クラスライブラリに渡して、データベースからデータを削除しています。

 また、サンプルでは、追加行はデータベースに保存したときにID値が振られる仕様になっているので、「e.Values("ID") Is Nothing」の時はデータベースへの反映ロジックを通さずに、RowDeletingイベントプロシージャから抜けています。

ドロップダウンリストの選択候補をバインドする

 入力データは、アンバウンドモードで使う場合でも、ドロップダウンリストの選択候補を、データソースからバウンドするような使い方もできます。

 今回のサンプルでは、ObjectDataSourceを使っているので、リスト5のようなデータ取得用のクラスを作成します。

リスト5 マスタ取得コード例
Public Function GetCategory(ByVal userID As String,
                           ByVal password As String) As DataSet
    Dim ds As New DataSet
    Using _cn As New OleDb.OleDbConnection()
        _cn.ConnectionString = String.Format(ConfigurationManager.AppSettings("ConnectionString"),
               userID,
               password)
        _cn.Open()
        Using _cmd As New OleDb.OleDbCommand
            _cmd.Connection = _cn
            _cmd.CommandText =
                "SELECT DISTINCT Bill.Category " &
                  "FROM Bill " &
                 "ORDER BY Bill.Category"
            Using _da As New OleDb.OleDbDataAdapter(_cmd)
                _da.Fill(ds, "Category")
            End Using
        End Using
        _cn.Close()
    End Using
    Return ds
End Function

 このクラスとメソッドを、ObjectDataSourceに定義します。aspxファイルを開いて、ObjectDataSourceコントロールをツールボックスからドラッグ&ドロップしたら、「データソースの構成」ダイアログでクラス名とメソッド名を選択します。

図4 「データソースの構成」ダイアログ
図4 「データソースの構成」ダイアログ

 図4のダイアログで設定すれば、aspxファイルには次のようなコードが自動設定されます。

リスト6 マスタ設定用データソース例
<asp:ObjectDataSource ID="ObjectDataSource2" runat="server" SelectMethod="GetCategory"
    TypeName="CZ1008Bound">
    <SelectParameters>
        <asp:Parameter Name="userID" Type="String" />
        <asp:Parameter Name="password" Type="String" />
    </SelectParameters>
</asp:ObjectDataSource>

 このデータソースをMultiRowのDropDownListセルに割り当てるには、リスト7のように、「datasourceid」「dataextfield」「datavaluefield」の設定を行います。今回は、「表示値 = 設定値」なので、dataextfielddatavaluefieldには同じ値を設定しています。

リスト7 選択候補にデータソースを設定する例
<dropdownlistcell datafield="Category"  
    datasourceid="ObjectDataSource2" datatextfield="Category" datavaluefield="Category" 
    location="196, 20" name="Category" tabindex="4"  />
図5 ドロップダウンリストの実行例
図5 ドロップダウンリストの実行例

バウンドモードを使って更新系画面を作成しよう

 本稿の冒頭に説明したとおり、AccessDataSourceを使った例はヘルプファイルや添付サンプルコードにあるので、今回は、ObjectDataSourceを使ったバウンドモードについて取り上げたいと思います。

 MultiRow for ASP.NET 1.0Jに近い標準コントロールといえば、GridViewになると思います。MultiRowのバウンドモードを使った時の設定とGridViewには、一部相違があるので、そのあたりを中心に説明します。

 ObjectDataSourceと連結(バウンド)するためには、ObjectDataSourceで指定するデータアクセス用のクラスを作成しなければなりません。今回のサンプル(CZ1008Update2Sample)では、「CZ1008」というクラスを作成して使用しています。

 バウンドモードで使うときのMultiRowの設定でキーとなるのは、DataSourceIDプロパティとDataKeyNamesプロパティです。DataSourceIDプロパティにはObjectDataSourceの名前を設定し、DataKeyNamesプロパティにはレコードの主キーとなる列名を指定します。

 DataKeyNamesプロパティに設定した名前は、レコード削除時にレコードを特定するために使用します。また、MultiRowではDataKeyNamesプロパティが設定されていないとレコード削除のポストバックも発生しないので、注意してください。

リスト8 選択候補にデータソースを設定する例
<GrapeCity:MultiRow ID="MultiRow1" runat="server" MultiSelect="False" AllowPaging="True"
                    ScrollMode="Row" Width="540px" 
                    DataSourceID="ObjectDataSource1" DataKeyNames="ID" >

 ObjectDataSourceの設定でポイントとなるのは、InsertParametersやUpdateParametersにはMultiRowの各セルに指定したDataField名を、すべて指定しなければならない点です。

 例えば、今回のサンプルでは「数量 × 単価」から金額を計算しているので、追加時や更新時はサーバー側で自動計算を行うため、ロジックとしてはInsertParametersやUpdateParametersに指定は不要です。しかし、指定しておかないと実行時に、指定していないDataFieldがパラメタとして自動的に追加されるため、データアクセス用のクラスのパラメタと合わなくなり実行時エラーが発生するので、注意してください。

 バウンドモードでは、通常はaspx.vb側には何も記述をする必要はありません。ただ、今回のサンプルではデータアクセス用クラスを呼ぶときに、「UserID」「パスワード」「請求NO」は画面ではなくaspx.vb側で設定して呼び出すように、意図的にしています。そのため、次のようなコードが必要です。

リスト9 選択候補にデータソースを設定する例
Partial Class MultiRowSample
    Inherits System.Web.UI.Page

    Private block As Object = Nothing
    Protected Sub Me_Load(ByVal sender As Object,
                          ByVal e As System.EventArgs) _
                      Handles MultiRowSample_Form.Load
        Me.ObjectDataSource1.SelectParameters("userID").DefaultValue = "admin"
        Me.ObjectDataSource1.SelectParameters("password").DefaultValue = ""
        Me.ObjectDataSource1.SelectParameters("billNo").DefaultValue = "WS-DF502"
    End Sub

    Protected Sub MultiRow1_RowDeleting(ByVal sender As Object,
                                        ByVal e As RowDeletingEventArgs) _
                                    Handles MultiRow1.RowDeleting
        Me.ObjectDataSource1.DeleteParameters("userID").DefaultValue = "admin"
        Me.ObjectDataSource1.DeleteParameters("password").DefaultValue = ""
        Me.ObjectDataSource1.DeleteParameters("billNo").DefaultValue = "WS-DF502"
    End Sub

    Protected Sub MultiRow1_RowInserting(ByVal sender As Object,
                                         ByVal e As RowInsertingEventArgs) _
                                     Handles MultiRow1.RowInserting
        Me.ObjectDataSource1.InsertParameters("userID").DefaultValue = "admin"
        Me.ObjectDataSource1.InsertParameters("password").DefaultValue = ""
        Me.ObjectDataSource1.InsertParameters("billNo").DefaultValue = "WS-DF502"
    End Sub

    Protected Sub MultiRow1_RowUpdating(ByVal sender As Object,
                                        ByVal e As RowUpdatingEventArgs) _
                                    Handles MultiRow1.RowUpdating
        Me.ObjectDataSource1.UpdateParameters("userID").DefaultValue = "admin"
        Me.ObjectDataSource1.UpdateParameters("password").DefaultValue = ""
        Me.ObjectDataSource1.UpdateParameters("billNo").DefaultValue = "WS-DF502"
    End Sub

    Protected Sub MultiRow1_RowPreRender(ByVal sender As Object,
                                         ByVal e As RowPreRenderEventArgs) _
                                     Handles MultiRow1.RowPreRender
        If e.Tag.Length > 0 Then
            e.Row.ErrorText = e.Tag
        End If
    End Sub
End Class

各モードの使い分けで細かい制御と効率化の両立を

 今回は、MultiRow for ASP.NETを使った更新画面の作成方法を、アンバウンドモードを中心に説明してきました。バウンドモード部分での解説からも分かるとおり、アンバウンドモードで紹介したコードの大半は、バウンドモードでは不要になります(データアクセス用クラス、または相当のデータソース定義は必要です)。

 日本のビジネスシーンで必要な、細かな制御が必要な画面はアンバウンドモードで作成し、副次的な画面はバウンドモードで作成するなどすれば、気配りと効率化を両立できるのではないでしょうか。

製品情報

この記事は参考になりましたか?

  • このエントリーをはてなブックマークに追加

この記事は参考になりましたか?

この記事をシェア

  • このエントリーをはてなブックマークに追加
CodeZine(コードジン)
https://codezine.jp/article/detail/5402 2010/09/02 15:15

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー

アクセスランキング

アクセスランキング