WPF MVVM 将 DataGrid 绑定到 IEnumerable 会导致 Int32 绑定错误

问题描述 投票:0回答:1

我正在编写一个简单的 WPF 接口来使用库组件检索 SwiftMessages。我无法更改组件。我有一个应该显示消息的 DataGrid,每行一条消息。

我对代码隐藏的唯一用途是实例化 DataContext(所以是的,亲爱的 MVVM 绝对主义者,它不是 100% MVVM,但请在我学习的过程中保持友善和有用):

public SwiftSearchWindow()
{
    InitializeComponent();
    DataContext = new UI.ViewModels.SwiftSearchViewModel();
}

相关的XAML是:

<DataGrid Name="gridMessages" ItemsSource="{Binding SwiftMessages}">
    <DataGrid.Columns>
        <DataGridTextColumn Header="Message Type"  Binding="{Binding MessageType, Mode=OneWay}" ></DataGridTextColumn>
        <DataGridTextColumn Header="Message Length" Binding="{Binding MessageLength, Mode=OneWay}"></DataGridTextColumn>
        <DataGridTextColumn Header="Counter Party"  Binding="{Binding CounterParty, Mode=OneWay}"></DataGridTextColumn>
        <DataGridTextColumn Header="Direction"  Binding="{Binding Direction, Mode=OneWay}"></DataGridTextColumn>
        <DataGridTextColumn Header="Message Date"  Binding="{Binding MessageDate, Mode=OneWay}"></DataGridTextColumn>
        <DataGridTextColumn Header="Network Interface Message Reference" Binding="{Binding NetworkInterfaceMessageReference, Mode=OneWay}"/>
        <DataGridTextColumn Header="Own Bic" Binding="{Binding OwnBic, Mode=OneWay}"></DataGridTextColumn>
    </DataGrid.Columns>
</DataGrid>

在我的 SwiftSearchViewModel 实例上有一个属性:

public SwiftMessages SwiftMessages
{
    get
    {
        if (_query == null) return null;
        return _query?.Messages;
    }
}

SwiftMessages 的相关部分是:

    public class SwiftMessages : IEnumerable
    {
        private List<SwiftMessage> _items;

        #region IEnumerable
        public IEnumerator GetEnumerator()
        {
            return (IEnumerator)(new SwiftMessagesEnumerator(_items));
        }
        #endregion
    }

    public class SwiftMessagesEnumerator : IEnumerator
    {
        List<SwiftMessage> _items;
        private int _position = -1;

        public SwiftMessagesEnumerator(List<SwiftMessage> oItems)
        {
            _items = oItems;
        }

        public object Current
        {
            get
            {
                return _items[_position];
            }
        }

        public bool MoveNext()
        {
            if (_position < _items.Count - 1)
            {
                _position++;
                return true;
            }
            else
            {
                return false;
            }
        }

        public void Reset()
        {
            _position = -1;
        }
    }

...以及 SwiftMessage 的相关部分如下。我省略了一些属性,但它们都是字符串,除了 MessageLength:

    public class SwiftMessage
    {
        public int MessageLength
        {
            get { return _messageLength; }
            set { _messageLength = value; }
        }


        public string MessageType
        {
            get { return _messageType; }
            set { _messageType = value; }
        }
    }

检索到两条消息,并且在 DataGrid 中显示两行。但是,各个单元格的值并不绑定。我收到这些错误:

