与 ObservableCollection 内部类的属性的双向 WPF 绑定

发布于 2024-12-16 14:55:20 字数 5360 浏览 1 评论 0原文

我已经尽我所能地进行了搜索,但我找不到这个特定问题的答案... WPF 绑定似乎很棒,但我最终更频繁地用头撞墙不是。

好的,我有一个单例类,它最终是我要绑定到的类:

Imports System.ComponentModel
Imports System.Collections.ObjectModel

Public Class AmandaSeyfried
    Implements INotifyPropertyChanged

    Shared _config As New config

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

    Protected Overridable Sub OnPropertyChanged(propertyName As String)
        RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
    End Sub

    Private Shared _thisInstance As AmandaSeyfried

    Protected Sub New()
        ' initialization goes here
    End Sub

    Public Shared Function GetSingleton() As AmandaSeyfried
        ' initialize object if it hasn't already been done
        If _thisInstance Is Nothing Then
            _thisInstance = New AmandaSeyfried
        End If

        ' return the initialized instance
        Return _thisInstance
    End Function

    Public Class CountryTranslation
        Implements INotifyPropertyChanged

        Private Property _englishCountryName As String = ""
        Public Property EnglishCountryName As String
            Get
                Return _EnglishCountryName
            End Get
            Set(value As String)
                If _englishCountryName <> value Then
                    _englishCountryName = value
                    RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("EnglishCountryName"))
                End If
            End Set
        End Property

        Private Property _foreignCountryName As String = ""
        Public Property ForeignCountryName As String
            Get
                Return _foreignCountryName
            End Get
            Set(value As String)
                If _foreignCountryName <> value Then
                    _foreignCountryName = value
                    RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("ForeignCountryName"))
                End If
            End Set
        End Property

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

    Private WithEvents _countryTranslations As New ObservableCollection(Of CountryTranslation)
    Public Property CountryTranslations As ObservableCollection(Of CountryTranslation)
        Get
            If _config.GetKeyTextValue("countryTranslations") <> "" Then
                Dim reader As New IO.StringReader(_config.GetKeyTextValue("countryTranslations"))
                Dim Serializer As New Xml.Serialization.XmlSerializer(_countryTranslations.GetType)
                _countryTranslations = Serializer.Deserialize(reader)
            End If

            Return _countryTranslations
        End Get
        Set(value As ObservableCollection(Of CountryTranslation))
            _countryTranslations = value
            RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("CountryTranslations"))
        End Set
    End Property

    Private Sub CountryTranslationCollectionChanged(sender As Object, e As Specialized.NotifyCollectionChangedEventArgs) Handles _countryTranslations.CollectionChanged
        Dim newStringWriter As New IO.StringWriter
        Dim NewSerializer As New Xml.Serialization.XmlSerializer(_countryTranslations.GetType)
        NewSerializer.Serialize(newStringWriter, _countryTranslations)
        _config.SaveKeyTextValue("countryTranslations", newStringWriter.ToString)
        RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("CountryTranslations"))
    End Sub

End Class

_config 是一个命名错误的帮助程序类,用于从本地 SqlCe 实例存储和检索数据。本质上,对象被序列化,存储在数据库中,然后在需要时随时从数据库中取出,并反序列化回对象。总而言之,它似乎运行得相当不错。

我的问题是,虽然我可以绑定到该对象,并且可以通过 CollectionChangedMethod 处理程序监视何时在 WPF DataGrid 中添加行,但当 CountryTranslation 的两个属性之一发生更改时,我没有收到任何通知。

我的其余相关代码是... XAML ...显然还有更多,但我不认为绑定的 XAML 部分是罪魁祸首,所以我将其修剪为相关的:

<toolkit:DataGrid Margin="12,12,12,12" ItemsSource="{Binding Path=KarenSmith.CountryTranslations, Mode=TwoWay}" AutoGenerateColumns="False" HorizontalAlignment="Stretch" Width="Auto" SelectionMode="Single">
    <toolkit:DataGrid.Columns>
        <toolkit:DataGridTextColumn Width="283" Binding="{Binding EnglishCountryName,Mode=TwoWay}" />
        <toolkit:DataGridTextColumn Width="283" Binding="{Binding ForeignCountryName,Mode=TwoWay}" />
    </toolkit:DataGrid.Columns>
</toolkit:DataGrid>

以及漂亮而简单的代码-后面:

