List View Selected Item Binding in wpf mvvm - wpf

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;
}
}

Related

Loop through the second column of WPF ListView and retrieve value of ComboBox in each row

I have a two-column ListView with CheckBoxes in the first column and ComboBoxes in the second column. I need to loop through the ComoboBoxes in the second column and retrieve the selected values (or indexes) from each ComboBox, along with some index or identifier of the ComboBox, and put the values in an array. For example, the layout looks like this:
COLUMN 1 COLUMN 2
======== ========
ChBx 1 Combo1
ChBx 2 Combo2
I need to grab the SelectedValue or SelectedIndex of each ComboBox in the second column and put it into an array in the right order. But, what I've found on the internet is to use: myListView.Items(0).SubItems(1).Text, to loop through the second column. However, my second column contains a ComboBox and I want its' value (not some Text property). Any ideas? My XAML markup is below.
<ListView IsSynchronizedWithCurrentItem="True" Margin="0,0,10,10" Name="patternList" Height="139" VerticalAlignment="Bottom" HorizontalAlignment="Right" Width="112" BorderBrush="{x:Null}" BorderThickness="1" Background="White" >
<ListView.View>
<GridView>
<GridView.Columns>
<GridViewColumn Header="Pattern">
<GridViewColumn.CellTemplate>
<DataTemplate>
<CheckBox Content="{Binding outContent}"
ToolTip="{Binding outToolTip}"
IsThreeState="False"
IsChecked="{Binding Path=outIsChecked, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Freq" Width="55">
<GridViewColumn.CellTemplate>
<DataTemplate>
<ComboBox HorizontalContentAlignment="Center"
Height="14"
Padding="0"
SelectionChanged="FrequencyChanged_OnSelectionChanged"
FontSize="10">
<ComboBoxItem Content="0%"/>
<ComboBoxItem Content="10%"/>
<ComboBoxItem Content="20%"/>
<ComboBoxItem Content="30%"/>
<ComboBoxItem Content="40%"/>
<ComboBoxItem Content="50%"/>
<ComboBoxItem Content="60%"/>
<ComboBoxItem Content="70%"/>
<ComboBoxItem Content="80%"/>
<ComboBoxItem Content="90%"/>
<ComboBoxItem Content="100%"/>
</ComboBox>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView.Columns>
</GridView>
</ListView.View>
</ListView>
Bind SelectedValue to a property in your ViewModel. And in the Setter of that property, also update the collection.
Second approach, in your FrequencyChanged_OnSelectionChanged event handler. You can keep updating your collection there too.
From my perspective it would be better to generate items for the combobox in list item viewmodel, and bind to selected item in that view model. Below is the code that illustrates the approach.
XAML
<Window x:Class="ComboboxesInGrid.MainWindow"
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:ComboboxesInGrid"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:MainWindowViewModel />
</Window.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="30"/>
</Grid.RowDefinitions>
<ListView IsSynchronizedWithCurrentItem="True" ItemsSource="{Binding ListItems}" Margin="0,0,10,10" Name="patternList" BorderBrush="{x:Null}" BorderThickness="1" Background="White" >
<ListView.View>
<GridView>
<GridView.Columns>
<GridViewColumn Header="Pattern" Width="100">
<GridViewColumn.CellTemplate>
<DataTemplate>
<CheckBox Content="{Binding outContent}"
ToolTip="{Binding outToolTip}"
IsThreeState="False"
IsChecked="{Binding Path=outIsChecked, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Freq" Width="155">
<GridViewColumn.CellTemplate>
<DataTemplate>
<ComboBox HorizontalContentAlignment="Center"
Height="14"
Padding="0"
SelectedItem="{Binding outComboSelected}"
ItemsSource="{Binding outComboValues}"
FontSize="10">
</ComboBox>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView.Columns>
</GridView>
</ListView.View>
</ListView>
<TextBlock Grid.Row="1" Text="{Binding SelectedComboItems}" />
</Grid>
</Window>
C#
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
namespace ComboboxesInGrid
{
public class MainWindowViewModel : ViewModelBase
{
private ObservableCollection<ListItemViewModel> _listItems = new ObservableCollection<ListItemViewModel>();
public string SelectedComboItems
{
get { return String.Join(",", _listItems.Select(li => $"{li.outContent}-{li.outIsChecked}-{li.outComboSelected}")); }
}
public ObservableCollection<ListItemViewModel> ListItems
{
get { return _listItems; }
set { _listItems = value; OnProperyChanged(); }
}
public MainWindowViewModel()
{
AddListItem(new ListItemViewModel() { outContent = "ChBx 1 ", outToolTip="Tooooool", outIsChecked = false, outComboSelected="30%" });
AddListItem(new ListItemViewModel() { outContent = "ChBx 2 ", outComboSelected = "70%" });
}
private void AddListItem(ListItemViewModel item)
{
item.PropertyChanged += (s, e) => OnProperyChanged(nameof(SelectedComboItems));
_listItems.Add(item);
}
}
public class ListItemViewModel : ViewModelBase
{
private string _outContent;
public string outContent
{
get { return _outContent; }
set { _outContent = value; OnProperyChanged(); }
}
private string _outToolTip;
public string outToolTip
{
get { return _outToolTip; }
set { _outToolTip = value; OnProperyChanged(); }
}
private bool? _outIsChecked;
public bool? outIsChecked
{
get { return _outIsChecked; }
set { _outIsChecked = value; OnProperyChanged(); }
}
private string _outComboSelected;
public string outComboSelected
{
get { return _outComboSelected; }
set { _outComboSelected = value; OnProperyChanged(); }
}
public IEnumerable<string> outComboValues
{
get
{
return Enumerable.Range(0, 11).Select(i => $"{i*10}%");
}
}
}
public class ViewModelBase : INotifyPropertyChanged
{
protected void OnProperyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
}

Binding a Combobox inside a ListView

I have a combobox in a ListView that I can bind to an itemsource. When selecting a value in the combobox, my selected item is not getting set. Can someone show me why my selected item (NumberOfIterations) is not getting set? The combobox works when I move it outside the ListView, making me thinkg that I do not have the binding completely correct, but I cannot figure out what is wrong. I use the SimpleMVVMToolkit framework.
<ListView Grid.Row="1" Grid.Column="2"
ItemsSource="{Binding SequenceListItems}"
SelectedItem="{Binding SequenceListItemSelected}"
ItemContainerStyle="{StaticResource alternatingListViewItemStyle}"
AlternationCount="2">
<ListView.View>
<GridView>
<GridViewColumn Header="# of Iterations" Width="125">
<GridViewColumn.CellTemplate>
<DataTemplate>
<StackPanel>
<ComboBox ItemsSource="{Binding
SequenceEditorViewModel.Iterations, Source={StaticResource ViewModelLocator}}"
SelectedItem="{Binding Path=NumberOfIterations, Mode=TwoWay}" SelectedIndex="0"
FontWeight="Bold" VerticalAlignment="Center" Width="60" />
</StackPanel>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
ViewModel Items...I am not using a Model at this time
// Constructor
public SequenceEditorViewModel()
{
// Populate the Iterations for displaying in the combobox in the gridview.
for (int x = 1; x <= 50; x++)
{
this.Iterations.Add(x);
}
}
//public property (Iterations is source for combobox and NumberOfIterations
//is for the selected item in the combobox)
public ObservableCollection<int> Iterations
{
get
{
return this.iterations;
}
set
{
this.iterations = value;
this.NotifyPropertyChanged(m => m.Iterations);
}
}
public int NumberOfIterations
{
get
{
return this.numberOfIterations;
}
set
{
this.numberOfIterations = value;
this.NotifyPropertyChanged(m => m.NumberOfIterations);
}
}

