Skip to main content Skip to footer

Multiple Row Selection in C1DataGrid Without Using Ctrl Key

Among the many features provided by Silverlight C1DataGrid, a very useful one is multiple row selection. This may be enabled by simply setting its 'SelectionMode' property to 'MultiRow'. This enables the user to select/unselect rows by keeping Ctrl/Shift keys pressed when rows are clicked via mouse. If the SelectionMode property is set to MultiRow and you click a row while pressing the SHIFT key, the selection includes all rows between the current row and an anchor row located at the position of the current row before the first click. Subsequent clicks while pressing SHIFT changes the current row, but not the anchor row. If the CTRL key is pressed when navigating, the arrow keys will navigate to the border cells; for example, if you are in the first row and you press CTRL + DOWN you will navigate to the last row, however, if the SHIFT key is pressed, all the rows will be selected. There's, however, one more possibility - selection of rows without using Ctrl key, while 'SelectionMode' is set 'MultiRow'. One might think why would anybody want to do this? Simple - if such is the client's requirement (as one of the users had and I had to figure this out), we won't have a choice, would we? To begin with, we need to create handlers for MouseLeftButtonDown and MouseLeftButtonUp events of DataGridRowPresenter in LoadedRowPresenter event of C1DataGrid. Then, create a List of type DataGridRow; this keeps track of selected rows and needs to be updated in MouseLeftButtonDown event of DataGridRowPresenter. Within MouseLeftButtonUp event of DataGridRowPresenter, we figure out if the current row (row that was clicked on) is already selected or not. This is accomplished by using 'Contains()' method of the List 'selectedRows', this is the list that we'll create and update in MouseLeftButtonDown event of DataGridRowPresenter. If the current row is in there, we remove it. If it's not, we add it to the list. Finally, we clear the grid's selection altogether and then select all those rows that are contained in the list. Here's the complete code:


public MainPage()  
{  
 InitializeComponent();  

 //Your code  
 c1DataGrid1.SelectionMode = C1.Silverlight.DataGrid.DataGridSelectionMode.MultiRow;  

 //Register presenter's MouseLeftButtonDown and MouseLeftButtonUp events  
 this.c1DataGrid1.LoadedRowPresenter += (s, e) =>  
 {  
  e.Row.Presenter.MouseLeftButtonDown += new MouseButtonEventHandler(Presenter_MouseLeftButtonDown);  
  e.Row.Presenter.MouseLeftButtonUp += new MouseButtonEventHandler(Presenter_MouseLeftButtonUp);  
 };  

 //Unregister presenter's MouseLeftButtonDown and MouseLeftButtonUp events  
 this.c1DataGrid1.UnloadedRowPresenter += (s, e) =>  
  {  
   e.Row.Presenter.MouseLeftButtonDown -= new MouseButtonEventHandler(Presenter_MouseLeftButtonDown);  
   e.Row.Presenter.MouseLeftButtonUp -= new MouseButtonEventHandler(Presenter_MouseLeftButtonUp);  
  };  
 }  

 C1.Silverlight.DataGrid.DataGridRow row;  
 //List to keep track of current selection  
 List<C1.Silverlight.DataGrid.DataGridRow> selectedRows;  

 void Presenter_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)  
 {  
  selectedRows = this.c1DataGrid1.Selection.SelectedRows.ToList();  
 }  

 void Presenter_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)  
 {  
 row = ((C1.Silverlight.DataGrid.DataGridRowPresenter)sender).Row;  
 /* If CTRL key has been pressed the entire code is not needed anyways.  
 * With SelectionMode set to MultiRow the grid will take care of  
 * addition/removal of rows from selection*/  
 if ((!C1.Silverlight.KeyboardUtil.Ctrl))  
  {  
   Dispatcher.BeginInvoke(() =>  
   {  
    if (selectedRows.Contains(row))  
    {  
     selectedRows.Remove(row);  
    }  
    else  
    {  
     selectedRows.Add(row);  
    }  
    //Clear selection before we impose a new one  
    c1DataGrid1.Selection.Clear();  
    foreach (var r in selectedRows)  
    {  
     this.c1DataGrid1.Selection.Add(r, r);  
    }  
  });  
 }  
}  

Here's the grid with a few rows selected using mouse clicks only, without Ctrl key. Download C# Sample Download Vb.Net Sample

While developing an application using C1TrueDbgrid which is bound to an object DataSource, one may come across a situation data needs to be displayed in a heirarchical DataView. Now, IF we need to create a heirarchical-grid-like user interface in an application, the easiest solution is to assign the gridfs DataSource property to the data of interest and let the control take care of the display of data in whichever veiw we need.However, this is simple if the data of interest is a DataSet object or other collection designed for easy integration with grids. But itfs not so easy if you want to connect the grid to a collection of application-specific objects. The grid is designed such that if you assign the DataSource property to an arbitrary list of objects, it'll typically use reflection to get the names and datatypes of the objectfs members. But if you want to control which columns are displayed, how the values are formatted, and so on, then this solution is inadequate. And it gets more difficult , if the objects contain lists of other objects and you want the grid to be able to display the sublists. Let's begin adding a new class to our project and name it "Customerlist". Then add three classes more classes to it.


