SelectAll In Datagrid - wpf

I found a nice solution for SelectAll Checkboxes in a datagrid using XAML only:
<DataGrid x:Name="TestGrid" Tag="false">
<DataGrid.Resources>
<DataTemplate x:Key="HeaderCheckbox">
<CheckBox Name="SelectAll" IsChecked="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=DataGrid}, Path=Tag, Mode=TwoWay}" />
</DataTemplate>
<DataTemplate x:Key="ItemCheckbox">
<CheckBox IsChecked="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=DataGrid}, Path=Tag, Mode=OneWay}" />
</DataTemplate>
</DataGrid.Resources>
<DataGrid.Columns>
<DataGridTemplateColumn HeaderTemplate="{StaticResource HeaderCheckbox}" CellTemplate="{StaticResource ItemCheckbox}" />
<DataGridTextColumn Binding="{Binding FirstName}" />
</DataGrid.Columns>
</DataGrid>
Source: Complete XAML Solution For SelectAll In Datagrid
But it's my question... In above example, the ItemCheckbox is bound to tag property of Datagrid, then how do I to bind the ItemCheckbox to my data field inclusive?

Easiest solution I can see it to use properties in your view model instead of Tag. First in class that holds items create SelectAll property that will update accordingly all items when changed:
public class MyItemCollection : INotifyPropertyChanged
{
private readonly ObservableCollection<MyItem> _items;
public ICollection<MyItem> Items { get { return _items; } }
private bool _selectAll;
public bool SelectAll
{
get { return _selectAll; }
set
{
if (_selectAll != value)
{
_selectAll = value;
OnPropertyChanged("SelectAll");
foreach (var item in _items) item.IsSelected = value;
}
}
}
}
Then add IsSelected property to your item. It will be updated either by SelectAll property or CheckBox in DataGrid
public class MyItem : INotifyPropertyChanged
{
private bool _isSelected;
public bool IsSelected
{
get { return _isSelected; }
set
{
if (_isSelected != value)
{
_isSelected = value;
OnPropertyChanged("IsSelected");
}
}
}
}
and then update your binding to point to new properties:
<DataGrid x:Name="TestGrid" ItemsSource="{Binding Items}">
<DataGrid.Resources>
<DataTemplate x:Key="HeaderCheckbox">
<CheckBox Name="SelectAll" IsChecked="{Binding RelativeSource={RelativeSource AncestorType=DataGrid}, Path=DataContext.SelectAll}" />
</DataTemplate>
<DataTemplate x:Key="ItemCheckbox">
<CheckBox IsChecked="{Binding Path=IsSelected}" />
</DataTemplate>
</DataGrid.Resources>
</DataGrid>

Related

WPF MVVM ListBox MultiSelect

