Notify viewmodel that model has changed (from combobox) - c#

I have one model that implements INotifyPropertyChanged through BaseModel class.
It has other model as element inside of it.
class SIDPoslJavnaUstanova : BaseModel
{
private int? _sid_posl_javna_ustanova_id;
...
private decimal? _udaljenost;
private SIDJavnaUstanova _sid_javna_ustanova;
public SIDJavnaUstanova SidJavnaUstanova
{
get { return _sid_javna_ustanova; }
set {
if (_sid_javna_ustanova != value)
{
_sid_javna_ustanova = value;
if (_sid_javna_ustanova != null)
{
_sid_javna_ustanova_id = _sid_javna_ustanova.SidJavnaUstanovaId;
}
else
{
_sid_javna_ustanova_id = null;
}
RaisePropertyChanged("SidJavnaUstanova");
}
}
}
I have viewmodel that has observable collection of this model objects.
class BaseViewModel<T> : ObservableObject
{
private ObservableCollection<T> _elements = new ObservableCollection<T>();
public ObservableCollection<T> Elements
...
class SIDPoslJavnaUstanovaViewModel : BaseViewModel<SIDPoslJavnaUstanova>
{
}
}
And finally, mainviewmodel that is bound to view:
class MainViewModel : BaseViewModel<Store>
{
private SIDJavnaUstanovaViewModel _sidJavnaUstanovaViewModel;
private SIDJavnaUstanova _sidJavnaUstanova;
public SIDPoslJavnaUstanovaViewModel SidPoslJavnaUstanovaViewModel
{
get { return _sidPoslJavnaUstanovaViewModel; }
set
{
if (_sidPoslJavnaUstanovaViewModel != value)
{
_sidPoslJavnaUstanovaViewModel = value;
RaisePropertyChanged("SidPoslJavnaUstanovaViewModel");
}
}
}
public SIDJavnaUstanovaViewModel SidJavnaUstanovaViewModel
{
get { return _sidJavnaUstanovaViewModel; }
set
{
if (_sidJavnaUstanovaViewModel != value)
{
_sidJavnaUstanovaViewModel = value;
RaisePropertyChanged("SidJavnaUstanovaViewModel");
}
}
}
SidJavnaUstanova is only used to populate combobox, and to bind to object when choosen.
I have combobox in datagrid, that has mulitple lines. Element is SIDJAVNAUSTANOVA , and dropdown is SIDJAVNAUSTANOVAVIEWMODEL.
Dropdown is SIDJAVNAUSTANOVAVIEWMODEL.ELEMENTS
(cannot show you picture not enough reputation)
<src:BaseWindow.Resources>
<viewmod:MainViewModel x:Key="StoreViewM"/>
</src:BaseWindow.Resources>
<DataGrid.Columns>
<DataGridTemplateColumn Width="140" Header="{StaticResource name}">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding Path=SidJavnaUstanovaViewModel.Elements,
Source={StaticResource StoreViewM}}"
SelectedItem="{Binding Path=SidJavnaUstanova,UpdateSourceTrigger=PropertyChanged,
Mode=TwoWay}"
DisplayMemberPath="Naziv"
SelectedValue="{Binding Path=SidJavnaUstanova, Mode=TwoWay}">
</ComboBox>
...
Everything is working fine except when combobox is changed, element SIDJavnaUstanova of object SIDPoslJavnaUstanova is changed, and I can catch this in its model property. But what I must have, is to catch change of this SidJavnaUstanova in viewmodel, so I can implement check-out if there are duplicates of sidjavnaustanova in sidposljavnaustanovaviewmodel.elements. I cannot realize how to do that.
Something like
SIDPoslJavnaUstanova.Elements.??? SIDJavnaUstanova
I cannot do this because elements is observable collection.
Maybe it is a bad model, please suggest something or help with current code.

You need to a) specify source for SelectedItem b) bind SelectedItem to the property of the same type, as elements in your collection (i.e. SIDPoslJavnaUstanova in your case).
This should work, i guess:
<ComboBox ItemsSource="{Binding Path=SidJavnaUstanovaViewModel.Elements,
Source={StaticResource StoreViewM}}"
SelectedItem="{Binding Path=SelectedModel,UpdateSourceTrigger=PropertyChanged,
Mode=TwoWay, Source={StaticResource StoreViewM}}"
DisplayMemberPath="Naziv">
</ComboBox>
.........................................
//MainViewModel
public SIDPoslJavnaUstanova SelectedModel
{
get { return _selectedModel; }
set
{
if (_selectedModel != value)
{
_selectedModel = value;
RaisePropertyChanged("SelectedModel");
}
}
}
And yes, this is some awful design.

