0

I have a class shown below.

class RegionSale
{
    DateTime DateSale;
    string Region;
    double DollarAmount; 
}

At run time the code wouldn't know how many RegionSale objects would be required, this is fine as I can just create List of RegionSale objects.

The problem I have is I'm now be asking to display the data via a wpf datagrid but in the format shown below,

 DateS      UK      US      EUxUK   JAP     Brazil
 2015-12-03 23634   22187   NULL    NULL    NULL
 2015-12-04 56000   22187   NULL    NULL    NULL
 2015-12-14 56000   10025   NULL    NULL    NULL

So I could create a new class like below (however feel this is a bad idea)

class RegionSaleNew
{
    DateTime DateSale;
    double UK;
    double US;
    double EUxUK;
    double JAP;
    double Brazil;
}

As I mentioned earlier I won't know at runtime the number of regions so the class above seems like a bad idea, however its obviously easy to bind to the datagrid.

The question is how best to structure my class bearing in mind the format of the datagrid & without knowing the number of regions until runtime? Is reflection a good idea?

mHelpMe
  • 6,336
  • 24
  • 75
  • 150
  • You should crate a class for this. You can youse `ObservableCollection(yourClass)` then and just bind the colums to the class properties. if you want nulls, you could use `double? UK; double?...` – Nitro.de Mar 22 '16 at 10:33
  • Yes I need to create a class to bind to the datagrid. I'm just wondering what is the best way to design my class – mHelpMe Mar 22 '16 at 10:37
  • Is this for viewing only or also needs to be editable? – Ivan Stoev Mar 22 '16 at 10:57
  • the datagrid would just be for viewing – mHelpMe Mar 22 '16 at 13:30

2 Answers2

0

I would write a class that derives the INotifyPropertyChanged interface and create a property for every region like in your example.

Then I would use a ObservableCollection<myClass> and bind to the properties.

class RegionSaleNew : INotifyPropertyChanged
{
    DateTime DateSale;
    double _uk;
    .....
    public double UK
    {
        get { return _monitor; }
        set
        {
            if (value == _uk) return;
            _uk = value;
            OnPropertyChanged();
        }
    }
    // Add all properties here

    public event PropertyChangedEventHandler PropertyChanged;

    [NotifyPropertyChangedInvocator]
    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

}

In XAML you could then bind like this

...
<DataGridTextColumn Width="Auto" Header="UK" Binding="{Binding UK, UpdateSourceTrigger=PropertyChanged}" IsReadOnly="True"/>
...

All you have to do then is to set the DataGrid.ItemsSource = myObservableCollection or in XAML <DataGrid ItemsSource="{Binding myObservableCollection} ... </DataGrid>

Nitro.de
  • 821
  • 10
  • 27
0

Actually I was interested in the same subject, so it was the target of the my first SO question Cross tabular data binding in WPF. The provided answer there covered it in general, but not for the DataGrid specific binding. So my conclusion was that the solution should be based on some System.ComponentModel concepts:

(1) Custom PropertyDescriptor implementation for providing "virtual" properties.
(2) The item class implementing ICustomTypeDescriptor to expose the "virtual" properties per item (3) The collection class implementing ITypedList to allow automatic data grid column creation from the "virtual" properties.

Having your model

class RegionSale
{
    public DateTime DateSale;
    public string Region;
    public double DollarAmount;
}

and data

IEnumerable<RegionSale> data = ...;

the "virtual" properties can be determined at runtime by simply getting a distinct list of the Region field:

var regions = data.Select(sale => sale.Region).Distinct().ToList();

For each region we'll create a property descriptor with region as Name, which later will be used as key into item internal dictionary to retrieve the value.

The items will be built by grouping the data by DateSale.

Here is the whole implementation:

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;

