© 2004 Microsoft Corporation. All rights reserved.

Figure 2 Data Relation is Set Up
private void Form1_Load(object sender, System.EventArgs e)
{
    // Fetch the data
    DataSet ds = LoadData();

    // Set relations up
    DataColumn dc1, dc2;
    dc1 = ds.Tables["Employees"].Columns["employeeid"];
    dc2 = ds.Tables["Orders"].Columns["employeeid"];
    DataRelation rel = new DataRelation("EmployeesToOrders", dc1, dc2);
    ds.Relations.Add(rel);

    // Realize an AUTOMATIC master/detail schema 
    // with a child datagrid 

    // Configure the two grids
    masterGrid.DataSource = ds; 
    masterGrid.DataMember = "Employees";
    detailGrid.DataSource = ds;
    detailGrid.DataMember = "Employees.EmployeesToOrders";
}

private DataSet LoadData()
{
    DataSet ds = new DataSet();
    SqlDataAdapter da = ConfigureDataAdapter();
    da.Fill(ds);
    return ds;
}

private SqlDataAdapter ConfigureDataAdapter()
{
    // Set up the connection
    String connString = "DATABASE=northwind;" + 
                        "INTEGRATED SECURITY=sspi;" +
                   "SERVER=localhost;";
    SqlConnection conn = new SqlConnection(connString);

    // Define the commands to execute
    String strCmd1 = "SELECT employeeid, lastname, 
                     firstname FROM Employees";
    String strCmd2 = "SELECT Employees.employeeid, " + 
             "    Orders.OrderID, Orders.OrderDate, " + 
             "    Sum([Order Details].UnitPrice * [Order Details].Quantity) 
                  AS Total " +  
             "FROM Employees " + 
             "INNER JOIN Orders ON Employees.EmployeeID = Orders.EmployeeID 
             " + 
             "INNER JOIN [Order Details] ON Orders.OrderID =
             [Order Details].OrderID " + 
             "    WHERE (YEAR(Orders.OrderDate) = @nYear) " + 
             "GROUP BY Orders.orderid, Orders.OrderDate, 
             Employees.EmployeeID " + 
             "ORDER BY Employees.employeeid";

    // Create the SELECT command
    SqlCommand cmd = new SqlCommand(strCmd1 + ";" + strCmd2, conn);
    SqlParameter p1 = new SqlParameter("@nYear", SqlDbType.Int);
    p1.Value = 1998;
    cmd.Parameters.Add(p1);

    // Create the data adapter
    SqlDataAdapter da = new SqlDataAdapter();
    da.TableMappings.Add("Table", "Employees");
    da.TableMappings.Add("Table1", "Orders");
    da.SelectCommand = cmd;

    return da;
}
Figure 6 DetailListView
using System;
using System.Windows.Forms;
using System.Data;
using System.Text;

namespace AutoBindListView
{
    /// Summary description for DetailListView.
    public class DetailListView : ListView
    {
        public DetailListView()
        {
            View = View.Details; 
            FullRowSelect = true;
        }
        
        // DataMember Property
            private String m_DataMember = "";
        private String m_RelationName = "";
        private String m_ParentTable = "";
        public String DataMember    
        {
            get {return m_DataMember;}
            set {m_DataMember = value;
                 if (m_DataMember == "")    
                 return;
                 char[] sep = {'.'};
                 Array a = m_DataMember.Split(sep); 
                 m_ParentTable = a.GetValue(0).ToString();
                 m_RelationName = a.GetValue(1).ToString();
                 if (m_DataSource != null)
                AutoBind(); 
            }
        }

        // DataSource Property
        private object m_DataSource = null;
        public object DataSource  
        {
            get {return m_DataSource;}
            set {m_DataSource = value;
                 if (m_DataMember != "")
                AutoBind();
            }
        }

        /// Connects to the binding context to get the objects 
        /// connected to the specified data source and parent table.
        /// Next, register to be invoked when the current selection
        /// changes in that control. When this happens, refreshes its view
        private void AutoBind()
        {
            CurrencyManager cm;
            cm = (CurrencyManager) BindingContext[DataSource, 
                 m_ParentTable];
            cm.CurrentChanged += new EventHandler(CurrentChanged);
            RefreshList(cm);
        }