Public Class Preferences

    Public Property KarenSmith As AmandaSeyfried = AmandaSeyfried.GetSingleton

    Sub New()

        ' This call is required by the designer.
        InitializeComponent()

        ' Add any initialization after the InitializeComponent() call.
        DataContext = Me

    End Sub

    Private Sub Close_Click(sender As System.Object, e As System.Windows.RoutedEventArgs)
        Me.Close()
    End Sub
End Class

如果我在 CountryTranslation 类的 Getter 和 Setters 上放置一些断点,我可以监视它们何时被更改(通过数据网格,因此绑定正在工作),但尽我所能,我无法弄清楚如何根据该事件发起事件主类随后更新数据存储以显示更改。

I've searched as best as I can, and I can't find an answer to this specific problem that I have... WPF binding seems to be great and all, but I end up banging my head against the wall more often than not.

Okay, I have a singleton class which is ultimately the one that I'm binding to:

Imports System.ComponentModel
Imports System.Collections.ObjectModel

Public Class AmandaSeyfried
    Implements INotifyPropertyChanged

    Shared _config As New config

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

    Protected Overridable Sub OnPropertyChanged(propertyName As String)
        RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
    End Sub

    Private Shared _thisInstance As AmandaSeyfried

    Protected Sub New()
        ' initialization goes here
    End Sub

    Public Shared Function GetSingleton() As AmandaSeyfried
        ' initialize object if it hasn't already been done
        If _thisInstance Is Nothing Then
            _thisInstance = New AmandaSeyfried
        End If

        ' return the initialized instance
        Return _thisInstance
    End Function

    Public Class CountryTranslation
        Implements INotifyPropertyChanged

        Private Property _englishCountryName As String = ""
        Public Property EnglishCountryName As String
            Get
                Return _EnglishCountryName
            End Get
            Set(value As String)
                If _englishCountryName <> value Then
                    _englishCountryName = value
                    RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("EnglishCountryName"))
                End If
            End Set
        End Property

        Private Property _foreignCountryName As String = ""
        Public Property ForeignCountryName As String
            Get
                Return _foreignCountryName
            End Get
            Set(value As String)
                If _foreignCountryName <> value Then
                    _foreignCountryName = value
                    RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("ForeignCountryName"))
                End If
            End Set
        End Property

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

    Private WithEvents _countryTranslations As New ObservableCollection(Of CountryTranslation)
    Public Property CountryTranslations As ObservableCollection(Of CountryTranslation)
        Get
            If _config.GetKeyTextValue("countryTranslations") <> "" Then
                Dim reader As New IO.StringReader(_config.GetKeyTextValue("countryTranslations"))
                Dim Serializer As New Xml.Serialization.XmlSerializer(_countryTranslations.GetType)
                _countryTranslations = Serializer.Deserialize(reader)
            End If

            Return _countryTranslations
        End Get
        Set(value As ObservableCollection(Of CountryTranslation))
            _countryTranslations = value
            RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("CountryTranslations"))
        End Set
    End Property

    Private Sub CountryTranslationCollectionChanged(sender As Object, e As Specialized.NotifyCollectionChangedEventArgs) Handles _countryTranslations.CollectionChanged
        Dim newStringWriter As New IO.StringWriter
        Dim NewSerializer As New Xml.Serialization.XmlSerializer(_countryTranslations.GetType)
        NewSerializer.Serialize(newStringWriter, _countryTranslations)
        _config.SaveKeyTextValue("countryTranslations", newStringWriter.ToString)
        RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("CountryTranslations"))
    End Sub

End Class

_config is a misnamed helper class that stores and retrieves data from a local SqlCe instance. Essentially the object is serialized, stored in the DB, and then pulled out of the DB any times it's needed and deserialized back into an object. All in all, it seems to be working fairly well.

My problem is that although I can bind to the object, and I can monitor when a row is added in a WPF DataGrid via the CollectionChangedMethod handler, I don't get any notification when either of the two properties of CountryTranslation are changed.

The rest of my related code is... XAML... there's obviously more, but I don't believe the XAML portion of the binding is to blame, so I'll trim it to the relevant:

<toolkit:DataGrid Margin="12,12,12,12" ItemsSource="{Binding Path=KarenSmith.CountryTranslations, Mode=TwoWay}" AutoGenerateColumns="False" HorizontalAlignment="Stretch" Width="Auto" SelectionMode="Single">
    <toolkit:DataGrid.Columns>
        <toolkit:DataGridTextColumn Width="283" Binding="{Binding EnglishCountryName,Mode=TwoWay}" />
        <toolkit:DataGridTextColumn Width="283" Binding="{Binding ForeignCountryName,Mode=TwoWay}" />
    </toolkit:DataGrid.Columns>
