Moving behind code to a viewModel class binding - c#

After learning about ObservableCollection and INotifyPropertyChanged, I'm trying use them to divide my code into MVVM.
But I'm having some trouble with binding outside of code-behind class.
My app have three boxes that let you input a person's name, income, age. Then it will display them on a DataGrid.
xaml:
<Window x:Class="myApp.MainWindow"
[...]
<Grid>
<DataGrid x:Name="peopleDisplay">
</DataGrid>
</Grid>
</Window>
in MainWindow.xaml.cs (no structure)
public partial class MainWindow : Window
{
private ObservableCollection<Person> peopleList = new ObservableCollection<Person>();
public MainWindow()
{
InitializeComponent();
peopleDisplay.ItemsSource = peopleList;
}
private void btnAddProduct_Click(object sender, RoutedEventArgs e)
{
peopleList.Add(new Person { personName = nameBox.text, income = incomebox.text, age = ageBox.text });
}
[...]
}
class People : INotifyPropertyChanged
{
private string personName;
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
public string PersonName {
get
{
return this.personName;
}
set
{
if( this.personName != value)
{
this.PersonName = value;
this.NotifyPropertyChanged("PersonName");
}
}
}
public int age { get; set; }
public double income { get; set; }
}
My main questions:
so now Im trying to do two things: add a new function that will calculate the total income of everyone, move the ObservableCollection above to a viewModel class
now in the new viewModel class I have the ObservableCollection personList (instead of inside behind code), but is it wrong to put the calculation method and the properties here too? If I put the calculation properties here this viewModel will be inheriting INotifyPropertyChanged, so when a the totalIncome properties changes it will change the UI automatically. it makes no sense to put it in the person model though, cause that class represent one person.
How do I bind this people List in viewModel to the xaml? If the list is in code-behind I can just do peopleDisplay.ItemsSource = peopleList;, but this viewModel is a class and not a ObservableCollection object, I cant set it to the dataGrid's ItemsSource. Is there a way to bind it in the viewModel class? Im in the progress of learning mvvm so I might be doing something wrong here too. Please advice

Your Model class is People. like below:
public class People : INotifyPropertyChanged
{
private string personName;
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public string PersonName
{
get
{
return this.personName;
}
set
{
if( this.personName != value)
{
this.PersonName = value;
this.NotifyPropertyChanged();
}
}
}
public int Age { get; set; }
public double Income { get; set; }
}
Your ViewModel like below:
public class PeopleViewModel
{
Public List<People> ListOfPeople { get; set; }
}
ViewModel can implement INotifyPropertyChanged interface to Notify the View.
Now you can set the data context as PeopleViewModel and bind your ListOfPeople to your DataGrid.
Set DataContext for your View you can do it from XAML or code behind.
Set ItemsSource for your DataGrid in your View .
XAML:
<Window x:Class="myApp.MainWindow" DataContext="{Binding PeopleViewModel }">
<Grid>
<DataGrid x:Name="peopleDisplay" ItemSource={Binding ListOfPeople}>
......
</DataGrid>
</Grid>
</Window>
Reference 1
Reference 2

1) I dont see any problem with your approach, but, what would happen if someday you want to test the method that calculate the "TotalIncome"? You could separate the calculation in an helper class.
2) First of all, you have to expose the collection in your ViewModel, using public properties. With that being said, you have to declare the binding in your xaml file.
<DataGrid x:Name="peopleDisplay"
ItemsSource="{Binding MyPropertyOnViewModel}">
</DataGrid>
Dont forget to set the DataContext of your window with your viewmodel.

Related

how to databind the datagrid Mvvm

Kinda stuck on this mvvm binding the datagrid with my viewmodel im making a ObservableCollection and then pass the dummy data into the gridview but im not sure how to do it I started doing it without mvvm and had no problem now im kinda stuck and unsure how to do it
this is my View - UserView.xaml
Grid>
<DataGrid x:Name="dt_Users" ItemsSource="{Binding UserItems}" HorizontalAlignment="Left" Height="100" Margin="161,222,0,0" VerticalAlignment="Top" Width="585" />
</Grid>
My viewmodel - userviewmodel.cs
public class UserViewModel: INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
ObservableCollection<User> UserItems = new ObservableCollection<User>();
private void fetchgrid()
{
UserItems.Add(new User { firstname = "h", lastname = "h" });
UserItems.Add(new User { firstname = "h", lastname = "h" });
}
}
and my model - user.cs
public class User
{
public string firstname { get; set; }
public string lastname { get; set; }
}
what am I overseeing?
Make sure to assign an instance of UserViewModel to the DataContext of your view.
Either in XAML, for example:
<Window>
<Window.DataContext>
<UserViewModel />
</Window.DataContext>
<Grid>
<DataGrid ... />
</Grid>
</Window>
or in code-behind, for example in the constructor:
public MainWindow()
{
InitializeComponent();
this.DataContext = new UserViewModel();
}
Alternatively define the instance of the view model in App.xaml and use {StaticResource} to set the DataContext in XAML.
There are many ways to do it.
You can only bind to public properties so the first thing to do is to change the UserItems field into a property in the view model:
public class UserViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public ObservableCollection<User> UserItems { get; } = new ObservableCollection<User>();
...
}
Then you also need to set the DataContext of the view to an instance of your view model class. The easiest way to do this is to add the following line to the constructor of the view in UserView.xaml.cs:
public UserView()
{
InitializeComponent();
DataContext = new UserViewModel(); //<-- add this
}

