none
リッチテキストボックスへのバインドデータが消える RRS feed

  • 質問

  • いつもお世話になっています。

    リッチテキストボックスへのデータのバインドの動作で質問があります。
    データベースはSQL Server 2008 ExpressEdition です。

    テーブルには「日付(Datetime型)」と「記録(nvarchar(MAX)型)」という列があり、
    フォーム上には、データ追加用のボタンと、テーブルの「日付」列の一覧を表示するためのDataGridView、
    「記録」を表示するためのリッチテキストボックスを配置しています。
    リッチテキストへのバインドは
    フォーム名.Designerファイル内で、
    コントロール名.DataBindings.Add(New System.Windows.Forms.Binding("Rtf", Me.テーブル名BindingSource, "記録", True))
    として、バインドしています。

    このまま使用する分には問題なく表示され、データの追加時にも何の問題もないのですが、
    BindingSourceに並び替え(Sort="記録 DESC")を設定すると、
    データの追加時や、日付のデータを変更したとき
    (データの並び順に影響が出る変更をしたとき)
    に、記録列のデータ(テキスト)が消えてしまいます。

    これを回避する方法はあるのでしょうか?
    それとも何か間違っているのでしょうか?

    よろしくお願いします。

    環境:
    Visual Studio 2008 Standard(VB) + SQL Server 2008 ExpressEdition
     

    2009年12月18日 5:21

回答

  • trapemiya さんの方が当たっていそうに思いますが、
    こちらでは別の不可解な動作をしてしまったため、それについて書かせていただきます。

    簡単なコードで試すと、こちらでは1行変更すると別の行まで更新されてしまうという不可解な動作になりました。
    試したのは次のコードです。

    '画面に DataGridView1、RichTextBox1、BindingSource1、Button1 を配置。
    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
        Dim dt = New DataTable()
        dt.Columns.Add("日付", GetType(DateTime))
        dt.Columns.Add("記録", GetType(String))
        For i As Integer = 0 To 9
            dt.Rows.Add(DateTime.Now.Date.AddDays(i), Nothing)
        Next
        BindingSource1.DataSource = dt
        DataGridView1.DataSource = BindingSource1
        BindingSource1.Sort = "[記録] desc"
        RichTextBox1.DataBindings.Add(New Binding("Rtf", BindingSource1, "記録", True))
    End Sub

    上記現象については、下記のようにすることで回避できました。
    記録列のデータ(テキスト)が消えてしまうという状況とは無関係かもしれませんが、もし trapemiya さんの方法で解決しない場合は、一度試していただけますでしょうか?

    新しいクラスの追加で下記のクラスを作成し、ビルドすると [ツールボックス] にこのクラスが表示されます。
    このクラスを画面に追加し、既存コードの
    コントロール名.DataBindings.Add(New System.Windows.Forms.Binding("Rtf", Me.テーブル名BindingSource, "記録", True))
    をコメントにし、次のコード
    MyRichTextBox1.DataBindings.Add(New Binding("MyRtf", BindingSource1, "記録", True))
    を追加して動作を確認してください。

    Public Class MyRichTextBox
        Inherits RichTextBox

        Public Event MyRtfChanged As EventHandler

        Private Sub MyRichTextBox_TextChanged(ByVal sender As Object, ByVal e As EventArgs) Handles MyBase.TextChanged
            RaiseEvent MyRtfChanged(Me, EventArgs.Empty)
        End Sub

        Public Property MyRtf() As String
            Get
                Return MyBase.Rtf
            End Get
            Set(ByVal value As String)
                MyBase.Rtf = value
            End Set
        End Property

    End Class

    Rtf プロパティには対応する RtfChanged イベントはありませんが、ソートを行う契機としてイベントを使っているのかもしれないと思いました。

    • 回答としてマーク にゅう 2009年12月18日 9:03
    2009年12月18日 8:35
  • インスタンス名・クラス名云々と書きましたが、Visual Basic ではクラスと同じ名前がつけられるのですね。
    変に思えたのですが、これには問題はなく、エラーとは無関係でした。

    試してみると、こちらでも同じエラーになる状況がありました。
    それは親データが1件もない場合です。
    にゅうさんもそうでしょうか?
    前に返信した私の検証コードでも、テストデータを追加する部分をコメントにすると、DataSource に見つからないというエラーになりました。

    親が1件も存在しない場合は、対応する子の準備処理自体も実行されないのだろうと想像します。
    この点は、いつか、ずっと先に検証したいと思います。

    回避策としては、次のようにするのが手っ取り早いと思いました。

    Dim index = If(bs子.Count = 0, -1, bs子.Find("SubID", 1))

    • 回答としてマーク にゅう 2009年12月21日 8:11
    2009年12月21日 7:33