</toolkit:DataGrid>

And the nice and simple code-behind:

Public Class Preferences

    Public Property KarenSmith As AmandaSeyfried = AmandaSeyfried.GetSingleton

    Sub New()

        ' This call is required by the designer.
        InitializeComponent()

        ' Add any initialization after the InitializeComponent() call.
        DataContext = Me

    End Sub

    Private Sub Close_Click(sender As System.Object, e As System.Windows.RoutedEventArgs)
        Me.Close()
    End Sub
End Class

If I throw some break points on the Getter and Setters of the CountryTranslation class, I can monitor when they're being changed (via the datagrid, so binding is working), but try as I might I can't figure out how to raise an event based upon that back in the main class to subsequently update the datastore to show the changes.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

愚人国度 2024-12-23 14:55:21

通常我会向 ObservableCollection 添加一个 CollectionChanged 事件,该事件会在添加项目时将 PropertyChanged 事件附加到其项目上,并且该事件侦听器会侦听更改并根据需要进行处理。

下面是一个示例:(希望语法正确,因为我刚刚通过 C# 到 VB.Net 转换器运行它)

Public Sub New()
    AddHandler MyCollection.CollectionChanged, AddressOf MyCollection_CollectionChanged
End Sub

Private Sub MyCollection_CollectionChanged(sender As Object, e As CollectionChangedEventArgs)
    If e.NewItems IsNot Nothing Then
        For Each item As MyItem In e.NewItems
            AddHandler item.PropertyChanged, AddressOf MyItem_PropertyChanged
        Next
    End If

    If e.OldItems IsNot Nothing Then
        For Each item As MyItem In e.OldItems
            RemoveHandler item.PropertyChanged, AddressOf MyItem_PropertyChanged
        Next
    End If
End Sub

Private Sub MyItem_PropertyChanged(sender As Object, e As PropertyChangedEventArgs)
    If e.PropertyName = "Some Property" Then
        DoWork()
    End If
End Sub

C# 版本如下所示:

public MyViewModel()
{
    MyCollection.CollectionChanged += MyCollection_CollectionChanged;
}

void MyCollection_CollectionChanged(object sender, CollectionChangedEventArgs e)
{
    if (e.NewItems != null)
        foreach(MyItem item in e.NewItems)
            item.PropertyChanged += MyItem_PropertyChanged;

    if (e.OldItems != null)
        foreach(MyItem item in e.OldItems)
            item.PropertyChanged -= MyItem_PropertyChanged;
}

void MyItem_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
    if (e.PropertyName == "Some Property")
        DoWork();
}

Usually I add a CollectionChanged event to the ObservableCollection, which attaches a PropertyChanged event to it's items when they get added, and that event listener listens for changes and would handle them as needed.

Here's an example: (hope the syntax is correct since I just ran it through a C# to VB.Net converter)

Public Sub New()
    AddHandler MyCollection.CollectionChanged, AddressOf MyCollection_CollectionChanged
End Sub

Private Sub MyCollection_CollectionChanged(sender As Object, e As CollectionChangedEventArgs)
    If e.NewItems IsNot Nothing Then
        For Each item As MyItem In e.NewItems
            AddHandler item.PropertyChanged, AddressOf MyItem_PropertyChanged
        Next
    End If

    If e.OldItems IsNot Nothing Then
        For Each item As MyItem In e.OldItems
            RemoveHandler item.PropertyChanged, AddressOf MyItem_PropertyChanged
        Next
    End If
End Sub

Private Sub MyItem_PropertyChanged(sender As Object, e As PropertyChangedEventArgs)
    If e.PropertyName = "Some Property" Then
        DoWork()
    End If
End Sub

The C# version looks like this:

public MyViewModel()
{
    MyCollection.CollectionChanged += MyCollection_CollectionChanged;
}

void MyCollection_CollectionChanged(object sender, CollectionChangedEventArgs e)
{
    if (e.NewItems != null)
        foreach(MyItem item in e.NewItems)
            item.PropertyChanged += MyItem_PropertyChanged;

    if (e.OldItems != null)
        foreach(MyItem item in e.OldItems)
            item.PropertyChanged -= MyItem_PropertyChanged;
}

void MyItem_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
    if (e.PropertyName == "Some Property")
        DoWork();
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文