How do I use the same ViewModel for two views?

So I have my MainWindow.xaml which has the DataContext of BaseViewModel set like this.
<Window.DataContext>
<viewModel:BaseViewModel/>
</Window.DataContext>
it works fine, for instance when I select an item in my ListView it binds and updates my ImageView in my MainWindow.
<ListView Background="Transparent"
ItemsSource="{Binding ImageGridViewModel.Images}"
SelectedItem="{Binding ImageGridViewModel.SelectedImage}">
And then it updates like so
<Image Source="{Binding ImageGridViewModel.SelectedImage}"
Margin="20">
Perfect no issues.
However, I recently added a second view which is a Window called WatermarkWindow and I set the DataContext just like I did with the MainWindow, in the XAML like so.
<Window.DataContext>
<viewModel:BaseViewModel/>
</Window.DataContext>
And then the binding for the Image control on that new Window
<Image Source="{Binding ImageGridViewModel.SelectedImage}"
Margin="20">
However when I open that window, the Image control's source is not bound to the property, the property actually returns NULL and I think I know why that is, I think it's because in my BaseViewModel I am instantiating a new instance of that ViewModel evertime it gets called.
The reason to why I am doing it that way is because I wanted to instantiate a instance of it so I can actually use it to bind stuff. Rather than it being null.
If that's not the issue then I'm still really eager to learn and understand what the issue is.
What's the proper way of setting up a BaseViewModel that contains all the extra ViewModels?
public class BaseViewModel : ObservableObject
{
public ImageGridViewModel ImageGridViewModel { get; set; } = new ImageGridViewModel();
}
And the ObservableObject
public class ObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
And not that it matters because I know the properties work fine, here's the ViewModel.
public class ImageGridViewModel
{
public string ImagePath { get; set; }
public string SelectedImage { get; set; }
public ObservableCollection<string> Images { get; set; }
...
What I ended up doing was setting the DataContext property to this where I instantiate the new WawtermarkWindow
if (wmw == null)
{
wmw = new WatermarkWindow();
wmw.DataContext = this;
}

WPF Databindings only updates when changed

So I've been sifting through similar questions on SO for a few days now. I just want to know why this problem occurs. I've got a Class with properties and a SeriesCollection used for Live Charts, which are binded to the UI. Since the properties need to be able to be Serialized, the SeriesCollection can not be part of the specific modelview (but need to binded to UI for plotting a chart). like so:
public class DLVOModel
{
public SeriesCollection table { get; set; }
public DLVOConfiguration DLVOConfiguration { get; set; }
}
public partial class DLVOModelizer : Window
{
public DLVOModel model { get; set; }
public DLVOModelizer()
{
InitializeComponent();
model = CreateModel();
DataContext = model; //Databinding
}
private DLVOModel CreateModel() => new DLVOModel()
{
DLVOConfiguration = new DLVOConfiguration(),
table = new SeriesCollection(),
};
public class DLVOConfiguration
{
public double HMax { get; set; }
public int Resolution { get; set; }
//... about 25 properties
}
`
XAML:
<window>
<lvc:CartesianChart Series="{Binding Path=table}"/>
<GroupBox DataContext="{Binding DLVOConfiguration}">
<Grid>
<TextBox Text="{Binding Path=HMax, Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}"/>
<TextBox Text="{Binding Path=Resolution, Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}"/>
</Grid>
</GroupBox>
So this all works well untill I try to deserialize an xml file. The model gets updated properly but the UI falls behind. The textboxxes updates to the models value when I try to change their text. This is odd because:
The Databinding works fine, so UI should update immediately.
When entering a new value in the UI the model property should change, not the UI.
(Also tried a version without UpdateSourceTrigger).
Before I binded directly to the DLVOConfiguration and everything worked fine.
I know there is a way in which your modelview inherits from INotifyPropertyChanged, but for some reason I run into the same problem.
EDIT:
I added code for the case in which I used INotifyPropertyChanged from this question:
WPF DataBinding not updating?
public class DLVOConfiguration : INotifyPropertyChanged
{
private double _HMax;
public double HMax
{
get { return _HMax; }
set
{
_HMax = value;
NotifyPropertyChanged("HMax");
}
}
private int _Resolution;
public int Resolution
{
get { return _Resolution; }
set
{
_Resolution = value;
NotifyPropertyChanged("Resolution");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
I guess you're replacing the instance being bound to somewhere. That breaks the data binding. As long as you're merely updating the properties with new values, it should work just fine.

ListBox binding, new item in binded collection

I'm trying to implement MVVM patter in my app, and got problem with data binding. Everything works pretty fine, untill a new item appears in my collection binded to ListBox. ListBox is just not updating, and after I try to click on that box exceptions about ItemsControl is thrown.
MainView.xaml
<Window.DataContext>
<ViewModel:MainViewModel/>
</Window.DataContext>
<ListBox x:Name="Scripts" ItemsSource="{Binding Scripts, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" DisplayMemberPath="Name" SelectedItem="{Binding SelectedScript}"/>
MainViewModel.cs
class MainViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public List<Script> Scripts
{
get; set;
}
public RelayCommand NewScriptCommand
{
get; set;
}
public MainViewModel()
{
Scripts = Script.Scripts;
NewScriptCommand = new RelayCommand(NewScript);
}
private void NewScript()
{
Script.NewScript();
OnPropertyChanged("Scripts");
}
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
And my Model, class Script.cs
public class Script
{
private static List<Script> _scripts;
public static List<Script> Scripts
{
get
{
if(_scripts == null)
{
_scripts = GetScripts();
}
return _scripts;
}
}
public static void NewScript()
{
_scripts.Add(new Script());
}
}
I was thinking about make ObservableCollection in MainViewModel, but then occurred other problem about updating that collection when List<Script> Scripts get new element.
Replace List with ObservableCollection and everything will start working. Not sure what "problem" you are talking about. Remove OnPropertyChanged("Scripts"). The Scripts property has not changed...the collection has changed. You need to raise a collection changed event which is what ObservableCollection does.

WPF Application With Multiple Child Views - MVVM

My problem is quite simple, I want to have a MainView which in turn will have multiple Views which are dynamic and intractable, like in the diagram below:
But to do this you need multiple ViewModels, and I do not know how to organise them.
My original Idea is to have a MainViewModel, within which I will create properties that will return all my ChildViewModels as shown below, but It seems unprofessional to me and a bad practice.
public class MainViewModel : BaseViewModel
{
private EditPropertiesViewModel _editPropertiesViewModel;
public EditPropertiesViewModel EditPropertiesViewModel
{
get { return _editPropertiesViewModel; }
set
{
_editPropertiesViewModel = value;
base.OnPropertyChanged();
}
}
private UsersDetailsViewModel _usersDetailsViewModel;
public UsersDetailsViewModel UsersDetailViewModel
{
get { return _usersDetailsViewModel; }
set
{
_usersDetailsViewModel = value;
base.OnPropertyChanged();
}
}
//etc. etc..
}
Then from My MainView, I would set the Datacontext to the MainViewModel
Please help me I have no idea what to do, I am totally paused right now.
If you wish to achieve this without PRISM you can make use of ContentControl. For every region you create ContentControl and for every ContentControl you create its ViewModel property. Then you manipulate selected ViewModel associated with ContentControl and ContentControl adjusts view based on type of ViewModel assigned. For clarification take a look
XAML:
<Window.DataContext>
<local:MainViewModel/>
</Window.DataContext>
<Window.Resources>
<DataTemplate DataType="{x:Type viewModel:SubArticleViewModel}">
<view:SubArticleView/>
</DataTemplate>
</Window.Resources>
<ContentControl Content="{Binding ArticleViewModel}"/>
C#
class BaseViewModel : INotifyPropertyChanged
{
public void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
class MainViewModel
{
public BaseViewModel ArticleViewModel { get; set; }
}
class SubArticleViewModel : BaseViewModel
{
}
Whenever you assign
ArticleViewModel = new SubArticleViewModel();
DataTemplate defined as resource will be placed as Content of Control.
Above way out creates a lots of work and is more vulnerable for omission. PRISM would be a better choice anyway.
Create AppViewModel class with static ctor like this:
class AppViewModel : BaseViewModel
{
static AppViewModel()
{
_AppModel = new AppViewModel();
}
private static AppViewModel _AppModel;
public static AppViewModel Current
{
get { return _AppModel; }
}
private AppViewModel()
{
//Initialize view models here
MainPageModel = new MainPageViewModel();
}
//VIEW MODELS
public MainPageViewModel MainPageModel { get; private set; }
}
Create BaseViewModel class. All of your VM's should be inherited from it:
class BaseViewModel //implement INotifyPropertyChanged if needed
{
public AppViewModel AppModel
{
get { return AppViewModel.Current; }
}
}
So now you can create UserControl called "MainView":
public partial class MainView : UserControl
{
public MainView()
{
InitializeComponent();
//Prevent view updating in Designer
if (DesignerProperties.GetIsInDesignMode(this))
{
return;
}
var mainVM = AppViewModel.Current.MainPageModel;
DataContext = mainVM;
}
}
In the MainWindow.xaml:
<Window x:Class="MVVM_Test.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:views="clr-namespace:MVVM_Test.Views"
Title="MainWindow" Height="350" Width="525">
<views:MainView />
</Window>

Categories