I have make a simple project to investigate on a performance issue in my Xamarin.Forms application.
I have two nested list for a classic master-detail structure. Accord to the MVVM pattern, I implemented the INotifyPropertyChanged interface for ViewModel classes and use ObservableCollection for lists.
In the main view I have two standard ListView, the first (masters) binded to the master collection, che second (details) binded to the details collection of the selected item of the master list. Both have CachingStrategy="RecycleElement".
When run UWP application, in Diagnostic Tools of Visual Studio can be see memory usage of application.
When an element of the masters list is selected, the details list is rendered and the used memory grow up, when masters list selected item change, the details list is redrowed with the respective items and used memory grow up, also if there is less items to display, if previous masters item is selected again, used memory grow up again. Every time that masters list selected items change and the details list is refreshed, used memory grow up without ever decreasing. Seems that objects used for render the details list items never be collected by GC.
I think it's too evident effect to be a bug no one has ever noticed, but the test project is very minimal, all viewmodels inherits from MvvmLight ViewModelBase class, all properties use Set method and all list properties type is ObservableCollection, the only custom code is for initial load.
I think that I make a mistake that I can't see.
Below viewmodels and view code
MainViewModel.cs
public class MainViewModel : ViewModelBase
{
public MainViewModel()
{
this.Load();
}
private RelayCommand _reloadCommand;
public RelayCommand ReloadCommand { get { return _reloadCommand ?? (_reloadCommand = new RelayCommand(this.Load)); } }
private ObservableCollection<ItemViewModel> _items;
public ObservableCollection<ItemViewModel> Items { get { return _items; } set { this.Set(ref _items, value); } }
private ItemViewModel _selectedItem;
public ItemViewModel SelectedItem { get { return _selectedItem; } set { this.Set(ref _selectedItem, value); } }
private SubitemViewModel _selectedSubitem;
public SubitemViewModel SelectedSubitem { get { return _selectedSubitem; } set { this.Set(ref _selectedSubitem, value); } }
private void Load()
{
int[] ranges = { 15, 10, 5, 20 };
this.Items = new ObservableCollection<ItemViewModel>(
from i in Enumerable.Range(1, 15)
select new ItemViewModel()
{
Label = $"Item {i}",
Items = new ObservableCollection<SubitemViewModel>(from s in Enumerable.Range(1, ranges[i % 4] )
select new SubitemViewModel() { Label = $"Subitem {i}.{s}" })
});
}
}
ItemViewModel.cs
public class ItemViewModel : ViewModelBase
{
private string _label;
public string Label { get { return _label; } set { this.Set(ref _label, value); } }
private ObservableCollection<SubitemViewModel> _items;
public ObservableCollection<SubitemViewModel> Items { get { return _items; } set { this.Set(ref _items, value); } }
}
SubItemViewModel.cs
public class SubitemViewModel : ViewModelBase
{
private string _label;
public string Label { get { return _label; } set { this.Set(ref _label, value); } }
}
MainView.xaml
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:vm="clr-namespace:App1.ViewModel"
x:Class="App1.MainPage">
<StackLayout>
<Grid Padding="10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="3*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="auto" />
<RowDefinition Height="*" />
<RowDefinition Height="auto" />
</Grid.RowDefinitions>
<Label Grid.Row="0" Grid.Column="0" Text="Masters" />
<Label Grid.Row="0" Grid.Column="1" Text="Details" />
<ListView Grid.Column="0" Grid.Row="1"
ItemsSource="{Binding Items}"
CachingStrategy="RecycleElement"
SelectedItem="{Binding SelectedItem, Mode=TwoWay}">
<ListView.ItemTemplate>
<DataTemplate x:DataType="vm:ItemViewModel">
<ViewCell>
<ViewCell.View>
<Label Grid.Row="0" Grid.Column="1" Text="{Binding Label}" />
</ViewCell.View>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<ListView Grid.Column="1" Grid.Row="1"
ItemsSource="{Binding SelectedItem.Items}"
SelectedItem="{Binding SelectedSubitem, Mode=TwoWay}"
CachingStrategy="RecycleElement">
<ListView.ItemTemplate>
<DataTemplate x:DataType="vm:SubitemViewModel">
<ViewCell>
<ViewCell.View>
<Label Grid.Row="0" Grid.Column="1" Text="{Binding Label}" />
</ViewCell.View>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</StackLayout>
</ContentPage>
The complete test project can be download here: MemoryTest.zip
Related
I use CollectionView to show data on screen, but when I change data, UI is not changing, although I am using OnPropertyChanged. Here is the code:
Xaml
<CollectionView ItemsSource="{Binding GridData}" Margin="15">
<CollectionView.ItemTemplate>
<DataTemplate>
<Grid Margin="15" Padding="5">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Label Grid.Row="0"
Grid.Column="0"
HorizontalTextAlignment="Start"
Text="{Binding Title}"
FontSize="Small"/>
<Label Grid.Row="0"
Grid.Column="1"
HorizontalTextAlignment="End"
Text="{Binding Data}"
TextColor="Black"
FontSize="Medium">
<Label.GestureRecognizers>
<TapGestureRecognizer
Command="{Binding Source={x:Reference Page} , Path=BindingContext.TapCommand}"
CommandParameter="{Binding Title}" />
</Label.GestureRecognizers>
</Label>
<BoxView Grid.Row="1"
Grid.Column="0"
Grid.ColumnSpan="2"
BackgroundColor="LightGray"
CornerRadius="2"
HorizontalOptions="FillAndExpand"
HeightRequest="1"></BoxView>
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
ViewModel
private List<CollectionEntity> _gridData;
public List<CollectionEntity> GridData
{
get => _gridData;
set
{
if (_gridData != value)
{
_gridData = value;
OnPropertyChanged(nameof(GridData));
}
}
}
public ICommand TapCommand
{
get
{
return new Command<CollectionView>((commandParameters) =>
{
OpenEditing(commandParameters.ToString());
OnPropertyChanged(nameof(GridData));
});
}
}
Model (is in the same file, as is ViewModel)
public class CollectionEntity: INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public string Title { get; set; }
public string Data { get; set; }
}
So, when I tap on the Label, UI does not react. I tried to write it according to this answer, but cannot understand, what is incorrect.
UPD: new command
public ICommand TapCommand => new Command<object>((commandParameters) =>
{
OpenEditing(commandParameters.ToString()); // changing data
});
Though you had write the code about INotifyPropertyChanged in your model but you didn't implement it on the property Title and Data . Modify the code like following
private string title;
public string Title
{
get => title;
set
{
if (title!= value)
{
title = value;
OnPropertyChanged(nameof(Title));
}
}
}
private string data;
public string Data
{
get => data;
set
{
if (data!= value)
{
data= value;
OnPropertyChanged(nameof(Data));
}
}
}
In addition, the code in TapCommand seems will not change the value of source . You could binding the whole model to the command and set the title or data in command as you want .
CommandParameter="{Binding .}"
public ICommand TapCommand
{
get
{
return new Command<CollectionView>((arg) =>
{
var model = arg as CollectionEntity;
// model.Title = "xxx";
});
}
}
I have an UserControl. At the top, there is a global parameter, bound to a static property in the class MultiSliceCommand. Below, there is a TabControl, populated by a Template and bound to public static ObservableCollection<GroupContainer> groups, also a property in MultiSliceCommand. GroupContainer contains various properties, mainly doubles, ints etc., displayed and editable in textboxes in the TabItems.
When I now change a value in TabItem, the corresponding property in the correct element of groups is set.
However, when I close & reopen the dialog, the all the GroupContainers in groups are reset to their defaults - even the properties not bound at any point to the dialog.
Changes to the global variables (outside of the TabControl) are preserved correctly. Changes to the TabControl are also preserved correctly if I remove the binding to the global variables - in explicit, if I remove the lines <local:MultiSliceCommand x:Key="mutliSliceCommand" /> and <TextBox x:Name="Mm_Per_Package" Text="{Binding Source={StaticResource mutliSliceCommand}, Path=Mm_Per_Package}" />
How can I change the bindings to preserve the changes to the global variable as well as the contents of the Tabs when closing & reopening the dialog?
The Xaml File:
<UserControl.Resources>
<DataTemplate x:Key="HeaderTemplate">
<Label Content="{Binding Group_Name}" />
</DataTemplate>
<local:MultiSliceCommand x:Key="mutliSliceCommand" />
<DataTemplate x:Key="ItemTemplate">
<Grid>
<TextBox x:Name="_length" Text="{Binding Path=Length, UpdateSourceTrigger=PropertyChanged, Delay=0}" />
</Grid>
</DataTemplate>
</UserControl.Resources>
<ScrollViewer>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<GroupBox
Header="Global Parameters"
Grid.Row="0"
Grid.Column="0"
>
<Grid Height="Auto" Width="Auto">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBox x:Name="Mm_Per_Package" Text="{Binding Source={StaticResource mutliSliceCommand}, Path=Mm_Per_Package}" />
</Grid>
</GroupBox>
<GroupBox
Header="Materials"
Grid.Row="1"
Grid.Column="0"
Grid.ColumnSpan="2"
>
<TabControl x:Name="TabControl1"
HorizontalAlignment="Left"
VerticalAlignment="Top"
ItemTemplate="{StaticResource HeaderTemplate}"
ContentTemplate="{StaticResource ItemTemplate}"
/>
</GroupBox>
<!--
<Button Content="Save settings"
Grid.Row="2"
HorizontalAlignment="Right"
Margin="10,10,0,0"
VerticalAlignment="Top"
Width="75"
Click="Btn_Save" />-->
</Grid>
</ScrollViewer>
The Class MultiSliceCommand
public class MultiSliceCommand
{
public static ObservableCollection<GroupContainer> groups { get; set; }
private static double _mm_per_package { get; set; } = 0;
public static double Mm_Per_Package
{
get { return _mm_per_package; }
set { _mm_per_package = value < 0 ? 0 : value; }
}
public MultiSliceCommand()
{
groups = new ObservableCollection<GroupContainer>
{
new GroupContainer("Group 1"),
new GroupContainer("Group 1"),
new GroupContainer("Group 3")
};
}
}
The class ObjectContainer
public class GroupContainer : INotifyPropertyChanged
{
private double _length { get; set; } = 0;
public double Length
{
get { return _length; }
set { _length = value < 0 ? 0 : value; NotifyPropertyChanged("Min_Vector_Length"); }
}
// Methods
public GroupContainer(string group_name)
{
}
// Helper Stuff
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(string sProp)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(sProp));
}
}
}
Ok, fixed it with an (somewhat dirty) hack:
I just outsourced the global variable to its own class, and bind the xaml to this class. In MultiSliceCommand, I use getter / setter on the property to just relay the value from the "isolation class"
Isolation class:
public class xaml_backend_variables
{
private static double _mm_per_package = 0;
public static double Mm_Per_Package
{
get { return _mm_per_package; }
set { _mm_per_package = value < 0 ? 0 : value; }
}
public xaml_backend_variables()
{
}
}
MultiSliceCommand
public static double Mm_Per_Package
{
get { return xaml_backend_variables.Mm_Per_Package; }
set { xaml_backend_variables.Mm_Per_Package = value; }
}
XAML Modifications
....
<local:xaml_backend_variables x:Key="xaml_backend_variables" />
....
<TextBox x:Name="Mm_Per_Package" Text="{Binding Source={StaticResource xaml_backend_variables}, Path=Mm_Per_Package}" />
But now all values are preserved correctly when closing and reopening the dialog.
Still, if someone has an explanation why this happens and what would be the correct / elegant way to solve this, I would like very much to know!
I wrote user control with 2 buttons and one check box and now I want to bind Commands to data context - for each button and checkbox.
But I don't know how to define command binding. I think I'll need some kind of ICommand property in User control - but how can I connect user's data context command delegate? I want to use user control to manage each item in collection like this:
<ItemsControl ItemsSource="{Binding Path=MoneyInfo}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<local:ChannelSetupControl
CurrentCount="{Binding Count}"
CoinValue="{Binding Value}"
UpCommand="{Binding DataContextUp}"
DownCommand="{Binding DataContextDown}"
ChangeCheckboxCommand="{Binding DataContextChange}"></local:ChannelSetupControl>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
XAML User control
<UserControl>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="3*"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Grid.ColumnSpan="2" Grid.Row="0" Text="{Binding CoinValue}" TextAlignment="Center"></TextBlock>
<TextBlock Grid.Column="0" Grid.Row="1" Text="{Binding CurrentCount, Mode=TwoWay}" TextAlignment="Center" VerticalAlignment="Center" FontSize="30"></TextBlock>
<StackPanel Grid.Column="1" Grid.Row="1" VerticalAlignment="Center">
<Button Content="+ 10" Padding="0 5"></Button>
<Button Content="- 10" Padding="0 5"></Button>
</StackPanel>
<CheckBox Grid.Column="0" Grid.ColumnSpan="2" Grid.Row="2" IsChecked="{Binding Cycling, Mode=TwoWay}" Content="recycling" VerticalContentAlignment="Center"></CheckBox>
</Grid>
</UserControl>
and code behind and this is where I'm lost - how to define UpCommand, DownCommand and ChangeCheckboxCommand?
public partial class ChannelSetupControl : UserControl, INotifyPropertyChanged
{
private int currentCount;
private bool cycling;
private double coinValue;
public int Step { get; set; }
public double CoinValue { get { return coinValue; } set { coinValue = value; NotifyPropertyChanged("CoinValue"); } }
public int CurrentCount { get { return currentCount; } set { currentCount = value; NotifyPropertyChanged("CurrentCount"); } }
public bool Cycling { get { return cycling; } set { cycling = value; NotifyPropertyChanged("Cycling"); } }
public ChannelSetupControl()
{
InitializeComponent();
DataContext = this;
CurrentCount = 0;
Step = 10;
Cycling = false;
CoinValue = 0;
}
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
First of all your ChannelSetupControl class extends UserControl, so it implicitly extends DependencyObject class. It means you can use Dependency Properties instead of implementing INotifyPropertyChanged.
So you can define a dependency property in your ChannelSetupControl class, like this one:
public static readonly DependencyProperty UpCommandProperty =
DependencyProperty.Register("UpCommand", typeof(ICommand), typeof(ChannelSetupControl));
public ICommand UpCommand
{
get { return (ICommand)GetValue(UpCommandProperty); }
set { SetValue(UpCommandProperty, value); }
}
At the same time in your control XAML:
<Button Command="{Binding RelativeSource={RelativeSource Mode=Self}, Path=UpCommand, Mode=OneWay}"
Content="+ 10" Padding="0 5" />
In this way in your window XAML you can wrote:
<local:ChannelSetupControl UpCommand="{Binding UpCommand, Mode=OneWay}" ... />
You can use the same "pattern" for the other controls.
Regarding ICommand, there are a lot of implementations. The one that I prefer is the so called delegate command (for a sample you can take a look here).
I hope this quick explanation can help you.
I'm trying to bind an ObservableCollection of custom objects called ItemGroup which itself contains an ObservableCollection of Item objects (among other Properties) to a LongListSelector (to create a grouping of Item objects) in WP8.1 Silverlight:
public class Item : INotifyPropertyChanged {
private string _name = "";
public string Name {
get {
return _name;
}
set {
if (value != _name) {
_name = value;
NotifyPropertyChanged("Name");
}
}
}
private int _cost = 0;
public int Cost {
get {
return _cost;
}
set {
if (value != _cost) {
_cost = value;
NotifyPropertyChanged("Cost");
}
}
}
}
public class ItemGroup : INotifyPropertyChanged {
private string _groupName = "";
public string GroupName {
get {
return _groupName;
}
set {
if (value != _groupName) {
_groupName = value;
NotifyPropertyChanged("GroupName");
}
}
}
private int _totalCost = 0;
public int TotalCost {
get {
return _totalCost;
}
set {
if (value != _totalCost) {
_totalCost = value;
NotifyPropertyChanged("TotalCost");
}
}
}
private ObservableCollection<Item> _items = new ObservableCollection<Item>();
public ObservableCollection<Item> Items {
get {
return _items;
}
set {
if (value != _items) {
_items = value;
NotifyPropertyChanged("Items");
}
}
}
}
ViewModel contains the property:
private ObservableCollection<ItemGroup> _itemGroupList = new ObservableCollection<ItemGroup>();
public ObservableCollection<ItemGroup> ItemGroupList {
get {
return _itemGroupList;
}
set {
if (value != _itemGroupList) {
_itemGroupList = value;
NotifyPropertyChanged("ItemGroupList");
}
}
}
And the XAML:
<phone:LongListSelector ItemsSource="{Binding ItemGroupList}">
<!-- Template for ItemGroup Items -->
<phone:LongListSelector.ItemTemplate>
<DataTemplate>
<Grid Height="Auto" Margin="12,0,2,15">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<!-- Item Name -->
<TextBlock Text="{Binding Items.Name}" Grid.Column="0"/>
<!-- Item Cost -->
<TextBlock Text="{Binding Items.Cost, StringFormat='{}{0} dollars'}" Grid.Column="1"/>
</Grid>
</DataTemplate>
</phone:LongListSelector.ItemTemplate>
<!-- Template for ItemGroup headers -->
<phone:LongListSelector.GroupHeaderTemplate>
<DataTemplate>
<Grid Background="#FFA2A2A2" Margin="0,10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<!-- ItemGroup Group Name -->
<TextBlock Grid.Column="0" Text="{Binding GroupName}"/>
<!-- ItemGroup Total Cost -->
<TextBlock Grid.Column="1" Text="{Binding TotalCost}"/>
</Grid>
</DataTemplate>
<phone:LongListSelector.GroupHeaderTemplate>
</phone:LongListSelector>
Which isn't working. The direct Properties of ItemGroup (GroupName and TotalCost) bind correctly and are visible, but no Item objects are shown! I suspect this is because I am passing to ItemTemplate the wrong source? I have tried binding the Collection directly in the LLS by ItemSource={Binding ItemGroupList.Items}, but that too doesn't work.
Update: Binding a Header control to Items.Count yields the actual number of Item objects inside Items of ItemGroup, which indicates that there indeed exists a list of Item objects for each ItemGroup.
Note that you've got a nested collection there, so you will need 2 items controls. You can't bind to "Items.Name" or "Items.Cost" because those are not properties. You could use "Items[0].Name", but I guess that you would probably want to list all the items, something like this (inside "LongListSelector.ItemTemplate"):
<ItemsControl ItemsSource="{Binding Items}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<!-- Item Name -->
<TextBlock Text="{Binding Items.Name}" Grid.Column="0"/>
<!-- Item Cost -->
<TextBlock Text="{Binding Items.Cost, StringFormat='{}{0} dollars'}" Grid.Column="1"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Can somebody explain to me how exactly to create a ViewModel for the MVVM Pattern.
I tried to understand the the tutorial here: http://msdn.microsoft.com/en-us/magazine/dd419663.aspx , but I was unable to understand what exactly is happening in the code.
Let's say we want to create a basic application about getting and adding people from and to a local database and displaying them in the View. How should the ViewModel look like and how to create the RelayCommands for it. First why do we set the variables twice: once privately and then again publicaly.
EDIT: Thanks for the help so far. I have one more thing that I don't know to do - how to bind the View to the ViewModel and Vice Versa
Here is the Model:
public class Student : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
private string name;
private string surname;
private string age;
public string Name
{
get
{
return name;
}
set
{
name = value;
OnPropertyChanged("Name");
}
}
public string Surname
{
get
{
return surname;
}
set
{
surname = value;
OnPropertyChanged("Surname");
}
}
public string Age
{
get
{
return age;
}
set
{
age = value;
OnPropertyChanged("Age");
}
}
}
and here is the ViewModel:
public class MainViewModel : ViewModelBase
{
ObservableCollection<Student> studentList;
Student selectedPerson;
public MainViewModel()
{
//populate some sample data
studentList = new ObservableCollection<Student>()
{
new Student(){Name="John", Surname="Smith", Age="28"},
new Student(){Name="Barbara", Surname="Anderson", Age="23"}
};
}
public ObservableCollection<Student> StudentList
{
get { return studentList; }
}
public Student SelectedPerson
{
get { return selectedPerson; }
set
{
selectedPerson = value;
RaisePropertyChanged("SelectedPerson");
}
}
private RelayCommand _addStudentCommand;
public ICommand AddStudentCommand
{
get
{
return _addStudentCommand
?? (_addStudentCommand = new RelayCommand(() =>
{
Student student = new Student();
studentList.Add(student);
}));
}
}
}
I have found a way to bind the ViewModel to the View using some code for the view in Csharp but the question how to bind the View to the ViewModel is still on my mind. To be more specific how to create a new student using the values a user has entered in the View.
Here is the View's XAML code
<Window x:Class="MVVMLight.View.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow"
SizeToContent="WidthAndHeight">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="2*"/>
<RowDefinition Height="2*"/>
<RowDefinition Height="2*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock x:Name="NameTextBlock"
Text="Name"
Style="{StaticResource TextBlockTextStyle}"/>
<TextBlock x:Name="SurnameTextBlock"
Grid.Row="1"
Text="Surname"
Style="{StaticResource TextBlockTextStyle}"/>
<TextBlock x:Name="AgeTextBlock"
Grid.Row="2"
Text="Age"
Style="{StaticResource TextBlockTextStyle}"/>
<TextBox x:Name="NameTextBox"
Grid.Column="1"
Style="{StaticResource TextBoxTextStyle}"/>
<TextBox x:Name="SurnameTextBox"
Grid.Row="1"
Grid.Column="1"
Style="{StaticResource TextBoxTextStyle}"/>
<TextBox x:Name="AgeTextBox"
Grid.Row="2"
Grid.Column="1"
Style="{StaticResource TextBoxTextStyle}"/>
<ListBox x:Name="StudentListBox"
Grid.ColumnSpan="2"
Grid.Row="4"
Style="{StaticResource ListBoxStyle}"
ItemsSource="{Binding StudentList}">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Name}"
Style="{StaticResource TextBlockTextStyle}"/>
<TextBlock Text="{Binding Surname}"
Grid.Column="1"
Style="{StaticResource TextBlockTextStyle}"/>
<TextBlock Text="{Binding Age}"
Grid.Column="2"
Style="{StaticResource TextBlockTextStyle}"/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Button x:Name="AddButton"
Grid.Row="7"
Grid.ColumnSpan="2"
HorizontalAlignment="Center"
Content="Add"
Margin="7,7,7,7"
Command="{Binding AddStudentCommand}"/>
</Grid>
And here is the View's Csharp code
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new MainViewModel();
}
}
I have some questions concerning the Binding between the View and The ViewModel:
What are the pros and cons of using this type of binding?
What is the best way of binding if I am going to use a database?
Is this how the ViewModel and Model should look like
How to create a RelayCommand for adding a student to the ObservableCollection
Why do we set things first privately and then again publically [Answered]
How to bind the View to the ViewModel and Vice Versa
in your property setters you should check to see if the new value is equal to the old value, if it is you should return and not fire the PropertyChanged event.
As for your questions:
Yes this looks fine.
There are a couple of ways to setup your relay commands. I prefer
private RelayCommand<Student> _addStudentCommand;
public ICommand AddStudentCommand
{
get
{
return _addStudentCommand
?? (_addStudentCommand = new RelayCommand<Student>((student) =>
{
studentList.Add(student);
}));
}
}
another way without passing in a student object
private RelayCommand _addStudentCommand;
public ICommand AddStudentCommand
{
get
{
return _addStudentCommand
?? (_addStudentCommand = new RelayCommand(() =>
{
Student student = new Student();
studentList.Add(student);
}));
}
}
That is how properties work in .net, You could use automatic properties, but since you need to fire change notification in the setter you have to declare the field that the property will work against.
Also since it looks like you are using mvvm light you should try the code snippets. They make properties very easy to create. type mvvvminpc then hit tab twice. then fill in the highlighted part and hit tab till you are finished.
You can bind the View To the Viewmodel a couple of ways. I know that it is an Antipattern but you could use a locator. The basic idea is to set the viewmodel as the views datacontext.
public class Locator
{
public Viewmodel1 Viewmodel1
{
return new Viewmodel1();
}
}
You then in you app.xaml you add this class
<Application.Resources>
<Locator x:key="VMLocator" />
</Application.Resources>
Then in your view in the xaml
<Page DataContext="{Binding Source="{StaticResource VMLocator}" Path=ViewModel1}">
</Page>