I tried to make some MVVM pattern into my app, and i ran into a problem with hte visual representation of data. The data if the binded observablecollecrion is updated, but the visual is not.
some code:
ViewModel:
public class HlavnaViewModel : BaseViewModel
{
public HlavnaViewModel()
{
}
private Doklady _selectedDok;
public Doklady vm_selectedDok
{
get => _selectedDok;
set
{
_selectedDok = value;
OnPropertyChanged(nameof(vm_selectedDok));
update_polozky();
}
}
public async void update_polozky()
{
Polozky dok = new Polozky() { id_doklad = _selectedDok.id };
ObservableCollection<Polozky> pol = new ObservableCollection<Polozky>(await App.Database.GetPolozkyAsync(dok));
vm_polozky = pol;
}
private ObservableCollection<Polozky> _polozky;
public ObservableCollection<Polozky> vm_polozky
{
get => _polozky;
set
{
_polozky =value;
OnPropertyChanged(nameof(vm_polozky));
}
}
}
in the XAML:
<CollectionView x:Name="polozky" SelectionMode="Single" ItemsSource="{Binding vm_polozky}">...
BaseViewModel:
public class BaseViewModel : INotifyPropertyChanged
{
string title = string.Empty;
public string Title
{
get { return title; }
set { SetProperty(ref title, value); }
}
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;
}
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
var changed = PropertyChanged;
if (changed == null)
return;
changed.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
finally in View:
public Hlavna()
{
InitializeComponent();
hvm = new HlavnaViewModel();
this.BindingContext = hvm;
}
if i select a row in CollectionView where the vm_selectedDok binding is set, it selects that item, fires the update_polozky(), the vm_polozky gets populated with the right data, but the visual just dont shows the items from vm_polozky.
Ive read couple of similar questions, but i cant figure out where i made a mistake.
EDIT:
so the problem was somewhere else, i had the grid.rowdefinitions set just wrong, therefore the grid was outside of the visible area.
#ToolmakerSteve made good suggestions on calling async/await, please read his answer.
There are two alternative ways to fix this.
One way is Gerald's answer. This is fine for small collections, but might be slower if there are many items being added.
The second way is to do what you've done - replace the collection. But there is a fix needed in your code.
The way you've called update_polozky won't work reliably. You don't start the async/await sequence inside an await.
Replace:
update_polozky();
With:
Device.BeginInvokeOnMainThread(async () => {
await update_polozky();
});
OPTIONAL: Might also make this change. (Though it shouldn't be necessary.) This gives a good place to put a breakpoint, to see whether "result" gets the expected contents.
Replace:
ObservableCollection<Polozky> pol = new ObservableCollection<Polozky>(await App.Database.GetPolozkyAsync(dok));
With:
var result = await App.Database.GetPolozkyAsync(dok);
ObservableCollection<Polozky> pol = new ObservableCollection<Polozky>(result);
IMPORTANT NOTES FOR OTHER CODERS:
The second approach ("replace the collection") relies on OnPropertyChanged(nameof(vm_polozky)); in the setter of the ObservableCollection.
You have that, so not an issue for you. I mention this for anyone else who might adapt this code.
For example, I've seen people attempt to set the private value directly, e.g.:
// Don't do this to replace the collection. XAML won't know you changed it!
_myPrivateField = new ObservableCollection<MyItem>(result);
I've also seen people try to have an ObservableCollection without a setter:
// This is okay UNLESS you replace the collection - in which case you need `OnPropertyChanged(nameof(MyCollection))` somewhere:
public ObservableCollection<MyItem> MyCollection { get; set; }
Basically it comes down to, don't do this: ObservableCollection<Polozky> pol = new ObservableCollection<Polozky>(await App.Database.GetPolozkyAsync(dok));
Whenever you create a new ObservableCollection it will lose the databinding to the UI. Clear your ObservableCollection with .Clear() and add new items to it with a for loop. For example:
public async void update_polozky()
{
Polozky dok = new Polozky() { id_doklad = _selectedDok.id };
var results = await App.Database.GetPolozkyAsync(dok);
vm_polozky.Clear();
foreach(var item in results)
vm_polozky.Add(item);
}
Related
I have a collection that gets populated with produce. Once the collection is populated, a BindableLayout/DataTemplate bound to a StackLayout will display the items and the user will be prompted with the option to change the Price property of a stock item by typing into an Entry.
A user can type into the provided Entry box to change Price property of each StockInfo object in the collection, and the change WILL SUCCESSFULLY be applied to the Observable Collection, BUT it WILL NOT fire the setter/property changed event of the Observable Collection.
I need the property changed event to fire so that I can effectively execute other parts of my code, but since it won't the fire setter or property changed of the collection, it never gets the chance to tell other parts of my code to do things.
namespace Test
{
public class Testing : BaseContentPage, INotifyPropertyChanged
{
public class StockInfo : BaseContentPage, INotifyPropertyChanged
{
private string description;
public string Description
{
get => description;
set
{
description = value;
OnPropertyChanged();
}
}
private int price;
public int Price
{
get => price;
set
{
price = value;
OnPropertyChanged();
}
}
}
private ObservableCollection<StockInfo> stockItems = new ObservableCollection<StockInfo>();
public ObservableCollection<StockInfo> StockItems
{
get => stockItems;
set
{
stockItems = value;
OnPropertyChanged();
OnPropertyChanged("SumPrices");
}
}
public double SumPrices
{
get
{
return StockItems.Sum(p => p.Price);
}
}
DataTemplate StockTemplate = new DataTemplate(() =>
{
return new StackLayout
{
Orientation = StackOrientation.Horizontal,
Children =
{
new Entry
{
}.Bind(Entry.TextProperty, path: "Description")
,
new Entry
{
Keyboard = Keyboard.Numeric
}.Bind(Entry.TextProperty, path: "Price")
}
};
});
public Testing()
{
BindingContext = this;
StockItems.Add(new StockInfo { Description = "Milk", Price = 20 });
StockItems.Add(new StockInfo { Description = "Cheese", Price = 15 });
Content = new StackLayout
{
Children =
{
new StackLayout
{
}.Invoke(layout => BindableLayout.SetItemTemplate(layout, StockTemplate))
.Bind(BindableLayout.ItemsSourceProperty, path: "StockItems")
,
new Label
{
}.Bind(Label.TextProperty, path: "SumPrices")
}
};
}
}
}
If I put a debugger stop line inside the get/set of the "Description" property in the StockInfo class and then type in the Entry, the debugger will pick it up and stop the program for debugging.
But if I put a debugger stop on a line some where in the set/get of the Observable Collection, the program will not stop inside of it.
*** Edits Below ***
I modified the code so that StockInfo now has a property that includes the price of a product. I also added a variable called SumPrices which will return the Sum of Price within StockItems using LINQ. The first time the page loads, the sum is calculated and the result is correct, but if I change the Entry box that the property is bound to for each object, it has no effect and the SumPrices variable never changes.
Ideally, I'd simply like for the Observable Collection to fire its setter/property change events whenever an Object's property within the collection is changed.
New Update Here
You cannot fire the setter of ObservableCollection when a property of an item in this collection has changed. I've searched so many info from the Internet and found a question similar to yours: ObservableCollection not noticing when Item in it changes (even with INotifyPropertyChanged). Bob Sammers abstract and define a new FullyObservableCollection class and put forward a pretty robust solution, including some of the techniques in other answers. This new class could get notified when a property of item has been changed. I have tested it and worked well.
Simply used it like the following code:
private FullyObservableCollection<StockInfo> stockItems = new FullyObservableCollection<StockInfo>();
public FullyObservableCollection<StockInfo> StockItems
{
get => stockItems;
set
{
stockItems = value;
OnPropertyChanged();
}
}
public Testing ()
{
...
StockItems.ItemPropertyChanged += StockItems_ItemPropertyChanged;
...
}
private void StockItems_ItemPropertyChanged(object sender, ItemPropertyChangedEventArgs e)
{
OnPropertyChanged(nameof(SumPrices));
}
Another workaround is what i've suggested in my previous answer, which is using TextChanged event handler.
In Datatemplate, add an EventHandler for entry:
new Entry
{
}.Bind(Entry.TextProperty, path: "Description",BindingMode.TwoWay)
.Invoke(entry=>entry.TextChanged+=Entry_TextChanged)
private void Entry_TextChanged(object sender, TextChangedEventArgs e)
{
OnPropertyChanged(nameof(SumPrices));
}
For more info, you could refer to Xamarin.Forms - CollectionView sample
Hope it works for you.
Basic question from a novice. I've been stuck on this and have read through a lot of material and several similar questions on SO; hopefully not a completely duplicate question. I simplified the code as much as I know how to.
I'm trying to make the ListView show a filtered ObservableCollection) property (as the ItemsSource?), based on the selection in the ComboBox.
Specifically, which "meetings" have this "coordinator" related to it.
I'm not seeing any data errors in the output while it's running and debugging shows the properties updating correctly, but the ListView stays blank. I'm trying to avoid any code-behind on the View, there is none currently.
Thanks!
public class ViewModel : INotifyPropertyChanged
{
private ObservableCollection<Meeting> meetings;
public ObservableCollection<Meeting> Meetings
{
get
{
return meetings;
}
set
{
meetings = value;
OnPropertyChanged("ListProperty");
OnPropertyChanged("Meetings");
}
}
private string coordinatorSelected;
public string CoordinatorSelected
{
get
{
return coordinatorSelected;
}
set
{
coordinatorSelected = value;
Meetings = fakeDB.Where(v => v.CoordinatorName == CoordinatorSelected) as ObservableCollection<Meeting>;
}
}
private ObservableCollection<string> comboProperty = new ObservableCollection<string> { "Joe", "Helen", "Sven" };
public ObservableCollection<string> ComboProperty
{
get
{
return comboProperty;
}
}
private ObservableCollection<Meeting> fakeDB = new ObservableCollection<Meeting>() { new Meeting("Joe", "Atlas"), new Meeting("Sven", "Contoso"), new Meeting("Helen", "Acme") };
public ObservableCollection<Meeting> ListProperty
{
get
{
return Meetings;
}
}
public class Meeting
{
public string CoordinatorName { get; set; }
public string ClientName { get; set; }
public Meeting(string coordinatorName, string clientName)
{
CoordinatorName = coordinatorName;
ClientName = clientName;
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
XAML:
<Window.Resources>
<local:ViewModel x:Key="VM"></local:ViewModel>
</Window.Resources>
<DockPanel DataContext="{StaticResource ResourceKey=VM}">
<ComboBox Margin="10" ItemsSource="{Binding ComboProperty}" SelectedItem="{Binding CoordinatorSelected}" DockPanel.Dock="Top"/>
<ListView Margin="10" ItemsSource="{Binding ListProperty, UpdateSourceTrigger=PropertyChanged}" DisplayMemberPath="ClientName"/>
</DockPanel>
Update:
This seems to show that the lambda is returning a Meeting object but the assignment to Meetings is failing. Is this an error in casting maybe?
Thanks again.
You always have to change a property's backing field before you fire a PropertyChanged event. Otherwise a consumer of the event would still get the old value when it reads the property.
Change the Meetings property setter like this:
public ObservableCollection<Meeting> Meetings
{
get
{
return meetings;
}
set
{
meetings = value;
OnPropertyChanged("ListProperty");
OnPropertyChanged("Meetings");
}
}
I believe I've found two solutions to the same problem. The error pointed out #Clemens was also part of the solution. The Meetings property problem is solved if I change ListProperty and Meetings to IEnumerable. Alternatively this approach without changing the type, which I believe invokes the collection's constructor with the filtered sequence as an argument.
set
{
coordinatorSelected = value;
var filteredList = fakeDB.Where(v => v.CoordinatorName == coordinatorSelected);
Meetings = new ObservableCollection<Meeting>(filteredList);
OnPropertyChanged("ListProperty");
}
Context
On the network are servers that advertise their names with UDP at regular intervals.
The datagrams come in on port 1967 and contain a string like this:
UiProxy SomeServerMachineName
New entries are added, existing entries are updated and stale entries age out of an observable collection that serves as the ItemsSource of a XAML combo box.
This is the combo box
<ComboBox x:Name="comboBox" ItemsSource="{Binding Directory}" />
and this is the supporting code. Exception handlers wrap everything dangerous but are here omitted for brevity.
public class HostEntry
{
public string DisplayName { get; set;}
public HostName HostName { get; set; }
public DateTime LastUpdate { get; set; }
public HostEntry(string displayname, HostName hostname)
{
DisplayName = displayname;
HostName = hostname;
LastUpdate = DateTime.Now;
}
public override string ToString()
{
return DisplayName;
}
}
HostEntry _selectedHost;
public HostEntry SelectedHost
{
get { return _selectedHost; }
set
{
_selectedHost = value;
UpdateWriter();
}
}
async UpdateWriter() {
if (_w != null)
{
_w.Dispose();
_w = null;
Debug.WriteLine("Disposed of old writer");
}
if (SelectedHost != null)
{
_w = new DataWriter(await _ds.GetOutputStreamAsync(SelectedHost.HostName, "1967"));
Debug.WriteLine(string.Format("Created new writer for {0}", SelectedHost));
}
}
ObservableCollection<HostEntry> _directory = new ObservableCollection<HostEntry>();
public ObservableCollection<HostEntry> Directory
{
get { return _directory; }
}
private async void _ds_MessageReceived(DatagramSocket sender, DatagramSocketMessageReceivedEventArgs args)
{
if (_dispatcher == null) return;
await _dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
{
var dr = args.GetDataReader();
var raw = dr.ReadString(dr.UnconsumedBufferLength);
var s = raw.Split();
if (s[0] == "UiProxy")
{
if (_directory.Any(x => x.ToString() == s[1]))
{ //update
_directory.Single(x => x.ToString() == s[1]).LastUpdate = DateTime.Now;
}
else
{ //insert
_directory.Add(new HostEntry(s[1], args.RemoteAddress));
}
var cutoff = DateTime.Now.AddSeconds(-10);
var stale = _directory.Where(x => x.LastUpdate < cutoff);
foreach (var item in stale) //delete
_directory.Remove(item);
}
});
}
The collection starts empty.
The UpdateWrite method called from the setter of SelectedHost destroys (if necessary) and creates (if possible) a DataWriter around a DatagramSocket aimed at the address described by the value of SelectedHost.
Goals
Automatically select when a value is added and the list ceases to be empty.
The list can also become empty. When this happens the selection must return to null with a selected index of -1.
As things stand, the list is managed and it is possible to interactively pick a server from the list.
At the moment I am setting SelectedHost like this but I am sure it could be done with binding.
private void comboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
SelectedHost = comboBox.SelectedItem as HostEntry;
}
The setter method for SelectedHost calls CreateWriter which manages the object used elsewhere to send data to the selected host. I've called this from the setter because it must always happen right after the value changes, and at no other time. It's in a method so it can be async.
I could move it to the SelectionChanged handler but if I do that then how can I guarantee order of execution?
Problem
I get errors when I try to programmatically set the selection of the combo box. I am marshalling onto the UI thread but still things aren't good. What is the right way to do this? I've tried setting SelectedIndex and SelectedValue.
I get errors when I try to programmatically set the selection of the combo box.
How are you doing it? In code-behind this should work so long as the collection you are bound to has an item at that index:
myComboBox.SelectedIndex = 4;
but I am sure it could be done with binding
Yes it can, looks like you forgot to implement INotifyPropertyChanged. Also since you are using UWP there is a new improved binding syntax Bind instead of Binding learn more here: https://msdn.microsoft.com/en-us/windows/uwp/data-binding/data-binding-in-depth
<ComboBox x:Name="comboBox" ItemsSource="{Binding Directory}"
SelectedItem="{Binding SelectedHost}" />
public event PropertyChangedEventHandler PropertyChanged;
HostEntry _selectedHost;
public HostEntry SelectedHost
{
get { return _selectedHost; }
set
{
_selectedHost = value;
RaiseNotifyPropertyChanged();
// What is this? propertys should not do things like this CreateWriter();
}
}
// This method is called by the Set accessor of each property.
// The CallerMemberName attribute that is applied to the optional propertyName
// parameter causes the property name of the caller to be substituted as an argument.
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
I'm new to C#/WPF and I would like some clarification on whether I have the proper implementation of my ViewModel.
I have created a simple window with a search text box and list box for the results.
<TextBox Text="{Binding SearchText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
<ListBox ItemsSource="{Binding Results}" />
Then I have a ViewModel with the following code.
private List<string> lstStr;
public ViewModel()
{
lstStr = new List<string>();
lstStr.Add("Mike");
lstStr.Add("Jerry");
lstStr.Add("James");
lstStr.Add("Mikaela");
}
public List<string> LstStr
{
get
{
return lstStr;
}
set
{
if (lstStr != value)
{
lstStr = value;
OnPropertyChanged("LstStr");
}
}
}
private string searchText;
public string SearchText
{
get
{
return searchText;
}
set
{
if (searchText != value)
{
searchText = value;
OnPropertyChanged("SearchText");
UpdateResults();
}
}
}
private ObservableCollection<string> results = new ObservableCollection<string>();
public ObservableCollection<string> Results
{
get
{
return results;
}
set
{
if (results != value)
{
results = value;
OnPropertyChanged("Results");
}
}
}
public void UpdateResults()
{
int i = 0;
results.Clear();
while (i < LstStr.Count)
{
if (LstStr.ElementAt(i).ToString() != null)
{
if (searchText != null && searchText != "")
{
if (LstStr.ElementAt(i).Trim().Contains(searchText))
{
results.Add(LstStr.ElementAt(i));
Console.WriteLine(LstStr.ElementAt(i));
}
}
else
results.Clear();
}
else
Console.WriteLine("NULL");
i++;
}
}
I see myself writing logic in the Get or Set section of code in the ViewModel. Let's say I will have more text boxes and lists that will want to implement. Is this the correct way of coding my logic in the properties or am I completely missing the point? Please help me understand this. Thanks in advance.
No, this isn't exactly right.
First, logic normally goes in the model, not the view model. That said, you have a filter, which is basically UI logic, so its probably OK here.
Second, the filter will only change when you set the search text, so the logic would go in the setter, not the getter. I also wouldn't inline the whole thing, put it in its own function so you can reuse it later:
public String SearchText
{
...
set
{
serachText = value;
NotifyPropertyChanged();
UpdateResults();
}
}
public void UpdateResults()
{
...
}
The one thing to keep in mind (and there isn't really a good way around this) is that if that function takes a long time to run, your UI will really slow down while the user is typing. If the execution time is long, try shortening it, then consider doing it on a separate thread.
ViewModels should have only the responsibility of "converting" data into another form that the view can handle (think INotifyPropertyChanged, ObservableCollection, etc.)
The only time where you'd get away with the ViewModel having any of the logic is when the logic is encapsulated entirely in a collection. e.g. if you can get everything you need out of List<T>, then the ViewModel effectively has all the logic. If you need value beyond that, it should be outside of the ViewModel.
In my first view model (renamed to MainViewModel) I have a list of ActionViewModels.
In my xaml i have a listbox which is bound to the list, in the listbox i have a template which binds to properties from the ActionViewModel.
So far so good and everything works.
When selecting one of the listitems i navigate to an ActionViewModel and pass the id with it.
The ActionViewModel retrieves information from a static list in memory from which the MainViewModel also retrieved the information to create the list of actionviewmodels.
So far still so good, i can edit the properties, all the bindings do work fine and i'm all happy.
By clicking the save button the information is gathered and stored in the static list.
When i hit the back button i go back to the list, but unfortunately the values showing there are still the same, is there some way to send a command to reload the items in the list? To pass a complete viewmodel as reference to a new ActionViewModel? Or some property which tells the parent 'this viewmodel in your list has been updated'?
I am sure the above text is a bit confusing, so here is some code to clarify it a bit (hopefully)
MainViewModel.cs
private List<ActionViewModel> _actionViewModels;
public List<ActionViewModel> ActionViewModels
{
get { return _actionViewModels; }
set { _actionViewModels = value; RaisePropertyChanged(() => ActionViewModels); }
}
private Cirrious.MvvmCross.ViewModels.MvxCommand<int> _navigateToAction;
public System.Windows.Input.ICommand NavigateToAction
{
get
{
_navigateToAction = _navigateToAction ?? new Cirrious.MvvmCross.ViewModels.MvxCommand<int>((action) => NavigateToTheDesiredAction(action));
return _navigateToAction;
}
}
private void NavigateToTheDesiredAction(int action)
{
ShowViewModel<ActionViewModel>(new { id = action });
}
// Get DTOs from server or from cache and fill the list of ActionViewModels
public async Task Load()
{
ActionService actionService = new ActionService();
List<ActionViewModel> actionViewModels = new List<ActionViewModel>();
MyActions = await actionService.GetMyActions();
foreach (ActionDTO action in MyActions)
{
ActionViewModel actionViewModel = new ActionViewModel();
await actionViewModel.Load(action.id);
actionViewModels.Add(actionViewModel);
}
ActionViewModels = actionViewModels;
}
ActionViewModel.cs
public int ID
{
get { return TheAction.id; }
set { TheAction.id = value; RaisePropertyChanged(() => ID); }
}
public string Title
{
get { return TheAction.Title; }
set { TheAction.Title = value; RaisePropertyChanged(() => Title); }
}
public async Task Load(int actionId)
{
ActionDTO TheAction = await actionService.GetAction(actionId);
this.ID = TheAction.id;
this.Title = TheAction.Title;
}
private Cirrious.MvvmCross.ViewModels.MvxCommand _save;
public System.Windows.Input.ICommand Save
{
get
{
_save = _save ?? new Cirrious.MvvmCross.ViewModels.MvxCommand(PreSaveModel);
return _save;
}
}
private void PreSaveModel()
{
SaveModel();
}
private async Task SaveModel()
{
ValidationDTO result = await actionService.SaveAction(TheAction);
}
ActionService.cs
public static List<ActionDTO> AllActions = new List<ActionDTO>();
public async Task<ActionDTO> GetAction(int actionId)
{
ActionDTO action = AllActions.FirstOrDefault(a => a.id == actionId);
if (action == null)
{
int tempActionId = await LoadAction(actionId);
if (tempActionId > 0)
return await GetAction(actionId);
else
return new ActionDTO() { Error = new ValidationDTO(false, "Failed to load the action with id " + actionId, ErrorCode.InvalidActionId) };
}
return action;
}
private async Task<int> LoadAction(int actionId)
{
ActionDTO action = await webservice.GetAction(actionId);
AllActions.Add(action);
return action.id;
}
public async Task<ValidationDTO> SaveAction(ActionDTO action)
{
List<ActionDTO> currentList = AllActions;
ActionDTO removeActionFromList = currentList.FirstOrDefault(a => a.id == action.id);
if (removeActionFromList != null)
currentList.Remove(removeActionFromList);
currentList.Add(action);
AllActions = currentList;
return await webservice.SaveAction(action);
}
There are 3 ways I can think of that would allow you to do this.
The ActionService could send out some sort of notification when data changes. One easy way to do this is to use the MvvmCross Messenger plugin. This is the way the CollectABull service works in CollectionService.cs in the N+1 days of mvvmcross videos (for more info watch N=13 in http://mvvmcross.wordpress.com)
This is the approach I generally use. It has low overhead, uses WeakReferences (so doesn't leak memory), it is easily extensible (any object can listen for changes), and it encourages loose coupling of the ViewModel and Model objects
You could implement some kind of Refresh API on the list ViewModel and could call this from appropriate View events (e.g. ViewDidAppear, OnNavigatedTo and OnResume).
I don't generally use this approach for Refreshing known data, but I have used it for enabling/disabling resource intensive objects - e.g. timers
For certain shape of model data (and especially how often it changes), then I can imagine scenarios where this approach might be more efficient than the messenger approach.
You could extend the use of INotifyPropertyChanged and INotifyCollectionChanged back into your model layer.
I've done this a few times and it's worked well for me.
If you do choose this approach, be careful to ensure that all Views do subscribe to change events using WeakReference subscriptions such as those used in MvvmCross binding - see WeakSubscription. If you didn't do this, then it could be possible for the Model to cause Views to persist in memory even after the UI itself has removed them.