Home > Mobile >  Databinding to last entry in each KeyValuePair<string, List<T>>
Databinding to last entry in each KeyValuePair<string, List<T>>

Time:09-17

I have a simple OHLC object that implements INotifyChanged. Each OHLC object relates to a particular candle and is part of a List. Each List relates to a particular stock and is stored in a ConcurrentDictionary<string, List>.

I'm trying to databind the last candle for each stock to a datagridview. This is how I have the databinding set up to get going. It's not very elegant!

    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();
       });
   }

What's the best way to set up databinding here, so that I can just concentrate on updating the collection and not have to re-bind on each update? I can throttle a call to datagridview.Refresh() so it doesn't go repainting on every atomic update, but don't want to have to rebind.

I know how to databind once to a BindingList of custom objects and get the updates propagated to the UI with INotifyPropertyChanged and a simple call to datagridview.Refresh(). I can also find some examples in XAML which show databinding to the last item in a BindingList. But I can't find any examples that bind to the 'last-per-key' of a dictionary of lists.

I'd appreciate any help at all with this.

CodePudding user response:

Note: This would be a WPF solution to the problem, not WinForms.

Don't transform your data to fit the view, create a value converter to handle that transformation.

[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();
}

Then apply the conversion using data bindings:

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>

Trigger updates as needed.

CodePudding user response:

Windows Forms' DataGridView has a way to override the way it formats the data through the CellFormatting event. Handle that event so you could change the value that will be used for the column. You could also style the cell from here as well if you wanted to.

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;
    }
}
  • Related