textbox input to get / set viewmodel - c#

I'm sure this has already been asked, but I'm still new to MVVM and WPF, and not too sure what I should be searching for.
I have a viewmodel which includes items in a Model, as well as some additional temporary data items which will all be passed to a process.start(). I have a stackpanel of textbox, and want to allow the user to type in a "ModelName", and if existing, The ViewModel will get and set "TemplateName" associated with the ModelName.
I'm a bit lost on how to implement this. Do I need to create a completely separate ViewModel, which then goes and extracts data from ModelViewModel? Do I just write some code under ModelName's set, where it can validate, query, and set TemplateName?
Model:
public partial class Model
{
public string ModelName { get; set; }
public virtual Template Template { get; set; }
and ViewModel, which takes the Model, and some temporary data:
public class LauncherViewModel:ViewModelBase
{
public LauncherViewModel()
{
_ESTContext = new ESTContext();
Models = new ObservableCollection<Model>(_ESTContext.Models);
}
private ESTContext _ESTContext;
private string _modelname;
private string _serialno;
private string _sonumber;
private string _templatename;
private string _outputname;
private Model _selectedmodel;
public ObservableCollection<Model> Models { get; set; }
public string ModelName
{
get { return _modelname; }
set
{
if (!string.Equals(_modelname, value))
{
_modelname = value;
};
}
}
public string TemplateName { get { return _templatename; }}
public string SerialNo { get { return _serialno; } }
public string SONumber { get { return _sonumber; } }
public string OutputName { get { return _outputname; } }
public Model SelectedModel
{
get { return _selectedmodel; }
set
{
if (_selectedmodel != value)
{
_selectedmodel = value;
}
}
}
}
My View:
<DockPanel>
<StackPanel Margin="0,78,0,68" Width="233" DataContext="{Binding Models}">
<ComboBox IsEditable="True" Text="{Binding ModelName}" SelectedItem="{Binding SelectedModel}"/>
<TextBox Height="23" TextWrapping="Wrap" Text="{Binding SONumber}"/>
<TextBox Height="23" TextWrapping="Wrap" Text="{Binding SerialNumber}"/>
<Button Content="Button"/>
</StackPanel>
</DockPanel>

For your gui to update you must implement INotifyPropertyChanged and a call to it on all your bound properties.
// basic base class for your models, you a
public class ModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator] // remove if you are not using R#
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
// your model
public class Model : ModelBase
{
private string modelName;
private Template template;
public string ModelName
{
get { return modelName; }
set
{
if (value == modelName) return;
modelName = value;
OnPropertyChanged();
}
}
public virtual Template Template
{
get { return template; }
set
{
if (Equals(value, template)) return;
template = value;
OnPropertyChanged();
}
}
}
View:
<DockPanel>
<StackPanel Margin="0,78,0,68" Width="233">
<ComboBox IsEditable="True" Text="{Binding ModelName, Mode='TwoWay'}" SelectedItem="{Binding SelectedModel}" ItemsSource="{Binding Models}"/>
<TextBox Height="23" TextWrapping="Wrap" Text="{Binding SONumber Mode='TwoWay'}"/>
<TextBox Height="23" TextWrapping="Wrap" Text="{Binding SerialNumber Mode='TwoWay'}"/>
<Button Content="Button"/>
</StackPanel>
Note the Mode='TwoWay' this makes the GUI change the values in your viewmodel, rather than just display them. So you need to set that on everything that's supposed to be editable as above. The default behaviour of WPF is that when a control looses focus it will update the bound property, if you set UpdateSourceTrigger='PropertyChanged' the property will be updated each time ie a letter is entered in a text box. I'll leave that part to you, but you will have to do it in your vm! At minimum the properties SONumber,SerialNumber and Models(if it's ref. changes).
VM: I assume you use galasoft here
public class LauncherViewModel : ViewModelBase
{
private ESTContext _ESTContext;
private string _templatename;
private string _modelname;
private string serialNumber;
private string _outputname;
private string modelName;
private ObservableCollection<Model> models;
private Model selectedModel;
private string soNumber;
public LauncherViewModel()
{
// dangerous ;)
_ESTContext = new ESTContext();
Models = new ObservableCollection<Model>(_ESTContext.Models);
}
public ObservableCollection<Model> Models
{
get { return models; }
set
{
if (Equals(value, models)) return;
models = value;
RaisePropertyChanged();
}
}
public string ModelName
{
get { return modelName; }
set
{
if (value == modelName) return;
modelName = value;
RaisePropertyChanged();
}
}
public string TemplateName { get { return _templatename; }}
public string SerialNumber // Note you spelled this wrong in your xaml. SONumber
{
get { return serialNumber; }
set
{
if (value == serialNumber) return;
serialNumber = value;
RaisePropertyChanged();
}
}
public string SONumber
{
get { return soNumber; }
set
{
if (value == soNumber) return;
soNumber = value;
RaisePropertyChanged();
}
}
public string OutputName { get { return _outputname; } }
public Model SelectedModel
{
get { return selectedModel; }
set
{
if (Equals(value, selectedModel)) return;
selectedModel = value;
RaisePropertyChanged();
}
}
}
A cool litle thing if you are using galasoft or sim together with R#.
public class YourViewModelBase : ViewModelBase
{
[NotifyPropertyChangedInvocator] // alt + enter = convert auto property to prop
// with backing field and change notification :)
override protected void RaisePropertyChanged([CallerMemberName] string propertyName = null)
{
base.RaisePropertyChanged(propertyName);
}
// .. :)
}
Cheers,
Stian

If I havent misunderstood your question , Bind Models to ItemsSource of ComboBox instead of Binding it to DataContext of StackPanel
Remove DataContext binding
<StackPanel Margin="0,78,0,68" Width="233" >
Bind Itemssource of ComboBox to Models and you will also have to specify DisplayMemberPath to the property of Model that you want to display in ComboBox.
<ComboBox IsEditable="True" ItemsSource="{Binding Models}" Text="{Binding ModelName}" SelectedItem="{Binding SelectedModel}"/>
And I am assuming you are setting DataContext of window to instance of LauncherViewModel class.

You need to raise the property changed event on properties you bind to i.e.:
private string _ModelName;
public string ModelName
{
get { return _ModelName; }
set
{
if (_ModelName != value)
{
_ModelName = value;
RaisePropertyChanged("ModelName");
}
}
}
In your base view model you need something like this (make sure you implement INotifyPropertyChanged):
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
In the constructor or loaded event of your view add this:
DataContext = new LauncherViewModel();

Related

Databinding problem in Xamarin.Forms CollectionView

I'm having trouble finding a good way of changing a property of an object inside a list using a CollectionView in Xamarin.Forms.
Below is my code (only the relevant code for readability).
I have a list which I'm databinding to a CollectionView. Each entry in the collectionview contains a label with a number and two buttons to increase and decrease that number by 1.
Note that the code below is working fine. However, I'm not satisfied with the INotifyPropertyChanged implementation in my model, which should just be a simple DTO. I'd like to remove this interface from my model along with the OnPropertyChanged. When I do that, the label with the number doesn't change anymore when I click a button.
So I should make these changes in the ViewModel, but I haven't been able to figure out how. What would be an appropriate way of implementing this in the viewmodel so I can keep my model clean with only a simple property?
Note that the BaseViewModel already implements the INotifyPropertyChanged interface.
Xaml:
<CollectionView ItemsSource="{Binding MyList}" SelectionMode="Single">
<CollectionView.ItemTemplate>
<DataTemplate>
<StackLayout Orientation="Horizontal">
<Button Text="-"
Command="{Binding Source={x:Reference MyPage}, Path=BindingContext.QuantityMinusCommand}"
CommandParameter="{Binding .}" />
<Label Text="{Binding Quantity, Mode=TwoWay}" />
<Button Text="+"
Command="{Binding Source={x:Reference MyPage}, Path=BindingContext.QuantityPlusCommand}"
CommandParameter="{Binding .}" />
</StackLayout>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
Viewmodel:
public class CollectionViewModel : BaseViewModel
{
private List<MyObject> _myList = new List<MyObject>();
public ICommand QuantityMinusCommand { get; }
public ICommand QuantityPlusCommand { get; }
public CollectionViewModel()
{
QuantityMinusCommand = new Command(OnQuantityMinusCommand);
QuantityPlusCommand = new Command(OnQuantityPlusCommand);
}
public List<MyObject> MyList
{
get => _myList;
set
{
_myList = value;
OnPropertyChanged("MyList");
}
}
private void OnQuantityMinusCommand(object o)
{
var myObject = (MyObject)o;
myObject.Quantity = --myObject.Quantity;
}
private void OnQuantityPlusCommand(object o)
{
var myObject = (MyObject)o;
myObject.Quantity = ++myObject.Quantity;
}
}
Model:
public class MyObject : System.ComponentModel.INotifyPropertyChanged
{
private int _quantity;
public int Quantity
{
get => _quantity;
set
{
_quantity = value;
OnPropertyChanged("Quantity");
}
}
public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = "")
{
var changed = PropertyChanged;
if (changed == null)
return;
changed.Invoke(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName));
}
}
Your BaseViewModel can inherit INotifyPropertyChanged. And then your model can be a simple DTO.
public class BaseViewModel : ObservableObject
{
public BaseViewModel()
{
}
bool isBusy = false;
public bool IsBusy
{
get { return isBusy; }
set { SetProperty(ref isBusy, value); }
}
}
And the ObservableObject
/// <summary>
/// Observable object with INotifyPropertyChanged implemented
/// </summary>
public class ObservableObject : INotifyPropertyChanged
{
protected bool SetProperty<T>(ref T backingStore, T value,
[CallerMemberName]string propertyName = "",
Action onChanged = null)
{
if (EqualityComparer<T>.Default.Equals(backingStore, value))
return false;
backingStore = value;
onChanged?.Invoke();
OnPropertyChanged(propertyName);
return true;
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
var changed = PropertyChanged;
if (changed == null)
return;
changed.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
And the model can be like this:
public class MyObject
{
public int Quantity { get; set; }
}
The viewmodel:
public class CollectionViewModel : BaseViewModel
{
// ...
}

Data Binding Issue: How do I get the Binding value to work both ways?

I am working on a MVVM WPF application and I have a CheckBox which I am trying to work on. What I want is for the value to be binding to a model property (which I have done). However, when I click it in a debugging session it never actually changes my IsChecked property to true from its default false. Please see code below:
Model
public class MyModel:INotifyPropertyChanged
{
private bool _isChecked;
public bool IsChecked
{
get { return _isChecked; }
set
{
if (_isChecked == value)
return;
_isChecked = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
View
<StackPanel Orientation="Horizontal" Height="51" Width="667" Canvas.Left="10" Canvas.Top="45">
<CheckBox IsChecked="{Binding IsChecked}" Command="{Binding CheckBoxClickCommand}" Content="We're in the matrix" VerticalAlignment="Center" Margin="10,10,200,10"/>
</StackPanel>
ViewModel
public class MyViewModel
{
private MyModel _myModel = new MyModel();
public ObservableCollection<MyModel> UrlsList { get; } = new ObservableCollection<MyModel>();
public ICommand CheckBoxClickCommand { get; private set; }
public MyViewModel()
{
CheckBoxClickCommand = new RelayCommand(CheckBoxOnClick);
}
public void CheckBoxOnClick()
{
var newList = new List<MyModel>();
if (_myModel.IsChecked)
{
foreach (var url in UrlsList)
{
if (!url.ExistsInDb)
newList.Add(url);
}
}
}
}
When I debug and get to the if statement in CheckBoxClickCommand it obviously goes to the model to get the property value, but it does not change from the default false to true. Any help is much appreciated, thanks!.
Bind to the model's property:
<CheckBox IsChecked="{Binding Model.IsChecked}" ...>
For this to work, the model has to be returned from a public property of the view model:
private MyModel _myModel = new MyModel();
public MyModel Model { get { return _myModel; }}

Binding constant Collection to ComboBox & SelectedItem to TextBox

I'm new to MVVM in WPF and I have the following problem.
What I try to have is two ComboBoxes, each binding to the same ObservableCollection<TwoProperties> DList property as ItemsSource and with synchronized SelectedItem, so I wrote this in my XAML
<ComboBox ItemsSource="{Binding DList}" DisplayMemberPath="Property1" SelectedItem="{Binding SelectedD}" />
<ComboBox ItemsSource="{Binding DList}" DisplayMemberPath="Property2" SelectedItem="{Binding SelectedD}" />
with this viewmodel
public class ViewModel : INotifyPropertyChanged
{
private ObservableCollection<TwoProperties> _dList =
new ObservableCollection<TwoProperties> {
new TwoProperties(1,"one"),
new TwoProperties(2,"two")
};
public ObservableCollection<TwoProperties> DList
{
get { return _dList; }
set { _dList = value; OnPropertyChanged("DList"); }
}
private TwoProperties _selectedD;
public TwoProperties SelectedD
{
get { return _selectedD; }
set { _selectedD = value; OnPropertyChanged("SelectedD"); }
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
where
public class TwoProperties
{
public double Property1 { get; set; }
public string Property2 { get; set; }
public TwoProperties (double p1, string p2)
{
Property1 = p1;
Property2 = p2;
}
}
I would also like to have two TextBoxes that display the properties of the currently SelectedItem of the synchronized ComboBoxes. The properties Property1 and Property2 of SelectedD should be editable, however the ObservableCollection<TwoProperties> _dList should remain constant/readonly and not change its values.
<TextBox Text="{Binding SelectedD.Property1}" />
<TextBox Text="{Binding SelectedD.Property2}" />
But when I edit the TextBoxes and therefore SelectedD, also _dList changes its values, which is not what I want.
I hope I could explain my problem. I'm sure I'm missing something simple here.
This could be implemented easily by changing the binding mode for the TextBoxes into one way as following:
<TextBox Text="{Binding SelectedD.Property1,Mode=OneWay}" />
<TextBox Text="{Binding SelectedD.Property2,Mode=OneWay}" />
Thus when you change the textBox value, the changes would not be reflected back to the Observable collection objects.
Note that you can get rid of magical strings in your view model OnPropertyChanged by modifying the method as following:
private void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
And then you can call it inside the setter of any property inside the view model without passing the name of the property as following:
private TwoProperties _selectedD;
public TwoProperties SelectedD
{
get { return _selectedD; }
set { _selectedD = value; OnPropertyChanged(); }
}
Edit 2:
Update my binding and view model to get the edited values inside the view model
View model updates:
private double? editPropertyOne;
public double? EditPropertyOne
{
get { return editPropertyOne; }
set
{
editPropertyOne = value;
OnPropertyChanged();
}
}
private string editPropertyTwo;
public string EditPropertyTwo
{
get { return editPropertyTwo; }
set
{
editPropertyTwo = value;
OnPropertyChanged();
}
}
private TwoProperties _selectedD;
public TwoProperties SelectedD
{
get { return _selectedD; }
set
{
_selectedD = value; OnPropertyChanged();
if (_selectedD != null)
{
EditPropertyOne = _selectedD.Property1;
EditPropertyTwo = _selectedD.Property2;
}
}
}
Xaml changes:
<TextBox Text="{Binding EditPropertyOne}" />
<TextBox Text="{Binding EditPropertyTwo}" />

WPF MVVM two-way updates

I'm trying to setup a working two-way update by using this example.
These are the relevant code snippets:
XAML:
<Button Click="clkInit">Initialize</Button>
<Button Click="clkStudent">Add student</Button>
<Button Click="clkChangeStudent">Change students</Button>
(...)
<TabControl Name="tabControl1" ItemsSource="{Binding StudentViewModels}" >
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=StudentFirstName}" />
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<Grid>
<Label Content="First Name" Name="label1" />
<TextBox Name="textBoxFirstName" Text="{Binding Path=StudentFirstName}" />
<Label Content="Last Name" Name="label2" />
<TextBox Name="textBoxLastName" Text ="{Binding Path=StudentLastName}" />
</Grid>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
Main Window:
public partial class MainWindow : Window
{
internal MainWindowViewModel myMWVM;
public MainWindow()
{
InitializeComponent();
}
private void clkInit(object sender, RoutedEventArgs e)
{
myMWVM= new MainWindowViewModel();
DataContext = myMWVM;
}
private void clkStudent(object sender, RoutedEventArgs e)
{
myMWVM.StudentViewModels.Add(new StudentViewModel());
}
// For testing - call a function out of the student class to make changes there
private void clkChangeStudent(object sender, RoutedEventArgs e)
{
for (Int32 i = 0; i < test.StudentViewModels.Count; i++)
{
myMWVM.StudentViewModels.ElementAt((int)i).changeStudent();
}
}
}
Main view:
class MainWindowViewModel : INotifyPropertyChanged
{
ObservableCollection<StudentViewModel> _studentViewModels =
new ObservableCollection<StudentViewModel>();
// Collection for WPF.
public ObservableCollection<StudentViewModel> StudentViewModels
{
get { return _studentViewModels; }
}
// Constructor. Add two stude
public MainWindowViewModel()
{
_studentViewModels.Add(new StudentViewModel());
_studentViewModels.Add(new StudentViewModel());
}
// Property change.
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Student view:
class StudentViewModel : INotifyPropertyChanged
{
Lazy<Student> _model;
string _studentFirstName;
public string StudentFirstName
{
get { return _studentFirstName; }
set
{
if (_studentFirstName != value)
{
_studentFirstName = value;
_model.Value.StudentFirstName = value;
OnPropertyChanged("StudentFirstName");
}
}
}
string _studentLastName;
public string StudentLastName
{
get { return _studentLastName; }
set
{
if (_studentLastName != value)
{
_studentLastName = value;
_model.Value.StudentLastName = value;
OnPropertyChanged("StudentLastName");
}
}
}
public void changeStudent()
{
_model.Value.changeStudent();
}
public StudentViewModel()
{
_studentFirstName = "Default";
_model = new Lazy<Student>(() => new Student());
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
THE student:
class Student
{
public string StudentFirstName { get; set; }
public string StudentLastName { get; set; }
public Student()
{
MessageBox.Show("Student constructor called");
}
public Student(string nm)
{
StudentLastName = nm;
}
public void changeStudent()
{
StudentLastName = "McDonald";
}
}
If you read until here I already thank you :) Still, by calling "clkChangeStudent" I don't see the changes in the textbox. I guess it's because I don't call the set-method of the StudentViewModel. The project I'm working on is a bit complex and a lot of things happen in the class (here Student) itself.
How can I get a textbox update by settings values in the Student-class itself?
Your actual code clearly won't notify changes to the interface. The reason is simple. Your method that changes the student name is in the Student model and that model does not implement the INotifyPropertyChanged.
There is 2 solutions to fix this issue depending on one question, does the changeStudent() method has to stick with the object model, that is to say, can your requirements allows you to move the changeStudent() method to the view model?
If yes then, first solution, simply remove the changeStudent method from the model and move it to the view model like this:
class StudentViewModel : INotifyPropertyChanged
{
...
public void changeStudent()
{
this.StudentLastName = "McDonald";
}
}
In the other case, second solution, you have to raise events whenever a model property changes and then get your view model to suscribe to these changes. You can proceed like this in the model:
class Student : INotifyPropertyChanged
{
...
private string studentLastName;
public string StudentLastName
{
get
{
return this.studentLastName;
}
set
{
if(this.studentLastname != value)
{
this.studentLastName = value;
this.OnPropertyChanged("StudentLastName");
}
}
}
}
And for the view model:
class StudentViewModel : INotifyPropertyChanged
{
...
public StudentViewModel(Student model)
{
this._model = model;
this._model.PropertyChanged += (sender, e) =>
{
if(e.PropertyName == "StudentLastName")
{
this.OnPropertyChanged("StudentLastName");
}
};
}
}
Both solution will work. It is really import that you understand that your code explicitely needs to notifies the interface whenever a value changes.
ChangeStudent doesn't call any of the methods that trigger a property notify event in the view model, it alters the underlying model instead. It's these events that trigger the view to update itself.
As an aside you should also look at command binding from the view instead of using click handlers in the code-behind. That way your view doesn't need to know anything about the view model that's attached and can be pure presentation.
First you should use commands instead of events.
In your current structure you have to add an
OnPropertyChanged("StudentLastName");
call to your ChangedStudent() Method in StudentViewModel.
After that you have to set the UpdateSourceTrigger of the Bindings to PropertyChanged
Text="{Binding Path=StudentFirstName, UpdateSourceTrigger=PropertyChanged}"

ObservableCollection not updating View

I am just starting with MVVM and have hit a hurdle that I hope someone can help me with. I am trying to create a simple View with 2 listboxes. A selection from the first listbox will populate the second list box. I have a class created that stores the information I want to bind to.
MyObject Class (Observable Object is just a base class that implements INotifyPopertyChanged)
public class MyObject : ObservableObject
{
String _name = String.Empty;
ObservableCollection<MyObject> _subcategories;
public ObservableCollection<MyObject> SubCategories
{
get { return _subcategories; }
set
{
_subcategories = value;
RaisePropertyChanged("SubCategories");
}
}
public String Name
{
get { return _name; }
set
{
_name = value;
RaisePropertyChanged("Name");
}
}
public MyObject()
{
_subcategories = new ObservableCollection<EMSMenuItem>();
}
}
In my viewmodel I have two ObservableCollections created
public ObservableCollection<EMSMenuItem> Level1MenuItems { get; set; }
public ObservableCollection<EMSMenuItem> Level2MenuItems { get; set; }
In my constructor of the ViewModel I have:
this.Level1MenuItems = new ObservableCollection<EMSMenuItem>();
this.Level2MenuItems = new ObservableCollection<EMSMenuItem>();
this.Level1MenuItems = LoadEMSMenuItems("Sample.Xml");
That works fine for the Level1 items and they correctly show in the View. However I have a command that gets called when the user clicks an item in the listbox, which has the following:
Level2MenuItems = ClickedItem.SubCategories;
For some reason this does not update the UI of the second listbox. If I put a breakpoint at this location I can see that Level2MenuItems has the correct information stored in it. If I write a foreach loop and add them individually to the Level2MenuItems collection then it does display correctly.
Also as a test I added the following to the constructor:
Level2MenuItems = Level1MenuItems[0].SubCategories;
And that updated correctly.
So why would the code work as expected in the constructor, or when looping through, but not when a user clicks on an item in the listbox?
You need to raise the change notification on the Level2MenuItems property.
Instead of having
public ObservableCollection<EMSMenuItem> Level2MenuItems { get; set; }
you need
private ObservableCollection<EMSMenuItem> _level2MenuItems;
public ObservableCollection<EMSMenuItem> Level2MenuItems
{
get { return _level2MenuItems; }
set
{
_level2MenuItems = value;
RaisePropertyChanged(nameof(Level2MenuItems));
}
}
The reason the former works in the constructor is that the Binding has not taken place yet. However since you are changing the reference via a command execute which happens after the binding you need to tell view that it changed
You need to make your poco class within the ObservableCollection implement INotifyPropertyChanged.
Example:
<viewModels:LocationsViewModel x:Key="viewModel" />
.
.
.
<ListView
DataContext="{StaticResource viewModel}"
ItemsSource="{Binding Locations}"
IsItemClickEnabled="True"
ItemClick="GroupSection_ItemClick"
ContinuumNavigationTransitionInfo.ExitElementContainer="True">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}" Margin="0,0,10,0" Style="{ThemeResource ListViewItemTextBlockStyle}" />
<TextBlock Text="{Binding Latitude, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Style="{ThemeResource ListViewItemTextBlockStyle}" Margin="0,0,5,0"/>
<TextBlock Text="{Binding Longitude, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Style="{ThemeResource ListViewItemTextBlockStyle}" Margin="5,0,0,0" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
public class LocationViewModel : BaseViewModel
{
ObservableCollection<Location> _locations = new ObservableCollection<Location>();
public ObservableCollection<Location> Locations
{
get
{
return _locations;
}
set
{
if (_locations != value)
{
_locations = value;
OnNotifyPropertyChanged();
}
}
}
}
public class Location : BaseViewModel
{
int _locationId = 0;
public int LocationId
{
get
{
return _locationId;
}
set
{
if (_locationId != value)
{
_locationId = value;
OnNotifyPropertyChanged();
}
}
}
string _name = null;
public string Name
{
get
{
return _name;
}
set
{
if (_name != value)
{
_name = value;
OnNotifyPropertyChanged();
}
}
}
float _latitude = 0;
public float Latitude
{
get
{
return _latitude;
}
set
{
if (_latitude != value)
{
_latitude = value;
OnNotifyPropertyChanged();
}
}
}
float _longitude = 0;
public float Longitude
{
get
{
return _longitude;
}
set
{
if (_longitude != value)
{
_longitude = value;
OnNotifyPropertyChanged();
}
}
}
}
public class BaseViewModel : INotifyPropertyChanged
{
#region Events
public event PropertyChangedEventHandler PropertyChanged;
#endregion
protected void OnNotifyPropertyChanged([CallerMemberName] string memberName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(memberName));
}
}
}
Your Subcategories property should be read-only.

Categories

Resources