-1

I'm trying to create a datagrid which has a entries which have properties coming from a json. The problem is that a lot of the properties should be dynamically generated based on the information that is in the json.

The first thing I tried was to make a very long list of all possible properties with their own getters & seters. But it's a very long list and it felt like there should be an automated solution for this problem. This is included at the end of Datagrid Entry View Model. So I am able to bind to Number when:

Number = obj.NUMBER;

After that I researched into dynamic types and expandoObjects. I learned that apperently it's not possible to bind WPF to an expandoObject's expanded properties. So I cannot bind to expando.Number

expando.Number = obj.NUMBER

I also tried to follow this example in which helper classes are created to help with the binding, but I can't seem to get it to work. When I run my solution I get an error saying that the property "Numberz" doesn't exist. So I cannot bind to Numberz:

AddProperty("Numberz", typeof(string));
SetPropertyValue("Numberz", obj.NUMBER);

At moment I'm out of ideas, so if anybody here could help, I would be very grateful. Maybe a few last notes. I'm not a very experienced programmer and I'm trying to work through an MVVM-pattern.

Datagrid entry view model

public class DatagridEntryViewModel : ICustomTypeProvider, INotifyPropertyChanged { #region Fields

    /// <summary>
    /// Custom Type provider implementation - delegated through static methods.
    /// </summary>
    private readonly CustomTypeHelper<DatagridEntryViewModel> _helper = new CustomTypeHelper<DatagridEntryViewModel>();

    #endregion

    #region Constructor

    public DatagridEntryViewModel(ExitCoil model) 
    {
        _helper.PropertyChanged += (s, e) => PropertyChanged(this, e);

        dynamic Json = JObject.Parse(model.JsonCoilData);
        //CreationDatePost = Json.CREATION_DATE_S_POST;
        ////Number = Json.NUMBER;
        //CreationDatePost = Json.CREATION_DATE_S_POST;
        ////LeverageOrder = Json.LEVERAGE_ORDER;
        //Client = Json.CLIENT;
        //Skinned = Json.SKINNED;

        dynamic expando = new ExpandoObject();
        dynamic obj = JsonConvert.DeserializeObject<ExpandoObject>(Json.ToString());
        //AddProperty("Numberz", typeof(string));
        //SetPropertyValue("Numberz", obj.NUMBER);
        //expando.AddItem("Number");
        //expando[0] = obj.NUMBER;
        //Number = expando.Number;


        Model = model;

        //var settings = new JsonSerializerSettings();
        //settings.Converters.Add(new Models.DynamicJsonConverter());
        //var serializer = JsonSerializer.Create(settings);
        //JsonReader reader = new JsonTextReader(new StringReader(JsonString));

        //var Data = serializer.Deserialize<DynamicObject>(reader);
    }

    #endregion

    #region Model & proxy

    private ExitCoil _model;
    public ExitCoil Model
    {
        set { _model = value; }
    }

    #endregion

    #region Public Methods

    // Methods to support dynamic properties.
    public static void AddProperty(string name)
    {
        CustomTypeHelper<DatagridEntryViewModel>.AddProperty(name);
    }

    public static void AddProperty(string name, Type propertyType)
    {
        CustomTypeHelper<DatagridEntryViewModel>.AddProperty(name, propertyType);
    }

    public static void AddProperty(string name, Type propertyType, List<Attribute> attributes)
    {
        CustomTypeHelper<DatagridEntryViewModel>.AddProperty(name, propertyType, attributes);
    }

    public void SetPropertyValue(string propertyName, object value)
    {
        _helper.SetPropertyValue(propertyName, value);
    }

    public object GetPropertyValue(string propertyName)
    {
        return _helper.GetPropertyValue(propertyName);
    }

    public PropertyInfo[] GetProperties()
    {
        return _helper.GetProperties();
    }

    Type ICustomTypeProvider.GetCustomType()
    {
        return _helper.GetCustomType();
    }

    public event PropertyChangedEventHandler PropertyChanged = delegate { };
    private void RaisePropertyChanged([CallerMemberName] string propertyName = "")
    {
        PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }

    #endregion