I Have created a list box containing list of items and i need to bind them on selection
changed(Select and deselect).
ABCD.xalm
<ListBox Grid.Column="2" Grid.ColumnSpan="9" Height="30" Margin="0 0 5 0" Foreground="{StaticResource AcresTheme}" SelectedItem="{Binding Path=UpdateSimulationItem,UpdateSourceTrigger=PropertyChanged}"
ItemsSource="{Binding SmulationTypes, NotifyOnSourceUpdated=True}"
Background="{Binding }"
MinHeight="65" SelectionMode="Multiple">
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox Foreground="{StaticResource AcresTheme}"
Content="{Binding Item}"
IsChecked="{Binding Path=IsSelected, Mode=TwoWay}"></CheckBox>
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="IsSelected" Value="{Binding Mode=TwoWay, Path=IsSelected}"/>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
ABCD.cs (View Model)
public List<string> SimulationTypesList { get; set; } = new List<string>();
private ObservableCollection<SimulationType> _simulationTypes = new ObservableCollection<SimulationType>();
public ObservableCollection<Items> SimulationTypes
{
get
{
return _simulationTypes;
}
set
{
_simulationTypes = value;
OnPropertyChanged("SimulationTypes");
}
}
private Items _updateSimulationItem;
public Items UpdateSimulationItem
{
get
{
return _updateSimulationItem;
}
set
{
//Logic for getting the selected item
_updateSimulationItem = value;
OnPropertyChanged("UpdateSimulationItem");
}
}
public ABCD()
{
SimulationTypes.Add(new SimulationType() { Item = "Simulation 1", IsSelected = false });
SimulationTypes.Add(new SimulationType() { Item = "Simulation 2", IsSelected = false });
SimulationTypes.Add(new SimulationType() { Item = "Simulation 3", IsSelected = false });
}
Items.cs
public class Items: ViewModelBase
{
private string item;
public string Item
{
get { return item; }
set
{
item = value;
this.OnPropertyChanged("Item");
}
}
private bool isSelected;
public bool IsSelected
{
get { return isSelected; }
set
{
isSelected = value;
this.OnPropertyChanged("IsSelected");
}
}
}
I did try the solution given in https://stackoverflow.com/a/34632944/12020323 This worked fine
for deleting a single item or selecting a single item.
When we select the second item it does not trigger the property change.
Error somewhere not in this code.
You may be confused about VM instances.
This is often the case for beginners.
There may be something wrong with the implementation of SimulationType. You didn't show it.
Here's a complete example of your code demonstrating that multiselect binding works correctly.
using Simplified;
using System;
using System.Collections.ObjectModel;
namespace Core2022.SO.ChaithanyaS
{
public class Item : ViewModelBase
{
private string _title = string.Empty;
public string Title
{
get => _title;
set => Set(ref _title, value ?? string.Empty);
}
private bool _isSelected;
public bool IsSelected
{
get => _isSelected;
set => Set(ref _isSelected, value);
}
}
public class SimulationType : Item
{
private int _count;
public int Count { get => _count; set => Set(ref _count, value); }
}
public class ItemsViewModel : ViewModelBase
{
private static readonly Random random = new Random();
private Item? _selectedItem;
public ObservableCollection<Item> Items { get; } =
new ObservableCollection<Item>()
{
new Item() {Title = "First" },
new SimulationType() { Title = "Yield Simulation", Count = random.Next(5, 15) },
new Item() {Title = "Second" },
new SimulationType() { Title = "HLR Simulation", Count = random.Next(5, 15) },
new SimulationType() { Title = "UnCorr HLR Simulation", Count = random.Next(5, 15)},
new Item() {Title = "Third" }
};
public Item? SelectedItem { get => _selectedItem; set => Set(ref _selectedItem, value); }
}
}
<Window x:Class="Core2022.SO.ChaithanyaS.ItemsWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
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:local="clr-namespace:Core2022.SO.ChaithanyaS"
mc:Ignorable="d"
Title="ItemsWindow" Height="450" Width="800">
<Window.DataContext>
<local:ItemsViewModel/>
</Window.DataContext>
<UniformGrid Columns="2">
<ListBox Margin="10"
SelectedItem="{Binding SelectedItem}"
ItemsSource="{Binding Items}"
SelectionMode="Multiple">
<FrameworkElement.Resources>
<DataTemplate DataType="{x:Type local:Item}">
<CheckBox Foreground="Red"
Content="{Binding Title}"
IsChecked="{Binding Path=IsSelected, Mode=TwoWay}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:SimulationType}">
<CheckBox Foreground="Green"
IsChecked="{Binding Path=IsSelected, Mode=TwoWay}">
<TextBlock>
<TextBlock.Text>
<MultiBinding StringFormat="{}{0} ({1})">
<Binding Path="Title"/>
<Binding Path="Count"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</CheckBox>
</DataTemplate>
</FrameworkElement.Resources>
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="IsSelected" Value="{Binding Mode=TwoWay, Path=IsSelected}"/>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
<ItemsControl ItemsSource="{Binding Items}" Margin="10"
BorderBrush="Green" BorderThickness="1"
Padding="10">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type local:Item}">
<DataTemplate.Resources>
<DataTemplate DataType="{x:Type local:Item}">
<TextBlock Foreground="Red"
Text="{Binding Title}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:SimulationType}">
<TextBlock Foreground="Green">
<TextBlock.Text>
<MultiBinding StringFormat="{}{0} ({1})">
<Binding Path="Title"/>
<Binding Path="Count"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</DataTemplate>
</DataTemplate.Resources>
<Label x:Name="cc" Content="{Binding}" Margin="1" BorderBrush="Gray" BorderThickness="1"/>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding IsSelected}" Value="True">
<Setter TargetName="cc" Property="Background" Value="LightPink"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</UniformGrid>
</Window>
Perhaps you mean that in multi-selection mode, the SelectedItem property always has only the item that was selected first, until it is deselected?
Unfortunately, the implementation of such a task is not easy.
It's better to make a custom AP property and then use it in XAML.
public static class MuliSelectorHelper
{
/// <summary>Returns the value of the IsSelectedItemLast attached property for <paramref name="multiSelector"/>.</summary>
/// <param name="multiSelector"><see cref="DependencyObject"/> whose property value will be returned.</param>
/// <returns><see cref="bool"/> property value.</returns>
public static bool GetIsSelectedItemLast(DependencyObject multiSelector)
{
return (bool)multiSelector.GetValue(IsSelectedItemLastProperty);
}
/// <summary>Sets the value of the IsSelectedItemLast attached property for <paramref name="multiSelector"/>.</summary>
/// <param name="multiSelector"><see cref="MultiSelector"/> whose property value will be returned.</param>
/// <param name="value"><see cref="bool"/> value for property.</param>
public static void SetIsSelectedItemLast(DependencyObject multiSelector, bool value)
{
multiSelector.SetValue(IsSelectedItemLastProperty, value);
}
/// <summary><see cref="DependencyProperty"/> for methods <see cref="GetIsSelectedItemLast(MultiSelector)"/>
/// and <see cref="SetIsSelectedItemLast(MultiSelector, bool)"/>.</summary>
public static readonly DependencyProperty IsSelectedItemLastProperty =
DependencyProperty.RegisterAttached(
nameof(GetIsSelectedItemLast).Substring(3),
typeof(bool),
typeof(MuliSelectorHelper),
new PropertyMetadata(false, OnIsSelectedItemLastChanged));
private static void OnIsSelectedItemLastChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is not Selector selector || selector.GetValue(ListBox.SelectedItemsProperty) is not IList list)
{
throw new NotImplementedException("Implemented only types that derive from Selector and that use the ListBox.SelectedItems dependency property.");
}
if (Equals(e.NewValue, true))
{
selector.SelectionChanged += OnSelectionChanged;
}
else
{
selector.SelectionChanged -= OnSelectionChanged;
}
}
private static async void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (e.AddedItems.Count == 0)
return;
Selector selector = (Selector)sender;
IList selectedItems = (IList)selector.GetValue(ListBox.SelectedItemsProperty);
if (selectedItems.Count != e.AddedItems.Count && !Equals(selectedItems[0], e.AddedItems[0]))
{
selector.SelectionChanged -= OnSelectionChanged;
int beginIndex = selectedItems.Count - e.AddedItems.Count;
var selectedItemsArray = new object[selectedItems.Count];
selectedItems.CopyTo(selectedItemsArray, 0);
selectedItems.Clear();
await selector.Dispatcher.BeginInvoke(() =>
{
for (int i = selectedItemsArray.Length-1; i >= beginIndex; i--)
{
selectedItems.Add(selectedItemsArray[i]);
}
for (int i = 0; i < beginIndex; i++)
{
selectedItems.Add(selectedItemsArray[i]);
}
});
selector.SelectionChanged += OnSelectionChanged;
}
}
}
<UniformGrid Columns="2">
<ListBox Margin="10"
SelectedItem="{Binding SelectedItem}"
ItemsSource="{Binding Items}"
SelectionMode="Multiple"
local:MuliSelectorHelper.IsSelectedItemLast="true">
<FrameworkElement.Resources>
<DataTemplate DataType="{x:Type local:Item}">
<CheckBox Foreground="Red"
Content="{Binding Title}"
IsChecked="{Binding Path=IsSelected, Mode=TwoWay}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:SimulationType}">
<CheckBox Foreground="Green"
IsChecked="{Binding Path=IsSelected, Mode=TwoWay}">
<TextBlock>
<TextBlock.Text>
<MultiBinding StringFormat="{}{0} ({1})">
<Binding Path="Title"/>
<Binding Path="Count"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</CheckBox>
</DataTemplate>
</FrameworkElement.Resources>
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="IsSelected" Value="{Binding Mode=TwoWay, Path=IsSelected}"/>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
<ContentControl Content="{Binding SelectedItem}">
<ContentControl.ContentTemplate>
<DataTemplate DataType="{x:Type local:Item}">
<DataTemplate.Resources>
<DataTemplate DataType="{x:Type local:Item}">
<TextBlock Foreground="Red"
Text="{Binding Title}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:SimulationType}">
<TextBlock Foreground="Green">
<TextBlock.Text>
<MultiBinding StringFormat="{}{0} ({1})">
<Binding Path="Title"/>
<Binding Path="Count"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</DataTemplate>
</DataTemplate.Resources>
<Label x:Name="cc" Content="{Binding}" Margin="1" BorderBrush="Gray" BorderThickness="1"/>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding IsSelected}" Value="True">
<Setter TargetName="cc" Property="Background" Value="LightPink"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ContentControl.ContentTemplate>
</ContentControl>
</UniformGrid>
Thank you #EldHasp, for the time taken to respond to my question. Very greatfull to the solution provided.
Currently I am intrested in having the complete code in ViewModel, I found the mistake that I had done in the ViewModel.
Old Code:
private ObservableCollection<SimulationType> _simulationTypes = new ObservableCollection<SimulationType>();
public ObservableCollection<Items> SimulationTypes
{
get
{
return _simulationTypes;
}
set
{
_simulationTypes = value;
OnPropertyChanged("SimulationTypes");
}
}
private Items _updateSimulationItem;
public Items UpdateSimulationItem
{
get
{
return _updateSimulationItem;
}
set
{
//Logic for getting the selected item
_updateSimulationItem = value;
OnPropertyChanged("UpdateSimulationItem");
}
}
_updateSimulationItem = value; Binds the first selected item to UpdateSimulationItem and propertychange will trigger only when that perticular item is changed.
For example:
SimulationTypes.Add(new SimulationType() { Item = "Simulation 1", IsSelected = false });
SimulationTypes.Add(new SimulationType() { Item = "Simulation 2", IsSelected = false });
SimulationTypes.Add(new SimulationType() { Item = "Simulation 3", IsSelected = false });
In this three items, if I select Simulation 1 then the UpdateSimulationItem will bind to Simulation 1 and the propertychange will narrow down to one item i.e. Simulation 1. Now if we click on Simulation 2 the peopertychange will not trigger as the UpdateSimulationItem is bound to only Simulation 1 item changes.
The change that I Made.
Updated code:
private Items _updateSimulationItem;
public Items UpdateSimulationItem
{
get
{
return _updateSimulationItem;
}
set
{
//Removed unnecessary code and the assignment of value to _updateSimulationItem
OnPropertyChanged("UpdateSimulationItem");
}
}
As we have binded the SimulationTypes to ItemSource in the ABC.XAML as shown below
<ListBox Foreground="{StaticResource AcresTheme}"
SelectedItem="{Binding Path=UpdateSimulationItem,UpdateSourceTrigger=PropertyChanged}"
ItemsSource="{Binding SimulationTypes, NotifyOnSourceUpdated=True}"
MinHeight="65" SelectionMode="Multiple">
when i click on the checkbox that is present in the view, it will automatically updat the SimulationTypes as i have bound the checkbox to IsSelected.
<CheckBox Foreground="{StaticResource AcresTheme}"
Content="{Binding Item}"
IsChecked="{Binding Path=IsSelected, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}">
#EldHasp the code changes that we have to do in your code is to remove the assignment to _selectedItem in the setter property and just keep the OnPropertychange(nameOf(SelectedItem)).
public Item? SelectedItem { get => _selectedItem; set => Set(ref _selectedItem, value); }
The bold text was making the SelectedItem to bind to one item, which was restricting the trigger when other item was selected.
Once Again Thank you #EldHasp for taking out your time on this.