Related

How to capture the value from ComboBox in ViewModel

I'm trying to capture the value of the selected item from a ComboBox in my ViewModel. I do get the value but for some reason, when I do a string comparison in an IF statement to determine which item has been selected the comparison doesn't work.
What am I missing?
Is this the right way to capture the value in ViewModel?
XAML
<ComboBox x:Name="comboBox"
SelectedItem="{Binding SelectedItemInFilter, UpdateSourceTrigger=PropertyChanged}>
<ComboBoxItem IsSelected="True">No Selection</ComboBoxItem>
<ComboBoxItem>Car</ComboBoxItem>
<ComboBoxItem>Truck</ComboBoxItem>
</ComboBox>
ViewModel
public class MyViewModel : ViewModelBase{
public string _selectedItemInFilter;
public string SelectedItemInFilter
{
get { return _selectedItemInFilter; }
set {
if (_selectedItemInFilter != value) {
_selectedItemInFilter = value;
ComboBoxChanged();
Console.WriteLine("SelectedItem: {0}", SelectedItemInFilter); // outputs the right item name
RaisePropertyChanged();
}
}
}
private void ComboBoxChanged()
{
if (SelectedItemInFilter.ToString() == "Car") {
Console.WriteLine("Do something with car...");
}
else {
Console.WriteLine("Is not Car...");
}
}
}
Since you explicitly add ComboBoxItems (instead of setting or binding the ItemsSource to a collection of strings), the SelectedItem also is a ComboBoxItem, and not a string. You may however bind to the Content string of the selected item by using SelectedValue and SelectedValuePath:
<ComboBox SelectedValue="{Binding SelectedItemInFilter}"
SelectedValuePath="Content">
<ComboBoxItem>No Selection</ComboBoxItem>
<ComboBoxItem>Car</ComboBoxItem>
<ComboBoxItem>Truck</ComboBoxItem>
</ComboBox>
Even simpler would be not to use ComboBoxItems at all:
xmlns:sys="clr-namespace:System;assembly=mscorlib"
...
<ComboBox SelectedItem="{Binding SelectedItemInFilter}">
<sys:String>No Selection</sys:String>
<sys:String>Car</sys:String>
<sys:String>Truck</sys:String>
</ComboBox>
You are getting it wrong because the items aren't really strings, if you need to do this way you could do the following:
public object _selectedItemInFilter;
public object SelectedItemInFilter
{
get
{
return _selectedItemInFilter;
}
set
{
if (_selectedItemInFilter != value)
{
_selectedItemInFilter = value;
ComboBoxChanged();
Console.WriteLine("SelectedItem: {0}", SelectedItemInFilter); // outputs the right item name
NotifyPropertyChanged("SelectedItemInFilter");
}
}
}
private void ComboBoxChanged()
{
if (((ComboBoxItem)SelectedItemInFilter).Content.ToString() == "Car")
{
Console.WriteLine("Do something with car...");
}
else
{
Console.WriteLine("Is not Car...");
}
}
It's not the same to bind an ObservableCollection than declaring the items into the ComboBox control.

View to ViewModel to Settings

