数据绑定到每个 KeyValuePair<string, List<T>> 中的最后一个条目

Posted

技术标签:

【中文标题】数据绑定到每个 KeyValuePair<string, List<T>> 中的最后一个条目【英文标题】:Databinding to last entry in each KeyValuePair<string, List<T>> 【发布时间】:2021-11-03 21:21:44 【问题描述】:

我有一个实现 INotifyChanged 的​​简单 OHLC 对象。每个 OHLC 对象都与特定的蜡烛相关,并且是列表的一部分。每个 List 都与特定股票相关,并存储在 ConcurrentDictionary 中。

我正在尝试将每只股票的最后一根蜡烛数据绑定到数据网格视图。这就是我设置数据绑定的方式。不是很优雅!

    ConcurrentDictionary<string, List<OHLC>> candles= new ConcurrentDictionary<string, List<OHLC>>();
    BindingList<OHLC> LastCandles = new BindingList<OHLC>();

    . . .
    form1.dataGridView1.DataSource = LastCandles;
    . . .
    public void OnUpdate()
    
        //Update the appropriate candles
        . . .
        //Pull out the last candle by symbol and re-bind
        BindingList<OHLC> lastBySymbol = new BindingList<OHLC>();
        foreach (KeyValuePair<string, List<OHLC>> dict in candles)
        
           if (dict.Value.Count > 0)
           
               lastBySymbol.Add(dict.Value.Last());
           
       

       LastCandles = lastBySymbol;

       form1.dataGridView1.BeginInvoke((MethodInvoker)delegate
       
           form1.dataGridView1.DataSource = LastCandles;
           form1.dataGridView1.Refresh();
       );
   

在这里设置数据绑定的最佳方式是什么,这样我就可以专注于更新集合而不必在每次更新时重新绑定?我可以限制对 datagridview.Refresh() 的调用,因此它不会在每次原子更新时重新绘制,但不想重新绑定。

我知道如何将数据绑定一次到自定义对象的 BindingList 并使用 INotifyPropertyChanged 和对 datagridview.Refresh() 的简单调用将更新传播到 UI。我还可以在 XAML 中找到一些示例,这些示例显示了与 BindingList 中最后一项的数据绑定。但我找不到任何绑定到列表字典的“last-per-key”的示例。

如果能提供任何帮助,我将不胜感激。

【问题讨论】:

唯一重要的是你如何在你的ConcurrentDictionary&lt;string, List&lt;OHLC&gt;&gt; 中设置List&lt;OHLC&gt;,这似乎是一个辅助线程。如果您不完全替换 List&lt;OHLC&gt;,而是创建一个新对象,而只是更新现有成员,则 Binding 也可以跨线程工作。如果您改为替换字典中的List&lt;OHLC&gt;,创建一个新对象,数据绑定当然不起作用,只是因为您已经销毁了它。 -- 您可以将 BindingList 设置为,例如,LastCandles.Add(Candles["First"].Last()); LastCandles.Add(Candles["Second"].Last()); [...] ...当您第一次初始化 BindingList 时,可能在您设置 DataGridView 的 DataSource 之前。 -- 如果你在字典中添加了新的 Keys,还要将新的Last() 对象添加到 BindingList 中(这个对象稍后会更新,从辅助线程)。 -- 这:form1.dataGridView1.[...] 根本不是什么好东西。另外,不要BeginInvoke()一个Control,总是使用父Form容器作为marshaller。 我可以尝试在初始化期间填充 ConcurrentDictionary,然后用每个键的最后一个 OHLC 对象填充 BindingList,然后将数据源设置为 BindingList。但是当一个新的蜡烛到达并且一个新的 OHLC 对象被添加到 ConcurrentDictionary 中特定键的 List 时,绑定不会丢失,因为 Last() 会改变吗? 唯一重要的是如何在 ConcurrentDictionary> 中设置 List:是的,确实如此。这就是介绍的原因。如果是这种情况,您可以在将新成员添加到 Dictionary 或 Last() 内部列表更改的条目时使用 IProgress&lt;T&gt; 委托来更新 UI(如果您修改列表成员的属性值则不能)。委托方法可以是:private void UpdateLastCandles() LastCandles.Clear(); foreach (var list in Candles.Values) LastCandles.Add(list.Last()); 。您只是在添加引用。 按照这个逻辑,你可以使用一个中间对象进行数据绑定:另一个只包含一个 List 成员的 Dictionary,它使用 main 中每个 List 的 Last() 条目的属性值进行更新收藏。在这种情况下,您无需执行任何操作,数据绑定将永远有效,无需直接更新LastCandlesBindingList 的内容。 【参考方案1】:

注意:这将是 WPF 解决方案,而不是 WinForms。

不要转换您的数据以适应视图,创建一个值转换器来处理该转换。

[ValueConversion(typeof(System.Collections.IEnumerable), typeof(object))]
public class LastItemConverter : IValueConverter

    public object Convert(object value, Type targetType, object parameter, CultureInfo culture) =>
        ((System.Collections.IEnumerable)value)?.Cast<object>().Last();

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) =>
        throw new NotSupportedException();

然后使用数据绑定应用转换:

YourControl.xaml.cs:

public partial class YourControl : UserControl

    public ConcurrentDictionary<string, List<OHLC>> Candles  get;  = ...;

YourControl.xaml:

<UserControl x:Class="YourNamespace.YourControl"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:local="clr-namespace:YourNamespace"
  DataContext="Binding RelativeSource=RelativeSource Self">

  <UserControl.Resources>
    <local:LastItemConverter x:Key="lastItemConverter" />
  </UserControl.Resources>

  <Grid>
    <DataGrid ItemsSource="Binding Candles" AutoGenerateColumns="False">
      <DataGrid.Columns>
        <DataGridTextColumn Header="Key"
          Binding="Binding Key" />
        <DataGridTextColumn Header="Value"
          Binding="Binding Value, Converter=StaticResource lastItemConverter" />
      </DataGrid.Columns>
    </DataGrid>
  </Grid>
</UserControl>

根据需要触发更新。

【讨论】:

感谢您的帮助。我在 WinForms 中执行此操作,但将切换到 WPF,这样我就不需要重新排列 ViewModel 以适应我想要显示的内容。 啊,抱歉,我看到了代码,还以为是 WPF 的。 WinForms 有类似的机制,但我不记得它们了。我会回到这个来寻找合适的 WinForms 解决方案。但我相信您仍然可以使用相同的转换器。【参考方案2】:

Windows 窗体的DataGridView 可以通过CellFormatting 事件覆盖其格式化数据的方式。处理该事件,以便您可以更改将用于列的值。如果您愿意,也可以从此处设置单元格样式。

form1.dataGridView1.CellFormatting += this.dataGridView1_CellFormatting;

//...

private void dataGridView1_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e)

    switch (dataGridView1.Columns[e.ColumnIndex].Name)
    
    case "Value": // assuming this is the corresponding column name
        if (e.Value is List<OHLC> asList)
        
            var lastValue = asList.Last();
            e.Value = lastValue;
            // example of additional styling
            if (lastValue.CostSavings > 0)
                e.CellStyle.ForeColor = Color.Green;
        
        return;
    

【讨论】:

以上是关于数据绑定到每个 KeyValuePair<string, List<T>> 中的最后一个条目的主要内容,如果未能解决你的问题,请参考以下文章

如何从组合框中设置选定的值?

将字典转换为 List<KeyValuePair>

从 IEnumerable<KeyValuePair<>> 重新创建字典

C#中的KeyValuePair类是干啥用的?

KeyValuePair的使用

从 List<KeyValuePair<string,string>> 返回匹配