1
votes

if I have a collection of objects called FXRate objects, defined as

public class FXRate
{
    public string CcyOne { get; set;}
    public string CcyTwo { get; set;}
    public decimal Rate { get; set;}
}

That I want to display in a grid, I have tried binding the ItemsSource of a DataGrid to an ObservableCollection and I can render it as follows

Ccy one         Ccy two         Rate 
EUR             GBP             1.2 
EUR             USD             1.5 
GBP             EUR             0.8

Etc... This lets the user (using an editable column style) to update the rate, and this updates the underlying FXRate objects property. So once the user makes their changes, the visual changes reflect directly in the underlying FXRate objects, then they can save and it is simple to save all the values.

However what I want is to render it as follows

    GBP      EUR      JPY
GBP   1        1.2      13.1
EUR   1.1      1        5.2
JPY   0.15     0.23     1

And to have the amount cells still editable and bound to the underlying objects, so the user can make a change in the GUI and have the relevant underlying FXRate object have its amount updated accordingly.

Can anyone think of a way to accomplish this with MVVM?

2

2 Answers

0
votes

I came across the same problem recently albeit in a different guise. The only way to do this is to create a custom DataGrid inheriting from DataGrid. See How to Bind a List<object> to DataGrid using MVVM at Runtime and the answer there. It also has a sample project for you to download...

I hope this helps.

0
votes

I ended up writing something custom myself which seems to work, though it's not the cleanest solution but one I'm satisfied enough with as it keeps the data in the ViewModels and updates simple to track. The biggest obstacle was accessing the styles from the viewModel but have done so below. Take a look if you're interested to see how I solved the problem.

In XAML

<ContentControl Content="{Binding CorrelationGrid}" Grid.Row="0" VerticalAlignment="Top" Background="Transparent" HorizontalAlignment="Left" />

In main ViewModel

 private ItemsControl _correlationGrid;
public ItemsControl CorrelationGrid
{
    get { return _correlationGrid; }
    set 
    { 
        _correlationGrid = value;
        RaisePropertyChanged("CorrelationGrid");
    }
}

private void RefreshCorrelationGrid()
{
    var resourceDictionary = new ResourceDictionary { Source = new Uri("/WMA.GUI;component/Styles.xaml", UriKind.RelativeOrAbsolute) };

    var grid = new DataGrid
                   {
                       HeadersVisibility = DataGridHeadersVisibility.All,
                       GridLinesVisibility = DataGridGridLinesVisibility.All,
                       Background = Brushes.Transparent,
                       CanUserAddRows = false,
                       CanUserResizeColumns = false,
                       CanUserReorderColumns = false,
                       CanUserSortColumns = false,
                       CanUserResizeRows = false,
                       AutoGenerateColumns = false,
                       HorizontalScrollBarVisibility = ScrollBarVisibility.Hidden,
                       BorderThickness = new Thickness(0),
                       RowHeaderTemplate = resourceDictionary["DataGridRowHeaderTemplate"] as DataTemplate,
                       RowHeaderStyle = resourceDictionary["DataGridRowHeader"] as Style,
                       RowHeaderWidth = 35,
                       SelectionMode = DataGridSelectionMode.Single,
                       RowHeight = 21,
                       VerticalContentAlignment = VerticalAlignment.Center
                   };

    var tableRows = new ObservableCollection<CorrelationTableRowViewModel>();
    var index = 0;

    foreach (var ccy in _availableCcys.OrderBy(c => c).Where(c => !c.ToUpper().Equals("USD")))
    {
        var row = new CorrelationTableRowViewModel(ccy);
        tableRows.Add(row);

        grid.Columns.Add(GetCorrelationColumn(row, index, resourceDictionary));

        foreach (var ccyMapping in _availableCcys.OrderBy(c => c).Where(c => !c.ToUpper().Equals("USD")))
        {
            var correlation = _fxCorrelations.FirstOrDefault(f => (f.CcyOne == row.Ccy && f.CcyTwo == ccyMapping) || f.CcyTwo == row.Ccy && f.CcyOne == ccyMapping);

            // If for some reason we don't have a mapped correlation for this ccy pairing, create it now
            if (correlation == null)
            {
                correlation = new FxCorrelation(row.Ccy, ccyMapping, 0.0m);
                _fxCorrelations.Add(correlation);
            }

            row.Correlations.Add(correlation);
        }

        index++;
    }

    grid.ItemsSource = tableRows;
    grid.Loaded += GridLoaded;
    CorrelationGrid = grid;
}

private DataGridTextColumn GetCorrelationColumn(CorrelationTableRowViewModel row, int index, ResourceDictionary resourceDictionary)
{
    // This gives us a setter we can use to assign background colours to correlation cells that have been modified
    var highlightStyleSetter = new Setter
    {
        Property = Control.BackgroundProperty,
        Value = resourceDictionary["GridModifiedCellBackground"] as Brush
    };

    var highlightStyle = new Style(typeof(DataGridCell));
    var trigger = new DataTrigger { Binding = new Binding("Correlations[" + index + "].Modified"), Value = true };
    trigger.Setters.Add(highlightStyleSetter);
    highlightStyle.Triggers.Add(trigger);

    return new DataGridTextColumn
        {
            Header = row.Ccy, 
            Width = new DataGridLength(50),
            Binding = new Binding("Correlations[" + index + "].Amount") { Mode = BindingMode.TwoWay, StringFormat = "0.####"},
            HeaderStyle = resourceDictionary["DataGridColumnHeader"] as Style,
            ElementStyle = resourceDictionary["DataGridTextStyle"] as Style,
            CellStyle = highlightStyle
        };
}

private static void GridLoaded(object sender, RoutedEventArgs e)
{
    var dep = sender as DependencyObject;

    while (!(dep is Button))
    {
        if (dep == null) return;
        if (VisualTreeHelper.GetChildrenCount(dep) == 0) return;
        dep = VisualTreeHelper.GetChild(dep, 0);
    }

    var resourceDictionary = new ResourceDictionary { Source = new Uri("/WMA.GUI;component/Styles.xaml", UriKind.RelativeOrAbsolute) };

    var button = dep as Button;
    button.Template = resourceDictionary["SelectAllButtonTemplate"] as ControlTemplate;
}

And the ViewModel for my grid row data

public class CorrelationTableRowViewModel : ViewModelBase
    {
        private string _ccy;
        private readonly IList<FxCorrelation> _correlations;

        public CorrelationTableRowViewModel(string ccy)
        {
            _ccy = ccy;
            _correlations = new List<FxCorrelation>();
        }

        public string Ccy
        {
            get { return _ccy; }
            set
            {
                _ccy = value;
                RaisePropertyChanged("Ccy");
            }
        }

        public IList<FxCorrelation>  Correlations
        {
            get { return _correlations; }
        }
    }