Poser une questionPoser une question
 

TraitéeWPF Databinding kills serialization

  • dimanche 8 novembre 2009 01:00GreatElectron Médailles de l'utilisateurMédailles de l'utilisateurMédailles de l'utilisateurMédailles de l'utilisateurMédailles de l'utilisateur
     
    I have a problem that when I databind a WPF control to a class, that class can no longer be serialized: If I try to serialize it, I get this exception:

    System.Runtime.Serialization.SerializationException was unhandled by user code
      Message="Type 'MS.Internal.Data.ValueChangedEventManager+ValueChangedRecord' in Assembly 'PresentationFramework, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35' is not marked as serializable."

    Anyone know how to avoid this? From the exception message it looks to me like the problem may be that the serialization is somehow picking up the event handler that WPF has stuck on the data object's relevent property_changed event.

    In more detail: The property concerned is called Primary, and to allow for databinding I've done the usual event stuff:

    Private _primary As Boolean

    Public Event PrimaryChanged As EventHandler

    Public Property Primary() As Boolean

          Get

                Return _primary

          End Get

          Set(ByVal value As Boolean)

                _primary = value

                RaiseEvent PrimaryChanged(Me, EventArgs.Empty)

          End Set

    End Property



    Then this XAML:

    <Button IsEnabled="{Binding Path=Primary, Mode=TwoWay}" ...



    In order to get the error, both of those bits of code need to be in place. If I remove either the PrimaryChangedEvent or the binding in the XAML, then the serialization works fine (but of course, then the button can't pick up changes to the Primary property).

    I'm not doing anything funny with serialization: The class is simply marked with the Serializable attribute, nothing else. I'm doing serialization because the class needs to be copied to a server using remoting in response to certain user actions.

Réponses

  • lundi 9 novembre 2009 23:51Adam Sills Médailles de l'utilisateurMédailles de l'utilisateurMédailles de l'utilisateurMédailles de l'utilisateurMédailles de l'utilisateur
     TraitéeA du code

    When you binary serialize a class, all of its private members are serialized. This includes any events on the class. To get around, you need to mark your event as NonSerialized except that attribute doesn't apply to events and so you instead need to apply it to a field.

    To get around this, you need to provide a custom addhandler and removehandler method for your event and keep a private delegate that you can mark with NonSerialized.

    The following code is a class that implementes INotifyPropertyChanged and implements its interface. It has a private PropertyChangedEventHandler field which is used to store the event subscriptions. Also note that the declaration of the event itself is marked "Custom" which allows you to handle the add/remove/invoke of the event manually. This allows you to keep the event non-serialized.

    <Serializable()> Public Class Class1
        Implements System.ComponentModel.INotifyPropertyChanged
    
        <NonSerialized()> Private propChangedEvent As System.ComponentModel.PropertyChangedEventHandler
    
        Public Custom Event PropertyChanged As System.ComponentModel.PropertyChangedEventHandler Implements System.ComponentModel.INotifyPropertyChanged.PropertyChanged
            AddHandler(ByVal value As System.ComponentModel.PropertyChangedEventHandler)
                propChangedEvent = [Delegate].Combine(propChangedEvent, value)
            End AddHandler
    
            RemoveHandler(ByVal value As System.ComponentModel.PropertyChangedEventHandler)
                propChangedEvent = [Delegate].Remove(propChangedEvent, value)
            End RemoveHandler
    
            RaiseEvent(ByVal sender As Object, ByVal e As System.ComponentModel.PropertyChangedEventArgs)
                propChangedEvent(sender, e)
            End RaiseEvent
        End Event
    End Class
    

Toutes les réponses

  • lundi 9 novembre 2009 23:51Adam Sills Médailles de l'utilisateurMédailles de l'utilisateurMédailles de l'utilisateurMédailles de l'utilisateurMédailles de l'utilisateur
     TraitéeA du code

    When you binary serialize a class, all of its private members are serialized. This includes any events on the class. To get around, you need to mark your event as NonSerialized except that attribute doesn't apply to events and so you instead need to apply it to a field.

    To get around this, you need to provide a custom addhandler and removehandler method for your event and keep a private delegate that you can mark with NonSerialized.

    The following code is a class that implementes INotifyPropertyChanged and implements its interface. It has a private PropertyChangedEventHandler field which is used to store the event subscriptions. Also note that the declaration of the event itself is marked "Custom" which allows you to handle the add/remove/invoke of the event manually. This allows you to keep the event non-serialized.

    <Serializable()> Public Class Class1
        Implements System.ComponentModel.INotifyPropertyChanged
    
        <NonSerialized()> Private propChangedEvent As System.ComponentModel.PropertyChangedEventHandler
    
        Public Custom Event PropertyChanged As System.ComponentModel.PropertyChangedEventHandler Implements System.ComponentModel.INotifyPropertyChanged.PropertyChanged
            AddHandler(ByVal value As System.ComponentModel.PropertyChangedEventHandler)
                propChangedEvent = [Delegate].Combine(propChangedEvent, value)
            End AddHandler
    
            RemoveHandler(ByVal value As System.ComponentModel.PropertyChangedEventHandler)
                propChangedEvent = [Delegate].Remove(propChangedEvent, value)
            End RemoveHandler
    
            RaiseEvent(ByVal sender As Object, ByVal e As System.ComponentModel.PropertyChangedEventArgs)
                propChangedEvent(sender, e)
            End RaiseEvent
        End Event
    End Class
    
  • vendredi 13 novembre 2009 15:01GreatElectron Médailles de l'utilisateurMédailles de l'utilisateurMédailles de l'utilisateurMédailles de l'utilisateurMédailles de l'utilisateur
     
    Thanks Adam, after some other googling, that does seem to be the simplest solution. Strange that you can solve it very easily in C# with [field:NonSerialized], but there's no equivalent in VB. Your suggestion does mean a lot of code that I don't want cluttering up my business objects, but seems like it's the least bad 'hack' available :(