Button inside a WPF List view/data grid

I am trying to get the value/ID of the clicked row. If the row is selected, this works fine. But if I just try to click the button inside, the selected customer is null. How do I do Command Parameters here.
I tried this see the answers to the following questions:
ListView and Buttons inside ListView
WPF - Listview with button
Here is the code:
<Grid>
<ListView ItemsSource="{Binding Path=Customers}"
SelectedItem="{Binding Path=SelectedCustomer}"
Width="Auto">
<ListView.View>
<GridView>
<GridViewColumn Header="First Name">
<GridViewColumn.CellTemplate>
<DataTemplate>
<StackPanel Margin="6,2,6,2">
<TextBlock Text="{Binding Name}"/>
</StackPanel>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Address">
<GridViewColumn.CellTemplate>
<DataTemplate>
<StackPanel Margin="6,2,6,2">
<Button Content="Address" Command="{Binding Path=DataContext.RunCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ItemsControl}}}"/>
</StackPanel>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
</Grid>
public class VM : ViewModelBase
{
public RelayCommand RunCommand { get; private set; }
private ObservableCollection<Customer> _Customers;
public ObservableCollection<Customer> Customers
{
get { return _Customers; }
set
{
if (value != _Customers)
{
_Customers = value;
RaisePropertyChanged("Customers");
}
}
}
private Customer _SelectedCustomer;
public Customer SelectedCustomer
{
get { return _SelectedCustomer; }
set
{
if (value != _SelectedCustomer)
{
_SelectedCustomer = value;
RaisePropertyChanged("SelectedCustomer");
}
}
}
public VM()
{
Customers = Customer.GetCustomers();
RunCommand = new RelayCommand(OnRun);
}
private void OnRun()
{
Customer s = SelectedCustomer;
}
}
in the OnRun Method, selected Customer is coming in as null. I want the data row(customer) here. How do I do this?
Three possible solutions that you can choose.
Pass current row object as a CommandParameter. In this case, you will modify OnRun method a little. (recommended)
<Button Content="Address" Command="{Binding Path=DataContext.RunCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ItemsControl}}}" CommandParameter={Binding} />
I think CommandParameter={Binding} works fine because the DataContext of each row is each Customer object.
and OnRun method needs to be modified in order to get a parameter as an argument.
private void OnRun(object o){
if(!(o is Customer)) return;
// Do something
}
Or, Write a little code-behind with SelectionChanged event handling. (not recommended)
Or, Use EventToCommand in MVVM-light toolkit. (not recommended)
CommandParameter does NOT entirely support data binding.
http://connect.microsoft.com/VisualStudio/feedback/details/472932/wpf-commandparameter-binding-should-be-evaluated-before-command-binding

