Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

DataGridSample in MC++

0.00/5 (No votes)
6 Sep 2004 1  
An article on implementing a DataGrid control bound to nested ArrayLists in a Windows Form

Figure 1. User Entry Form with DataGrid Control � Records Expanded

Introduction

The purpose of this article is to provide an example of using a DataGrid control in a Windows Form programmed in Managed C++ (MC++). I wanted the DataSource for the DataGrid to be 2 nested ArrayLists of objects. The accompanying sample Project was my first attempt at using a DataGrid control. Most of the articles and examples that I found online were written in C# or VB .Net, so I felt compelled to help out other C++ programmers with some of the lessons I learned while familiarizing myself with the DataGrid control.

The user interface had to permit adding new Positions to the parent ArrayList and related ScaleFactors using other data entry controls. It also had to allow deletion of a selected child ScaleFactor or deletion of a selected parent Position from the DataGrid control. Finally, the UI had to permit the removal of all ScaleFactors in a given Position or the removal of all Positions.

The DataGrid control is probably the most powerful class derived from the System.Windows.Forms.Control class. A DataGrid object in a WinForm is normally bound to a data source of related data tables. Only one table is displayed in a DataGrid at any time. If a parent-child relationship is established between data tables and if navigation is enabled for the DataGrid, the user can navigate between the related tables by clicking an expander in the row header of a record that generates a Web-like link to the child table. See Figure 1. When the link is clicked, the records in the child table related to the single parent record are displayed. See Figure 2. While the DataGrid is displaying records from the child table, a back button is available in the DataGrid�s header allowing the user to navigate back to the parent table.

Figure 2. DataGrid Control with 2 ScaleFactor Objects Entered

How It Works

The WinForm shown in Figure 1 shows the final form for the sample Project after some data has been entered by the user. All of the TextBoxes are set with initial values at Design Time to provide examples of the type of data that are expected. The ScaleFactor Group becomes enabled when there is at least one parent Position object in the ArrayList that is the DataSource for the DataGrid. All of the initial values are cleared after each new record is added. ScaleFactors are added to a given Position by selecting a Position record and then entering data in the TextBoxes of the ScaleFactor GroupBox. Whichever record that the record indicator is in is the current record that will receive the ScaleFactors entered.

To remove a selected Position object, you click on the RowHeader (the left-most gray column) of a chosen record and then click the Remove Selected button. When you remove a Position object, you also remove all of the related child ScaleFactor objects. To remove a selected ScaleFactor object, you first click on the expander (the + button in the RowHeader) of a chosen Position record and then click on the Web-like link to display the list of related child ScaleFactors. Then, you select a ScaleFactor RowHeader and click the Remove Selected in the ScaleFactor GroupBox.

To clear all of the ScaleFactors related to a given Position object, you select a Position row and click the ScaleFactor GroupBox Clear All button. To clear all Position records, you click the main form�s Clear All button, which wipes the entire DataGrid clean and even removes the data binding to the underlying ArrayList of Positions.

I refresh the DataGrid in almost every method of the Project unless it isn�t necessary, such as when clearing all of the Positions. This may seem excessive, but it ensures that the presentation of the DataGrid is kept in a consistent state after each manipulation.

The user can edit any data member of a Position or ScaleFactor by clicking in a cell and editing the value and then clicking on another record or typing CTRL + Enter. In fact, there is a whole list of keyboard shortcuts in the Visual Studio .Net help, appropriately named Keyboard Shortcuts for the Windows Forms DataGrid Control.

To bind a DataGrid control to an array of objects, the object class must contain public properties. The parent Position ArrayList contains a data member that is itself an ArrayList of ScaleFactor objects as shown in the following code snippet. This establishes the parent-child relationship between the Data Members of the DataGrid�s DataSource.

// Parent class containing the ScaleFactor class

public: __gc class Position {
private:
  float x;
  float y;
  float z;
  myArrayList * scaleFactors;

public:
  Position( void ) {
        // Allocate memory on the heap for each child ArrayList

        // when a parent is created.

  scaleFactors = new myArrayList;
  };
  __property float get_X( void ) { return x; };
  __property void set_X( float input ) { x = input; };
  __property float get_Y( void ) { return y; };
  __property void set_Y( float input ) { y = input; };
  __property float get_Z( void ) { return z; };
  __property void set_Z( float input ) { z = input; };
  __property myArrayList * get_ScaleFactors( void ) { return scaleFactors; };
  __property void set_ScaleFactors( myArrayList * input ) 
    { scaleFactors = input; };
};

Binding to the ArrayLists

Parent and child objects are removed through the DataGrid using a CurrencyManager object, which updates the records displayed in the DataGrid. At the same time, the objects are removed from the parent or a given parent�s child ArrayList.

