Objects of arbitrary classes

The grid provides broad capabilities of working with data, including arbitrary class objects, row arrays, objects implementing IList or IDictionary interfaces and objects with variable number of fields.

As an example of usage we shall demonstrate a simple class and a collection of objects of this class that can be later displayed in the grid.

C# Copy imageCopy
//An example of a simple class 
public class MyCustomClass 
{ 
    public MyCustomClass(int intValue, double doubleValue, string stringValue) 
    { 
        IntValue    = intValue; 
        DoubleValue = doubleValue; 
        StringValue = stringValue; 
    } 

    public int    IntValue { get; set; } 
    public double DoubleValue { get; set; } 
    public string StringValue { get; set; } 
} 

//Collection with some MyCustomClass objects 
public class MyCollection : BindingList<MyCustomClass> 
{ 
    public MyCollection() 
    { 
        //Add some data 
        Add(new MyCustomClass(10, 11.12, "some string 1")); 
        Add(new MyCustomClass(20, 21.33, "some string 2")); 
    } 
}

There are many methods of binding grid to data. Let's demonstrate an xaml file as the simplest example of this. To do it, we shall add GridControl from toolbox to VisualStudio and create a header and some columns. Data is created in DataTypesWindow resources and bound to the grid as a static resource: ItemsSource="{StaticResource myCollection}"

XAML Copy imageCopy
<Window x:Class="TestApplication.Tutorial.DataTypesWindow" 
           xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
           xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
           xmlns:df="clr-namespace:Dapfor.Wpf.Controls;assembly=Dapfor.Wpf" 
           xmlns:Tutorial="clr-namespace:TestApplication.Tutorial"
           Title="Objects of arbitrary classes" Height="352" Width="671"> 

  <Window.Resources> 
    <Tutorial:MyCollection x:Key="myCollection"/> 
  </Window.Resources> 

  <df:GridControl Name="grid" ItemsSource="{StaticResource myCollection}"> 
    <df:GridControl.Headers> 
      <df:Header ScrollType="Stretch"> 
        <df:Header.Columns> 
          <df:Column Id="IntValue" Title="Int Value" /> 
          <df:Column Id="DoubleValue" Title="Double Value" /> 
          <df:Column Id="StringValue" Title="String Value" /> 
        </df:Header.Columns> 
      </df:Header> 
    </df:GridControl.Headers> 
  </df:GridControl> 
</Window>

Besides the above example, the grid can also be bound to data with programming means. For example, in DataTypesWindow class designer.

 Copy imageCopy
// Interaction logic for DataTypesWindow.xaml 
public partial class DataTypesWindow : Window 
{ 
    public DataTypesWindow() 
    { 
        InitializeComponent(); 

        //Bind the Wpf GridControl to the collection MyCollection using program means 
        MyCollection myCollection = new MyCollection(); 
        grid.ItemsSource = myCollection; 
    } 
}

The code in xaml example will look almost the same. The only difference is that there is no binding to MyCollection. Another way of adding data involves use of GridControl.Rows.Add(Object) / Row..::..Add(Object) methods without binding the grid to data collections.

 Copy imageCopy
public DataTypesWindow() 
{ 
    InitializeComponent(); 

    //Add two objects to the top hierarchical level of the grid 
    grid.Rows.Add(new MyCustomClass(10, 11.12, "some string 1")); 
    grid.Rows.Add(new MyCustomClass(20, 21.33, "some string 2")); 
}
data types 1

Object or string arrays

The grid enables work with arrays of strings or any other objects. This method is applicable only to very simple applications. The grid doesn’t have any limitations of data types, however working with untyped data in a large application often causes a lot of errors, makes the code much more complicated and indicates improper application design.

C# Copy imageCopy
//Add an array of objects to a grid 
grid.Rows.Add(new object[] { 10, 11.12, "some string 1" }); 
grid.Rows.Add(new object[] { 20, 21.33, "some string 2" }); 

//Bind a collection of arrays to a grid 
List<object> collection = new List<object>(); 
collection.Add(new object[] { 10, 11.12, "some string 1" }); 
collection.Add(new object[] { 20, 21.33, "some string 2" }); 
grid.ItemsSource = collection;

Dictionaries

IDictionary<string, object> can be used for objects with variable number of fields. The key in this container is a string that identifies the data field. If it matches a column identifier, the value of this object is displayed in a cell. This method is usually applicable to data that is not modified by the application. Dictionary<string, object> type objects take a lot of memory. Therefore, it’s not worth bothering to create many objects of this kind. The recommended number of objects of this type shouldn’t exceed 10 000 per grid.

C# Copy imageCopy
//Bind a collection of dictionaries to a grid 
IList<IDictionary<string, object>> collection = new List<IDictionary<string, object>>();

collection.Add(new Dictionary<string, object>());
collection[0].Add("IntValue", 10);
collection[0].Add("StringValue", "some string 1");

collection.Add(new Dictionary<string, object>());
collection[1].Add("IntValue", 20);
collection[1].Add("DoubleValue", 21.33);

grid.ItemsSource = collection;
data types 2

Variable number of fields with UnboundValueAccessor

Use of UnboundValueAccessor for data with variable number of fields. This container differs from IDictionary as it enables automatic grid notification of data modification, updating, sorting, grouping and filtering.

C# Copy imageCopy
//-------------------------------------------------------------------------------
//Add an object to the grid 
grid.Rows.Add(new UnboundValueAccessor()); 

//Set dynamically some fields 
grid.Rows[0]["IntValue"].Value = 10; 
grid.Rows[0]["DoubleValue"].Value = 11.12; 

//Create and add some other object 
UnboundValueAccessor dataObject2 = new UnboundValueAccessor(); 
grid.Rows.Add(dataObject2); 