Binding not triggering Action<>

I have some code like this
this is a custom datagrid which displays hierarchical data which should close and open.
<UserControl.DataContext>
<loc:Def1 x:Name="initdef1"/>
</UserControl.DataContext>
<Grid x:Name="LayoutRoot" HorizontalAlignment="Center" VerticalAlignment="Center" >
<data:DataGrid x:Name="_dataGrid" AutoGenerateColumns="False"
ItemsSource="{Binding Display, Mode=OneWay}"
SelectionMode="Extended" >
<data:DataGrid.Columns>
<data:DataGridTemplateColumn Header="Col1">
<data:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" >
<CheckBox IsChecked="{Binding IsExpanded, Mode=TwoWay}" Margin="{Binding Path=Level, Converter={StaticResource ConvertToThickness}}"/>
<TextBlock Text="{Binding Cells[0]}" Margin="4" />
</StackPanel>
</DataTemplate>
</data:DataGridTemplateColumn.CellTemplate>
</data:DataGridTemplateColumn>
<data:DataGridTextColumn Header="Col2" Binding="{Binding Cells[1]}" />
</data:DataGrid.Columns>
</data:DataGrid>
with this is def1
this class has the actual logic for all the processing and loading and mapping the data to the grid.
public Def1()
{
_columns = new List<ColumnDef>();
_source = new ObservableCollection<Def2>();
Def2.RowExpanding += new Action<Def2>(RowDef_RowExpanding);
Def2.RowCollapsing += new Action<Def2>(RowDef_RowCollapsing);
}
void RowDef_RowExpanding(Def2 row)
{
foreach (RowDef child in row.Children)
child.IsVisible = true;
OnPropertyChanged("Display");
}
void RowDef_RowCollapsing(Def2 row)
{
foreach (Def2 child in row.Children)
{
if (row.IsExpanded.HasValue && row.IsExpanded.Value)
RowDef_RowCollapsing(child);
child.IsVisible = false;
}
OnPropertyChanged("Display");
}
and this in def2
this class has the logic on how should the rows behave.
public bool? IsExpanded
{
get { return _isExpanded; }
set
{
if (_isExpanded != value)
{
_isExpanded = value;
if (_isExpanded.Value)
{
if (RowDef.RowExpanding != null)
RowDef.RowExpanding(this);
}
else
{
if (RowDef.RowCollapsing != null)
RowDef.RowCollapsing(this);
}
}
}
}
The thing is when the checkbox is checked or unchecked nothing happens.
Ok I found the answer from a similar post wpf 4.0 datagrid template column two-way binding problem
So I changed the code to
<CheckBox IsChecked="{Binding Mode=TwoWay, Path=IsExpanded, UpdateSourceTrigger=PropertyChanged}" Margin="{Binding Path=Level, Converter={StaticResource ConvertToThickness}}" Checked="CheckBox_Checked" Unchecked="CheckBox_Unchecked" x:Name="checkbox1"/>
Now it works.
But can anybody explain why i needed to set UpdateSourceTrigger=PropertyChanged ?
It's not always required.
Thanks

