When I was working for a project, we had requirements like a list should be displayed in a listview and the user should be able to select items through a checkbox. Since Silverlight doesn't have a Listbox control with a checkbox, we tried many ways to achieve this. Finally we found a way to do it. In the following article, I will explain how we did that.
In a UI, the user will have an option to select a list of courses in a listbox and the selected courses will be listed in the gridview like in the following figure.
Course Class
I have a class Course with the following properties.
- CourseName
- CourseId
- NoOfStudents
Class.cs
using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
namespace ListBoxWithCheckBoxSLXAML
{
public class Course
{
public string CourseName { get; set; }
public int CourseId { get; set; }
public int NoOfStudents { get; set; }
}
}
ViewModel
As usual I have one BaseViewModel and a viewmodel for a view which inherits from baseviewmodel.
BaseViewModel.cs
using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.ComponentModel;
namespace ListBoxWithCheckBoxSLXAML
{
public class BaseViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
ViewModel.cs
I have 2 properties in the viewmodel. One is for a list of courses and the other is for a list of selected courses.
using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Collections.ObjectModel;
namespace ListBoxWithCheckBoxSLXAML
{
public class ViewModel : BaseViewModel
{
private ObservableCollection<Course> _courses;
private ObservableCollection<Course> _selectedcourses;
public ObservableCollection<Course> Courses
{
get
{
return _courses;
}
set
{
_courses = value;
NotifyPropertyChanged("Courses");
}
}
public ObservableCollection<Course> SelectedCourses
{
get
{
return _selectedcourses;
}
set
{
_selectedcourses = value;
NotifyPropertyChanged("SelectedCourses");
}
}
public ViewModel()
{
Courses = new ObservableCollection<Course>()
{
new Course(){CourseId=1,CourseName="JAVA",NoOfStudents=10},
new Course(){CourseId=2,CourseName=".Net",NoOfStudents=10},
new Course(){CourseId=3,CourseName="MainFrame",NoOfStudents=10}
};
SelectedCourses = new ObservableCollection<Course>();
SelectedCourses.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(SelectedCourses_CollectionChanged);
}
void SelectedCourses_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
NotifyPropertyChanged("SelectedCourses");
}
}
}
View
MainPage.xaml
In my XAML file under usercontrol.resources, I added the following code.
<Style x:Key="CheckBoxList" TargetType="ListBox" >
<Setter Property="ItemContainerStyle">
<Setter.Value>
<Style TargetType="ListBoxItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Border Background="Transparent" Margin="{TemplateBinding Padding}">
<CheckBox Content="{Binding CourseName}" VerticalContentAlignment="Center"
IsChecked="{Binding Path=IsSelected,RelativeSource={RelativeSource TemplatedParent},Mode=TwoWay}"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Setter.Value>
</Setter>
</Style>
Here within the listbox, for the listbox item I am adding a template as a checkbox and binding a CourseName with checkbox content. Also, in the green highlighted code, there I am binding the IsChecked property of the checkbox to the IsSelected property of the Listbox. So whenever a checkbox (listboxitem) is checked, that item will be added to the SelectedItems property of the Listbox. Simple, isn't it?
You can find the complete XAML code below.
<UserControl x:Class="ListBoxWithCheckBoxSLXAML.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:viewmodel="clr-namespace:ListBoxWithCheckBoxSLXAML"
DataContext="{Binding Path=ViewModel}"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">
<UserControl.Resources>
<viewmodel:ViewModel x:Key="ViewModel"/>
<Style x:Key="CheckBoxList" TargetType="ListBox" >
<Setter Property="ItemContainerStyle">
<Setter.Value>
<Style TargetType="ListBoxItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Border Background="Transparent" Margin="{TemplateBinding Padding}">
<CheckBox Content="{Binding CourseName}" VerticalContentAlignment="Center"
IsChecked="{Binding Path=IsSelected,RelativeSource={RelativeSource TemplatedParent},Mode=TwoWay}"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Setter.Value>
</Setter>
</Style>
</UserControl.Resources>
<Grid x:Name="LayoutRoot" Background="White" DataContext="{Binding Source={StaticResource ViewModel}}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ListBox x:Name="Courses" Grid.Row="0" SelectionMode="Multiple" Style="{StaticResource CheckBoxList}" HorizontalAlignment="Stretch"
ItemsSource="{Binding Courses}" Height="100" Width="100" SelectionChanged="Courses_SelectionChanged"/>
<sdk:DataGrid x:Name="gvSelectedCourses" ItemsSource="{Binding SelectedCourses}" Grid.Row="1" AutoGenerateColumns="False" Width="300">
<sdk:DataGrid.Columns>
<sdk:DataGridTextColumn Header="Course Name" Binding="{Binding CourseName}" Width="100"/>
<sdk:DataGridTextColumn Header="Course Id" Binding="{Binding CourseId}" Width="100"/>
<sdk:DataGridTextColumn Header="No Of Students" Binding="{Binding NoOfStudents}" Width="100"/>
</sdk:DataGrid.Columns>
</sdk:DataGrid>
</Grid>
</UserControl>
This is not over yet. Here is another problem for us. Since we can't bind a SelectedItems property in XAML, we have to bind that in code behind. Wiring up a viewmodel in a view is not a violattion of MVVM, sometimes we can use that. So in the code behind, in the constructor, I added the following code, since I am binding the viewmodel with the grid in XAML and not with the entire view.
this.DataContext = LayoutRoot.DataContext;
In the selection changed event of listbox, I added the following code to add selecteditems to the selectedcourses.
((ViewModel)DataContext).SelectedCourses.Clear();
foreach (Course item in Courses.SelectedItems)
{
((ViewModel)DataContext).SelectedCourses.Add(item);
}
This is my complete code behind code.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Collections.ObjectModel;
namespace ListBoxWithCheckBoxSLXAML
{
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
//since i bound viewmodel to grid, here am binding to entire view.
this.DataContext = LayoutRoot.DataContext;
}
private void Courses_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
((ViewModel)DataContext).SelectedCourses.Clear();
foreach (Course item in Courses.SelectedItems)
{
((ViewModel)DataContext).SelectedCourses.Add(item);
}
}
}
}
You can play around the application now.