WPF MVVM Databinding Nested Datagrid

I'm a beginner to WPF and MVVM pattern. I still have a issue with databinding in case of backward binding. I would like to bind selectedValues of the Comboboxes into my Oberservable Collection. I learned is possible to bind the value of a combobox to a property, but in this case I would like to bind to a property of a collection and the collection is the parent.
Could somebody explain me how to bind the combobox selectedvalues to my observablecollection?
I have a workaround in my mind to make for each combobox a new property in my viewmodel and collect all values and store by button press this values into my collection, but this seems for me wrong, because is not the behavouir of databinding.
EDIT The comboboxes items are corrected populated from each model, but how i can bind the selectedvalue of the combobox to my collection property?
<ComboBox ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type Window}},
Path=DataContext.Cities}"
DisplayMemberPath="Name"
SelectedValue="{Binding Path=RowEntries }"/>
The SelectedValue="{Binding Path=RowEntries}" is wrong or is this correct?
EDIT 2
I added a Listview binded to my collection to see, if the properties are binded to the selectedvalue of my combobox, but is keeps empty.
<ListView ItemsSource="{Binding RowEntries}" BorderBrush="Black" BorderThickness="1">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=CityName}"></TextBlock>
<TextBlock Text="{Binding Path=CountryName}"></TextBlock>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
I have this solution:
My Models:
// INPC implemented by Propertychanged.Fody
public class RowEntry : BaseModel
{
public string CityName { get; set; }
public string CountryName { get; set; }
}
// INPC implemented by Propertychanged.Fody
public class City : BaseModel
{
public string Name { get; set; }
}
// INPC implemented by Propertychanged.Fody
public class Country : BaseModel
{
public string Name { get; set; }
}
My ViewModel:
public class TestViewModel : ViewModelBase
{
#region properties
// INPC implemented by Propertychanged.Fody
public ObservableCollection<City> Cities { get; set; } = new ObservableCollection<City>();
public ObservableCollection<Country> Countries { get; set; } = new ObservableCollection<Country>();
public ObservableCollection<RowEntry> RowEntries { get; set; } = new ObservableCollection<RowEntry>();
#endregion
#region constructors and destructors
/// <summary>
/// Initializes a new instance of the MainViewModel class.
/// </summary>
public TestViewModel()
{
// Sample Data
var temp = new City { Name = "Rom" };
Cities.Add(temp);
var temp2 = new City { Name = "Sydney" };
Cities.Add(temp2);
var temp3 = new Country { Name = "Italy" };
Countries.Add(temp3);
var temp4 = new Country { Name = "Australia" };
Countries.Add(temp4);
RowEntries.Add(new RowEntry());
}
#endregion
}
My Ui:
<StackPanel>
<DataGrid ItemsSource="{Binding RowEntries}" AlternationCount="{Binding Items.Count, RelativeSource={RelativeSource Self}}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding AlternationIndex, RelativeSource={RelativeSource AncestorType=DataGridRow}}" Header="#"/>
<DataGridTemplateColumn Header="City">
<DataGridTemplateColumn.CellTemplate>
<HierarchicalDataTemplate>
<ComboBox ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type Window}},
Path=DataContext.Cities}"
DisplayMemberPath="Name"
SelectedValue="{Binding Path=RowEntries }"/>
</HierarchicalDataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Countries">
<DataGridTemplateColumn.CellTemplate>
<HierarchicalDataTemplate>
<ComboBox ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type Window}},
Path=DataContext.Countries}"
DisplayMemberPath="Name"/>
</HierarchicalDataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
<Button Content="Add Row" Margin="0,0,1797,0"></Button>
</StackPanel>
You should bind the SelectedValue property of the ComboBoxes to the CityName and CountryName properties of the RowEntry object and set the SelectedValuePath property of the ComboBoxes to "Name". Also set the UpdateSourcePropertyTrigger of the bindings to PropertyChanged:
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding AlternationIndex, RelativeSource={RelativeSource AncestorType=DataGridRow}}" Header="#"/>
<DataGridTemplateColumn Header="City">
<DataGridTemplateColumn.CellTemplate>
<HierarchicalDataTemplate>
<ComboBox ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.Cities}"
DisplayMemberPath="Name"
SelectedValuePath="Name"
SelectedValue="{Binding Path=CityName, UpdateSourceTrigger=PropertyChanged}"/>
</HierarchicalDataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Countries">
<DataGridTemplateColumn.CellTemplate>
<HierarchicalDataTemplate>
<ComboBox ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.Countries}"
DisplayMemberPath="Name"
SelectedValuePath="Name"
SelectedValue="{Binding Path=CountryName, UpdateSourceTrigger=PropertyChanged}"/>
</HierarchicalDataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
Then the setters of the source properties (CityName and CountryName) will be called when you select an item in the ComboBoxes.
If you want to select some values initially, you simply set these properties to some values that are present in the ComboBoxes:
public TestViewModel()
{
...
RowEntries.Add(new RowEntry() { CityName = "Sydney", CountryName = "Australia" });
}

