我正在编写一个简单的 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')
我的问题是:
根据评论修复了我的 IEnumerable 的错误实现,该实现导致了上述问题 1-3。这些错误是因为我从 IEnumerator 返回计数器,而不是第 n 个对象。 @Clemens 如果您想在下面总结您的评论,我很高兴将其标记为答案。 我仍然对上面问题 4 的答案感兴趣。
您应该避免实现从
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>