none
INotifyPropertyChangedを実装したクラスのシリアライズ RRS feed

  • 質問

  • タイトルの動作がvb.netで実現できません。

    具体的にはBinaryFormatterのSerializeメソッドで次のエラーが出ます。
    'System.ComponentModel.PropertyChangedEventManager' はシリアル化可能として設定されていません。

    そこで<NonSerialized()>属性を下記のハンドラに記述しようと思ったのですがうまくいきません。

    Public Event PropertyChanged(ByVal sender As Object, ByVal e As PropertyChangedEventArgs) Implements INotifyPropertyChanged.PropertyChanged

    「PropertyChangedに適用できません」とエラーが出ます。

    c#だと「field:NonSerialized」で一発解決するらしいのですがvb.netではできないようです。
    なにか良い方法はあるのでしょうか?どうぞよろしくお願いします。
    2009年7月30日 5:26

回答

  • PropertyChanged イベントを、カスタムイベントとして定義します。
    http://ameblo.jp/sukeken3298/entry-10024956952.html
    こちらなどが比較的分かりやすいサンプルでしょうか。
    // CancelEventHandler のサンプル実装になっているので、PropertyChangedEventHandler の場合とは e.Cancel 辺りが異なりますけど。
    そして、このなかで Private なフィールドとして宣言した PropertyChangedEventHandler (上記のサンプルで言うところの m_event ですね。変数名は妥当なのに変更してください)に対して、NonSerialized 属性を付加します。
    • 回答としてマーク momo1995 2009年8月4日 0:19
    2009年7月30日 6:06