class Customer  
{  
public string CustName = null;  
public IBindingList CustOrders = null;  
}  

class Order  
{  
public DateTime OrderDate;  
public IBindingList OrderItems;  
}  

class OrderItem  
{  
public int ItemQuantity;  
public string ItemDescription;  
}  

Now, we implement Microsoftfs ITypedList interface . It does provide a solution and is rather elegant and minimal.Underlying this solution is the PropertyDescriptor class, which provides all of the information required to deal with a particular table column, i.e. its datatype, display name, how to get its value given a row-level object, and so on. Specifically this classfs PropertyType property returns the columnfs datatype as a Type object, itfs DisplayName property returns the columnfs display name as a string, itfs GetValue method takes a row-level object and returns the columnfs value, and so on. This is a great building block. All we need to do is provide the grid with a suitable list of property descriptors whenever the grid needs to know how to display a row. Thatfs where the ITypedList interface comes in. It provides a method called GetItemProperties which returns a list of PropertyDescriptor objects providing the grid with the information it needs. Let's create a class within "Customerlist.cs" using the same basic structure:


class CustomerList : ArrayList, ITypedList  
{  
PropertyDescriptorCollection ITypedList.GetItemProperties(PropertyDescriptor[] listAccessors)  
{  
if (listAccessors == null)  
{  
// Return the property descriptors for top-level rows  
return new PropertyDescriptorCollection (new PropertyDescriptor[]  
{  
new MyPropertyDescriptor("CustName"),  
new MyPropertyDescriptor("CustOrders")  
});  
}  
else  
{  
// Return the property descriptors for second-level and third-level rows  
string parentDescriptorName = listAccessors[listAccessors.Length - 1].Name;  
switch (parentDescriptorName)  
{  
case "CustOrders":  
return new PropertyDescriptorCollection(new PropertyDescriptor[]  
{  
new MyPropertyDescriptor("OrderDate"),  
new MyPropertyDescriptor("OrderItems")  
});  

case "OrderItems":  
return new PropertyDescriptorCollection(new PropertyDescriptor[]  
{  
new MyPropertyDescriptor("ItemQuantity"),  
new MyPropertyDescriptor("ItemDescription")  
});  

default:  
throw new Exception("Not implemented: " + parentDescriptorName);  
}  
}  
}  

string ITypedList.GetListName(PropertyDescriptor[] listAccessors)  
{  
return "";  
}  
}  

Now, create a subclass called myPropertyDescriptor with no additional properties. The idea is to allocate one of these whenever we need a property descriptor, storing only the name in the base part of the object. The various methods (PropertyType, DisplayName, etc.) will be overridden with versions that switch on the name to dispatch to the right code.


class MyPropertyDescriptor : PropertyDescriptor  
{  
public MyPropertyDescriptor(string descriptorName)  
: base(descriptorName, new Attribute[0])  
{  
}  

public override Type ComponentType  
{  
get  
{  
switch (this.Name)  
{  
case "CustName":         return typeof(Customer);  
case "CustOrders":       return typeof(Customer);  
case "OrderDate":        return typeof(Order);  
case "OrderItems":       return typeof(Order);  
case "ItemQuantity":     return typeof(OrderItem);  
case "ItemDescription":  return typeof(OrderItem);  
default:                 return null;  
}  
}  
}  

public override Type PropertyType  
{  
get  
{  
switch (this.Name)  
{  
case "CustName":           return typeof(string);  
case "CustOrders":         return typeof(ArrayList);  
case "OrderDate":          return typeof(DateTime);  
case "OrderItems":         return typeof(ArrayList);  
case "ItemQuantity":       return typeof(int);  
case "ItemDescription":    return typeof(string);  
default:                   return null;  
}  
}  
}  

public override object GetValue(object component)  
{  
switch (this.Name)  
{  
case "CustName":         return ((Customer)component).CustName;  
case "CustOrders":       return ((Customer)component).CustOrders;  
case "OrderDate":        return ((Order)component).OrderDate;  
case "OrderItems":       return ((Order)component).OrderItems;  
case "ItemQuantity":     return ((OrderItem)component).ItemQuantity;  
case "ItemDescription":  return ((OrderItem)component).ItemDescription;  
default:                 return null;  
}  
}  

public override string DisplayName  
{  
get  
{  
switch (this.Name)  
{  
case "CustName":         return "Customer name";  
case "CustOrders":       return "Customer orders";  
case "OrderDate":        return "Order date";  
case "OrderItems":       return "Order items";  
case "ItemQuantity":     return "Quantity";  
case "ItemDescription":  return "Description";  
default:                 return null;  
}  
}  
}  

public override bool IsReadOnly { get { return true; } }  
public override void SetValue(object component, object value)  { throw new Exception("Not implemented."); }  
public override void ResetValue(object component)              { throw new Exception("Not implemented."); }  
public override bool CanResetValue(object component)           { throw new Exception("Not implemented."); }  
public override bool ShouldSerializeValue(object component)    { throw new Exception("Not implemented."); }  
}  

