Editing copies of an ObservableCollection's Object without editing the ObservableCollection - c#

I have a WPF form that I want to use to manipulate a database table. To get the data out of the database I use Entity Framework Core. I try to follow the MVVM pattern.
So I have a Model called Employee with a number of string properties like Firstname and so on.
The form has a ListView that shows an ObservableCollection of Employee. Further I have a number of TextBoxes to display and manipulate the properties of single Employee objects. The selectedMemberPath of the ListView is bound to a property SelectedEmployee. The setter of SelectedEmployee than copies it's own properties to EditedEmployee. The TextBoxes are bound to EditedEmployee. The idea is, that I don't want to directly edit the ObservableCollection. This should only happen if a save button is clicked.
This is my view model.
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.ChangeTracking;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using XdcCore.Models;
using XdcCore.ViewModels.Commands;
namespace XdcCore.ViewModels
{
class EmployeeEditorVM : List<Employee>, INotifyPropertyChanged
{
public EmployeeSaveCommand EmployeeSaveCommand { get; set; }
public EmployeeEditorVM()
{
EmployeeSearch();
EmployeeSaveCommand = new EmployeeSaveCommand(this);
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private string employeeSearchText;
public string EmployeeSearchText
{
get { return employeeSearchText; }
set
{
employeeSearchText = value;
OnPropertyChanged("EmployeeSearch");
EmployeeSearch();
}
}
private Employee selectedEmployee;
public Employee SelectedEmployee
{
get { return selectedEmployee; }
set
{
selectedEmployee = value;
OnPropertyChanged("SelectedEmployee");
EditedEmployee = SelectedEmployee;
}
}
private Employee editedEmployee;
public Employee EditedEmployee
{
get { return editedEmployee; }
set
{
editedEmployee = value;
OnPropertyChanged("EditedEmployee");
}
}
private ObservableCollection<Employee> employees;
public ObservableCollection<Employee> Employees
{
get { return employees; }
set
{
employees = value;
OnPropertyChanged("Employees");
}
}
public void EmployeeSearch()
{
using (var context = new XdcContext())
{
if (employeeSearchText != null)
{
var _employees = context.Employee
.Where(x => x.Firstname.Contains(employeeSearchText));
Employees = new ObservableCollection<Employee>(_employees);
}
else
{
var _employees = context.Employee.Select(x => x);
Employees = new ObservableCollection<Employee>(_employees);
}
}
}
public void EmployeeSave()
{
using (var context = new XdcContext())
{
var entity = context.Employee.First(x => x.IdEmployee == SelectedEmployee.IdEmployee);
{
entity.Firstname = SelectedEmployee.Firstname;
entity.Lastname = SelectedEmployee.Lastname;
entity.Initials = SelectedEmployee.Initials;
context.SaveChanges();
}
EmployeeSearch();
}
}
}
}
this is my View:
<Window
x:Class="XdcCore.Views.Editors.EmployeeEditor"
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:local="clr-namespace:XdcCore.Views.Editors"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="clr-namespace:XdcCore.ViewModels"
Title="Employee"
Width="800"
Height="450"
mc:Ignorable="d">
<Window.Resources>
<vm:EmployeeEditorVM x:Key="vm" />
</Window.Resources>
<Grid DataContext="{StaticResource vm}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="300" />
<ColumnDefinition Width="115*" />
<ColumnDefinition Width="277*" />
</Grid.ColumnDefinitions>
<!-- left column -->
<Border BorderBrush="DarkGray" BorderThickness="1">
<Grid Grid.Column="0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="aUTO" />
</Grid.RowDefinitions>
<Label
Grid.Row="0"
Margin="3,3,3,3"
Content="Filter:" />
<TextBox
x:Name="TextBoxSearch"
Grid.Row="1"
Height="25"
Margin="3,0,3,3"
HorizontalContentAlignment="Left"
VerticalContentAlignment="Center"
Text="{Binding EmployeeSearchText, Mode=TwoWay}" />
<ListView
x:Name="ListViewOverview"
Grid.Row="2"
Margin="3,0,3,0"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
ItemsSource="{Binding Employees, Mode=OneWay}"
SelectedItem="{Binding SelectedEmployee, Mode=OneWayToSource}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Initials}" />
<TextBlock Text=" : " />
<TextBlock Text="{Binding Firstname}" />
<TextBlock Text=" " />
<TextBlock Text="{Binding Lastname}" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Grid Grid.Row="3">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Button
x:Name="ButtonDelete"
Grid.Column="0"
Height="25"
Margin="3,3,3,3"
Content="Delete"
IsEnabled="False" />
<Button
x:Name="ButtonNew"
Grid.Column="1"
Height="25"
Margin="3,3,3,3"
Content="Add New ..."
IsEnabled="True" />
</Grid>
</Grid>
</Border>
<!-- right Column -->
<Grid Grid.Column="1" Grid.ColumnSpan="2">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<!-- right column, first row -->
<Border
Grid.Row="0"
BorderBrush="DarkGray"
BorderThickness="1">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="25" />
<RowDefinition Height="25" />
<RowDefinition Height="25" />
<RowDefinition Height="25" />
<RowDefinition Height="25" />
<RowDefinition Height="25" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100" />
<ColumnDefinition Width="200" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Label
Grid.Row="0"
Grid.Column="0"
HorizontalAlignment="Right"
Content="First Name" />
<Label
Grid.Row="1"
Grid.Column="0"
HorizontalAlignment="Right"
Content="Last Name" />
<Label
Grid.Row="2"
Grid.Column="0"
HorizontalAlignment="Right"
Content="Initials" />
<Label
Grid.Row="3"
Grid.Column="0"
HorizontalAlignment="Right"
VerticalContentAlignment="Center"
Content="ID" />
<Label
Grid.Row="4"
Grid.Column="0"
HorizontalAlignment="Right"
VerticalContentAlignment="Center"
Content="RCU" />
<Label
Grid.Row="5"
Grid.Column="0"
HorizontalAlignment="Right"
VerticalContentAlignment="Center"
Content="RCT" />
<TextBox
x:Name="TextBoxFirstName"
Grid.Row="0"
Grid.Column="1"
Margin="0,3,3,3"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
HorizontalContentAlignment="Left"
VerticalContentAlignment="Center"
Text="{Binding EditedEmployee.Firstname, Mode=TwoWay}" />
<TextBox
x:Name="TextBoxLastName"
Grid.Row="1"
Grid.Column="1"
Margin="0,3,3,3"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
HorizontalContentAlignment="Left"
VerticalContentAlignment="Center"
Text="{Binding EditedEmployee.Lastname, Mode=TwoWay}" />
<TextBox
x:Name="TextBoxInitials"
Grid.Row="2"
Grid.Column="1"
Margin="0,3,3,3"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
HorizontalContentAlignment="Left"
VerticalContentAlignment="Center"
Text="{Binding EditedEmployee.Initials, Mode=TwoWay}" />
<TextBox
x:Name="TextBoxId"
Grid.Row="3"
Grid.Column="1"
Margin="0,3,3,3"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
HorizontalContentAlignment="Left"
VerticalContentAlignment="Center"
IsReadOnly="True" />
<TextBox
x:Name="TextBoxRCU"
Grid.Row="4"
Grid.Column="1"
Margin="0,3,3,3"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
HorizontalContentAlignment="Left"
VerticalContentAlignment="Center"
IsReadOnly="True" />
<TextBox
x:Name="TextBoxRCT"
Grid.Row="5"
Grid.Column="1"
Margin="0,3,3,3"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
HorizontalContentAlignment="Left"
VerticalContentAlignment="Center"
IsReadOnly="True" />
</Grid>
</Border>
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Button
x:Name="ButnSave"
Grid.Column="1"
Width="76"
Height="25"
Margin="60,3,60,0"
HorizontalAlignment="Center"
VerticalAlignment="Top"
Command="{Binding EmployeeSaveCommand}"
Content="Save"
IsEnabled="True" />
</Grid>
<!-- Add new -->
</Grid>
</Grid>
</Grid>
</Window>
In other words: I want to click on an item in the list view, it's details are shown in the text boxes. I can change the text in the text boxes but the changes are not applied to the observable collection (or even database) until I click "save". The problem is, that exactly that happens: when I edit text in the text boxes the changes are also shown in the list view. And I can#t see why.

So, I got the concept of "copy vs point" of a variable wrong.
I had two solutions for my problem.
To my Employee model I added the method Copy:
public partial class Employee
{
public int IdEmployee { get; set; }
public string Initials { get; set; }
public string Firstname { get; set; }
public string Lastname { get; set; }
public string Rcu { get; set; }
public DateTime Rct { get; set; }
public Employee Copy()
{
return (Employee)this.MemberwiseClone();
}
}
Then I Changed my ViewModel so that EditedEmployee is a copy of SelectedEmployee
private Employee selectedEmployee;
public Employee SelectedEmployee
{
get { return selectedEmployee; }
set
{
selectedEmployee = value;
OnPropertyChanged("SelectedEmployee");
if (SelectedEmployee != null)
{
EditedEmployee = SelectedEmployee.Copy();
}
}
}
private Employee editedEmployee;
public Employee EditedEmployee
{
get { return editedEmployee; }
set
{
editedEmployee = value;
OnPropertyChanged("EditedEmployee");
}
}
The problem with this approach is, that if I mess around with the database and then need to run Scaffold-DbContext (I prefer the code-first approach of EFC) again, I would have to modify all models again. A solution to that might be some a second, modified Model that inherits from the original Model and then adds the Copy method.
My second approach is to bind the ListView via SelectedIndex instead of SelectedItem.
<ListView
...
ItemsSource="{Binding Employees, Mode=OneWay}"
SelectedIndex="{Binding SelectedItemIndex, Mode=TwoWay}">
In the ViewModel this is then represented by an int property. If this int is set (and >= 0) Employee gets copied EmployeeWorkingCopy. Furthermore if EmployeeWorkingCopy is set a single Employee dubbed EditedEmployee is set as the Employee of the working copy with the selected index.
private int selectedItemIndex;
public int SelectedItemIndex
{
get { return selectedItemIndex; }
set
{
selectedItemIndex = value;
if (SelectedItemIndex >= 0)
EmployeesWorkingCopy = new ObservableCollection<Employee>(Employees);
}
}
private Employee editedEmployee;
public Employee EditedEmployee
{
get { return editedEmployee; }
set
{
editedEmployee = value;
OnPropertyChanged("EditedEmployee");
}
}
private ObservableCollection<Employee> employeesWorkingCopy;
public ObservableCollection<Employee> EmployeesWorkingCopy
{
get { return employeesWorkingCopy; }
set
{
employeesWorkingCopy = value;
EditedEmployee = EmployeesWorkingCopy[SelectedItemIndex];
}
}
Thanks a lot for pointing out my misconception.

Related

Retrieve the string value of a WPF ComboBox’s selected item in the code behind

I’m having trouble retrieving the string value of a WPF ComboBox’s selected item in the code behind.
I have set up three different types of array which provide the ItemSources for three ComboBoxes. I’ve bound each ComboBox selected item to a property of a class called DataBase. I verify the binding works by echoing the value of each DataBase property back to a TextBox.
In the code behind I want to retrieve the string value of each ComboBox’s selected item.
I can do this for the systemComboBox where SelectedItem and SelectedValue both return the string value of the selected item.
I can’t get it to work for the oneDComboBox where SelectedItem and SelectedValue return “ComboBoxes.OneD” or for the twoDComboBox which correctly returns the SelectedValue but returns “ComboBoxes.TwoD” as the value for the SelectedItem
Can anyone tell me how to get the string value of the oneDComboBox and twoDComboBox selected items?
In Visual Studio I’ve set the Output Type of the code below Assembly to “Console Application” so I can write to the console.
WPF
<Window x:Class="ComboBoxes.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:local="clr-namespace:ComboBoxes"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:sys="clr-namespace:System;assembly=mscorlib" Title="MainWindow" Width="1200" Height="450"
mc:Ignorable="d">
<Window.Resources>
<x:Array x:Key="OneDArray" Type="{x:Type local:OneD}">
<local:OneD OneDName="OneD-0" />
<local:OneD OneDName="OneD-1" />
<local:OneD OneDName="OneD-2" />
<local:OneD OneDName="OneD-3" />
<local:OneD OneDName="OneD-4" />
</x:Array>
<x:Array x:Key="TwoDArray" Type="{x:Type local:TwoD}">
<local:TwoD TwoDName="TwoD-0" TwoDNumber="0" />
<local:TwoD TwoDName="TwoD-1" TwoDNumber="1" />
<local:TwoD TwoDName="TwoD-2" TwoDNumber="2" />
<local:TwoD TwoDName="TwoD-3" TwoDNumber="3" />
<local:TwoD TwoDName="TwoD-4" TwoDNumber="4" />
</x:Array>
<x:Array x:Key="SystemStringArray" Type="sys:String">
<sys:String>SystemString-0</sys:String>
<sys:String>SystemString-1</sys:String>
<sys:String>SystemString-2</sys:String>
<sys:String>SystemString-3</sys:String>
<sys:String>SystemString-4</sys:String>
</x:Array>
</Window.Resources>
<Grid>
<Grid.Resources>
<local:DataBase x:Key="dataBase" />
</Grid.Resources>
<Grid.DataContext>
<Binding Source="{StaticResource dataBase}" />
</Grid.DataContext>
<Grid.RowDefinitions>
<RowDefinition Height="10" />
<RowDefinition Height="auto" />
<RowDefinition Height="20" />
<RowDefinition Height="Auto" />
<RowDefinition Height="10" />
</Grid.RowDefinitions>
<Grid Grid.Row="1" ShowGridLines="True">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="20" />
<ColumnDefinition Width="120" />
<ColumnDefinition Width="120" />
<ColumnDefinition Width="40" />
<ColumnDefinition Width="120" />
<ColumnDefinition Width="120" />
<ColumnDefinition Width="40" />
<ColumnDefinition Width="120" />
<ColumnDefinition Width="120" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="20" />
</Grid.ColumnDefinitions>
<Label Grid.Row="1" Grid.Column="1" Content="OneDComboBox:" HorizontalAlignment="Right"/>
<ComboBox x:Name="oneDComboBox" Grid.Row="1" Grid.Column="2" Width="120" DisplayMemberPath="OneDName"
ItemsSource="{StaticResource OneDArray}"
SelectedItem="{Binding Path=DataBaseOneDName, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"
SelectionChanged="ComboBox_SelectionChanged"
/>
<Label Grid.Row="1" Grid.Column="4" Content="SystemComboBox:" HorizontalAlignment="Right"/>
<ComboBox x:Name="systemComboBox" Grid.Row="1" Grid.Column="5" Width="120"
ItemsSource="{StaticResource SystemStringArray}"
SelectedItem="{Binding Path=DataBaseSystemString, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"
SelectionChanged="ComboBox_SelectionChanged"
/>
<Label Grid.Row="1" Grid.Column="7" Content="TwoDComboBox:" HorizontalAlignment="Right"/>
<ComboBox x:Name="twoDComboBox" Grid.Row="1" Grid.Column="8" Width="120" DisplayMemberPath="TwoDName"
ItemsSource="{Binding Source={StaticResource TwoDArray}}"
SelectedValue="{Binding Path=DataBaseTwoDNumber, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"
SelectedValuePath="TwoDNumber" SelectionChanged="ComboBox_SelectionChanged"
/>
</Grid>
<Grid Grid.Row="3" ShowGridLines="True">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="20" />
<ColumnDefinition Width="120" />
<ColumnDefinition Width="120" />
<ColumnDefinition Width="40" />
<ColumnDefinition Width="120" />
<ColumnDefinition Width="120" />
<ColumnDefinition Width="40" />
<ColumnDefinition Width="120" />
<ColumnDefinition Width="120" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="20" />
</Grid.ColumnDefinitions>
<Label Grid.Row="2" Grid.Column="1" Content="BoundOneDName:" HorizontalAlignment="Right"/>
<TextBox x:Name="oneDComboBoxEcho" Grid.Row="3" Grid.Column="2" Width="120"
Text="{Binding Path=DataBaseOneDName, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"
TextWrapping="Wrap" />
<Label Grid.Row="2" Grid.Column="4" Content="BoundSystemString:" HorizontalAlignment="Right"/>
<TextBox x:Name="systemComboBoxEcho" Grid.Row="5" Grid.Column="5" Width="120"
Text="{Binding Path=DataBaseSystemString, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"
TextWrapping="Wrap" />
<Label Grid.Row="2" Grid.Column="7" Content="BoundTwoDNumber:" HorizontalAlignment="Right"/>
<TextBox x:Name="twoDComboBoxEcho" Grid.Row="5" Grid.Column="8" Width="120"
Text="{Binding Path=DataBaseTwoDNumber, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"
TextWrapping="Wrap" />
</Grid>
</Grid>
</Window>
C#
using System;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
namespace ComboBoxes
{
/// <summary>
/// Interaction logic for MainWindow.xaml
///
/// The code below reflects the suggestions made by:
/// 1 Ben Broadley (to use e.AddedItems[0]) and Benny (To look in the ItemsSource object) in the responses to https://stackoverflow.com/questions/4351603/get-selected-value-from-combo-box-in-c-sharp-wpf.
/// 2 Adam Nathan in the FAQ box on page 266 of his book "WPF 4.5 Unleashed".
///
///
///
/// </summary>
public partial class MainWindow : Window
{
//public MainWindow mainWindow;
DataBase dataBase;
public MainWindow()
{
InitializeComponent();
dataBase = new DataBase();
//DataContext = this;
}
private void ComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (e.AddedItems.Count > 0) // Test to ensure that an item has been selected.
{
ComboBox sourceComboBox = (ComboBox)sender;
int selectedIndex = -1;
string selectedItem = "???";
string selectedValue = "???";
string displayMemberPath = "???";
if (sourceComboBox.Name == "oneDComboBox")
{
// Because this ComboBox.ItemsSource is bound to an array of types the selected value has to be unbundled from the selected type.
if (oneDComboBox.SelectedValue is OneD) // Verify ComboBox.ItemsSource is bound to what we think its bound to.
{
selectedIndex = sourceComboBox.SelectedIndex;
selectedItem = sourceComboBox.SelectedItem.ToString();
selectedValue = (oneDComboBox.SelectedValue as OneD).OneDName;
displayMemberPath = sourceComboBox.DisplayMemberPath.ToString();
}
}
else
if (sourceComboBox.Name == "systemComboBox")
{
selectedIndex = sourceComboBox.SelectedIndex;
selectedItem = sourceComboBox.SelectedItem.ToString();
selectedValue = sourceComboBox.SelectedValue.ToString();
displayMemberPath = sourceComboBox.DisplayMemberPath.ToString();
}
else
if (sourceComboBox.Name == "twoDComboBox")
{
// Because this ComboBox.ItemsSource is bound to an array of types the selected value has to be unbundled from the selected type.
if (e.AddedItems[0] is TwoD) // Verify ComboBox.ItemsSource is bound to what we think its bound to.
{
selectedIndex = sourceComboBox.SelectedIndex;
selectedItem = sourceComboBox.SelectedItem.ToString();
selectedValue = (e.AddedItems[0] as TwoD).TwoDName;
//selectedValue = (twoDComboBox.SelectedValue as TwoD).TwoDName; // Why doesn't this work? It works for oneDComboBox.
displayMemberPath = sourceComboBox.DisplayMemberPath.ToString();
}
}
Console.WriteLine($"\nComboBox Name = {sourceComboBox.Name}");
Console.WriteLine($"Selected Index = {selectedIndex} Selected Item = {selectedItem} Selected Value = {selectedValue} DisplayMemberPath = {displayMemberPath}");
}
}
}
public partial class DataBase : INotifyPropertyChanged
{
private string _dataBaseOneDName = "OneDArray";
public string DataBaseOneDName
{
get { return _dataBaseOneDName; }
set
{
if (_dataBaseOneDName != value)
{
_dataBaseOneDName = value;
NotifyPropertyChanged("DataBaseOneDName");
}
}
}
private string _dataBaseSystemString = "System_String";
public string DataBaseSystemString // String property used in binding examples.
{
get { return _dataBaseSystemString; }
set
{
if (_dataBaseSystemString != value)
{
_dataBaseSystemString = value;
NotifyPropertyChanged("DataBaseSystemString");
}
}
}
private int _dataBaseTwoDNumber = 99;
public int DataBaseTwoDNumber
{
get { return _dataBaseTwoDNumber; }
set
{
if (_dataBaseTwoDNumber != value)
{
_dataBaseTwoDNumber = value;
NotifyPropertyChanged("DataBaseTwoDNumber");
}
}
} // Int property used in binding examples.
#region INotifyPropertyChanged Members
/// Need to implement this interface in order to get data binding
/// to work properly.
private void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
public partial class OneD // This class supports the "OneDArray" in the XAML.
{
public string OneDName { get; set; }
}
public partial class TwoD // This class supports the "TwoDArray" in the XAML.
{
public int TwoDNumber { get; set; }
public string TwoDName { get; set; }
}
}
Set the SelectedValuePath property to OneDName and bind the SelectedValue value property to DataBaseOneDName in your XAML markup:
<ComboBox x:Name="oneDComboBox" Grid.Row="1" Grid.Column="2" Width="120" DisplayMemberPath="OneDName"
ItemsSource="{StaticResource OneDArray}"
SelectedValuePath="OneDName"
SelectedValue="{Binding Path=DataBaseOneDName, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"
SelectionChanged="ComboBox_SelectionChanged"
/>
Then you cast SelectedItem to a OneD and then access its OneDName property:
private void ComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
ComboBox sourceComboBox = (ComboBox)sender;
int selectedIndex = sourceComboBox.SelectedIndex;
OneD oneD = sourceComboBox.SelectedItem as OneD;
if (oneD != null)
{
string name = oneD.OneDName;
}
string displayMemberPath = sourceComboBox.DisplayMemberPath.ToString();
}
The same thing for TwoD.
The other option would be to override the ToString() method of your classes, e.g.:
public partial class OneD
{
public string OneDName { get; set; }
public override string ToString()
{
return OneDName;
}
}

