Currently, I'm developing a simple task manager application to exercise my Windows 10 development skills, using the Universal Windows Application (UWP) template and the MVVM design pattern.
In my ViewModel (which implements INotifyPropertyChanged), I've got an ObservableCollection<BasicTask> which is databound to a ListView in XAML, where BasicTask is a simple class I developed.
My commands are instantiated within the ViewModel as properties, and are databound to the 'Command' property of (in this case, ) an AppBarButton UI element. The command's function is to call a method in the ViewModel, which is defined within the constructor of the command like so;
class DeleteTaskCommand : ICommand
{
public DeleteTaskCommand(MainViewModel viewModel)
{
ViewModel = viewModel;
}
public MainViewModel ViewModel { get; private set; }
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter) => (ViewModel.SelectedTask != BasicTask.Default) ? true : false;
public void Execute(object parameter) => ViewModel.DeleteTask();
}
The DeleteTask() method in the ViewModel affects the ObservableCollection<BasicTask>, which, as I stated before, is databound to a ListView and therefore (to my knowledge) should push the changes immediately to the ListView. And this is exactly what is doesn't do :(
I can confirm that the command calls the method in the ViewModel properly as I replaced the body of the DeleteTask() method with await new MessageDialog("").ShowAsync();, which worked.
I've tried this with many projects with absolutely no success so far. One apparent solution is here, however, Visual Studio notifies that the CommandManager class doesn't exist :/
UPDATE
The XAML of the ListView mentioned above is the following;
<ListView ItemsSource="{Binding TaskLists}"
SelectedIndex="{Binding SelectedTaskListIndex, Mode=TwoWay}"
SelectionMode="Single">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Grid>[...]</Grid>
<TextBlock Text="{Binding Title}"/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
TaskLists is an ObservableCollection<TaskList> TaskLists, and the TaskList class is as follows;
class TaskList
{
public string Title { get; set; }
public string Description { get; set; }
public List<BasicTask> Items { get; set; }
public static TaskList Generate(string title, string description, List<BasicTask> items)
=> new TaskList() { Title = title, Description = description, Items = items };
}
A second ListView binds to the Items property of the selected TaskList within the TaskLists ObservableCollection, and it's the second ListView that does not get updated.
public int SelectedTaskListIndex
{
get { return selectedTaskListIndex; }
set
{
selectedTaskListIndex = value;
SelectedTaskList = (value != -1) ? TaskLists[value] : TaskList.Default;
RaisePropertyChanged(nameof(SelectedTaskListIndex));
}
}
public TaskList SelectedTaskList
{
get { return selectedTaskList; }
set
{
selectedTaskList = value;
RaisePropertyChanged(nameof(SelectedTaskList));
}
}
<ListView ItemsSource="{Binding SelectedTaskList.Items}"
Any and all help with this issue will be appreciated :)
In my ViewModel (which implements INotifyPropertyChanged), I've got an
ObservableCollection which is databound to a ListView in
XAML
class TaskList {
public string Title { get; set; }
public string Description { get; set; }
public List Items { get; set; } ... }
Actually, I noticed that you created a List property in TaskList, if you replace it with ObservableCollection and also notify the UI in the right way, the UI updating should works, here is my DeleteTask() method:
private void DeleteTask()
{
SelectedTaskList.Items.Remove(SelectedTask);
}
Both SelectedTaskList and SelectedTask has notified the UI timely.
By the way, to update/raise CanExecuteChanged, I used this solution provided by Jerry Nixon - MSFT in this case
public TaskList SelectedTaskList
{
get { return selectedTaskList; }
set
{
selectedTaskList = value;
RaisePropertyChanged(nameof(SelectedTaskList));
DeleteTaskCommand.RaiseCanExecuteChanged();
}
}
public BasicTask SelectedTask
{
get { return selectedTask; }
set
{
selectedTask = value;
RaisePropertyChanged(nameof(SelectedTask));
DeleteTaskCommand.RaiseCanExecuteChanged();
}
}
Please check my feasible sample on Github
Related
I am struggling to pass an object from one view to another using .Net Maui and a UI independent MVVM pattern.
Most or all MAUI examples I have found to date use and promote MVVM. They also tend to store the View-Models within the UI project and utilize MAUI App Shell navigation directly. This in my opinion and understanding potentially omits some of the benefits of MVVM as View Models would not work with other UI projects.
I have attempted to create a working example* with the View Models, Models and Services being in a separate UI independent project and referenced by the UI project(s) which contain the Views. Sample Project Published to GitHub I would like to update the project with a solution so it can be a working example.
I am specifically stuck where when clicking on a single object from a list of objects it opens a detail view but the object is not passed successfully. In this case my example utilizes a list of customer orders and I am attempting to open the order in a detail view when clicking on an order from the order list.
My problem lies somewhere between the XAML binding to the GoToOrdersCommand, how the command is implemented, and Order object passed to the Order View-Model.
Why do I have both a DelegateCommand and RelayCommand, I am not sure. Different examples I have viewed have used these names, I am not sure if one name is correct. Or if they should be combined into one class. I believe the only significant difference for this example is that RelayCommand accepts an object as a parameter.
Orders View-Model
namespace Orders.Common.ViewModel
{
public class OrdersViewModel : ViewModelBase
{
public ObservableCollection<Order> Orders { get; } = new();
private readonly OrderDataProvider _orderDataProvider;
private IOrderNavigation _navigationService;
public RelayCommand<Order> GoToOrdersCommand { get; }
private Order _order;
public OrdersViewModel(IOrderNavigation navigationService)
{
Orders = new ObservableCollection<Order>();
_orderDataProvider = new OrderDataProvider();
_navigationService = navigationService;
_order = new Order();
GoToOrdersCommand = new RelayCommand<Order>((order) => OrderDetails(_order));
Load();
}
public void Load()
{
var orders = _orderDataProvider.GetAllOrders();
Orders.Clear();
foreach (var order in orders)
Orders.Add(order);
}
public void OrderDetails(Order order)
{
if (order == null)
return;
_navigationService.NavigateToOrderAsync(order);
}
}
}
Order View-Model
public class OrderViewModel : ViewModelBase
{
private Order _order;
public Order Order
{
get => _order;
set
{
if (_order != value)
{
_order = value;
RaisePropertyChanged();
}
}
}
private OrderDataProvider _orderDataProvider = new OrderDataProvider();
public DelegateCommand SaveCommand { get; }
public ICommand SaveOrder { get; set; }
public OrderViewModel()
{
_order = new Order() { OrderID = -1, Customer = "", OrderDate = DateTime.Now };
SaveCommand = new DelegateCommand(Save, () => CanSave);
}
public OrderViewModel(Order order)
{
_order = order;
SaveCommand = new DelegateCommand(Save, () => CanSave);
}
public bool CanSave => !string.IsNullOrEmpty(CustomerName) && CustomerName.Length >= 3;
public void Save()
{
throw new NotImplementedException();
}
public int OrderID
{
get => _order.OrderID;
set
{
if (_order.OrderID != value)
{
_order.OrderID = value;
RaisePropertyChanged();
}
}
}
public string CustomerName
{
get => _order.Customer;
set
{
if (_order.Customer != value)
{
_order.Customer = value;
RaisePropertyChanged();
RaisePropertyChanged(nameof(CanSave));
SaveCommand.RaiseCanExecuteChanged();
}
}
}
public DateTime OrderDate
{
get => _order.OrderDate;
set
{
if (_order.OrderDate != value)
{
_order.OrderDate = value;
RaisePropertyChanged();
}
}
}
}
Inherited UI Specific Navigation
namespace TestOrders.Navigate
{
public class OrderNavigationService : IOrderNavigation
{
public void NavigateToOrdersAsync()
{
Shell.Current.GoToAsync(nameof(OrdersPage));
}
public void NavigateToOrderAsync(Order order)
{
Shell.Current.GoToAsync(nameof(OrderPage), true, new Dictionary<string, object> { { "Order", order } });
}
}
}
RelayCommand
namespace Orders.Common.ViewModel.Command
{
public class RelayCommand<T> : ICommand
{
private Action<object> _execute;
private Func<object, bool> _canExecute;
public RelayCommand(Action<object> execute, Func<object, bool> canExecute = null)
{
_execute = execute ?? throw new ArgumentNullException(nameof(execute));
_canExecute = canExecute;
}
public event EventHandler CanExecuteChanged;
public void RaiseCanExecuteChanged()
{
CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}
public bool CanExecute(object parameter)
{
return _canExecute == null || _canExecute(parameter);
}
public void Execute(object parameter)
{
_execute(parameter);
}
}
}
Orders View
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="TestOrders.Pages.OrdersPage"
Title="OrdersPage"
xmlns:model="clr-namespace:Orders.Common.Model;assembly=Orders.Common"
xmlns:viewmodel="clr-namespace:Orders.Common.ViewModel;assembly=Orders.Common"
x:DataType="viewmodel:OrdersViewModel">
<VerticalStackLayout>
<Label Text="Orders" HorizontalOptions="Center"/>
<CollectionView
ItemsSource="{Binding Orders}"
SelectionMode="None">
<CollectionView.ItemTemplate>
<DataTemplate x:DataType="model:Order">
<Grid Padding="10">
<Frame HeightRequest="70">
<Frame.GestureRecognizers>
<TapGestureRecognizer
Command="{Binding Source={RelativeSource AncestorType={x:Type viewmodel:OrdersViewModel}}, Path=GoToOrdersCommand}"
CommandParameter="{Binding .}"/>
</Frame.GestureRecognizers>
<Grid Padding="0" ColumnDefinitions="20,*">
<Label Text="{Binding OrderID}" Grid.Column="0"/>
<Label Text="{Binding Customer}" Grid.Column="1"/>
</Grid>
</Frame>
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</VerticalStackLayout>
</ContentPage>
*Being new to .Net Maui, XAML based UI's, MVVM patterns and inexperienced with additional topics such as interfaces and delegates there are likely best practices or coding conventions I have missed. I am also trying to avoid utilizing the MVVM community toolkit or other toolkits until I have a better understanding of MVVM and how the interfaces and commands function.
I believe that this
GoToOrdersCommand = new RelayCommand<Order>((order) => OrderDetails(_order));
should be
GoToOrdersCommand = new RelayCommand<Order>((order) => OrderDetails(order));
_order is a new instance that you create on the preceding line, NOT the instance being passed in from the view's binding
Change to OrdersViewModel Constructor
public OrdersViewModel(IOrderNavigation navigationService)
{
Orders = new ObservableCollection<Order>();
_orderDataProvider = new OrderDataProvider();
_navigationService = navigationService;
GoToOrdersCommand = new RelayCommand<Order>((order) => OrderDetails((Order)order));
GoToNewOrderCommand = new DelegateCommand(NewOrder); // Additional navigate without object
Load();
}
Additional Command method to navigate to a New/Empty Order Detail for reference added to the OrdersViewModel
private void NewOrder()
{
_navigationService.NavigateToOrderAsync();
}
Updated Order Navigation
public class OrderNavigationService : IOrderNavigation
{
public void NavigateToOrdersAsync()
{
Shell.Current.GoToAsync(nameof(OrdersPage));
}
public void NavigateToOrderAsync(Order order)
{
Shell.Current.GoToAsync(nameof(OrderPage), true, new Dictionary<string, object> { { "Order", order } });
}
public void NavigateToOrderAsync()
{
Shell.Current.GoToAsync(nameof(OrderPage));
}
}
OrderPage View Code Behind File
Added QueryProperty, a field and method to create an OrderViewModel if an Order object is provided.
[QueryProperty(nameof(order), "Order")]
public partial class OrderPage : ContentPage
{
public Order order
{
set
{
Load(value);
}
}
public OrderPage()
{
InitializeComponent();
BindingContext = new OrderViewModel();
}
private void Load(Order order)
{
if (order != null)
BindingContext = new OrderViewModel(order);
}
}
It is a problem that I face for a long time.
Let say that we have a POCO class called Person (INotifyPropertyChanged is provided using Foldy and its [AddINotifyPropertyChangedInterface] attribute)
[AddINotifyPropertyChangedInterface]
public class Person
{
public int Id{ get; set; }
[StringLength(20)]
[Required(ErrorMessage = "Field required")]
public string FirstName { get; set; }
[Required(ErrorMessage = "Field required")]
public string LastName{ get; set; }
}
And in ViewModel I reference this class as a Property
public class SomeViewModel
{
public Person Person
{
get => person;
set
{
person= value;
SomeMethod();
}
}
// Rest of the code
}
And question is how can I invoke "SomeMethod" when first name is changed in textbox.
Textbox is bound to property as follows:
<TextBox Text="{Binding Person.FirstName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged)/>
I tried to work around it binding to:
public string FirstName
{
get => firstName;
set
{
firstName= value;
Person.FirstName=value;
SomeMethod();
}
}
But the problem is with validation in the user form because I use data annotation attributes in POCO class for validation.
Thank you in advance for your help and your time!
The way I see it, there are 2 options for you to achieve what you want:
Create a view model for Person and move you method to the setter for FirstName. Bind the Textbox to the FirstName property of PersonViewModel and SomeMethod() will be invoked whenver you change texts in TextBox.
public class PersonViewModel
{
private string _firstName;
public string FirstName
{
get => _firstName;
set
{
_firstName = value;
SomeMethod();
}
}
In some cases you must implement SomeMethod() inside the owner ViewModel, then just use the interactivity to bind to TextChanged event of the Textbox and assign you method as invoke method.
xmlns:i = "http://schemas.microsoft.com/expression/2010/interactivity"
<TextBox Name="your_textBox" Text={Binding ...}>
<i:Interactivity.Triggers>
<i:EventTrigger EventName="TextChanged">
<i:InvokeCommandAction Commmand="{Binding YourMethodInViewModel, ElementName=your_textbox}", CommandParameter="{Binding ElementName=your_textbox}"/>
</i:EventTrigger>
</i:Interactivity.Triggers>
</TextBox>
public ICommand YourMethodInViewModel{get;set;}
You would have to listen to property changes of Person:
public class SomeViewModel
{
public ViewModel()
{
this.Person = new Person();
}
public Person Person
{
get => this.person;
set
{
if (this.Person != null)
{
this.Person.PropertyChanged -= OnPersonFirstNameChanged;
}
this.person= value;
if (this.Person != null)
{
this.Person.PropertyChanged += OnPersonFirstNameChanged;
}
}
}
public void OnPersonFirstNameChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(Person.FirstName))
{
SomeMethod();
}
}
}
Alternatively implement a Person.FirstNameChanged event that Person can raise. This improves readability. PropertyChanged is intended primarily for the data binding engine. Since you have to switch on PropertyChangedEventArgs.PropertyChanged in order to find out what happened, you can't handle this event gracefully.
Thanks to the #Thế Long who pointed me to the right direction, for those who faced the same problem - I used "SourceUpdated" event in order to be able to set delay in binding
msdn SourceUpdated
xmlns:i = "http://schemas.microsoft.com/expression/2010/interactivity"
<TextBox Text={Binding = " ..., Delay=500"}>
<i:Interactivity.Triggers>
<i:EventTrigger EventName="SourceChanged">
<i:InvokeCommandAction Commmand="{Binding SomeMethodCommand}"/>
</i:EventTrigger>
</i:Interactivity.Triggers>
</TextBox>
I have my Model class which implements INotifyPropertyChanged.
My View has 5 TextBox, 2 Button and a ListView to display grid.
In my ViewModel I was previously adding default values to the ObservableCollection of my Model class and displaying it to a ListView.
The button implementation is done using ICommand and RelayCommand.
Now I want to add data to the ObservableCollection from the user from the UI TextBox. How can I achieve that? The UI TextBox hasbindings with properties of the Model class.
My View
<ListView Name="UserGrid" Grid.Row="1" Margin="4,178,12,13" ItemsSource="{Binding UserDatas}" >
<TextBox Grid.Row="1" Height="23" HorizontalAlignment="Left" Margin="80,7,0,0" Name="txtUserId" VerticalAlignment="Top" Width="178" Text="{Binding UserId}" />
<TextBox Grid.Row="1" Height="23" HorizontalAlignment="Left" Margin="80,35,0,0" Name="txtFirstName" VerticalAlignment="Top" Width="178" Text="{Binding FirstName}" />
Like these there are 5 TextBoxes
The Model class:
public class User : INotifyPropertyChanged
{
private int userId;
private string firstName;
public int UserId
{
get
{
return userId;
}
set
{
userId = value;
RaisePropertyChanged("UserId");
}
}
}
The ViewModel:
public class UsersViewModel:INotifyPropertyChanged
{
private ObservableCollection<User> userDatas;
public ObservableCollection<User> UserDatas
{
get
{
if (userDatas == null)
{
userDatas = new ObservableCollection<User>();
}
return userDatas;
}
set
{
userDatas = value;
RaisePropertyChanged("UserDatas");
}
}
}
private CommandBase _LoadCommand;
public ICommand LoadCommand
{
get
{
if (this._LoadCommand == null)
this._LoadCommand = new CommandBase(LoadData);
return this._LoadCommand;
}
}
private void LoadData(object obj)
{
//What need to be done here to access the textboxes of UI which are binded to User.cs class.
User newUser = new User();
UserDatas.Add(newUser);
}
Now what I need to write in the LoadData method to take inputfrom textboxes from UI and store it in my ObservableCollection
There are several things you can do. Most obvious, is to have a "Add New" Command, which creates a new empty object and stores it in a CurrentUser or SelectedUser property.
This property is bound to the Template's (or Form's) context. You'd have 3 commands (Add New User, Save User, Cancel to cancel addition of a new user creation).
For example
public class UsersViewModel : INotifyPropertyChanged
{
public UsersViewModel()
{
UserDatas = new ObservableCollection<User>();
AddNewUserCommand = new RelayCommand(AddNewUser, param => !this.IsNewUser);
SaveUserCommand = new RelayCommand(SaveUser);
CancelNewUserCommand = new RelayCommand(CancelNewUser, param => this.IsNewUser);
}
private ObservableCollection<User> userDatas;
public ObservableCollection<User> UserDatas
{
get { return userDatas; }
set
{
userDatas = value;
RaisePropertyChanged("UserDatas");
}
}
private User selectedUser;
public User SelectedUser
{
get { return selectedUser; }
set
{
selectedUser = value;
RaisePropertyChanged("SelectedUser");
RaisePropertyChanged("IsNewUser");
}
}
public bool IsNewUser
{
get
{
if(SelectedUser==null)
return false;
return SelectedUser.UserId == 0;
}
}
public ICommand AddNewUserCommand { get; private set; }
public ICommand CancelNewUserCommand { get; private set; }
public ICommand SaveUserCommand { get; private set; }
private void AddNewUser()
{
SelectedUser = new User();
}
private void SaveUser()
{
// Just in case of concurency
var newUser = SelectedUser;
if(newUser == null)
{
return;
}
var isNewUser = newUser.UserId == 0;
// Persist it to the database
this.userRepository.Add(newUser);
this.userRepository.SaveChanges();
// If all worked well, add it to the observable collection
if(isNewUser)
{
// Only add if new, otherwise it should be already in the collection
UserDatas.Add(newUser)
}
}
}
But again, it's very discouraged to work directly on the model and bind it to the View. You should also create a ViewModel for your User and put validation (implement the IDataErrorInfo interface on the UserViewModel) in there and handling of state, for example tracking if the UserViewModel is dirty (i.e. data was changed).
All these are presentation concerns and not business logic, so they belong to a ViewModel and not to the Model itself.
So I'm building a program that pulls Table and Field names out of a Microsoft Access database and puts them in to two different Listbox items. As a test for my binding I had both boxes setup identically (Just a simple copy/paste because I was getting ahead of myself) and I got the Table names to bind successfully but only using lbTables.DataContext = this;(I tried to use lbTables.SetBinding (ListBox.DataContextProperty, new Binding ("MDBtoCSV.MainWindow")); but it doesn't work for some reason). The DataContext doesn't seem to inherit from the window above it which from what I've read is what what it's supposed to do.
When I began trying to pin down the DataContext on lbFields in XAML I found an odd problem. if I define and initialize the collection at the same time, globally(as below), private ObservableCollection<CheckedListItem<Table>> _listTables = new ObservableCollection<CheckedListItem<Label>> (); both ListBox behave how I would expect them too.
But if I instead initialize the collection in my code and use lbTables.DataContext = this;, then only lbTables populates while lbFields remains blank. Is there a preferred method or less fragile method to explicitly defining the DataContext and ItemSource?
Below is my XAML:
<ListBox x:Name="lbFields" HorizontalAlignment="Left" Height="70" Margin="10,113,0,0" VerticalAlignment="Top" Width="240"
DataContext="{Binding ElementName=appMainWindow, Mode=OneWay}" ItemsSource="{Binding Path=ListTables, Mode=OneWay}">
</ListBox>
</Grid>
TL;DR: Why would DataContext not inherit from Window? Why doesn't ListBox.DataContext = this act the same as the XAML version?
Edit:
private ObservableCollection<CheckedListItem<Table>> _listTables;// = new ObservableCollection<CheckedListItem<Table>> ();
public ObservableCollection<CheckedListItem<Table>> ListTables
{
get { return _listTables; }
private set { _listTables = value;}
}
public class CheckedListItem<T> : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private bool isChecked;
private T item;
private string name;
public string Name { get; set; }
public CheckedListItem () { }
public CheckedListItem (T item, bool isChecked = false)
{ this.item = item;
this.isChecked = isChecked;}
public T Item
{ get { return item; }
set
{ item = value;
NotifyPropertyChanged("Item");}
}
public bool IsChecked
{ get { return isChecked; }
set
{ isChecked = value;
NotifyPropertyChanged ("IsChecked");}
}
public class Table
{ public string name { get; set; }
private ObservableCollection<string> _listFields = new ObservableCollection<string> (
new string[]{"null","null","null","null"});
public ObservableCollection<string> ListFields { get { return _listFields; } set { _listFields = value; } }
}
I highly recommend you take a look at the MVVM pattern example and use that for your WPF and bindings. It makes all of this a lot easier.
I would suggest three ViewModel classes: MainWindowViewModel, DatabaseTableViewModel and DatabaseTableColumnViewModel. As you might imagine, they are nested in each respective one with some properties of their own like so:
class MainWindowViewModel : ViewModelBase
{
// Make sure you implement INotifyPropertyChanged and invoke on each property!
public string Title { get; set; }
public ObservableCollection<DatabaseTableViewModel> Tables { get; set; }
public DatabaseTableViewModel SelectedTable { get; set; }
}
class DatabaseTableViewModel : ViewModelBase
{
public string TableName { get; set; }
public ObservableCollection<DatabaseTableColumnViewModel> Columns { get; set; }
}
class DatabaseTableColumnTableViewModel : ViewModelBase
{
public string ColumnName { get; set; }
public string ColumnType { get; set; }
public DatabaseTableColumnViewModel SelectedColumn { get; set; }
}
So your XAML bindings would look something like this:
<ListBox ItemsSource="{Binding Path=Tables}"
SelectedItem="{Binding Path=SelectedTable}">
</ListBox>
<ListBox ItemsSource="{Binding Path=SelectedTable.Columns}"
SelectedItem="{Binding Path=SelectedTable.SelectedColumn}">
</ListBox>
You will have to work a little bit with this to get multiple selection working but that's the general idea with bindings.
You should be setting your MainWindow.DataContext to an instance of MainWindowViewModel.
Everything I'm doing turned out to work just fine, what I was missing was the INotifyPropertyChanged in the MainWindow. I couldn't figure it out before, but then I found this which is something that I've been looking for for three months.
In this class :
[Export(typeof(IScreen))]
public class BolleViewModel : Screen
{
....
}
i have this List :
public List<Article> List { get; private set; }
This list is the Binding of Datagrid to List :
<DataGrid HorizontalAlignment="Stretch" SelectedItem="{Binding SelectedArticle}"
Margin="14,41,12,61" VerticalAlignment="Stretch" AutoGenerateColumns="False" x:Name="List">
I want that when I call the method UPDATE , updates the values of the List and Datagrid.
This is my update method:
public void Update(List<Article> list)
{
List = list;
NotifyOfPropertyChange("List");
}
What i wrong ? ?
Caliburn.Micro doesn't support convention based binding for DataGrid out of the box, you can see this by checking the ConventionManager static constructor.
You can write your own convention using the ConventionManager, or you can just set the ItemsSource property binding instead in your view.
E.g.
<DataGrid ItemsSource="{Binding Articles}" SelectedItem="{Binding SelectedArticle}"
Margin="14,41,12,61" AutoGenerateColumns="False"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch"> ...
Other points:
List isn't a very good property name for your list of articles
Caliburn.Micro provides a lambda based override for NotifyOfPropertyChange which you should use to catch refactorings
A better pattern for implementing an INPC property is the following (this is because it's no longer the responsibility of the consumer who changes the property to invoke the PropertyChanged event)
Use:
private List<Article> articles;
public List<Article> Articles
{
get
{
return this.articles;
}
private set
{
if (this.articles == value)
{
return;
}
this.articles = value;
this.NotifyOfPropertyChange(() => this.Articles);
}
}
As this is a collection type, you should also ensure that you always return a collection rather than null. This prevents the need for consumers to check to avoid null reference exceptions.
This is viewModel of Datagrid's view:
[Export(typeof(IScreen))]
public class BViewModel : Screen
{
private List<Article> articles;
public List<Article> Articles
{
get
{
return this.articles;
}
private set
{
if (this.articles == value)
{
return;
}
this.articles = value;
this.NotifyOfPropertyChange(() => this.Articles);
}
}
public BolleViewModel()
{
Articles = recoverArticles(); //returns a list of articles
}
public void Update(List<Article> list)
{
Articles = list;
}
//is associated with a button
public void Detail()
{
if(SelectedArticle!=null)
WindowManager.ShowWindow(new DetailArticleViewModel(SelectedArticle, Articles), null, null);
else
{
MessageBox.Show("Select an article","Error!",MessageBoxButton.OK,MessageBoxImage.Error);
}
}
}
The DetailArticleViewModel change an item of Articles list and call Update method of BViewModel.
[Export(typeof(IScreen))]
public class DetailArticleViewModel : Screen
{
public List<Article > GeneralList;
public Article ArticleSelected;
public BViewModel bw;
public DetailArticleViewModel(Article art,List<Article> arts,BViewModel viewmodel)
{
ArticleSelected = art;
GeneralList = arts;
bw = viewmodel;
}
// is associated with a button
public void Save()
{
var index = GeneralList.FindIndex(item => item.Code.CompareTo(ArticleSelected.Code)==0);
GeneralList[index].Price = 900;
bw.Update(List);
}
}
But the price of Selected articles is not 900 ! Why ?