    #region Properties

    //public string Number { get; private set; }

    //public string CreationDatePost { get; private set; }

    //public string LeverageOrder { get; private set; }

    public string Client { get; private set; }

    //public int OrderZnTop { get; private set; }

    //public int OrderZnBottom { get; private set; }

    //public string OrderAspect { get; private set; }

    //public string OderSideTrimming { get; private set; }

    //public string OrderPassivation { get; private set; }

    //public string OrderOilType { get; private set; }

    //public string OrderOilQuantity { get; private set; }


    #endregion

}

Datagrid view model

public class DatagridViewModel : MainViewModelBase
    {
        #region Singleton

        public static DatagridViewModel Instance = new DatagridViewModel();

        #endregion

        #region Fields

        private ObservableCollection<DatagridEntryViewModel> _coilEntries = new ObservableCollection<DatagridEntryViewModel>();
        private readonly IViewManager _viewManager;
        private readonly IDockManager _dockManager;

        #endregion

        #region Properties

        public ObservableCollection<DatagridEntryViewModel> CoilEntries
        {
            get { return _coilEntries; }
            set
            {
                _coilEntries = value;
                RaisePropertyChanged(() => CoilEntries);
            }
        }

        public ProductionExitCoilReport Report { get; set; }

        #endregion

        #region Constructor

        public DatagridViewModel()
        {
            _viewManager = Globals.Container.Resolve<IViewManager>();
            _dockManager = Globals.Container.Resolve<IDockManager>();


            getReport();

            foreach (var Coil in Report.Coils)
            {
                CoilEntries.Add(new DatagridEntryViewModel(Coil));
            }
        }

        #endregion

        #region Methods

        public void getReport()
        {
            var proxy = new Proxy<IReportService>();
            proxy.Open();
            var LijnId = new LijnIdentificatie { Department = "HDG", Firm = "LIE", LineNumber = "05" };
            var Periode = new Period { FromDate = new DateTime(2017 - 11 - 10), FromShift = 1, ToDate = new DateTime(2017 - 11 - 10), ToShift = 1 };
            var request = new ProductionReportRequest() { LijnIdentificatie = LijnId, Period = Periode };
            Report = proxy.Service.GetProductionExitCoilReport(request);


        }

        #endregion
    }

Datagrid XAML

<DataGrid x:Class="Promo.Presentation.Views.CoilGrid"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:mc="http://schemas.openxmlformats.org/marukp-compatibility/2006" 
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
            xmlns:local="clr-namespace:Promo.Presentation.Views"
            xmlns:components="clr-namespace:Promo.Presentation.Components"
            xmlns:Data="clr-namespace:Promo.Presentation.ViewModels"
            AutoGenerateColumns="False"
            GridLinesVisibility="none"
            Background="White"
            Height="Auto"
            Width="Auto"
            MinWidth="500"
            MinHeight="500"
            DataContext="{x:Static Data:DatagridViewModel.Instance}"
            ItemsSource="{Binding CoilEntries}"
            IsReadOnly="True"
            HeadersVisibility="Column"
            Padding="10"
          >

    <DataGrid.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="../Styles/Colors.xaml"/>
                <ResourceDictionary Source="../Styles/Datagrid.xaml"/>
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </DataGrid.Resources>

    <DataGrid.Columns>
        <DataGridTextColumn Binding="{Binding Numberz}" Header="Rolnummer" MaxWidth="125"/>
        <DataGridTextColumn Binding="{Binding CreationDatePost}" Header="Post" MaxWidth="200"/>
        <DataGridTextColumn Binding="{Binding LeverageOrder}" Header="Leverage order" MaxWidth="200"/>
        <DataGridTextColumn Binding="{Binding Client}" Header="Client" MaxWidth="400"/>
        <DataGridTextColumn Binding="{Binding OrderZnTop}" Header="Order zinc top" MaxWidth="200"/>
        <DataGridTextColumn Binding="{Binding OrderZnBottom}" Header="Order zinc bottom" MaxWidth="200"/>
        <DataGridTextColumn Binding="{Binding OrderAspect}" Header="Order aspect" MaxWidth="125"/>
        <DataGridCheckBoxColumn Binding="{Binding OrderSideTrimming, Converter={local:StringToBoolConverter}}" Header="Order Side Trimming" MaxWidth="200"/>
        <DataGridTextColumn Binding="{Binding OrderPassivation}" Header="Order Passivation" MaxWidth="200"/>

        <DataGridCheckBoxColumn Binding="{Binding Skinned, Converter={local:StringToBoolConverter}}" Header="Skinned" MaxWidth="200"/>
    </DataGrid.Columns>