List View Selected Item Binding in wpf mvvm

I am using a ListView in wpf mvvm pattern whose SelectedItem binding is done to the ViewModel. The problem what I am facing is as soon as I check the checkbox, The SelectedItem binding is not working immediately. It work only when I click again somewhere outside the checkbox and its respective content.
My ListView is like this:
<Grid>
<Grid.Resources>
<DataTemplate x:Key="checkboxHeaderTemplate">
<CheckBox IsChecked="{Binding Path=DataContext.AllSelected,RelativeSource={RelativeSource AncestorType=UserControl },Mode=TwoWay}">
</CheckBox>
</DataTemplate>
<DataTemplate x:Key="CheckBoxCell">
<!--<CheckBox Checked="CheckBox_Checked" />-->
<CheckBox IsChecked="{Binding Path=IsSelected, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" >
</CheckBox>
</DataTemplate>
<DataTemplate x:Key="TextCell">
<TextBlock Text="Usecasename">
</TextBlock>
</DataTemplate>
<DataTemplate x:Key="ButtonCell">
<Button Content="{Binding Path=UsecaseName, Mode=TwoWay}" >
</Button>
</DataTemplate>
</Grid.Resources>
<ListView SelectedItem="{Binding SelectedSection}" ItemsSource="{Binding Path=UsecaseListItems}" >
<ListView.View>
<GridView>
<GridView.Columns>
<GridViewColumn HeaderTemplate="{StaticResource checkboxHeaderTemplate}"
CellTemplate="{StaticResource CheckBoxCell}" Width="auto">
</GridViewColumn>
<GridViewColumn HeaderTemplate="{StaticResource TextCell}"
CellTemplate="{StaticResource ButtonCell}" Width="auto">
</GridViewColumn>
</GridView.Columns>
</GridView>
</ListView.View>
</ListView>
</Grid>
The HomeViewModel with which I am binding the Selected itm of List View is like this:
private UseCase _selectedSection;
public UseCase SelectedSection
{
get { return _selectedSection; }
set
{
_selectedSection = value;
if (this.SelectedSection.UsecaseName == ("CCS01") && (this.SelectedSection.IsSelected == true))
{
this.ContentWindow = new CCS01();
}
else if (this.SelectedSection.UsecaseName == ("CCS02") && (this.SelectedSection.IsSelected == true))
{
this.ContentWindow = new CCS02();
}
else if (this.SelectedSection.UsecaseName == ("ECS52") && (this.SelectedSection.IsSelected == true))
{
this.ContentWindow = new ECS52();
}
else
this.ContentWindow = new Default();
OnPropertyChanged("SelectedSection");
}
}
and The UseCase class is this:
public class UseCase: BaseNotifyPropertyChanged
{
public string UsecaseName { get; set; }
private bool _IsSelected;
public bool IsSelected
{
get { return _IsSelected; }
set
{
_IsSelected = value;
OnPropertyChanged("IsSelected");
}
}
}
Please suggest what correction should I do so that, It should hit the binding directly as I check the Checkboxes.
You should change the checkbox binding of the CheckBoxCell to something like this :
<DataTemplate x:Key="CheckBoxCell">
<!--<CheckBox Checked="CheckBox_Checked" />-->
<CheckBox IsChecked="{Binding Path=IsSelected,
RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type ListViewItem}},
Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" >
</CheckBox>
</DataTemplate>
And for this work you need to put SelectionMode to Single
<ListView SelectedItem="{Binding SelectedSection}"
ItemsSource="{Binding Path=UsecaseListItems}" SelectionMode="Single">
And do this in your viewmodel
private UseCase _selectedSection;
public UseCase SelectedSection
{
get { return _selectedSection; }
set
{
DeSelectAll();
_selectedSection = value;
_selectedSection.IsSelected = true;
OnPropertyChanged("SelectedSection");
}
}
private void DeSelectAll()
{
foreach (var item in UsecaseListItems)
{
item.IsSelected = false;
}
}