We now have all of the building blocks we need to handle simple bindable lists (i.e. without sublists). We simply implement the ITypedList.GetItemProperties method and have it return a suitable list of TutorialExamplePropertyDescriptor objects. Then we can bind a grid to our list and the rows should display exactly as we want. However, what if we want to have rows which contain sublists? For example what if we are binding a list of Customer objects which each have a sublist of Order objects? How does the grid know how to display the Order rows? And if the Order objects contain sublists of OrderItem objects, how does the grid know how to display those? The first point is that the grid needs to know which columns represent list values. Thatfs easily done ? it checks the property descriptors and if any of them are list types (e.g. ArrayList) it displays a gplush next to the row indicating that the row can be expanded to show the sublist. The second point is that the grid needs a separate list of property descriptors for each type of row it can display. It obtains these lists by calling the ITypedList.GetItemProperties method every time it needs a list of property descriptors for a particular type of row, passing an argument called listAccessors to specify which type of row it wants to know about. When the grid wants to know about the top-level (primary) rows, it passes a null argument. But then say the user navigates to the Orders sublist for a particular customer; then the grid calls the GetItemProperties method passing it the Orders property descriptor, meaning that it is looking for the PropertyDescriptor list for the Order row type. If the user then navigates to the OrderItems field within the order, then the grid calls the GetItemProperties method passing it an array with the Orders property descriptor as the first element, and the OrderItems property descriptor as the second element. The listAccessors array thus represents the array of PropertyDescriptors that the user has navigated to so far. This listAccessors argument to the GetItemProperties method is simply the gridfs way of asking ggiven where Ifve navigated so far, how do I display the next kind of row?h Generally the implementor of this method needs to look at only the last entry in that array to figure out the answer, e.g. if the user has navigated to the OrderItems property, then you know which property descriptors to return as the result. But imagine a Customer class containing the properties CurrentOrders and ShippedOrders, both containing lists of OrderItem objects ? and itfs conceivable that you might want to display the OrderItem objects differently for each of the two order lists. In that case, whenever the last listAccessors entry is of type OrderItem, you would check the second-last entry as well, and return different property descriptors depending on whether it was for CurrentOrders or ShippedOrders. All this complexity applies only to lists that contain sublists. If you want to bind a simple list without sublists, then the listAccessors property is simple to implement ? you always return the same list of property descriptors (for the top-level rows) without even looking at the listAccessors argument. Or if you want to be tidy, you can throw an exception if listAccessors is non-null (since it shouldnft be in this case). Finally, we need instantiate a "CustomerList" object, populate it and then assign it as C1TrueDbgrid's DataSource. Please make sure that you set the grid's DataView property to "DataViewEnum.Hierarchical".


private void Form1_Load(object sender, EventArgs e)  
{  
c1TrueDBGrid1.DataView = C1.Win.C1TrueDBGrid.DataViewEnum.Hierarchical;  
c1TrueDBGrid1.DataSource = GetCustomerList();  
}  

private CustomerList GetCustomerList()  
{  
CustomerList customers = new CustomerList();  

Order order1 = new Order();  
order1.OrderDate = DateTime.Now;  
order1.OrderItems = new BindingList<OrderItem>();  
addOrderItem(order1, 2, "Ice Station");  
addOrderItem(order1, 3, "Area7");  

Order order2 = new Order();  
order2.OrderDate = DateTime.Now;  
order2.OrderItems = new BindingList<OrderItem>();  
addOrderItem(order2, 7, "Scarecrow");  
addOrderItem(order2, 1, "Hell Island");  

Customer c1 = new Customer();  
c1.CustName = "Matthew R.";  
c1.CustOrders = new BindingList<Order>();  
c1.CustOrders.Add(order1);  
c1.CustOrders.Add(order2);  

customers.Add(c1);  

Order order3 = new Order();  
order3.OrderDate = DateTime.Now;  
order3.OrderItems = new BindingList<OrderItem>();  
addOrderItem(order3, 12, "The Godfather");  

Order order4 = new Order();  
order4.OrderDate = DateTime.Now;  
order4.OrderItems = new BindingList<OrderItem>();  
addOrderItem(order4, 6, "The Sicilian");  
addOrderItem(order4, 1, "Omerta");  

Customer c2 = new Customer();  
c2.CustName = "Mario Puzo";  
c2.CustOrders = new BindingList<Order>();  
c2.CustOrders.Add(order3);  
c2.CustOrders.Add(order4);  

customers.Add(c2);  

return customers;  
}  

private void addOrderItem(Order order, int quantity, string description)  
{  
OrderItem item = new OrderItem();  
item.ItemQuantity = quantity;  
item.ItemDescription = description;  
order.OrderItems.Add(item);  
}  

Finally, this is how the grid looks like:

MESCIUS inc.

comments powered by Disqus