class RegionSalePivotViewItem : CustomTypeDescriptor
{
    private RegionSalePivotView container;
    private Dictionary<string, double> amountByRegion;
    internal RegionSalePivotViewItem(RegionSalePivotView container, DateTime date, IEnumerable<RegionSale> sales)
    {
        this.container = container;
        DateSale = date;
        amountByRegion = sales.ToDictionary(sale => sale.Region, sale => sale.DollarAmount);
    }
    public DateTime DateSale { get; private set; }
    public double? GetAmount(string region)
    {
        double value;
        return amountByRegion.TryGetValue(region, out value) ? value : (double?)null;
    }
    public override PropertyDescriptorCollection GetProperties()
    {
        return container.GetItemProperties(null);
    }
}

class RegionSalePivotView : ReadOnlyCollection<RegionSalePivotViewItem>, ITypedList
{
    private PropertyDescriptorCollection properties;
    public RegionSalePivotView(IEnumerable<RegionSale> source) : base(new List<RegionSalePivotViewItem>())
    {
        // Properties
        var propertyList = new List<PropertyDescriptor>();
        propertyList.Add(new Property<DateTime>("DateSale", (item, p) => item.DateSale));
        foreach (var region in source.Select(sale => sale.Region).Distinct().OrderBy(region => region))
            propertyList.Add(new Property<double?>(region, (item, p) => item.GetAmount(p.Name)));
        properties = new PropertyDescriptorCollection(propertyList.ToArray());
        // Items
        ((List<RegionSalePivotViewItem>)Items).AddRange(
            source.GroupBy(sale => sale.DateSale,
                (date, sales) => new RegionSalePivotViewItem(this, date, sales))
                .OrderBy(item => item.DateSale)
        );
    }
    public PropertyDescriptorCollection GetItemProperties(PropertyDescriptor[] listAccessors) { return properties; }
    public string GetListName(PropertyDescriptor[] listAccessors) { return null; }
    class Property<T> : PropertyDescriptor
    {
        Func<RegionSalePivotViewItem, Property<T>, T> getValue;
        public Property(string name, Func<RegionSalePivotViewItem, Property<T>, T> getValue) : base(name, null) { this.getValue = getValue; }
        public override Type ComponentType { get { return typeof(RegionSalePivotViewItem); } }
        public override Type PropertyType { get { return typeof(T); } }
        public override object GetValue(object component) { return getValue((RegionSalePivotViewItem)component, this); }
        public override bool IsReadOnly { get { return true; } }
        public override bool CanResetValue(object component) { return false; }
        public override void ResetValue(object component) { throw new NotSupportedException(); }
        public override void SetValue(object component, object value) { throw new NotSupportedException(); }
        public override bool ShouldSerializeValue(object component) { return false; }
    }
}

Sample test:

ViewModel:

class ViewModel
{
    public RegionSalePivotView PivotView { get; set; }
}

XAML:

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApplication1"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <DataGrid x:Name="dataGrid" HorizontalAlignment="Left" Margin="28,33,0,0" VerticalAlignment="Top" Height="263" Width="463" ItemsSource="{Binding PivotView}"/>
    </Grid>
</Window>

Code behind:

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        var data = new[]
        {
            new RegionSale { DateSale = new DateTime(2015, 12, 03), Region = "UK", DollarAmount = 23634 },
            new RegionSale { DateSale = new DateTime(2015, 12, 03), Region = "US", DollarAmount = 22187 },
            new RegionSale { DateSale = new DateTime(2015, 12, 04), Region = "UK", DollarAmount = 56000 },
            new RegionSale { DateSale = new DateTime(2015, 12, 04), Region = "US", DollarAmount = 22187 },
            new RegionSale { DateSale = new DateTime(2015, 12, 14), Region = "UK", DollarAmount = 56000 },
            new RegionSale { DateSale = new DateTime(2015, 12, 14), Region = "US", DollarAmount = 10025 },
        };
        DataContext = new ViewModel { PivotView = new RegionSalePivotView(data) };
    }
}

Result:

enter image description here

Community
  • 1
  • 1
Ivan Stoev
  • 195,425
  • 15
  • 312
  • 343