すべての返信

  • PropertyChanged イベントを、カスタムイベントとして定義します。
    http://ameblo.jp/sukeken3298/entry-10024956952.html
    こちらなどが比較的分かりやすいサンプルでしょうか。
    // CancelEventHandler のサンプル実装になっているので、PropertyChangedEventHandler の場合とは e.Cancel 辺りが異なりますけど。
    そして、このなかで Private なフィールドとして宣言した PropertyChangedEventHandler (上記のサンプルで言うところの m_event ですね。変数名は妥当なのに変更してください)に対して、NonSerialized 属性を付加します。
    • 回答としてマーク momo1995 2009年8月4日 0:19
    2009年7月30日 6:06
  • 返信ありがとうございます。

    下記のようにコーディングしてみました。

        Private m_event As PropertyChangedEventHandler
        Public Custom Event PropertyChanged As PropertyChangedEventHandler
            AddHandler(ByVal value As PropertyChangedEventHandler)
                Me.m_event = DirectCast([Delegate].Combine(Me.m_event, value), PropertyChangedEventHandler)
            End AddHandler

            RemoveHandler(ByVal value As PropertyChangedEventHandler)
                Me.m_event = DirectCast([Delegate].Remove(Me.m_event, value), PropertyChangedEventHandler)
            End RemoveHandler

            RaiseEvent(ByVal sender As Object, ByVal e As PropertyChangedEventArgs)
                If Me.m_event IsNot Nothing Then
                    For Each handler As PropertyChangedEventHandler In Me.m_event.GetInvocationList()
                        handler.Invoke(sender, e)
                    Next
                End If
            End RaiseEvent
        End Event

    でイベントを発生させるところで
    RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("Test"))
    としてもhandler.Invoke(sender, e)までいきません。

    しらべてみるとm_event が Nothing のようです。

    動作の仕組みを理解できていないのが一番の問題なのですが、もうひとつヒントをいただけませんでしょうか?
    実際に動くようになってからトレーシングして動きを追いたいと思っています。

    どうぞよろしくお願いします。

    2009年7月30日 8:08
  • そのままコピペするだけで問題なく動いたので、単にイベントにイベントハンドラを追加してないだけとかじゃないでしょうか。イベントを処理するハンドラを何も登録していない場合、m_event は Nothing のままです。

    本題とは関係ありませんが、RaiseEvent 内で、GetInvocationList して For Each を回していますが、これは不要です。
    Me.m_event(Me, New ...)
    と記述するのが普通です。
    2009年7月30日 8:38
  • 返信ありがとうございます。

    おっしゃるとおり、イベントハンドラに追加していませんでした。
    プログラムを修正したところ、サンプルページの

    'Class1.CustomTestイベントが発生したときに呼ばれるメソッド

    と記述してあるところにイベントが来るようになったのですがウィンドウの更新は自分で行うということでしょうか?

    2009年7月30日 12:45
  • 'Class1.CustomTestイベントが発生したときに呼ばれるメソッド

    と記述してあるところにイベントが来るようになったのですがウィンドウの更新は自分で行うということでしょうか?
    いきなりウィンドウの更新とか言われても、なんのことやらさっぱりです。

    今回用意した簡単なカスタムイベントの場合、カスタムしない場合内部で自動的に用意される PropertyChangedEventHandler フィールドの代わりに、自分でそのフィールドを用意した、というだけです。
    外からこのクラスを見た場合、カスタムしない場合と何ら違いはありません。
    イベントが起こったときに何をするかはそのハンドラの作成者次第です。

    最初の投稿にある例外メッセージからすると、WeakEvent パターンを実装してたんですよね?
    2009年7月30日 13:02
  • 返信ありがとうございます。次のようなコーディングをしています。

    Window1.xaml

    <Window x:Class="Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Window1" Height="300" Width="300">

        <StackPanel x:Name="MainPanel" Loaded="MainPanel_Loaded">
            <TextBox Margin="10" Text="{Binding Firstname, Mode=TwoWay}"/>
            <Button Margin="10" Content="Set" Click="Button_Click"/>
        </StackPanel>
    </Window>


    Window1.xaml.vb

    Imports System.ComponentModel
    Imports System.IO
    Imports System.Runtime.Serialization.Formatters.Binary

    Class Window1
        Private Sub MainPanel_Loaded(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs)
            Dim per = New Person

            MainPanel.DataContext = per
        End Sub

        Private Sub Button_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs)
            Dim p As Person = CType(MainPanel.DataContext, Person)
            p.Firstname = "momo"

            Dim fs As New FileStream("c:\person.dat", FileMode.Create, FileAccess.Write)
            Dim bf As New BinaryFormatter
            bf.Serialize(fs, p)
            fs.Close()
        End Sub
    End Class

    <Serializable()> _
    Public Class Person
        Implements ComponentModel.INotifyPropertyChanged

        Private _firstname As String

        Public Property Firstname() As String
            Get
                Return _firstname
            End Get
            Set(ByVal value As String)
                _firstname = value
                RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("Firstname"))
            End Set
        End Property

        Public Event PropertyChanged(ByVal sender As Object, ByVal e As System.ComponentModel.PropertyChangedEventArgs) Implements System.ComponentModel.INotifyPropertyChanged.PropertyChanged
    End Class


    ボタンを押すとテキストボックスにmomoと入りc:\person.datにシリアル化される予定なのですが

    bf.Serialize(fs, p)の位置で次のエラーが出ます。

    アセンブリ 'WindowsBase, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35' の型 'System.ComponentModel.PropertyChangedEventManager' はシリアル化可能として設定されていません。

    これを回避するための方法を探していました。

    その後、アドバイスしていただいたページを参考にしてカスタムイベントを記述し
    シリアル化はできるようになったのですがテキストボックスの内容が更新されません。

    根本的に間違っているような気がしてきました。

    • 編集済み momo1995 2009年8月3日 6:29
    2009年8月3日 6:16
  • このコードにカスタムイベント分の修正を加えれば、問題なく動作します。
    Text へのバインディングにおける Binding.UpdateSourceTrigger の既定値は LostFocus なので、TextBox からフォーカスを動かさずに Button_Click メソッドが呼び出された場合は別ですが。
    2009年8月3日 9:53
  • 返信ありがとうございます。

    下記のようにクラスを修正しましたらOKでした。

    <Serializable()> _
    Public Class Person
        Implements ComponentModel.INotifyPropertyChanged

        Private _firstname As String

        Public Property Firstname() As String
            Get
                Return _firstname
            End Get
            Set(ByVal value As String)
                _firstname = value
                RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("Firstname"))
            End Set
        End Property

        <NonSerialized()> _
        Dim m_PropertyChangedHandler As PropertyChangedEventHandler
        Public Custom Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
            AddHandler(ByVal value As PropertyChangedEventHandler)
                m_PropertyChangedHandler = DirectCast([Delegate].Combine(m_PropertyChangedHandler, value), PropertyChangedEventHandler)
            End AddHandler

            RemoveHandler(ByVal value As PropertyChangedEventHandler)
                m_PropertyChangedHandler = DirectCast([Delegate].Remove(m_PropertyChangedHandler, value), PropertyChangedEventHandler)
            End RemoveHandler

            RaiseEvent(ByVal sender As Object, ByVal e As PropertyChangedEventArgs)
                If m_PropertyChangedHandler IsNot Nothing Then
                    m_PropertyChangedHandler(sender, e)
                End If
            End RaiseEvent
        End Event

        Friend Overridable Sub NotifyPropertyChanged(ByVal Information As String, Optional ByVal sender As Object = Nothing)
            If sender Is Nothing Then
                RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(Information))
            Else
                RaiseEvent PropertyChanged(sender, New PropertyChangedEventArgs(Information))
            End If
        End Sub
    End Class

    動作しなかった原因は
    Implements ComponentModel.INotifyPropertyChanged
    が抜けていたせいでした。

    根気良く付き合ってもらい大変助かりました。
    ありがとうございます。

    2009年8月4日 0:17