</DataGrid> 

Part of the Json

{"NUMBER":"G571015110 ","CREATION_DATE_S_POST":"2017-05-11T00:00:00""LEVERAGE_ORDER":"FD72BIGA5W/8","CLIENT":"ARCELORMITTAL BELGIUM SA "}

Bart Pinnock
  • 153
  • 2
  • 10
  • Possible duplicate of [How to build A WPF Datagrid with an unknown number of columns](https://stackoverflow.com/questions/44202718/how-to-build-a-wpf-datagrid-with-an-unknown-number-of-columns) – ASh Dec 04 '17 at 13:30
  • Please do read through the whole problem @ASh. Mine is that my rows have dynamical properties, not the columns of the table itself. – Bart Pinnock Dec 04 '17 at 13:40
  • 1
    I did read the whole problem, but some important bits of information are missing. Example of json structure will be helpful. DataGrid generates a columns for each entry property, so dynamic properties means dynamic columns to me – ASh Dec 04 '17 at 13:46
  • Then say it like that instead of just saying that it's duplicate. Anyhow, I've added extra information now. – Bart Pinnock Dec 04 '17 at 13:56
  • Do you need all of those properties to be displayed? Could you not rely on a set of properties and then display more info? That is, if it is possible for you. What you are describing here is a scenario where there could be multitude of columns for each object, so I would limit what you can display and focus on a set of properties that would allow me to display concise data. – XAMlMAX Dec 04 '17 at 13:59
  • @XAMlMAX I see where you are going, but I don't see how that would solve the problem at this moment. I still would need to bind to a dynamic object or expandoObject of some kind? The difficulty for me is to convert the json to a list of properties which are bindable in WPF. Showing or hiding columns is a problem for later. But for example let's just say I want only to display the property "Number" without hardcoding (I've got more than 200 properties which I have to display somewhere). – Bart Pinnock Dec 04 '17 at 14:04
  • 1
    If you have a json with all of the properties then Visual Studio can generate class for you! no need to create them your self. I worked on a similar scenario in the past, this has been done before :-). Look into generating classes in Visual Studio based on json. This should make it a bit easier for you. – XAMlMAX Dec 04 '17 at 14:07
  • @XAMlMAX That is very interesting, thanks. That made me cry a little (for all the unnecessary work I did). But we're moving to the right direction. The only thing I'm not sure of, is that I will ever have a json that contains all properties. Else it would be necessary to do that copy programmatically. – Bart Pinnock Dec 04 '17 at 14:17
  • I don't understand every detail of your problem, but there might be a solution with `ICustomTypeDescriptor` and `ITypedList`... just remembered a related topic I worked on some time ago: https://stackoverflow.com/q/46950202/5265292 however, you should read on your own whether the presented concepts might be useful for your case, its not really straight forward :). Some more original topic: [Binding DynamicObject to a DataGrid with automatic column generation?](https://stackoverflow.com/q/2473739/5265292) – grek40 Dec 04 '17 at 16:33
  • @XAMlMAX You can post your comment as answer. It's not a complete solution but it comes the closest and it was very helpful. – Bart Pinnock Dec 06 '17 at 13:32

1 Answers1

1

If you have a json with all of the properties then Visual Studio can generate class for you! no need to create them your self. I worked on a similar scenario in the past, this has been done before.
To paste a class generated from json click in your .cs file where you want to generate the code, then click on Edit in the Visual Studio menu, next select Paste Special and then select Paste JSON As Classes.
This should make it a bit easier for you.

XAMlMAX
  • 2,268
  • 1
  • 14
  • 23