すべての返信

  • ソートを実行することによってBindingSourceのPositionプロパティがリセットされているのではないかと思います。
    もし、今表示しているレコードをソート後にふたたび表示するのであれば、ソート後にBindingSourceのFindメソッドでその行を検索し、その値を
    BindingSourceのPositionプロパティにセットすれば良いと思います。当然ながらFindメソッドで一意に決まる列がなければダメですが・・・
    ★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://blogs.wankuma.com/trapemiya/
    2009年12月18日 7:31
    モデレータ
  • trapemiya さんの方が当たっていそうに思いますが、
    こちらでは別の不可解な動作をしてしまったため、それについて書かせていただきます。

    簡単なコードで試すと、こちらでは1行変更すると別の行まで更新されてしまうという不可解な動作になりました。
    試したのは次のコードです。

    '画面に DataGridView1、RichTextBox1、BindingSource1、Button1 を配置。
    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
        Dim dt = New DataTable()
        dt.Columns.Add("日付", GetType(DateTime))
        dt.Columns.Add("記録", GetType(String))
        For i As Integer = 0 To 9
            dt.Rows.Add(DateTime.Now.Date.AddDays(i), Nothing)
        Next
        BindingSource1.DataSource = dt
        DataGridView1.DataSource = BindingSource1
        BindingSource1.Sort = "[記録] desc"
        RichTextBox1.DataBindings.Add(New Binding("Rtf", BindingSource1, "記録", True))
    End Sub

    上記現象については、下記のようにすることで回避できました。
    記録列のデータ(テキスト)が消えてしまうという状況とは無関係かもしれませんが、もし trapemiya さんの方法で解決しない場合は、一度試していただけますでしょうか?

    新しいクラスの追加で下記のクラスを作成し、ビルドすると [ツールボックス] にこのクラスが表示されます。
    このクラスを画面に追加し、既存コードの
    コントロール名.DataBindings.Add(New System.Windows.Forms.Binding("Rtf", Me.テーブル名BindingSource, "記録", True))
    をコメントにし、次のコード
    MyRichTextBox1.DataBindings.Add(New Binding("MyRtf", BindingSource1, "記録", True))
    を追加して動作を確認してください。

    Public Class MyRichTextBox
        Inherits RichTextBox

        Public Event MyRtfChanged As EventHandler

        Private Sub MyRichTextBox_TextChanged(ByVal sender As Object, ByVal e As EventArgs) Handles MyBase.TextChanged
            RaiseEvent MyRtfChanged(Me, EventArgs.Empty)
        End Sub

        Public Property MyRtf() As String
            Get
                Return MyBase.Rtf
            End Get
            Set(ByVal value As String)
                MyBase.Rtf = value
            End Set
        End Property

    End Class

    Rtf プロパティには対応する RtfChanged イベントはありませんが、ソートを行う契機としてイベントを使っているのかもしれないと思いました。

    • 回答としてマーク にゅう 2009年12月18日 9:03
    2009年12月18日 8:35
  • trapemiya さん、TH01さん 返信ありがとうございます。

    TH01さんの方、ビンゴです。

    すいません、私の記述が悪く誤解を与えてしまいました。
    「記録列のデータ(テキスト)が消えてしまいます。」
    は、
    「別のレコードの記録列のデータ(テキスト)が消えてしまいます。」
    でした。

    けど、TH01さんが再現して下さったおかげで解決できました!
    ありがとうございます。

    ちなみに、初歩的質問で申し訳ないのですが、
    trapemiyaさんのアドバイスにあるFindメソッドですが、
    親テーブルがあり、その子テーブルを同じフォーム内にDataGridViewなどで配置している場合、
    列名はどう指定したらよいのでしょうか?
    そのままの列名では「DataMember プロパティ '列名'は DataSource に見つかりません。」
    となってしまいます。
    ※別スレッドで質問した方がよいでしょうか?



    2009年12月18日 9:03
  • 解決して何よりです。
    ただ、もっとスマートな対処方法があれば良いのですが…。
    それと、Changed イベントがなくてもソートはされていましたので、ソートを行う契機という話は関係ありませんね…。
    なぜ回避できるかわからなくなりました。

    Sort や Filter や BindingSource の Find では関数を使うことはできませんので、
    BindingSource にバインドしている DataTable に対して
    dt.Columns.Add("親の列", GetType(String)).Expression = "parent.親の列"
    のような列を追加し、それを指定します。(リレーションシップが必要です)

    • 編集済み TH01 2009年12月18日 10:03 親子が逆っぽいので訂正
    2009年12月18日 9:23

  • Sort や Filter や BindingSource の Find では関数を使うことはできませんので、
    BindingSource にバインドしている DataTable に対して
    dt.Columns.Add("親の列", GetType(String)).Expression = "parent.親の列"
    のような列を追加し、それを指定します。(リレーションシップが必要です)

    親とリレーションのある列しか、検索対象に出来ないということでしょうか?

    親:MainID
        その他列

    子:SubID
        MainID
        日付
      記録

    の場合、SubIDを検索対象にはできないのでしょうか?

    子BindingSource.DataSource=DataSet
    子BindingSource.DataMember="子"

    の場合は

    子BindingSource.Find("SubID",検索値)

    で検索できるのですが・・・

    子BindingSource.DataSource=親BindingSource
    子BindingSource.DataMember="FK_子_親"

    のような場合、エラーになっています。
    2009年12月19日 4:15
  • 勢いで返信してしまいましたが、状況は十分理解できていませんでした。
    (私の返信で別スレッドにしづらくなってしまいましたが、別にした方が回答は得られやすいでしょうね。失礼しました。)

    > 親とリレーションのある列しか、検索対象に出来ないということでしょうか?

    そういうことはないです。
    念のため付け足した「(リレーションシップが必要です)」が、逆に誤解を与えてしまったようですね。

    にゅうさんの状況ですが、以下のソース内の A または B のどちらでもないのでしょうか?
    データソースに直接存在する項目でしたらエラーは発生しませんので、状況は違うようですね。列が見つからないとのことでしたので、リレーション先にある項目を検索されたいのかと早合点しました。
    状況が異なる場合、C を追加して教えてください。

    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        Dim ds = New DataSet()

        Dim dt親 = ds.Tables.Add("親")
        dt親.Columns.Add("MainID", GetType(Integer))
        dt親.Columns.Add("その他の列…", GetType(String))
        dt親.Rows.Add(1, "親1")
        dt親.Rows.Add(2, "親2")

        Dim dt子 = ds.Tables.Add("子")
        dt子.Columns.Add("SubID", GetType(Integer))
        dt子.Columns.Add("MainID", GetType(Integer))
        dt子.Columns.Add("日付", GetType(DateTime))
        dt子.Columns.Add("記録", GetType(String))
        dt子.Rows.Add(1, 1, DateTime.Now.Date, "rtf1")
        dt子.Rows.Add(2, 1, DateTime.Now.Date, "rtf2")
        dt子.Rows.Add(3, 2, DateTime.Now.Date, "rtf3")

        'A または B ?
        Dim testCase = "A"

        Dim bs親, bs子 As BindingSource
        Select Case testCase
            Case "A"
                ds.Relations.Add("FK_子_親", dt親.Columns("MainID"), dt子.Columns("MainID"), False)
                dt子.Columns.Add("親の列", GetType(String)).Expression = "parent.[その他の列…]"
                bs親 = New BindingSource(ds, "親")
                bs子 = New BindingSource(bs親, "FK_子_親")
            Case "B"
                ds.Relations.Add("FK_子_親", dt子.Columns("MainID"), dt親.Columns("MainID"), False)
                dt子.Columns.Add("親の列", GetType(String)).Expression = "max(child.[その他の列…])"
                bs子 = New BindingSource(ds, "子")
                bs親 = New BindingSource(bs子, "FK_子_親")
            Case Else
                Throw New NotImplementedException()
        End Select

        'データソースにある項目を検索。
        Dim index = bs子.Find("SubID", 1)
        MessageBox.Show(index.ToString())

        'リレーション先にある項目を検索(Expression 列として追加済み)。
        index = bs子.Find("親の列", "親1")
        MessageBox.Show(index.ToString())

        'Dim dgv親 = New DataGridView() : Me.Controls.Add(dgv親)
        'dgv親.Dock = DockStyle.Top
        'dgv親.DataSource = bs親
        'Dim dgv子 = New DataGridView() : Me.Controls.Add(dgv子)
        'dgv子.Dock = DockStyle.Fill : dgv子.BringToFront()
        'dgv子.DataSource = bs子
    End Sub

    2009年12月20日 7:24
  • 丁寧にありがとうございます。

    >以下のソース内の A または B のどちらでもないのでしょうか?
    Aのパターンだと思うのですが、

    SQL Serverでテーブルを作成し(リレーションも作成しています)
    フォームのデザイナ画面で、DataSet、BindingSource、DataGridViewを作成した場合、
    下記コードを実行すると、「※」の位置で
    「DataMember プロパティ 'SubID' は DataSource に見つかりません。」
    となってしまいます。


            Using TA As New TestDataSetTableAdapters.親TableAdapter
                TA.Fill(Me.TestDataSet.親)
            End Using

            Using TA As New TestDataSetTableAdapters.子TableAdapter
                TA.Fill(Me.TestDataSet.子)
            End Using

            Me.親BindingSource.DataSource = Me.TestDataSet
            Me.親BindingSource.DataMember = "親"

            Me.子BindingSource.DataSource = Me.親BindingSource
            Me.子BindingSource.DataMember = "FK_子_親"


            'データソースにある項目を検索。
            Dim index = 子BindingSource.Find("SubID", 1)   -----※
            MessageBox.Show(index.ToString())

    ただ、下記のようにコード内でDataSetを宣言、作成した場合エラーになりません。
    この2つの違いはどうしてなのでしょうか?
        
            Dim DS As New TestDataSet

            Using TA As New TestDataSetTableAdapters.親TableAdapter
                TA.Fill(DS.親)
            End Using

            Using TA As New TestDataSetTableAdapters.子TableAdapter
                TA.Fill(DS.子)
            End Using

            Me.親BindingSource.DataSource = DS
            Me.親BindingSource.DataMember = "親"

            Me.子BindingSource.DataSource = Me.親BindingSource
            Me.子BindingSource.DataMember = "FK_子_親"


            'データソースにある項目を検索。
            Dim index = 子BindingSource.Find("SubID", 1)
            MessageBox.Show(index.ToString())

    >勢いで返信してしまいましたが、状況は十分理解できていませんでした。
    >(私の返信で別スレッドにしづらくなってしまいましたが、別にした方が回答は得られやすいでしょうね。失礼しました。)

    こちらこそすいません。
    ここまで来てしまったので、どうしたらスムーズに別スレッドに移行してよいかわからないので、
    このまま行かせてもらってもよいですかね~?

    2009年12月21日 4:05
  • 書かれた2つのコードは、TestDataSet がインスタンスだったりクラスだったりしていますね。
    エラーの発生する方では TestDataSet をインスタンスとして扱われていて、
    エラーの発生しない方ではクラスとして扱われています。
    これは Visual Studio でデザイナを使った場合には普通のことなのかよく知りませんので、これから試してみようと思っています。テーブルの作成からになりますので、時間はかかると思います。
    最終的には、Visual Studio のデザイナが作成したコードを教えてもらう必要があるかもしれませんが、全コードだと長いので、ポイントになりそうな点をお聞きすることになると思います。

    > どうしたらスムーズに別スレッドに移行してよいかわからないので

    新スレッドにこのスレへのリンクを貼り、このスレの続きであることが解るようにされると良さそうに思いました(こちらにも移動先のリンク)。

    > このまま行かせてもらってもよいですかね~?

    にゅうさんさえよろしければ。

    2009年12月21日 6:16
  • インスタンス名・クラス名云々と書きましたが、Visual Basic ではクラスと同じ名前がつけられるのですね。
    変に思えたのですが、これには問題はなく、エラーとは無関係でした。

    試してみると、こちらでも同じエラーになる状況がありました。
    それは親データが1件もない場合です。
    にゅうさんもそうでしょうか?
    前に返信した私の検証コードでも、テストデータを追加する部分をコメントにすると、DataSource に見つからないというエラーになりました。

    親が1件も存在しない場合は、対応する子の準備処理自体も実行されないのだろうと想像します。
    この点は、いつか、ずっと先に検証したいと思います。

    回避策としては、次のようにするのが手っ取り早いと思いました。

    Dim index = If(bs子.Count = 0, -1, bs子.Find("SubID", 1))

    • 回答としてマーク にゅう 2009年12月21日 8:11
    2009年12月21日 7:33
  • いろいろと試して頂きありがとうございます。

    こちらでは、
    親データがあっても、デザイナで作成したDataSetを利用した場合はエラーになってしまいます・・・

    とりあえず、コード内で宣言した場合に回避できそうですし、
    このスレッドの本来の目的は達成されましたので、
    あまり、TH01さんの時間を裂くのも申し訳ないので、一旦解決という形にしたいと思います。

    また何か進展や、問題がありましたら、別スレッドにて質問させて頂きたいと思います。

    ありがとうございました。

    2009年12月21日 8:11