Binding a Combobox inside a ListView - wpf

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

Related

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

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

how to get column from listview in WPF

If we are using the DataGrid to get the value from the column I can use
var a = datagrid1.Columns[1].GetCellContent(item)
Can anyone please tell me how can I get the same value when using ListView instead of DataGrid. This ListView contains a GridView.
Try this
xaml
<ListView Margin="10" Name="UsersList" SelectionChanged="UsersList_SelectionChanged_1">
<ListView.View>
<GridView>
<GridViewColumn Header="Name" Width="120">
<GridViewColumn.CellTemplate>
<DataTemplate>
<Grid x:Name="colGrid">
<TextBlock Text="{Binding Name}" x:Name="txtBlock"/>
</Grid>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Age" Width="50">
<GridViewColumn.CellTemplate>
<DataTemplate>
<Grid x:Name="colGrid">
<TextBlock Text="{Binding Age}" x:Name="txtBlock"/>
</Grid>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Mail" Width="150">
<GridViewColumn.CellTemplate>
<DataTemplate>
<Grid x:Name="colGrid">
<TextBlock Text="{Binding Mail}" x:Name="txtBlock"/>
</Grid>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
SelectionChanged Event Handler of List View
private void UsersList_SelectionChanged_1(object sender, SelectionChangedEventArgs e)
{
var gridView = UsersList.View as GridView;
ListBoxItem myListBoxItem =UsersList.ItemContainerGenerator.ContainerFromItem(UsersList.SelectedItem) as ListBoxItem;
//ContentControl does not directly host the elements provided by the expanded DataTemplate.
//It uses a ContentPresenter to do the work.
//If we want to use the FindName method we will have to pass that ContentPresenter as the second argument, instead of the ContentControl.
ContentPresenter myContentPresenter = myListBoxItem.GetVisualChild<ContentPresenter>();
foreach (var column in gridView.Columns)
{
//Here using FindName we can get the controls of cell template
var grid = column.CellTemplate.FindName("colGrid", myContentPresenter);
}
}
Visual Helper
public static class VisualHelper
{
public static T GetVisualChild<T>(this Visual parent) where T : Visual
{
T child = default(T);
for (int index = 0; index < VisualTreeHelper.GetChildrenCount(parent); index++)
{
Visual visualChild = (Visual)VisualTreeHelper.GetChild(parent, index);
child = visualChild as T;
if (child == null)
child = GetVisualChild<T>(visualChild);//Find Recursively
else
return child;
}
return child;
}
}
I hope this will help.

Two way binding with a DataGrid vs. a ListView

I'm having trouble understanding a discrepancy in the handling of two-way binding between a DataGrid vs. a ListView. To illustrate, I have a class DataItem with a few properties, and a List of DataItems for binding to the DataGrid/ListView:
public class DataItem
{
public bool Flag { get; set; }
public int IntValue { get; set; }
public string StringValue { get; set; }
public List<DataItem> SubList { get; set; }
public DataItem()[...]
}
I create a main DataItem object with a number of additional DataItem objects adde into the SubList. The main DataItem is set to the DataContext of a containing Grid, and the SubList is bound to both a ListView:
<ListView ItemsSource="{Binding Path=SubList}">
<ListView.View>
<GridView>
<GridViewColumn Header="Flag" Width="Auto">
<GridViewColumn.CellTemplate>
<DataTemplate>
<CheckBox Name="FlagCheckBox" IsChecked="{Binding Path=Flag}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="String Value" Width="100">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBox Name="StringTextBox" Text="{Binding Path=StringValue}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
and to a DataGrid:
<DataGrid ItemsSource="{Binding Path=SubList}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTemplateColumn Header="Flag" Width="SizeToCells">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<CheckBox Name="FlagCheckBox" IsChecked="{Binding Path=Flag}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="String Value" Width="SizeToCells">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox Name="StringTextBox" Text="{Binding Path=StringValue}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
Both the ListView and the DataGrid properly display the SubList items. However, when I modify the data in the UI, and examine the source DataItem.SubList, the ListView works and the DataGrid doesn't: I can see the changes when they are made in the ListView, but when the changes are made in the DataGrid, there are no changes.
The bindings must be correct, otherwise I wouldn't see the values displayed properly. But for some reason, two-way binding works in the ListView to move changes made in the UI back to the source object, but not in the DataGrid.
You need to set UpdateSourceTrigger to PropertyChanged to propagate changes back to your DataObject class in case of DataGrid.
<CheckBox Name="FlagCheckBox" IsChecked="{Binding Path=Flag,
UpdateSourceTrigger=PropertyChanged}"/>
And on TextBox too -
<TextBox Name="StringTextBox" Text="{Binding Path=StringValue,
UpdateSourceTrigger=PropertyChanged}"/>

Filtering a Collection in a ListView in WPF

