Introduction
In LightSwitch Beta 1, I'm pretty sure that we had the ability to select multiple rows in a grid. In Beta 2, that ability disappeared, and has been sorely missed by many ever since.
People have come up with a couple of workarounds, like adding a boolean property to an entity being displayed in the grid, so that each row has a checkbox in it that can be ticked. But I've never been all that happy with having to do anything like that.
I did some research, and discovered that the Silverlight 4 grid was in fact capable of multiple selection, but for some reason the functionality hadn't been exposed in B2. The situation didn't change for the RTM.
I've been pretty vocal in the community about the fact that I felt that this was a pretty basic grid requirement, and should definitely be included in the default grid. It was one of the first things that my clients asked for when I demoed their application to them for the first time. When I told them that you couldn't do it they were very surprised, and not very happy. They mournfully pointed out that this functionality had been available in the MS Access version of the previous program I had written for them.
So my search continued, & today (2011/08/26) I finally found the solution. I'm going to share that solution with you now, in this article.
The prerequisites for this sample are:
Description
The first step is to create a LightSwitch project.
On the Menu Bar in Visual Studio:
choose File
choose New Project
In the New Project dialog box
select the LightSwitch node
then select LightSwitch Application (Visual Basic)
in the Name field, type AddUrlToNavigationMenu as the name for your project
click the OK button to create a solution that contains the LightSwitch project
Next, we need to add a table to hold some data.
In the Solution Explorer in:
right-click the Data Sources folder
select Add Table (see Figure 1)
Figure 1. Adding new table
change the Name to DemoItem (see Figure 2)
Figure 2. Changing table name
double-click the table to open it in the Table Designer
add two string properties (see Figure 3)
Figure 3. Adding two properties
save & close the table
Now we need to add a screen that has a grid on it.
In the Solution Explorer in:
right-click the Screens folder
select Add Screen
select Editable Grid Screen (see figure 4-1)
set Screen Data to DataItems (see figure 4-2)
set Screen Name to DemoItemList (see figure 4-3)
click OK
Figure 4. Adding new screen
Figure 5. Screen with two-column grid
Now let's test the application so far, add some data, & demonstrate, that by default we can only select one grid row at a time.
Press F5:
in the screen's grid, enter a few rows of data (see Figure 6)
Figure 6. Adding data
click on the screen's Save button
click on any row
notice that you can't select multiple rows, using the Control and Shift keys, as you normally would in other applications
With just a little bit of code, we can enable multiple row selection.
We're going to need a reference to System.Windows.Controls.Data (to be able to access the Silverlight grid control), so let's do that now.
In Solution Explorer:
click the View button (see Figure 7)
select File View
Figure 7. File view
right-click the Client project
select Add Reference
find and click System.Windows.Controls.Data (see Figure 8)
click OK
Figure 8. Adding System.Windows.Controls.Data reference
Switch back to Logical View
click the View button (see Figure 9)
select Logical View
Figure 9. Selecting Logical view
In the Screen Designer:
click the Write Code dropdown (see Figure 10)
select DemoItemList_InitializeDataWorkspace
Figure 10. Writing screen code
Replace the all of the code that was created for you in DemoItemList.vb, with the code in Listing 1
Listing 1. DemoItemList class code
//we need this to be able to use DataGrid in our code using System.Windows.Controls; namespace LightSwitchApplication { public class DemoItemList { //although not strictly necessary for this small example //it's good practice to use constants/variables like these //in case you want to use them in more than just the ControlAvailable method //this has to match the actual name of the grid control (by default it gets called "grid") private const string ITEMS_CONTROL = "grid"; //this is somewhere to store a reference to the grid control private DataGrid _itemsControl = null; #region Event Handlers private void DemoItemList_InitializeDataWorkspace(System.Collections.Generic.List<Microsoft.LightSwitch.IDataService> saveChangesTo) { //here we're adding an event handler to get a reference to the grid control, when it becomes available //and we have no way of knowing when that will be this.FindControl(ITEMS_CONTROL).ControlAvailable += DemoItems_ControlAvailable; } private void DemoItems_ControlAvailable(object send, ControlAvailableEventArgs e) { //we know that the control is a grid, but we use TryCast, just in case _itemsControl = e.Control as DataGrid; //if the cast failed, just leave, there's nothing more we can do here if (_itemsControl == null) { return; } //set the property on the grid that allows multiple selection _itemsControl.SelectionMode = DataGridSelectionMode.Extended; _itemsControl.SelectionChanged += new SelectionChangedEventHandler(ItemsControl_SelectionChanged); } #endregion } }
'we need this to be able to use DataGrid in our code Imports System.Windows.Controls Namespace LightSwitchApplication Public Class DemoItemList 'although not strictly necessary for this small example 'it's good practice to use constants/variables like these 'in case you want to use them in more than just 'the ControlAvailable method 'this has to match the actual name of the grid control '(by default it gets called "grid") Private Const ITEMS_CONTROL As String = "grid" 'this is somewhere to store a reference to the grid control Private WithEvents _itemsControl As DataGrid = Nothing #Region " Event Handlers " Private Sub DemoItemList_InitializeDataWorkspace( _ saveChangesTo As System.Collections.Generic.List(Of Microsoft.LightSwitch.IDataService) _ ) 'here we're adding an event handler to get a reference to the grid control 'when it becomes available 'and we have no way of knowing when that will be AddHandler Me.FindControl(ITEMS_CONTROL).ControlAvailable, AddressOf DemoItems_ControlAvailable End Sub Private Sub DemoItems_ControlAvailable(send As Object, e As ControlAvailableEventArgs) 'we know that the control is a grid, but we use TryCast, just in case _itemsControl = TryCast(e.Control, DataGrid) 'if the cast failed, just leave, there's nothing more we can do here If (_itemsControl Is Nothing) Then Return 'set the property on the grid that allows multiple selection _itemsControl.SelectionMode = DataGridSelectionMode.Extended End Sub #End Region End Class End Namespace
//we need this to be able to use DataGrid in our code using System.Windows.Controls; namespace LightSwitchApplication { public class DemoItemList { //although not strictly necessary for this small example //it's good practice to use constants/variables like these //in case you want to use them in more than just the ControlAvailable method //this has to match the actual name of the grid control (by default it gets called "grid") private const string ITEMS_CONTROL = "grid"; //this is somewhere to store a reference to the grid control private DataGrid _itemsControl = null; #region Event Handlers private void DemoItemList_InitializeDataWorkspace(System.Collections.Generic.List<Microsoft.LightSwitch.IDataService> saveChangesTo) { //here we're adding an event handler to get a reference to the grid control, when it becomes available //and we have no way of knowing when that will be this.FindControl(ITEMS_CONTROL).ControlAvailable += DemoItems_ControlAvailable; } private void DemoItems_ControlAvailable(object send, ControlAvailableEventArgs e) { //we know that the control is a grid, but we use TryCast, just in case _itemsControl = e.Control as DataGrid; //if the cast failed, just leave, there's nothing more we can do here if (_itemsControl == null) { return; } //set the property on the grid that allows multiple selection _itemsControl.SelectionMode = DataGridSelectionMode.Extended; _itemsControl.SelectionChanged += new SelectionChangedEventHandler(ItemsControl_SelectionChanged); } #endregion } }
Let's test the application again, & check that now we can in fact select multiple grid rows.
Press F5:
in the screen's grid, select any number rows of data, by holding the Control key while you click each row (see Figure 11)
Figure 11. Selecting multiple rows
Now that we're able to select multiple rows, it'd be nice to be able to do something with them. We'll add a button to the grid, that processes the selected rows when you click it.
In the Screen Designer (double-click the DemoItemList screen, if it's not already open):
expand the data grid's Command Bar (see Figure 12-1)
expand the Add dropdown (see Figure 12-2)
select New Button (see Figure 12-3)
Figure 12. Adding a grid button
set the Name to ProcessItems (see Figure 13)
Figure 13. New Method name
In the Left Pane:
click on the ProcessItems method (see Figure 14)
Figure 14. ProcessItems method
In the Properties Pane:
click on the Edit CanExecute Code link (see Figure 15-1)
add the code for the CanExecute method (see Listing 2)
Listing 2. CanExecute method code
//insert this event handler wireup in the screen's DemoItems_ControlAvailable method _itemsControl.SelectionChanged += new System.EventHandler(ItemsControl_SelectionChanged); //this is an optional advanced technique, discussed in the book //that Tim Leung & I are currently writing //Pro Visual Studio LightSwitch 2011 Development private void ItemsControl_SelectionChanged() { switch (_itemsControl == null) { case true: _selectedCount = 0; break; case false: _selectedCount = _itemsControl.SelectedItems.Count; break; } } private void ProcessItems_CanExecute(ref bool result) { //only enable rows have actually been selected result = (_selectedCount > 0); }
'this is an optional advanced technique, discussed in the book that 'Tim Leung & I are currently writing 'Pro Visual Studio LightSwitch 2011 Development Private Sub ItemsControl_SelectionChanged() _ Handles _itemsControl.SelectionChanged Select Case (_itemsControl Is Nothing) Case True _selectedCount = 0 Case False _selectedCount = _itemsControl.SelectedItems.Count End Select End Sub Private Sub ProcessItems_CanExecute(ByRef result As Boolean) 'only enable rows have actually been selected result = (_selectedCount > 0) End Sub
//insert this event handler wireup in the screen's DemoItems_ControlAvailable method _itemsControl.SelectionChanged += new System.EventHandler(ItemsControl_SelectionChanged); //this is an optional advanced technique, discussed in the book //that Tim Leung & I are currently writing //Pro Visual Studio LightSwitch 2011 Development private void ItemsControl_SelectionChanged() { switch (_itemsControl == null) { case true: _selectedCount = 0; break; case false: _selectedCount = _itemsControl.SelectedItems.Count; break; } } private void ProcessItems_CanExecute(ref bool result) { //only enable rows have actually been selected result = (_selectedCount > 0); }
click on the Edit Execute Code link (see Figure 15-2)
add the code for the Execute method (see Listing 3)
Listing 3. Execute method code
private void ProcessItems_Execute() { //if the variable hasn't been set, just leave //there's nothing more we can do here if (_itemsControl == null) { return; } StringBuilder names = new StringBuilder(); //loop through the selected rows //we're casting each selected row as a DemoItem //so we get access to all the properties of the entity that the row represents foreach (DemoItem item in _itemsControl.SelectedItems) { //you would normally process each row //but here we're just concatenating the properties as //proof that we are processing the selected rows names.Append(item.PropertyOne); names.Append("; "); } //simply display the result this.ShowMessageBox(string.Format("The values of the selected rows are: {0}", names), caption:"Demo Items", button:MessageBoxOption.Ok); }
Private Sub ProcessItems_Execute() 'if the variable hasn't been set, just leave, there's nothing more we can do here If (_itemsControl Is Nothing) Then Return Dim names As New StringBuilder 'loop through the selected rows 'we're casting each selected row as a DemoItem 'so we get access to all the properties of the entity that the row represents For Each item As DemoItem In _itemsControl.SelectedItems 'you would normally process each row 'but here we're just concatenating the properties as 'proof that we are processing the selected rows names.Append(item.PropertyOne) names.Append("; ") Next 'simply display the result Me.ShowMessageBox(String.Format("The values of the selected rows are: {0}", names) _ , caption:="Demo Items" _ , button:=MessageBoxOption.Ok _ ) End Sub
private void ProcessItems_Execute() { //if the variable hasn't been set, just leave //there's nothing more we can do here if (_itemsControl == null) { return; } StringBuilder names = new StringBuilder(); //loop through the selected rows //we're casting each selected row as a DemoItem //so we get access to all the properties of the entity that the row represents foreach (DemoItem item in _itemsControl.SelectedItems) { //you would normally process each row //but here we're just concatenating the properties as //proof that we are processing the selected rows names.Append(item.PropertyOne); names.Append("; "); } //simply display the result this.ShowMessageBox(string.Format("The values of the selected rows are: {0}", names), caption:"Demo Items", button:MessageBoxOption.Ok); }
Figure 15. Code links
The code in the DemoItemList.vb file should now look like Listing 4
Listing 4. Complete DemoItemList class code
//we need this to be able to use DataGrid in our code using System.Windows.Controls; //this is for the StringBuilder using System.Text; namespace LightSwitchApplication { public class DemoItemList { //although not strictly necessary for this small example //it's good practice to use constants/variables like these //in case you want to use them in more than just the ControlAvailable method //this has to match the actual name of the grid control (by default it gets called "grid") private const string ITEMS_CONTROL = "grid"; //this is somewhere to store a reference to the grid control private DataGrid _itemsControl = null; //this is somewhere to store the selected row count private int _selectedCount = 0; #region Event Handlers private void DemoItemList_InitializeDataWorkspace(System.Collections.Generic.List<Microsoft.LightSwitch.IDataService> saveChangesTo) { //here we're adding an event handler to get a reference to the grid control //when it becomes available //and we have no way of knowing when that will be this.FindControl(ITEMS_CONTROL).ControlAvailable += DemoItems_ControlAvailable; } private void DemoItems_ControlAvailable(object send, ControlAvailableEventArgs e) { //we know that the control is a grid, but we use TryCast, just in case _itemsControl = e.Control as DataGrid; //if the cast failed, just leave, there's nothing more we can do here if (_itemsControl == null) { return; } //set the property on the grid that allows multiple selection _itemsControl.SelectionMode = DataGridSelectionMode.Extended; _itemsControl.SelectionChanged += new SelectionChangedEventHandler(ItemsControl_SelectionChanged); } //this is an optional advanced technique, discussed in the book //that Tim Leung & I are currently writing //Pro Visual Studio LightSwitch 2011 Development private void ItemsControl_SelectionChanged() { switch (_itemsControl == null) { case true: _selectedCount = 0; break; case false: _selectedCount = _itemsControl.SelectedItems.Count; break; } } #endregion #region Process Items private void ProcessItems_CanExecute(ref bool result) { //only enable the button if the variable has been initialised //& the rows have actually been selected result = (_selectedCount > 0); } private void ProcessItems_Execute() { //if the variable hasn't been set, just leave, there's nothing more we can do here if (_itemsControl == null) { return; } StringBuilder names = new StringBuilder(); //loop through the selected rows //we're casting each selected row as a DemoItem //so we get access to all the properties of the entity that the row represents foreach (DemoItem item in _itemsControl.SelectedItems) { //you would normally process each row //but here we're just concatenating the properties as //proof that we are processing the selected rows names.Append(item.PropertyOne); names.Append("; "); } //simply display the result this.ShowMessageBox(string.Format("The values of the selected rows are: {0}", names), caption: "Demo Items", button: MessageBoxOption.Ok); } #endregion } }
'we need this to be able to use DataGrid in our code Imports System.Windows.Controls 'this is for the StringBuilder Imports System.Text Namespace LightSwitchApplication Public Class DemoItemList 'although not strictly necessary for this small example 'it's good practice to use constants/variables like these 'in case you want to use them in more than just the ControlAvailable method 'this has to match the actual name of the grid control (by default it gets called "grid") Private Const ITEMS_CONTROL As String = "grid" 'this is somewhere to store a reference to the grid control Private WithEvents _itemsControl As DataGrid = Nothing 'this is somewhere to store the selected row count Private _selectedCount As Integer = 0 #Region " Event Handlers " Private Sub DemoItemList_InitializeDataWorkspace( _ saveChangesTo As System.Collections.Generic.List(Of Microsoft.LightSwitch.IDataService) _ ) 'here we're adding an event handler to get a reference to the grid control 'when it becomes available 'and we have no way of knowing when that will be AddHandler Me.FindControl(ITEMS_CONTROL).ControlAvailable, AddressOf DemoItems_ControlAvailable End Sub Private Sub DemoItems_ControlAvailable(send As Object, e As ControlAvailableEventArgs) 'we know that the control is a grid, but we use TryCast, just in case _itemsControl = TryCast(e.Control, DataGrid) 'if the cast failed, just leave, there's nothing more we can do here If (_itemsControl Is Nothing) Then Return 'set the property on the grid that allows multiple selection _itemsControl.SelectionMode = DataGridSelectionMode.Extended End Sub 'this is an optional advanced technique, discussed in the book that 'Tim Leung & I are currently writing 'Pro Visual Studio LightSwitch 2011 Development Private Sub ItemsControl_SelectionChanged() _ Handles _itemsControl.SelectionChanged Select Case (_itemsControl Is Nothing) Case True _selectedCount = 0 Case False _selectedCount = _itemsControl.SelectedItems.Count End Select End Sub #End Region #Region " Process Items " Private Sub ProcessItems_CanExecute(ByRef result As Boolean) 'only enable the button if the variable has been initialised, & 'the rows have actually been selected result = (_selectedCount > 0) End Sub Private Sub ProcessItems_Execute() 'if the variable hasn't been set, just leave, there's nothing more we can do here If (_itemsControl Is Nothing) Then Return Dim names As New StringBuilder 'loop through the selected rows 'we're casting each selected row as a DemoItem 'so we get access to all the properties of the entity that the row represents For Each item As DemoItem In _itemsControl.SelectedItems 'you would normally process each row 'but here we're just concatenating the properties as 'proof that we are processing the selected rows names.Append(item.PropertyOne) names.Append("; ") Next 'simply display the result Me.ShowMessageBox(String.Format("The values of the selected rows are: {0}", names) _ , caption:="Demo Items" _ , button:=MessageBoxOption.Ok _ ) End Sub #End Region End Class End Namespace
//we need this to be able to use DataGrid in our code using System.Windows.Controls; //this is for the StringBuilder using System.Text; namespace LightSwitchApplication { public class DemoItemList { //although not strictly necessary for this small example //it's good practice to use constants/variables like these //in case you want to use them in more than just the ControlAvailable method //this has to match the actual name of the grid control (by default it gets called "grid") private const string ITEMS_CONTROL = "grid"; //this is somewhere to store a reference to the grid control private DataGrid _itemsControl = null; //this is somewhere to store the selected row count private int _selectedCount = 0; #region Event Handlers private void DemoItemList_InitializeDataWorkspace(System.Collections.Generic.List<Microsoft.LightSwitch.IDataService> saveChangesTo) { //here we're adding an event handler to get a reference to the grid control //when it becomes available //and we have no way of knowing when that will be this.FindControl(ITEMS_CONTROL).ControlAvailable += DemoItems_ControlAvailable; } private void DemoItems_ControlAvailable(object send, ControlAvailableEventArgs e) { //we know that the control is a grid, but we use TryCast, just in case _itemsControl = e.Control as DataGrid; //if the cast failed, just leave, there's nothing more we can do here if (_itemsControl == null) { return; } //set the property on the grid that allows multiple selection _itemsControl.SelectionMode = DataGridSelectionMode.Extended; _itemsControl.SelectionChanged += new SelectionChangedEventHandler(ItemsControl_SelectionChanged); } //this is an optional advanced technique, discussed in the book //that Tim Leung & I are currently writing //Pro Visual Studio LightSwitch 2011 Development private void ItemsControl_SelectionChanged() { switch (_itemsControl == null) { case true: _selectedCount = 0; break; case false: _selectedCount = _itemsControl.SelectedItems.Count; break; } } #endregion #region Process Items private void ProcessItems_CanExecute(ref bool result) { //only enable the button if the variable has been initialised //& the rows have actually been selected result = (_selectedCount > 0); } private void ProcessItems_Execute() { //if the variable hasn't been set, just leave, there's nothing more we can do here if (_itemsControl == null) { return; } StringBuilder names = new StringBuilder(); //loop through the selected rows //we're casting each selected row as a DemoItem //so we get access to all the properties of the entity that the row represents foreach (DemoItem item in _itemsControl.SelectedItems) { //you would normally process each row //but here we're just concatenating the properties as //proof that we are processing the selected rows names.Append(item.PropertyOne); names.Append("; "); } //simply display the result this.ShowMessageBox(string.Format("The values of the selected rows are: {0}", names), caption: "Demo Items", button: MessageBoxOption.Ok); } #endregion } }
For more information on LightSwitch Development, see the LightSwitch Developer Center, or the LightSwitch Forum.