WPF Sortable Listview with Checkbox in MVVM pattern

I have a WPF ListView with checkbox in MVVM pattern. I need to accomplish the following two tasks.
1) Sort by any column when I click on column header. If column is already in ascending order then it should reorder in descending and vice versa.
2) SelectedTaskItem is not communicating with ViewModel when i check or uncheck a checkbox.
TaskView.xaml
<UserControl x:Class="MyProject.TaskView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="569" Width="954"
HorizontalContentAlignment="Center"
VerticalContentAlignment="Center"
>
<UserControl.Resources>
<DataTemplate x:Key="FirstCellCheckBox">
<CheckBox
Command="{Binding IsSelected, Mode= TwoWay}"
IsChecked="{Binding Path=IsSelected, RelativeSource={RelativeSource
FindAncestor, AncestorType={x:Type ListViewItem}}}"
CommandParameter="{Binding Path=SelectedTaskItem,
ElementName=dgTaskList, UpdateSourceTrigger=PropertyChanged}"
/>
</DataTemplate>
</UserControl.Resources>
<ListView Grid.Row="1"
Name="ListViewTask"
Margin="12,49,26,79"
ItemsSource="{Binding TaskList}"
ScrollViewer.HorizontalScrollBarVisibility="Auto"
>
<ListView.View >
<GridView x:Name="gvTaskList">
<GridViewColumn Header="Select"
CellTemplate="{StaticResource FirstCellCheckBox}"
Width="30"/>
<GridViewColumn Header="Internal File"
DisplayMemberBinding="{Binding TaskID}"
Width="100"/>
<GridViewColumn Header="TaskDescription"
DisplayMemberBinding="{Binding TaskDescription}"
Width="100" />
<GridViewColumn Header="Task Status"
DisplayMemberBinding="{Binding TaskStatus}"
Width="100" />
</GridView>
</ListView.View>
</ListView>
TaskViewModel.cs
namespace MyProject
{
public class TaskViewModel: ViewModelBase
{
ObservableCollection<TaskModel> _TaskList;
public TaskViewModel()
{
TaskDAO dal = new TaskDAO();
_TaskList= dal.GetUpFileList();
}
public ObservableCollection<TaskModel> TaskList
{
get { return _TaskList; }
set
{
if (_TaskList!= value)
{
this._TaskList= value;
this.OnPropertyChanged("TaskList");
}
}
}
private TaskModel _selectedTaskItem;
public TaskModel SelectedTaskItem
{
get { return _selectedTaskItem; }
set
{
if (value != null)
{
_selectedTaskItem= value;
OnPropertyChanged("SelectedTaskItem");
if (null != _selectedTaskItem)
{
ObservableCollection<TaskModel> oCol =
new ObservableCollection<TaskModel>();
foreach (TaskModel itm in TaskList)
{
if (itm.TaskID == _selectedTaskItem.TaskID)
{
itm.IsSelected = true;
}
oCol.Add(itm);
}
TaskList.Clear();
TaskList = oCol;
OnPropertyChanged("TaskList");
}
}
}
}
}
You are binding the CheckBox's IsChecked value to ListBoxItem.IsChecked, but I don't see anything that binds ListBoxItem.IsChecked to your ViewModel.
Try adding the following to your ListBox.Resources
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected}" />
</Style>
For sorting, I'd recommend using a DataGrid instead of a ListView, since sorting is built into the DataGrid. If you don't want to do that, you'll probably have to make some custom ListViewHeaders which execute a SortCommand in your ViewModel

Resources