        /// Invoked when the selection changes, refresh the list view
        private void CurrentChanged(object sender, EventArgs e)
        {
            RefreshList((CurrencyManager) sender);
        }

        /// Refreshes the child view using the DataRelation to get to
        /// the child rows of the selected row
        private void RefreshList(CurrencyManager cm)
        {
            // Assuming it is bound to a DataTable/DataView

            DataRowView drv;
            drv = (DataRowView) cm.Current;    

            // Use the relation to extract the child rows
            DataRow row = drv.Row;
            DataRow [] rgChildRows = row.GetChildRows(m_RelationName); 

            // The listview is in Details view mode so add columns as 
            // needed
            DataTable dt = rgChildRows[0].Table;
            Columns.Clear();
            foreach(DataColumn c in dt.Columns)
            {
                ColumnHeader ch = new ColumnHeader();
                ch.Text = c.ColumnName;
                ch.Width = 100;
                ch.TextAlign = HorizontalAlignment.Left;
                if (c.DataType == typeof(System.Int32) || 
                    c.DataType == typeof(Decimal))
                    ch.TextAlign = HorizontalAlignment.Right;                    
                Columns.Add(ch); 
            }
            
            // Populates the listview with the child rows
            Items.Clear();
            foreach(DataRow r in rgChildRows)
            {
                ListViewItem lvi = new ListViewItem();
                lvi.Text = r[0].ToString();
                for(int i=1; i<dt.Columns.Count; i++)  
                    lvi.SubItems.Add(r[i].ToString());
 
                Items.Add(lvi); 
            }
        }
    }
}
Figure 7 Dynamically Adding Columns
DataTable dt = rgChildRows[0].Table;
Columns.Clear();
foreach(DataColumn c in dt.Columns)
{
  ColumnHeader ch = new ColumnHeader();
  ch.Text = c.ColumnName;
  ch.Width = 100;
  ch.TextAlign = HorizontalAlignment.Left;
  if (c.DataType == typeof(System.Int32) || 
      c.DataType == typeof(Decimal))
    ch.TextAlign = HorizontalAlignment.Right; 
    Columns.Add(ch); 
}
Figure 9 Customizing the DataGrid's Style
private void Form1_Load(object sender, System.EventArgs e)
{
    // Fetch the data
    DataSet ds = LoadData();

    // Bind the master grid to data 
    masterGrid.DataSource = ds;
    masterGrid.DataMember = "Employees";
              
    // Make the grid display a subset of columns
    // 

    // Step 1 - Create a table style and map it to the table name 
    // (case-sensitive)
    DataGridTableStyle gridStyle = new DataGridTableStyle();
    gridStyle.MappingName = "Employees";
    gridStyle.BackColor = System.Drawing.Color.Ivory;   

    // Step 2 - Create column styles and map to column names
    DataGridTextBoxColumn col1 = new DataGridTextBoxColumn(); 
    col1.TextBox.Enabled = false;
    col1.Alignment = HorizontalAlignment.Right;
    col1.HeaderText = "ID"; 
    col1.MappingName = "employeeid";
    gridStyle.GridColumnStyles.Add(col1);  

    DataGridTextBoxColumn col2 = new DataGridTextBoxColumn(); 
    col2.TextBox.Enabled = false;
    col2.HeaderText = "Last Name"; 
    col2.MappingName = "lastname";
    gridStyle.GridColumnStyles.Add(col2);  

    DataGridTextBoxColumn col3 = new DataGridTextBoxColumn(); 
    col3.TextBox.Enabled = false;
    col3.HeaderText = "First Name"; 
    col3.MappingName = "firstname";
    gridStyle.GridColumnStyles.Add(col3);  

    // Step 3 - Connect grid and style. (Must be done here.)
    masterGrid.TableStyles.Add(gridStyle);  

    // Configure the row viewer custom control
    rowViewer.DataSource = ds;
    rowViewer.DataMember = "Employees";
}