Collection is not Filtering the List Items in WPF

I have a Observable Collection, I want to Filter the collection. The Collection class like a dictionary. I have tried the following code But it is not filtering the collection. I know it is very basic question. I am new to c#. I am studying. Please any one help me to filter the collection. I have mentioned the code below, please refer
GroupModel
public class GroupModel
{
public string Key { get; set; }
public ObservableCollection<ValueModel> Values { get; set; }
}
ValueModel
public class ValueModel
{
public int Id { get; set; }
public string Name { get; set; }
}
ViewModel
Public Class ViewModel
{
public ObservableCollection<GroupModel> MainValues
{
get { return mainValues; }
set { mainValues = value; }
}
public string SearchIndicator
{
get { return searchIndicator; }
set
{
searchIndicator = value;
OnPropertyChanged(SearchIndicator);
ItemsView.Refresh();
}
}
public ViewModel(IMainValueCollection mainValueCollection)
{
MainValue = new ObservableCollection<GroupModel>();
this.MainValue = mainValueCollection;
ItemsView.Filter = new Predicate<object>(o => Filter(o as ValueModel));
}
public ICollectionView ItemsView
{
get
{
var value = (MainValues.SelectMany(a => a.Values)).ToList();
return CollectionViewSource.GetDefaultView((MainValues.SelectMany(a => a.Values)));
}
}
private bool Filter(ValueModel value)
{
if (SearchIndicator == null)
{
return true;
}
return value.Name.IndexOf(SearchIndicator, StringComparison.OrdinalIgnoreCase) != -1;
}
}
MainViewUI
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid
Grid.Row="0"
Height="35"
Margin="5,5,0,5">
<Border
Margin="10,10,10,0"
Padding="1"
BorderBrush="LightGray"
BorderThickness="1"
CornerRadius="0">
<Grid
Height="30"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
Background="White">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid
Grid.Row="0"
Grid.Column="0"
Margin="2,0,0,0"
HorizontalAlignment="Center"
VerticalAlignment="Center">
<Image
Width="25"
Height="25"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Source="/Images/SearchIcon.png" />
</Grid>
<Grid
Name="TextBlockGrid"
Grid.Row="0"
Grid.Column="1"
Height="30"
HorizontalAlignment="Stretch"
VerticalAlignment="Center">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock
Padding="5,0,0,0"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
Foreground="Gray"
Text="Search">
<TextBlock.Visibility>
<MultiBinding Converter="{StaticResource MultiStringToVisibilityConverter}">
<Binding ElementName="searchBox" Path="Text.IsEmpty" />
<Binding ElementName="searchBox" Path="IsFocused" />
</MultiBinding>
</TextBlock.Visibility>
</TextBlock>
<TextBox
x:Name="searchBox"
Width="{Binding ActualWidth, ElementName=TextBlockGrid}"
Padding="5,0,0,0"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Center"
Background="Transparent"
BorderBrush="Transparent"
BorderThickness="0"
Text="{Binding SearchIndicator, UpdateSourceTrigger=PropertyChanged}" />
</Grid>
</Grid>
</Border>
</Grid>
<ItemsControl
Grid.Row="1"
MinWidth="150"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Background="White"
BorderThickness="0"
ItemsSource="{Binding MainValues, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Expander IsExpanded="True">
<Expander.Header>
<Grid>
<TextBlock x:Name="keys" Text="{Binding Key}" />
</Grid>
</Expander.Header>
<ListBox
Margin="15,0,0,0"
BorderThickness="0"
DisplayMemberPath="Name"
ItemsSource="{Binding Values}" />
</Expander>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
You are not binding to ItemsView at all.
Replace
ItemsSource="{Binding MainValues, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
with
ItemsSource="{Binding ItemsView}">
Also change the ItemsView implementation to
public ICollectionView ItemsView
{
get { return CollectionViewSource.GetDefaultView(MainValues); }
}