Is it possible to refactor the currentDevices into a collection?
Basically, I have three comboboxes in which the selectedvalue is bound to currentDevices. then the currentDevices are taken from a settings file.
View
<ComboBox ItemsSource="{Binding availableDevices}"
SelectedValue="{Binding currentDevice1}">
</ComboBox>
<ComboBox ItemsSource="{Binding availableDevices}"
SelectedValue="{Binding currentDevice2}">
</ComboBox>
<ComboBox ItemsSource="{Binding availableDevices}"
SelectedValue="{Binding currentDevice3}">
</ComboBox>
ViewModel
public string currentDevice1 {
get
{
return SampleSettings.Default.Device1;
}
set
{
SampleSettings.Default.Device1 = value;
}
}
public string currentDevice2
{
get
{
return SampleSettings.Default.Device2;
}
set
{
SampleSettings.Default.Device2 = value;
}
}
public string currentDevice3
{
get
{
return SampleSettings.Default.Device3;
}
set
{
SampleSettings.Default.Device3 = value;
}
}
I'd write a DeviceOptionViewModel, and give the main viewmodel an ObservableCollection.
DeviceOptionViewModel.cs
public class DeviceOptionViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _currentDevice;
public String CurrentDevice {
get { return _currentDevice; }
set {
_currentDevice = value;
PropertyChanged?.Invoke(this,
new PropertyChangedEventArgs(nameof(CurrentDevice));
}
}
// Parent event assigns this to his own availableDevices
// when he creates this.
public IEnumerable AvailableDevices { get; set; }
}
Main VM:
public ObservableCollection<DeviceOptionViewModel>
CurrentDevices { get; private set; }
= new ObservableCollection<DeviceOptionViewModel>();
XAML:
<ItemsControl
ItemsSource="{Binding CurrentDevices}"
>
<ItemsControl.ItemTemplate>
<DataTemplate>
<!-- DataContext here is DeviceOptionViewModel. We gave it its
own reference to AvailableDevices to simplify binding. -->
<ComboBox
ItemsSource="{Binding AvailableDevices}"
SelectedValue="{Binding CurrentDevice}"
/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Back to main viewmodel:
protected void PopulateCurrentDevices(IEnumerable<String> stringsFromWherever)
{
CurrentDevices.Clear();
foreach (var device in stringsFromWherever)
{
var dovm = new DeviceOptionViewModel() {
CurrentDevice = device,
AvailableDevices = this.availableDevices
};
dovm.PropertyChanged += DeviceOptionViewModel_PropertyChangedHandler;
CurrentDevices.Add(dovm);
}
}
protected void DeviceOptionViewModel_PropertyChangedHandler(object sender,
PropertyChangedEventArgs e)
{
var dopt = sender as DeviceOptionViewModel;
if (e.PropertyName == nameof(DeviceOptionViewModel.CurrentDevice))
{
// Do stuff
}
}
So you populate and repopulate CurrentDevices in your viewmodel as needed, and the UI will magically appear if all the notifications are done correctly.
If you create a new ObservableCollection and assign that to the CurrentDevices property, you'll need to raise PropertyChanged(nameof(CurrentDevices)) on the main viewmodel. I made the setter private to avoid having to implement that detail. If it's not a huge collection, may as well just Clear() and Add() on the same old instance.

Binding Comboboxes which are in listbox to the viewmodels property(s)