//Each modification of dataObject2 will trigger grid notification 
dataObject2["IntValue"].Value = 20; 
dataObject2["DoubleValue"].Value = 21.33; 
dataObject2["StringValue"].Value = "some string 2"; 

//-------------------------------------------------------------------------------
//The same is true for binding to a BindingList<UnboundValueAccessor> collection: 
BindingList<UnboundValueAccessor> collection = new BindingList<UnboundValueAccessor>(); 

//Populate collection with 2 objects 
collection.Add(new UnboundValueAccessor()); 
collection.Add(new UnboundValueAccessor()); 

//Set some fields in the collection: 
collection[0]["IntValue"].Value = 10; 
collection[0]["StringValue"].Value = "some string 1"; 
collection[1]["StringValue"].Value = "some string 2"; 

//Bind grid to a colelction grid.ItemsSource = collection; 

//Each data modification will trigger notification of the grid 
collection[0]["IntValue"].Value = 55; 
collection[1]["DoubleValue"].Value = 21.33;
data types 3

Event-driven model: A special case of objects of arbitrary classes

As it has been mentioned above, it is recommended to use objects of arbitrary classes in the grid. There are many reasons for this. First of all, it is easier and more convenient to work with typed data in applications of high and medium complexity. Besides that, objects of arbitrary classes consume less memory than string[] objects.

Event-driven model is another important reason to use objects of arbitrary classes. If data objects implement INotifyPropertyChаnged interface, the grid subscribes to events of this object and on notification it automatically sorts, groups and filters them. To update data in a traditional model it is necessary to find a Row associated with data and to perform all these operations manually (which is fairly hard). Just compare: MyCustomClass type object is associated with two rows in grid1 and grid2. Grid1 uses sorting and grid2 uses data filtering. On change of e.g. IntValue in data object, grid1 and grid2 will receive notifications and then grid1 will sort the row to the required position and grid2 will hide it.

Let's declare MyCustomClass implementing the INotifyPropertyChanged interface:

C# Copy imageCopy
public class MyCustomClass : INotifyPropertyChanged 
{ 
    private int _intValue; 
    //... 

    public int IntValue 
    { 
        get { return _intValue; } 
        set 
        { 
            if (_intValue != value) { _intValue = value; RaisePropertyChanged("IntValue"); } 
        } 
    } 

    private void RaisePropertyChanged(string fieldId) 
    { 
        if(PropertyChanged != null) 
        { 
            PropertyChanged(this, new PropertyChangedEventArgs(fieldId)); 
        } 
    } 

    public event PropertyChangedEventHandler PropertyChanged; 
}

Let's declare grid1 & grid2 in the xaml file:

XAML Copy imageCopy
<Window x:Class="TestApplication.Tutorial.DataTypesWindow" 
           xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
           xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
           Title="Wpf Grid: Event-driven model" Height="352" Width="671" 
           xmlns:df="clr-namespace:Dapfor.Wpf.Controls;assembly=Dapfor.Wpf" 
           xmlns:Tutorial="clr-namespace:TestApplication.Tutorial"> 

  <Grid> 
    <Grid.RowDefinitions> 
      <RowDefinition Height="*" /> 
      <RowDefinition Height="*" /> 
    </Grid.RowDefinitions> 

    <!--Declaration of the grid1--> 
    <df:GridControl Name="grid1"> 
      <df:GridControl.Headers> 
        <df:Header ScrollType="Stretch"> 
          <df:Header.Columns> 
            <df:Column Id="IntValue" Title="Int Value" /> 
            <df:Column Id="DoubleValue" Title="Double Value" /> 
            <df:Column Id="StringValue" Title="String Value" /> 
          </df:Header.Columns> 
        </df:Header> 
      </df:GridControl.Headers> 
    </df:GridControl> 

    <!--Declaration of the grid2--> 
    <df:GridControl Name="grid2" Grid.Row="1"> 
      <df:GridControl.Headers> 
        <df:Header ScrollType="Stretch"> 
          <df:Header.Columns> 
            <df:Column Id="IntValue" Title="Int Value" /> 
            <df:Column Id="DoubleValue" Title="Double Value" /> 
            <df:Column Id="StringValue" Title="String Value" /> 
          </df:Header.Columns> 
        </df:Header> 
      </df:GridControl.Headers> 
    </df:GridControl> 
  </Grid> 
</Window>

Adding some interaction logic for DataTypesWindow

C# Copy imageCopy
public partial class DataTypesWindow : Window 
{ 
    public DataTypesWindow() 
    { 
        InitializeComponent(); 

        BindingList<MyCustomClass> collection = new BindingList<MyCustomClass>(); 
        Random r = new Random(); 
        for (int i = 0; i < 5; ++i) 
        { 
            collection.Add(new MyCustomClass(i, r.NextDouble(), "some string " + i)); 
        } 

        grid1.ItemsSource = collection; 
        grid2.ItemsSource = collection; 

        //Sort the first grid: 
        grid1.Headers[0]["IntValue"].SortDirection = SortDirection.Ascending; 
        grid1.Headers[0]["DoubleValue"].SortDirection = SortDirection.Descending; 

        //Filter data in the second grid. Grid2 will hide all rows with IntValue > 30 
        grid2.Filter = new Filter(delegate(Row row) 
        { 
            MyCustomClass dataObject = row.DataObject as MyCustomClass; 
            return dataObject != null && dataObject.IntValue > 30; 
        }); 

        //Change int-value of some object in the collection 
        collection[1].IntValue = 55; 
    } 
}
data types 4

Please note that non-event-driven model implementation of this simple example will require much more code for searching rows in the grid and controlling sorting, visibility handling and filtering.

Back to Wpf Grid tutorials