select all checkbox binding in mvvm

I am working on 'Select All' checkbox in datagrid header in wpf using mvvm pattern. On clicking the checkbox, all the checkboxes gets checked and on uncheking it, the reverse happens.
But I am unable to fetch and bind the selected items to the View Model.
My code is like this
<DataGrid Grid.Row="0" Background="LightGray" CanUserAddRows="False" AutoGenerateColumns="False" HorizontalAlignment="Left" Name="dataGridCustomers" ItemsSource="{Binding Path=UsecaseListItems}" CanUserResizeRows="False">
<DataGrid.Columns>
<DataGridTemplateColumn >
<DataGridTemplateColumn.Header>
<CheckBox x:Name="headerCheckBox" IsChecked="{Binding Path=MainWindowViewModel.AllSelected, Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}" Command="{Binding DoStuffCommand}" CommandParameter="{Binding ElementName=UserCaseListControl}"/>
</DataGridTemplateColumn.Header>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<CheckBox Name="chkSelectAll" HorizontalAlignment="Center" IsChecked="{Binding IsChecked, ElementName=headerCheckBox, Mode=OneWay}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn Binding="{Binding Path=UsecaseName}" Header="UsecaseName" IsReadOnly="True" >
<DataGridColumn.HeaderStyle>
<Style TargetType="DataGridColumnHeader">
<Setter Property="FontWeight" Value="Bold"/>
</Style>
</DataGridColumn.HeaderStyle>
</DataGridTextColumn>
ViewModel is like:
private bool _IsSelected;
public bool IsSelected
{
get { return _IsSelected; }
set
{
_IsSelected = value;
OnPropertyChanged("IsSelected");
}
}
private bool _AllSelected;
public bool AllSelected
{
get { return _AllSelected; }
set
{
_AllSelected = value;
foreach (var reportListItemModel in UsecaseListItems)
{
reportListItemModel.IsSelected = this._AllSelected;
OnPropertyChanged("IsSelected");
}
OnPropertyChanged("AllSelected");
}
}
private ObservableCollection<UseCase> _usecaseListItems = new ObservableCollection<UseCase>();
public ObservableCollection<UseCase> UsecaseListItems
{
get { return _usecaseListItems; }
set {
_usecaseListItems = value;
OnPropertyChanged("UsecaseListItems");
}
}
With your code, there will be no direct binding of selected items. As all your checkboxes were bind to headerCheckBox.IsChecked.
You could bind to your UsecaseListItem.IsSelected instead, and find another way to to execute selectAll:
e.g. Define the UseCase that inherit ObservableObject and use your above code in AllSelected to change each item's IsSelected property.
Or use a Command (for example) in your headerCheckBox.