Please look at the image below:
Only three items in the listbox are displayed in the above image but it can be any number of items depending on the user's choice.
Now, as you can see in the image above each item has two comboboxes. Now I want to have selectedItem or SelectedValue in my viewModel from which I should be able to get the user's selection. Now I don't know how to bind these comboboxes for getting the user's selection.
Suppose I have only one item instead of the list then I would declare a property of type int so that I can easily get the selectedValue but for the list I am very much confused. Can anybody point me to the right direction?
To start of, lets say the class you are going to be binding the combo box is
public class UnitSource :INotifyPropertyChanged
{
public IEnumerable Units
{
get { return new[] { "Test Unit", "Alternate Unit" }; }
}
string _selectedComboItem1;
public string SelectedComboItem1
{
get
{
return _selectedComboItem1;
}
set
{
if (_selectedComboItem1 == value)
return;
_selectedComboItem1 = value;
OnPropertyChanged();
}
}
string _selectedComboItem2;
public string SelectedComboItem2
{
get
{
return _selectedComboItem2;
}
set
{
if (_selectedComboItem2 == value)
return;
_selectedComboItem2 = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName]string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
Then in your view model you will have an ObservableCollection of the UnitSource Like below
public ObservableCollection<UnitSource> MuchoUnitSources
{
get; set;
}
To get the selected ListBoxItem have this in your ViewModel
private UnitSource _selectedUnitSource;
public UnitSource SelectedUnitSource
{
get
{
return _selectedUnitSource;
}
set
{
if (_selectedUnitSource == value)
return;
_selectedUnitSource = value;
OnPropertyChanged();
}
}
Lets assume it is initialized like so
MuchoUnitSources = new ObservableCollection<UnitSource>(new []{ new UnitSource(),new UnitSource() });
The in your view your listbox should look like below
<ListBox Name ="TestList1" ItemsSource="{Binding MuchoUnitSources}" SelectedItem="{Binding SelectedUnitSource}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical">
<ComboBox SelectedItem="{Binding SelectedComboItem1}" ItemsSource="{Binding Units}" />
<ComboBox SelectedItem="{Binding SelectedComboItem2}" ItemsSource="{Binding Units}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Now whenever you select an item from any of the combobox they will update the objectbeing bound to.

Combo box binding in mvvm

I am not able to find out Why is combo box binding not working?
I have a view model which looks like (2 properties)
public ProcessMaintenanceDataObject CurrentProcess
{
get
{
return _CurrentProcess;
}
set
{
_CurrentProcess = value;
OnPropertyChanged("CurrentProcess");
}
}
public ObservableCollection<ProcessMaintenanceDataObject > Processes
{
get
{
return _Processes;
}
set
{
_Processes = value;
OnPropertyChanged("Processes");
}
}
public ObservableCollection<FolderInfo> Folders
{
get
{
return _folders;
}
set
{
_folders = value;
OnPropertyChanged("Folders");
}
}
The following is the ProcessMaintenanceDataObject definition
[DataMember]
public string ProcessName
{
get
{
return _ProcessName;
}
set
{
_ProcessName = value;
OnPropertyChanged("ProcessName");
}
}
[DataMember]
public string Id
{
get
{
return _Id;
}
set
{
_Id = value;
OnPropertyChanged("Id");
}
}
[DataMember]
public string FolderId
{
get
{
return _FolderId;
}
set
{
_FolderId = value;
OnPropertyChanged("FolderId");
}
}
[DataMember]
public FolderInfo Folder
{
get
{
return _Folder;
}
set
{
_Folder = value;
if (_Folder != null)
FolderId = _Folder.FolderId;
OnPropertyChanged("Folder");
}
}
The FolderInfo class has FolderName and FolderId Property.
I have a method in viewmodel which fills the Processes.
In my view I have structure something like, I have a treeview which will be bound to Processes and while selecting any of the item from the treeview, i need to allow user to edit that entity.
In the view the combo box binding is as:
<ComboBox ItemsSource="{Binding Path=Folders, Mode=OneWay}"
DisplayMemberPath="FolderName"
SelectedItem="{Binding Source={StaticResource viewModel}, Path=CurrentProcess.Folder, Mode=TwoWay}">
...
This binding doesn't work I mean when I select any object from the tree it fills other information like ProcesName in the textBox but it doesn't make the Folder object as the selected item in combobox, however the combo box will be filled.
Any suggestion.
Do refer this:
If you want to bind a ComboBox to your folders property in two way mode with edit support,
Then you should define data template for your combo box and then bind properties of FolderInfo class to those text boxes
Binding display member path will not solve your problem
I'd suggest you to change DisplayMemberPath with appropriate DataTemplate:
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding FolderName}">
</StackPanel>
<DataTemplate>
This will fix SelectedItem context.
Maybe just maybe, your Folderinfo class is not a notificationObject. If it is the case make sure it implements INotifyPropertyChange.
you have to use SelectedValuePath and SelectedValue instead of SelectedItem like below,
<ComboBox ItemsSource="{Binding Path=Folders, Mode=OneWay}"
DisplayMemberPath="FolderName"
SelectedValuePath="FolderId"
SelectedValue="{Binding Path=FolderId, Mode=TwoWay}">
SelectedItem binds the whole object whereas SelectedValue binds only particular properties of the object.

Validating bound ObservableCollection in ViewModel using MVVM Pattern

I'm new to MVVM, just recently started my first project following the MVVM pattern. I have an issue trying to validate an ObservableCollection using the IDataErrorInfo Interface. My ObservableCollection looks like this:
ObservableCollection<Magazine> magazineRepository;
public ObservableCollection<Magazine> MagazineRepository
{
get { return magazineRepository; }
set
{
if (value != null)
{
bladRepository = value;
OnPropertyChanged("MagazineRepository");
}
}
}
And my XAML like this:
<ListBox x:Name="listMagazineRepository"
Grid.ColumnSpan="2"
ItemsSource="{Binding}"
DataContext="{Binding MagazineRepository}"
DisplayMemberPath="Navn"
SelectedItem="{Binding Path=SelectedItem}"/>
<TextBox x:Name="txtName" Grid.Row="1" Grid.Column="0"
Text="{Binding ElementName=listMagazineRepository, Path=SelectedItem.Navn, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" />
<TextBox x:Name="txtPrice" Grid.Row="2" Grid.Column="0"
Text="{Binding ElementName=listMagazineRepository, Path=SelectedItem.Pris, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" />
It's just a simple listBox containing objects, when you select an item, the selected objects properties is displayed in the textboxes, and is then bound to the listbox object.
My Problem is, that when I set my code up like this, the only way I can figure out how to validate my data is in the Domain Model, which really isn't a good practise, I'd like to validate in the ViewModel before it gets there. Basically I want to validate each property in the MagazineRepository, in the ViewModel, How would you go about doing this?
PS: I'm new to posting on this board (and programming boards in general) if my question is lacking information, please let me know and I will supply the needed details.
Thanks a lot.
If i understand correctly you want to validate the Magazine object. If that's the case, one way to do it is to wrap that class in a viewmodel, let's call it MagazineVM, that implements IDataErrorInfo and keep the magazine object updated. You then bind to the view a list of MagazineVM. As a very simple example:
public class MagazineVM : IDataErrorInfo, INotifyPropertyChanged
{
private Magazine _magazine;
public int FirstMagazineProperty
{
get { return _magazine.FirstMagazineProperty; }
set { _magazine.FirstMagazineProperty = value; RaisePropertyChanged("FirstMagazineProperty"); }
}
//INotifyPropertyChanged implementation
//IDataErrorInfo implementation
}
Firstly, as Dtex says, you should use a MagazineViewModel class rather than a Magazine class. E.G.
public class MagazineViewModel : INotifyPropertyChanged, IDataErrorInfo
{
private string navn;
private string pris;
private string error;
public string Navn
{
get { return navn; }
set
{
if (navn != value)
{
navn = value;
RaisePropertyChanged("Navn");
}
}
}
public string Pris
{
get { return pris; }
set
{
if (pris != value)
{
pris = value;
RaisePropertyChanged("Pris");
}
}
}
public string Error
{
get { return error; }
set
{
if (error != value)
{
error = value;
RaisePropertyChanged("Error");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
public string this[string columnName]
{
get
{
var result = string.Empty;
switch (columnName)
{
case "Pris":
if (string.IsNullOrWhiteSpace(Pris))
{
result = "Pris is required";
}
break;
case "Navn":
if (string.IsNullOrWhiteSpace(Navn))
{
result = "Navn is required";
}
break;
}
return result;
}
}
private void RaisePropertyChanged(string PropertyName)
{
var e = PropertyChanged;
if (e != null)
{
e(this, new PropertyChangedEventArgs(PropertyName));
}
}
}
The important property to note is "public string this[string columnName]". ColumnName will be one of your bound properties and this is where you can do validation.
The next thing to consider is your MainViewModel (Your DataContext). E.G.
public class MainViewModel : INotifyPropertyChanged
{
//Use a readonly observable collection. If you need to reset it use the .Clear() method
private readonly ObservableCollection<MagazineViewModel> magazines = new ObservableCollection<MagazineViewModel>();
private MagazineViewModel selectedItem;
//Keep the item being edited separate to the selected item
private MagazineViewModel itemToEdit;
public ObservableCollection<MagazineViewModel> Magazines { get { return magazines; } }
public MagazineViewModel SelectedItem
{
get { return selectedItem; }
set
{
if (selectedItem != value)
{
selectedItem = value;
RaisePropertyChanged("SelectedItem");
//When the selected item changes. Copy it to the ItemToEdit
//This keeps the the copy you are editing separate meaning that invalid data isn't committed back to your original view model
//You will have to copy the changes back to your original view model at some stage)
ItemToEdit = Copy(SelectedItem);
}
}
}
public MagazineViewModel ItemToEdit
{
get { return itemToEdit; }
set
{
if (itemToEdit != value)
{
itemToEdit = value;
RaisePropertyChanged("ItemToEdit");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
public MainViewModel()
{
//Ctor...
}
//Create a copy of a MagazineViewModel
private MagazineViewModel Copy(MagazineViewModel ToCopy)
{
var vm = new MagazineViewModel();
vm.Navn = ToCopy.Navn;
vm.Pris = ToCopy.Pris;
return vm;
}
private void RaisePropertyChanged(string PropertyName)
{
//...
}
}
The only thing missing here is how you copy the changes back to the original view model. You could do it before the selected item changes (if the ItemToEdit is valid) or have a Commit button that is only enabled when the ItemToEdit is valid. If you can allow your original view models to go into an invalid state you don't need to worry about the copying.
Finally the XAML
An implicit style to show the error tooltip
<Style
TargetType="{x:Type TextBox}">
<Setter
Property="ToolTip"
Value="{Binding RelativeSource={RelativeSource Self}, Path=(Validation.Errors)[0].ErrorContent}" />
</Style>
And the controls and bindings
<ListBox
ItemsSource="{Binding Magazines}"
DisplayMemberPath="Navn"
SelectedItem="{Binding Path=SelectedItem, Mode=TwoWay}" />
<TextBox
Margin="5"
x:Name="txtName"
Grid.Row="1"
Grid.Column="0"
Text="{Binding ItemToEdit.Navn, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" />
<TextBox
Margin="5"
x:Name="txtPrice"
Grid.Row="2"
Grid.Column="0"
Text="{Binding ItemToEdit.Pris, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" />
The TextBoxes bind to ItemToEdit. ItemToEdit will be an in-sync copy of the SelectedItem.

Categories

Resources