The DataGrid serves to display the current state of the nested ArrayLists, allows the user to modify the field values in any record, and permits the deletion of a selected parent or child ArrayList Item. The public properties of the Position and ScaleFactor classes are mapped to the columns of data in the DataGrid.

The System.Collections.ArrayList Class implements the IList interface using an array whose size is dynamically increased as required. Several Classes in the Visual Studio .Net help discuss the use of an ArrayList as the bound data source of a DataGrid and, in most cases, the Help warns that ". . . the ArrayList must have items in it when it is bound. An empty ArrayList will result in an empty grid." The DataSource and DataMember of the DataGrid are set using the SetDataBinding method at run-time, as Microsoft recommends and as shown in the next code snippet. The binding takes place with every Position added to the parent ArrayList, called positionList. This way of binding is necessary because there is no DataAdapter associated with an ArrayList and the DataGrid�s DataSource must be updated with each new ArrayList Item added to the data source.

  Position * newParentObj = new Position;
  newParentObj->X = Single::Parse( tboX->Text );
  newParentObj->Y = Single::Parse( tboY->Text );
  newParentObj->Z = Single::Parse( tboZ->Text );
  int addIndex = positionList->Add( newParentObj );
  // Bind the DataGrid to positionList

  dataGrid2->SetDataBinding( positionList, S"" );

Through the DataGridTableStyle you can control the appearance of the data displayed in the DataGrid. All DataGridTableStyles and DataGridColumnStyles have to be created programmatically because no DataSource exists at design time. Also, when creating DataGridColumnStyles and DataGridTableStyles, the Visual Studio .Net help cautions to:

"Always create DataGridColumnStyle objects and add them to the GridColumnStylesCollection before adding DataGridTableStyle objects to the GridTableStylesCollection. When you add an empty DataGridTableStyle to the collection, DataGridColumnStyle objects are automatically generated for you. Consequently, an exception will be thrown if you try to add [your] new DataGridColumnStyle objects with duplicate MappingName values to the GridColumnStylesCollection."

The Visual Studio .Net help also specifies that when binding a DataGrid control to an ArrayList, you should set the MappingName of the DataGridTableStyle to "ArrayList" (the type name). The MappingName for each DataGridTableStyle must also be unique. By creating a child class identical to the ArrayList with a different name, both the PositionObjectsTable and the ScaleFactorsTable can be mapped to ArrayLists as shown in the next code snippet.

// Giving ArrayList a new name to keep unique MappingNames

public: __gc class myArrayList : public ArrayList { };
      .
      .
      .
  PositionsStyle->MappingName = S"ArrayList";
      .
      .
      .
  ScaleFactorsStyle->MappingName = S"myArrayList";

The DataGrid is refreshed in almost every method that adds, removes, or clears a record in the DataSource. The CurrencyManager of the parent ArrayList also invokes the Refresh method.

Removing a Record

Determining which row in the DataGrid a user selected and then determining whether the selected row was a record from the parent or child table were the most difficult logical elements of the Project to formulate. The DataGrid Class Overview in the Visual Studio .Net help provides the answer to the first of these difficulties:

"To determine which part of the control the user clicked, use the HitTest method in the MouseDown event [of the DataGrid control]. The HitTest method returns a DataGrid.HitTestInfo object, which contains the row and column of a clicked area."

To determine whether a parent or child record was selected, we must know which list in the DataSource that the DataGrid control is displaying. The DataMember property gets this list for us. A slight complication arises from the fact that when the DataSource is an ArrayList, the top-most DataMember is an empty string. The next code snippet shows how I determine which table is being displayed and then sets the Position property of the parent or child Currency Manager to the selected record, which makes that record the current or active record.

    if( parentCurrencyMgr != 0 ) {
       if( dataGrid2->DataMember->ToString()->Equals(String::Empty) ) {
          parentCurrencyMgr->Position = hti->Row;
       }

       if( dataGrid2->DataMember->ToString()->Equals(S"ScaleFactors") ) {
          Position * currentParentObj = 
             dynamic_cast(parentCurrencyMgr->Current);
          // get rid of any pre-existing CM pointing to a child ArrayList

          if( childCurrencyMgr != 0 ) childCurrencyMgr = 0;
          childCurrencyMgr = dynamic_cast<CurrencyManager *>
             (dataGrid2->BindingContext->get_Item( currentParentObj->
             ScaleFactors, S"" ));
          if( 0 <= hti->Row && hti->Row < childCurrencyMgr->Count )
             childCurrencyMgr->Position = hti->Row;

Summary

This prototype MC++ Project was my first attempt at using the DataGrid control, perhaps the most powerful control in the Control class. Having experienced the frustrations and rewards of working with this control, I want to move on to using typed DataSets as the DataGrid�s DataSource. Questions or polite comments can be e-mailed to me.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here