System.Windows.Data Error: 40 : BindingExpression path error: 'MessageType' property not found on 'object' ''Int32' (HashCode=1)'. BindingExpression:Path=MessageType; DataItem='Int32' (HashCode=1); target element is 'TextBlock' (Name=''); target property is 'Text' (type 'String')
System.Windows.Data Error: 40 : BindingExpression path error: 'MessageLength' property not found on 'object' ''Int32' (HashCode=1)'. BindingExpression:Path=MessageLength; DataItem='Int32' (HashCode=1); target element is 'TextBlock' (Name=''); target property is 'Text' (type 'String')
System.Windows.Data Error: 40 : BindingExpression path error: 'CounterParty' property not found on 'object' ''Int32' (HashCode=1)'. BindingExpression:Path=CounterParty; DataItem='Int32' (HashCode=1); target element is 'TextBlock' (Name=''); target property is 'Text' (type 'String')
System.Windows.Data Error: 40 : BindingExpression path error: 'Direction' property not found on 'object' ''Int32' (HashCode=1)'. BindingExpression:Path=Direction; DataItem='Int32' (HashCode=1); target element is 'TextBlock' (Name=''); target property is 'Text' (type 'String')
System.Windows.Data Error: 40 : BindingExpression path error: 'MessageDate' property not found on 'object' ''Int32' (HashCode=1)'. BindingExpression:Path=MessageDate; DataItem='Int32' (HashCode=1); target element is 'TextBlock' (Name=''); target property is 'Text' (type 'String')
System.Windows.Data Error: 40 : BindingExpression path error: 'NetworkInterfaceMessageReference' property not found on 'object' ''Int32' (HashCode=1)'. BindingExpression:Path=NetworkInterfaceMessageReference; DataItem='Int32' (HashCode=1); target element is 'TextBlock' (Name=''); target property is 'Text' (type 'String')
System.Windows.Data Error: 40 : BindingExpression path error: 'OwnBic' property not found on 'object' ''Int32' (HashCode=1)'. BindingExpression:Path=OwnBic; DataItem='Int32' (HashCode=1); target element is 'TextBlock' (Name=''); target property is 'Text' (type 'String')
System.Windows.Data Error: 40 : BindingExpression path error: 'MessageType' property not found on 'object' ''Int32' (HashCode=2)'. BindingExpression:Path=MessageType; DataItem='Int32' (HashCode=2); target element is 'TextBlock' (Name=''); target property is 'Text' (type 'String')
System.Windows.Data Error: 40 : BindingExpression path error: 'MessageLength' property not found on 'object' ''Int32' (HashCode=2)'. BindingExpression:Path=MessageLength; DataItem='Int32' (HashCode=2); target element is 'TextBlock' (Name=''); target property is 'Text' (type 'String')
System.Windows.Data Error: 40 : BindingExpression path error: 'CounterParty' property not found on 'object' ''Int32' (HashCode=2)'. BindingExpression:Path=CounterParty; DataItem='Int32' (HashCode=2); target element is 'TextBlock' (Name=''); target property is 'Text' (type 'String')
System.Windows.Data Error: 40 : BindingExpression path error: 'Direction' property not found on 'object' ''Int32' (HashCode=2)'. BindingExpression:Path=Direction; DataItem='Int32' (HashCode=2); target element is 'TextBlock' (Name=''); target property is 'Text' (type 'String')
System.Windows.Data Error: 40 : BindingExpression path error: 'MessageDate' property not found on 'object' ''Int32' (HashCode=2)'. BindingExpression:Path=MessageDate; DataItem='Int32' (HashCode=2); target element is 'TextBlock' (Name=''); target property is 'Text' (type 'String')
System.Windows.Data Error: 40 : BindingExpression path error: 'NetworkInterfaceMessageReference' property not found on 'object' ''Int32' (HashCode=2)'. BindingExpression:Path=NetworkInterfaceMessageReference; DataItem='Int32' (HashCode=2); target element is 'TextBlock' (Name=''); target property is 'Text' (type 'String')
System.Windows.Data Error: 40 : BindingExpression path error: 'OwnBic' property not found on 'object' ''Int32' (HashCode=2)'. BindingExpression:Path=OwnBic; DataItem='Int32' (HashCode=2); target element is 'TextBlock' (Name=''); target property is 'Text' (type 'String')