I'm creating an WPF application that allows a user to enter some details about their Employee, using Entity Framework, CRUD operations and MVVM.
So far, I have two ListViews. One contains a list of employees names (listview1), while the other (listview2) lists their details such as Date of Birth, address etc. The Image below will give you a better picture of what I'm creating;
I am using a CollectionViewSoruce to enable me to filter the results on listview2 when you select a specific name from listbox1. So far I am able to achieve this, but When I add an employee or delete, it throws an exception;
An unhandled exception of type 'System.StackOverflowException' occurred in *.UI.exe
Here are the code snippets that might help
ViewModel:
private EmployeeListViewModel()
: base("")
{
EmployeeList = new ObservableCollection<EmployeeViewModel>(GetEmployees());
this._employeeCol = new ListCollectionView(this.employeeList);
}
private ListCollectionView _employeeCol;
public ICollectionView EmployeeCollection
{
get { return this._employeeCol; }
}
private ObservableCollection<EmployeeViewModel> employeeList;
public ObservableCollection<EmployeeViewModel> EmployeeList
{
get { return employeeList; }
set
{
employeeList = value;
OnPropertyChanged("EmployeeList");
}
}
private EmployeeViewModel selectedEmployee = null;
public EmployeeViewModel SelectedEmployee
{
get
{
return selectedEmployee;
}
set
{
selectedEmployee = value;
OnPropertyChanged("SelectedEmployee");
EmployeeCollection.Filter = new Predicate<object>(o => SelectedEmployee != null && o != null && ((EmployeeViewModel)o).EmployeeID == SelectedEmployee.EmployeeID);
}
}
internal ObservableCollection<EmployeeViewModel> GetEmployees()
{
if (employeeList == null)
employeeList = new ObservableCollection<EmployeeViewModel>();
employeeList.Clear();
foreach (DataObjects.Employee i in new EmployeeRepository().GetAllEmployees())
{
EmployeeViewModel c = new EmployeeViewModel(i);
employeeList.Add(c);
}
return employeeList;
}
ListView2 - EmployeeListView;
<ListView Name="lsvEmpoyeeList" Height="170" Width="700"
ScrollViewer.VerticalScrollBarVisibility="Visible"
ScrollViewer.HorizontalScrollBarVisibility="Visible"
HorizontalAlignment="Center"
IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding EmployeeCollection}"
SelectedItem="{Binding SelectedEmployee}">
<ListView.View>
<GridView>
<GridViewColumn Header="Position" DisplayMemberBinding="{Binding Position}" Width="100" />
<GridViewColumn Header="DateOfBirth" DisplayMemberBinding="{Binding DateOfBirth, StringFormat={}\{0:dd/MM/yyyy\}}" Width="100" />
</GridView>
</ListView.View>
</ListView>
ListView1 - EmployeeSetUpView;
<ListView Height="380" HorizontalAlignment="Left" Name="lsNames" VerticalAlignment="Top" Width="170"
ScrollViewer.VerticalScrollBarVisibility="Visible"
ScrollViewer.HorizontalScrollBarVisibility="Visible"
SelectedItem="{Binding SelectedEmployee}"
ItemsSource="{Binding EmployeeList}" Grid.RowSpan="2" Grid.Row="1">
<ListView.View>
<GridView>
<GridViewColumn Header="FirstName" DisplayMemberBinding="{Binding FirstName}" Width="80" />
<GridViewColumn Header="Surname" DisplayMemberBinding="{Binding Surname}" Width="80" />
</GridView>
</ListView.View>
</ListView>
<ContentControl Grid.Column="1" Grid.Row="1" HorizontalAlignment="Center"
Content="{Binding}" ContentTemplate="{StaticResource EmployeeListView}" />
As you can see, I have put the filter within the setaccessor. I placed it within the constructor but what seems to happen is that none of the details appeared on the ListView2.
Furthermore, if I select a row from listview2 rather then from listview1, it also produces the StackOverFlowException which I am unsure why.
Any help would be appreciated or advice. Also, sorry for the large question!
I don't think the UI knows that EmployeeCollection has changed
Try adding a PropertyChanged event for EmployeeCollection in the SelectedEmployee setter after the filter is applied.
public EmployeeViewModel SelectedEmployee
{
get { return selectedEmployee;}
set
{
selectedEmployee = value;
OnPropertyChanged("SelectedEmployee");
EmployeeCollection.Filter = new Predicate<object>(o => SelectedEmployee != null && o != null && ((EmployeeViewModel)o).EmployeeID == SelectedEmployee.EmployeeID);
// EmployeeCollection view has changed, Notify UI
OnPropertyChanged("EmployeeCollection");
}
}
And as for the StackOverflowException I think this is caused by the fact both ListView have a TwoWay binding on SelectedEmployee, so when one ListView1 changes SelectedItem it causes ListView2 to update its selected item which updates ListView1 and so on, and so on.
Try setting the binding to OneWay for SelectedEmployee on ListView2
SelectedItem="{Binding SelectedEmployee, Mode=OneWay}">

Resources