How to implement editable DataGridComboBoxColumn in WPF DataGrid

I want to enable the user to edit some data in WPF DataGrid ( from the .net Framework 4.0). The "instruments" column should allow the user to select an available intrument from a static list or to write a free text.
My DataGrid is binded to data using MVVM. I've tried many solutions I've found in internet but none of them work correctly.
Here is my code:
<DataGrid Margin="0,6" ItemsSource="{Binding Path=Orders}" AutoGenerateColumns="False" CanUserAddRows="False" CanUserDeleteRows="False" CanUserResizeRows="True">
<DataGrid.Columns>
<DataGridComboBoxColumn Header="Instrument" MinWidth="140"
ItemsSource="{x:Static ViewModel.Instruments}" SelectedItemBinding="{Binding Path=SelectedInstrument}">
<DataGridComboBoxColumn.EditingElementStyle>
<Style TargetType="ComboBox">
<Setter Property="IsEditable" Value="True"/>
</Style>
</DataGridComboBoxColumn.EditingElementStyle>
</DataGridComboBoxColumn>
</DataGrid.Columns>
</DataGrid>
The drop-down-list is shown correctly. The field can be edited with any text, but it sets a null to the SelectedInstrument after the drop-down is closed for the free text. It works only for the selected item. I've tried to change to SelectedValueBinding, but it doesn't help.
How to implement this requirements properly? Can someone post here a working sample?
Additional:
Orders is ObservableCollection
Order has Property like string Title, DateTime Ordered, string SelectedInstrument,
Instruments is a string[]
Solutions:
Following suggest as a workaround from bathineni works:
<DataGrid Margin="0,6" ItemsSource="{Binding Path=Orders}" AutoGenerateColumns="False" CanUserAddRows="False" CanUserDeleteRows="False" CanUserResizeRows="True">
<DataGrid.Columns>
<DataGridTemplateColumn Header="Instrument" MinWidth="140">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=SelectedInstrument, Mode=OneWay}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<ComboBox IsEditable="True" Text="{Binding Path=SelectedInstrument}"
ItemsSource="{x:Static ViewModel.Instruments}"/>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
this is happening because the free text which is enter is of type string and selected item what you have binded to the comboBox is of some complex type....
instead of using DataGridComboBoxColumn use DataGridTemplateColumn and you can bind Text property of the comboBox to some property which will hold the free text value after closing drop down list.
you can get better idea by looking at the following sample.
<DataGrid>
<DataGrid.Columns>
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox IsEditable="True"
Text="{Binding NewItem}"
ItemsSource="{Binding Sourcelist.Files}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
Try to use SelectedValue only but along with it use DisplayMemberPath and TextSearch.TextPath.
<ComboBox IsEditable="True" DisplayMemberPath="MyDisplayProperty" SelectedValuePath="MyValueProperty" SelectedValue="{Binding MyViewModelValueProperty}" TextSearch.TextPath="MyDisplayProperty" />
For editable comboboxes we must synchronize what value the combo selects, what value the items display and what value we must search based on user input.
But If you are using a string collection to bind your combobox then you can try following...
Add a new property in your ViewModel called InstrumentsView. This returns a new ListCollectionView.
public static string ListCollectionView InstrumentsView
{
get
{
return new ListCollectionView(Instruments);
}
}
Change your DataGridComboBoxColumn XAML as below...
<DataGridComboBoxColumn Header="Instrument" MinWidth="140"
ItemsSource="{x:Static ViewModel.InstrumentsView}">
<DataGridComboBoxColumn.EditingElementStyle>
<Style TargetType="ComboBox">
<Setter Property="IsEditable" Value="True"/>
<Setter Property="IsSynchronizedWithCurrentItem" Value=True" />
<Setter Property="SelectedItem" Value="{Binding SelectedInstrument, Mode=OneWayToSource}" /> <!-- Assuming that SelectedInstrument is string -->
</Style>
</DataGridComboBoxColumn.EditingElementStyle>
</DataGridComboBoxColumn>
Tell me if this works....
You can create your own ComboBox column type by subclassing DataGridBoundColumn. Compared to bathineni's solution of subclassing DataGridTemplateColumn the below solution has the benefit of better user experience (no double-tabbing) and you have more options to tune the column to your specific needs.
public class DataGridComboBoxColumn : DataGridBoundColumn {
public Binding ItemsSourceBinding { get; set; }
protected override FrameworkElement GenerateElement(DataGridCell cell, object dataItem) {
var textBox = new TextBlock();
BindingOperations.SetBinding(textBox, TextBlock.TextProperty, Binding);
return textBox;
}
protected override FrameworkElement GenerateEditingElement(DataGridCell cell, object dataItem) {
var comboBox = new ComboBox { IsEditable = true };
BindingOperations.SetBinding(comboBox, ComboBox.TextProperty, Binding);
BindingOperations.SetBinding(comboBox, ComboBox.ItemsSourceProperty, ItemsSourceBinding);
return comboBox;
}
protected override object PrepareCellForEdit(FrameworkElement editingElement, RoutedEventArgs editingEventArgs) {
var comboBox = editingElement as ComboBox;
if (comboBox == null) return null;
comboBox.Focus(); // This solves the double-tabbing problem that Nick mentioned.
return comboBox.Text;
}
}
You can then use the component for example like this.
<DataGrid AutoGenerateColumns="False" ItemsSource="{Binding MyItems}">
<DataGrid.Columns>
<DataGridTextColumn Header="Name" Binding="{Binding Name}"/>
<local:DataGridComboBoxColumn Header="Thingy" Binding="{Binding Thingy}"
ItemsSourceBinding="{Binding
RelativeSource={RelativeSource AncestorType={x:Type local:MainWindow}},
Path=Thingies}"/>
</DataGrid.Columns>
</DataGrid>
I got this solution by following this answer to a similar question.
Maybe it'll still be useful to someone. This solution allows to add new entered values to selection list and has no side effects while editing.
XAML:
<DataGridTemplateColumn Header="MyHeader" Width="Auto">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox IsEditable="True"
Text="{Binding MyTextProperty, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
DisplayMemberPath="MyTextProperty"
SelectedValuePath="MyTextProperty"
ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGrid}}, Path=DataContext.SelectionList}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
ViewModel:
public class MyViewModel
{
public class MyItem : INotifyPropertyChanged {
private string myTextProperty;
public string MyTextProperty {
get { return myTextProperty; }
set { myTextProperty = value;
OnPropertyChanged("MyTextProperty"); }
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged([CallerMemberName]string prop = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(prop));
}
}
public ObservableCollection<MyItem> MyItems { get; set; }
public object SelectionList { get; set; }
}
CodeBehinde:
MyWindow.DataContext = MyViewModelInstance;
MyDataGrid.ItemsSource = MyItems;
// Before DataGrid loading and each time after new MyProperty value adding, you must execute:
MyViewModelInstance.SelectionList = MyViewModelInstance.MyItems.OrderBy(p => p.MyTextProperty).GroupBy(p => p.MyTextProperty).ToList();

Resources