我的问题是:

  1. 它试图绑定到什么 Int32?
  2. 我知道我只是使用 C# 库中的 IEnumerable 并尝试绑定到它。我认为图书馆生成的数据就是我的模型。我见过其他示例,其中模型构造 ObservableCollections 等(在本例中不可能)来提供 UI,但这肯定打破了模型应该独立于 UI 工作的概念。
  3. 如何修复此问题以便显示属性?
  4. 最后,我还有 SwiftMessage 的其他一些属性,即 string[]。我想将数组连接成一个 CSV 字符串并在单元格中显示。如何将此计算置于 XAML 和模型数据之间?

根据评论修复了我的 IEnumerable 的错误实现,该实现导致了上述问题 1-3。这些错误是因为我从 IEnumerator 返回计数器,而不是第 n 个对象。 @Clemens 如果您想在下面总结您的评论,我很高兴将其标记为答案。 我仍然对上面问题 4 的答案感兴趣。

c# wpf mvvm data-binding
1个回答
0
投票

您应该避免实现从

IEnumerator
返回的自定义
IEnumerable.GetEnumerator
。让编译器正确实现(在您的情况下,您可以避免一些紧张的时间?调试您的代码)并使用
yield
更安全。您还应该实现通用
IEnumerable<T>
接口:

public class SwiftMessages : IEnumerable<SwiftMessage>
{
  private readonly List<SwiftMessage> _items = new List<SwiftMessage>();

  public IEnumerator<SwiftMessage> GetEnumerator() => EnumerateItems();
  IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable<SwiftMessage>)this).GetEnumerator();

  private IEnumerator<SwiftMessage> EnumerateItems()
  {
    foreach (SwiftMessage item in _items)
    {
      yield return item;
    }
  }
}

在您的情况下,您可以简单地返回支持集合的枚举器:

public class SwiftMessages : IEnumerable<SwiftMessage>
{
  private readonly List<SwiftMessage> _items = new List<SwiftMessage>();

  public IEnumerator<SwiftMessage> GetEnumerator() => _items.GetEnumerator();
  IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable<SwiftMessage>)this).GetEnumerator();
}

这也将解决您的问题 1-3。

关于问题 4),您可以引入一个专用的计算属性来返回 CSV 格式的字符串。

请注意,每个用作绑定源的类都必须实现

INotifyPropertyChanged
(不在绑定源上实现
INotifyPropertyChanged
将导致数据绑定产生内存泄漏):

public class SwiftMessage : INotifyPropertyChanged
{
  public event PropertyChangedEventHandler? PropertyChanged;
  private string[] values;

  public string[] Values
  {
    get => this.values;
    set
    {
      this.values = value;
      OnPropertyChanged();
      OnPropertyChanged(nameof(this.CsvValues));
    }
  }

  public string CsvValues => string.Join(';', this.Values);

  protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    => this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
<DataGrid ItemsSource="{Binding SwiftMessages}">
  <DataGrid.Columns>
    <DataGridTextColumn Header="CSV Values"  
                        Binding="{Binding CsvValues}" /> <!-- Binding is OneWay by default -->
  </DataGrid.Columns>
</DataGrid>

或者,例如,当您无法修改数据模型以添加新属性时,您可以使用

IValueConverter
:

class StringValuesToCsvStringConverter : IValueConverter
{
  public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 
    => value is IEnumerable<string> values 
      ? string.Join(';', values)
      : Binding.DoNothing;

  public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 
    => throw new NotSupportedException();
}
<Window>
  <Window.Resources>
    <StringValuesToCsvStringConverter x:Key="StringValuesToCsvStringConverter" />
  </Window.Resources>

  <DataGrid ItemsSource="{Binding SwiftMessages}">
    <DataGrid.Columns>
      <DataGridTextColumn Header="CSV Values"  
                          Binding="{Binding Values, Converter={StaticResource StringValuesToCsvStringConverter}}" />
    </DataGrid.Columns>
  </DataGrid>
</Window>
© www.soinside.com 2019 - 2024. All rights reserved.