Search code examples
c#data-bindingdynamicdatagridpagedcollectionview

Binding array of arrays or dynamic to a datagrid


I am attempting to bind an array of arrays to a DataGrid.

I am aware that I could do this fairly easily by converting the data to a DataTable and then binding that. BUT this isn't what I need as I need to be able to add groupings to the data via a PagedCollectionVIew.

The reason for doing this is: I have a set of controls that create a structure for a document, I want to reflect this structure with dummy data dynamically while the structure controls are being used. The dummy data is a simple symmetric matrix of random ints where the rows are the data elements, I want to bind each grid column to the elemtns of the array, NOT the array object.

_dataGenerator = new DummyDataGenerator();
_dummyDataView = new ObservableCollection<int[]>();
DummyData = new PagedCollectionView(_dummyDataView);

Where the _dummyDataView is populated by

// Set up the dummy data for the fields available.
_dataGenerator.CreateData(ReportFields).ForEach(_dummyDataView.Add);

and the XAML is just a DataGrid binding to the DummyData... then I will be dynamically adding Groupings and Sortings to is as the user plays with the document data. I have searched everywhere but can't find a solution, but there must be a way (someone must have blogged this) to bind to elements of an array in xaml! Really need some help here.


Solution

  • I have the answer (well I had it ages ago, but I remembered I posted this question). It's a frankencode monster from about a zillion snippets and my own brain, so I can't attribute everyone. I am going to put it up. Because I think it is awesome. Even if nobody else cares:

    It uses: - The DLR - A bit of reflection - A bit of IL building.

    Should probably put all of this into a blog post somewhere but I don't keep a blog.

    This is the code that creates the data for the view called by the ViewModel.

        public IEnumerable<dynamic> CreateData(ObservableCollection<ReportFieldVm> reportFields)
        {
            // Find the length of the array.
            var size = reportFields.Count;
    
            // Create matrix.
            var b = new int[size, size];
    
            // Random for the values.
            var rand = new Random((int)DateTime.Now.Ticks);
    
            // Build the symmetric matrix.
            for (var i = 0; i < size; i++)
            {
                for (var j = 0; j < size; j++)
                {
                    if (i == j)
                    {
                        b[i,j] = rand.Next(0, 100);
                    }
                    else
                    {
                        var a = rand.Next(0, 100);
                        b[i,j] = a;
                        b[j,i] = a;
                    }
                }
            }
    
            // Define the assembly to add out new type to.
            var asmName = new AssemblyName("DummyAssembly");
            var ab = AppDomain.CurrentDomain.DefineDynamicAssembly(asmName, AssemblyBuilderAccess.Run);
            var mb = ab.DefineDynamicModule("DummyModule");
    
            // Define our type.
            var d = mb.DefineType("DummyType", TypeAttributes.Public);
    
            const MethodAttributes GetSetAttr = MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig;
    
            // Define all the fields for the new type.
            foreach (var rf in reportFields.OrderBy(rf => rf.SelectOrder))
            {
                var propertyName = rf.FieldName;
    
                var field = d.DefineField("m_" + propertyName, typeof(int), FieldAttributes.Private);
    
                var property = d.DefineProperty(propertyName, PropertyAttributes.HasDefault, typeof(int), null);
    
                var dGetAccessor = d.DefineMethod("get_" + propertyName, GetSetAttr, typeof(int), Type.EmptyTypes);
    
                var numberGetIl = dGetAccessor.GetILGenerator();
                numberGetIl.Emit(OpCodes.Ldarg_0);
                numberGetIl.Emit(OpCodes.Ldfld, field);
                numberGetIl.Emit(OpCodes.Ret);
    
                var dSetAccessor = d.DefineMethod("set_" + propertyName, GetSetAttr, null, new Type[] { typeof(int) });
                var numberSetIl = dSetAccessor.GetILGenerator();
                numberSetIl.Emit(OpCodes.Ldarg_0);
                numberSetIl.Emit(OpCodes.Ldarg_1);
                numberSetIl.Emit(OpCodes.Stfld, field);
                numberSetIl.Emit(OpCodes.Ret);
    
                property.SetGetMethod(dGetAccessor);
                property.SetSetMethod(dSetAccessor);
            }
    
            // Create the type.
            var dummyType = d.CreateType();
    
            var array = new List<dynamic>();
    
            // Convert the matrix into the array of the dynamic.
            for (var i = 0; i < size; i++)
            {
                var obj = Activator.CreateInstance(dummyType);
                int j = 0;
                foreach (var rf in reportFields.OrderBy(rf => rf.SelectOrder))
                {
                    var type = obj.GetType();
                    var prop = type.GetProperty(rf.FieldName);
                    prop.SetValue(obj, b[j, i], null);
                    j++;
                }
                array.Add(obj);
            }
    
            return array;
        }
    

    This is the code that binds the view, called in the view.

    Where :

    1. _dynamicReportPreview is the named grid in the XAML.

    2. ViewModel is a property that exposes the DataContext of the view set in the view constructor.

      private void BuildPreviewGridColumns()
      {
          if (_dynamicReportPreview == null)
              return;
      
          _dynamicReportPreview.Columns.Clear();
      
          var initialFields = ViewModel.ReportFields.OrderBy(rf => rf.SelectOrder);
      
          foreach (var rf in initialFields)
          {
              var col = new Column
              {
                  ColumnName = rf.FieldName, 
                  Binding = new Binding(rf.FieldName)
              };
      
              _dynamicReportPreview.Columns.Add(col);
      
              if (!rf.IsVisible)
              {
                  col.Visible = false;
              }
          }
      }