Databinding a user control within an itemscontrol

I have a UserControl that has a ViewModel containing an instance of my own custom class, call it Person. This ViewModel is set as the DataContext of the control. I am wanting to use this control in my main window which has, in its ViewModel a List of type Person called People. I am calling the control in my xaml like this:
<ItemsControl ItemsSource="{Binding People}">
<ItemsControl.ItemsTemplate>
<DataTemplate>
<userControls:PersonDetails />
</DataTemplate>
</ItemsControl.ItemsTemplate>
</ItemsControl>
Properties within the control are bound in this manner.
<TextBlock Text="{Binding Person.Name}" />
When I run I am getting the correct number of Controls for the amount of People in the list but no details are populated. I know I am doing something simple wrong but can't find it. Please help.
EDIT:
My UserControl xaml looks like this:
<UserControl x:Class="AddressBook.UserControls.PersonDetails"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:AddressBook.UserControls"
xmlns:vm="clr-namespace:AddressBook.ViewModels"
xmlns:enums="clr-namespace:AddressBook.Models"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="1000">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="40" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="1*" />
</Grid.ColumnDefinitions>
<TextBlock Style="{StaticResource PersonDetailHeader}" Text="Person Name:" />
<TextBlock Style="{StaticResource PersonDetailValue}" Grid.Column="1" Text="{Binding Person.Name,
UpdateSourceTrigger=PropertyChanged}" />
<TextBlock Style="{StaticResource PersonDetailHeader}" VerticalAlignment="Center" Grid.Row="1" Text="Street Address:" />
<TextBlock Style="{StaticResource PersonDetailValue}" Grid.Row="1" Grid.Column="1"
Text="{Binding Person.StreetAddress, UpdateSourceTrigger=PropertyChanged}" />
<TextBlock Style="{StaticResource PersonDetailHeader}" Grid.Row="2" Text="Town:" />
<TextBlock Style="{StaticResource PeronDetailValue}" Grid.Row="2" Grid.Column="1"
Text="{Binding Person.Town, UpdateSourceTrigger=PropertyChanged}" />
<TextBlock Style="{StaticResource PersonDetailHeader}" Grid.Row="3" Text="County:" />
<TextBlock Style="{StaticResource PersonDetailValue}" Grid.Row="3" Grid.Column="1"
Text="{Binding Person.County, UpdateSourceTrigger=PropertyChanged}" />
<TextBlock Style="{StaticResource PersonDetailHeader}" Grid.Row="4" Text="Postcode:" />
<TextBlock Style="{StaticResource PersonDetailValue}" Grid.Row="4" Grid.Column="1"
Text="{Binding Person.Postcode, UpdateSourceTrigger=PropertyChanged}" />
<TextBlock Style="{StaticResource PersonDetailHeader}" Grid.Row="5" Text="Phone:" />
<TextBlock Style="{StaticResource PersonDetailValue}" Grid.Row="5" Grid.Column="1"
Text="{Binding Person.Phone, UpdateSourceTrigger=PropertyChanged}" />
<StackPanel Grid.Row="7" Grid.ColumnSpan="2" HorizontalAlignment="Center" Orientation="Horizontal">
<Button Style="{StaticResource ButtonStyle}" Content="Click" Command="{Binding ButtonClickCommand}" />
</StackPanel>
</Grid>
UserControl ViewModel:
public class PersonDetailsViewModel
{
public Person Person { get; set; }
public Command ButtonClickCommand { get; }
public PersonDetailsViewModel()
{
ButtonClickCommand = new Command(ButtonClick);
}
private void ButtonClick(object obj)
{
throw new NotImplementedException();
}
UserControl.xaml.cs:
public partial class PersonDetails : UserControl
{
public PersonDetails()
{
InitializeComponent();
}
}
Person.cs
public class Person : BaseModel
{
public string Name
{
get { return name; }
set
{
name = value;
NotifyPropertyChanged();
}
etc....
MainViewModel:
public class MainViewModel : BaseViewModel
{
public ObservableCollection<Person> People { get; }
= new ObservableCollection<Person>();
public MainViewModel()
{
PopulatePeople();
}
}
Now I understand why you were creating the PersonDetailsViewModel in the UserControl XAML, and what the problem was with the command.
The simplest way to fix this is to make MainViewModel.People a collection of PersonDetailsViewModel instead of Person. That's what I'd do.
But if you want to leave MainViewModel.People as it is, while still using PersonDetailsViewModel inside your UserControl, that makes sense to me. You could do this:
.xaml
<UserControl
x:Class="AddressBook.UserControls.PersonDetails"
DataContextChanged="PersonDetails_DataContextChanged"
...etc...
>
<!-- Give the Grid a name... -->
<Grid x:Name="OuterGrid">
.xaml.cs
public PersonDetails()
{
InitializeComponent();
}
private void PersonDetails_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
// ...so we can give the Grid a different DataContext
OuterGrid.DataContext = new PersonDetailsViewModel {
Person = (Person)this.DataContext
};
}
Now it will inherit a Person for its DataContext, but internally it will use a PersonDetailsViewModel based on that Person.
Not sure if that's what you are trying to do.
If you only wish to display some carac of every Person present in your People List you could do :
<Grid>
<ItemsControl ItemsSource="{Binding People}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid Margin="0,0,0,5">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="100" />
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Name}" />
<TextBlock Grid.Column="1" Text="{Binding Age}" />
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
That will bind you Text on the Name property of each element of your list, so on the name of each Person of your list.
As your ItemsSource is already bound to People, the Binding of your Items is already on each Person of your List, so you should call your properties directly as {Binding Name} and not {Binding Person.Name}

Trouble binding nested collections

I'm a beginner at WPF, and I'm trying to bind a nested collection.
I've found many topics online about binding, and I tried following this question/answer. I tried changing the Datacontext and ItemSource values, but I just can't seem to get it right.
XAML:
<UserControl x:Class="WpfApplication1.Nav1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="620" d:DesignWidth="950">
<Grid Background="#FF741125">
<TextBlock Height="61" HorizontalAlignment="Left" Margin="10,10,0,0" Name="textBlock1" Text="BEVERAGES" VerticalAlignment="Top" Width="250" FontSize="20" FontWeight="Black" />
<ItemsControl x:Name="Stack" DataContext="{Binding myMenu}" ItemsSource="{Binding Subs}" BorderThickness="0" Margin="0,60,0,0">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<Label Content="{Binding Name}" FontWeight="Bold" />
<ItemsControl ItemsSource="{Binding Nodes}" BorderThickness="0" Margin="0,60,0,0">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid Height="60" Width="900">
<Grid.Background>
<ImageBrush ImageSource="/WpfApplication1;component/Images/MenuGrid.fw.png" />
</Grid.Background>
<Grid.RowDefinitions>
<RowDefinition Height="10" />
<RowDefinition Height="10" />
<RowDefinition Height="10" />
<RowDefinition Height="10" />
<RowDefinition Height="10" />
<RowDefinition Height="10" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="50" />
<ColumnDefinition Width="50" />
<ColumnDefinition Width="50" />
<ColumnDefinition Width="50" />
<ColumnDefinition Width="50" />
<ColumnDefinition Width="50" />
<ColumnDefinition Width="50" />
<ColumnDefinition Width="50" />
<ColumnDefinition Width="50" />
<ColumnDefinition Width="50" />
<ColumnDefinition Width="50" />
<ColumnDefinition Width="50" />
<ColumnDefinition Width="50" />
<ColumnDefinition Width="50" />
<ColumnDefinition Width="50" />
<ColumnDefinition Width="50" />
<ColumnDefinition Width="50" />
<ColumnDefinition Width="50" />
</Grid.ColumnDefinitions>
<Label Content="{Binding Name}" Grid.Column="0" Grid.Row="0" Grid.RowSpan="3" Grid.ColumnSpan="14" FontWeight="Bold" />
<Label Content="{Binding Description}" Grid.Column="1" Grid.Row="2" Grid.RowSpan="3" Grid.ColumnSpan="14" />
<Label Content="{Binding Cost}" ContentStringFormat="{}${0}" Grid.Column="16" Grid.Row="0" Grid.RowSpan="5" Grid.ColumnSpan="2" />
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
Class:
public class Menu
{
public ObservableCollection<Category> Subs;
public Menu()
{
Subs = new ObservableCollection<Category>();
}
}
public class Category
{
public ObservableCollection<MenuItem> Nodes;
public Category()
{
Nodes = new ObservableCollection<MenuItem>();
}
private string name;
public string Name
{
get { return this.name; }
set { this.name = value; }
}
public Category(string name)
{
this.name = name;
Nodes = new ObservableCollection<MenuItem>();
}
}
public class MenuItem
{
public MenuItem()
{
}
public MenuItem(string name, string description, double cost)
{
this.itemName = name;
this.description = description;
this.cost = cost;
}
private string itemName;
public string ItemName
{
get { return this.itemName; }
set { this.itemName = value; }
}
private string description;
public string Description
{
get { return this.description; }
set { this.description = value; }
}
private double cost;
public double Cost
{
get { return this.cost; }
set { this.cost = value; }
}
}
CS:
public Nav1()
{
InitializeComponent();
// Init Model
Menu myMenu = new Menu();
myMenu.Subs.Add(new Category("Soft Drinks"));
myMenu.Subs.Add(new Category("Coffee"));
myMenu.Subs.Add(new Category("Premium"));
myMenu.Subs[0].Nodes.Add(new MenuItem("Pepsi", "Cool & Refreshing", 1.39));
myMenu.Subs[0].Nodes.Add(new MenuItem("Diet Pepsi", "Cool & Refreshing", 1.39));
myMenu.Subs[0].Nodes.Add(new MenuItem("7Up", "Cool & Refreshing", 1.39));
myMenu.Subs[0].Nodes.Add(new MenuItem("Mug Root Beer", "Cool & Refreshing", 1.39));
myMenu.Subs[0].Nodes.Add(new MenuItem("Brisk Iced Tea", "Cool & Refreshing", 1.39));
myMenu.Subs[0].Nodes.Add(new MenuItem("Bottled Water", "Thirsty? Aquafina.", 2.75));
// Set DataContext for StackPanel
Stack.DataContext = myMenu.Subs;
}
It displays the category names (eg. "Soft Drinks" or "Coffee") but I can't display the individual items. Also, are there better ways to do this? As mentioned, I'm a beginner at WPF and there seem to be many interesting functions I'm missing.
Thanks for reading!
Using a DataTemplate does not allow for the using of nested items since it does not provide an ItemsSource property.
<DataTemplate>
<TextBlock Text={Binding MyTextValue}/>
</DataTemplate>
However, if you use a HierarchicalDataTemplate, you can use the ItemsSource property to define the next level of items.
<HierarchicalDataTemplate ItemsSource={Binding MyListOfLevelTwoItems}>
<TextBlock Text={Binding MyLevelOneValue}/>
</HierarchicalDataTemplate>
<DataTemplate DataType={x:Type MyLevelTwoItem}>
<TextBlock Text={Binding MyLevelTwoValue}/>
</DataTemplate>

How do I bind controls to the selected item in a listbox using WPF and the MVVM pattern?

I currently have a working method, but does not work for what I need. Here's how the Settings window looks:
Right now I am binding the Department tab DataContext property to an ObservableCollection of Department objects, and then I just refer to the properties of the Department object in the text boxes to the right. Here is the code for this tab:
<TabItem Header="Department Settings"
DataContext="{Binding Departments}">
<DockPanel Margin="3,3,3,3">
<DockPanel DockPanel.Dock="Left"
Width="200">
<StackPanel DockPanel.Dock="Bottom"
Margin="0,5,0,0"
Orientation="Horizontal">
<TextBox x:Name="tbxAddDepartmentName"
Width="160"
Padding="0,5,0,5" />
<Button x:Name="btnAddDepartment"
Content="Add"
Margin="5,0,5,0"
Padding="5,5,5,5"
Command="{Binding AddDepartmentCommand, UpdateSourceTrigger=PropertyChanged}" />
</StackPanel>
<ListBox ItemsSource="{Binding}" />
</DockPanel>
<StackPanel DockPanel.Dock="Bottom">
<Button Content="Save Changes"
Padding="5,5,5,5"
HorizontalAlignment="Right" />
</StackPanel>
<Grid Margin="5">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<!-- Labels -->
<Label Grid.Row="0" Grid.Column="0">Department Name:</Label>
<Label Grid.Row="1" Grid.Column="0">Report Number:</Label>
<Label Grid.Row="2" Grid.Column="0">Address:</Label>
<!-- Input -->
<TextBox x:Name="tbxDepartmentName"
Text="{Binding Name}"
Grid.Row="0" Grid.Column="1"
VerticalContentAlignment="Center"
Margin="0,5,0,5"
Padding="5" />
<TextBox Grid.Row="1" Grid.Column="1"
Text="{Binding ReportNumber}"
VerticalContentAlignment="Center"
Margin="0,5,0,5"
Padding="5" />
<TextBox Grid.Row="2" Grid.Column="1"
Text="{Binding Address}"
VerticalContentAlignment="Center"
AcceptsReturn="True"
Margin="0,5,0,5"
Padding="5" />
</Grid>
</DockPanel>
</TabItem>
What I want to do is have a pointer to the specific Department object that is selected with the ListBox inside my ViewModel. This way, I should be able to do a one way binding on the textboxes to the right, and save change to the SQL Compact DB after I hit the "Save Changes" Button.
Sample ViewModel:
public class ViewModel : INotifyPropertyChanged, INotifyPropertyChanging
{
public class MyObj
{
public string Test1 { get; set; }
public string Test2 { get; set; }
}
#region selectedItem
private MyObj _selectedItem;
public MyObj selectedItem
{
get
{
return _selectedItem;
}
set
{
if (_selectedItem != value)
{
NotifyPropertyChanging("selectedItem");
_selectedItem = value;
NotifyPropertyChanged("selectedItem");
}
}
}
#endregion
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
// Used to notify the page that a data context property changed
protected void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
#region INotifyPropertyChanging Members
public event PropertyChangingEventHandler PropertyChanging;
// Used to notify the data context that a data context property is about to change
protected void NotifyPropertyChanging(string propertyName)
{
if (PropertyChanging != null)
{
PropertyChanging(this, new PropertyChangingEventArgs(propertyName));
}
}
#endregion
}
Add to your ListBox:
SelectedItem="{Binding selectedItem, UpdateSourceTrigger=PropertyChanged}"
Your dataContext should be whole ViewModel, not a collection.
Then you can set in listBox
ItemsSource="{Binding Departments}"
and in the Grid
DataContext="{Binding Departments}"

Categories

Resources