I have created ChatMessageGroup and ChatMessageGroupCollection and a ListView with ItemsSource set to CollectionViewSource:
<ListView x:Name="ChatMessageLv" ItemsSource="{Binding SelectedChat.ChatMessageGroupCollection.Cvs.View}" ItemTemplateSelector="{StaticResource ChatMessageDataTemplateSelector}">
public class ChatMessageGroup : IGrouping<DateTime, ChatMessage>, INotifyCollectionChanged
{
private ObservableCollection<ChatMessage> _chatMessages;
public DateTime Key { get; set; }
public ObservableCollection<ChatMessage> ChatMessages
{
get { return _chatMessages; }
set
{
if (_chatMessages != null)
_chatMessages.CollectionChanged -= CollectionChanged;
_chatMessages = value;
_chatMessages.CollectionChanged += CollectionChanged;
}
}
public ChatMessageGroup()
{
ChatMessages = new ObservableCollection<ChatMessage>();
}
public IEnumerator<ChatMessage> GetEnumerator()
{
return ChatMessages.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public event NotifyCollectionChangedEventHandler CollectionChanged;
}
public class ChatMessageGroupCollection : IEnumerable<ChatMessageGroup>
{
private readonly ObservableCollection<ChatMessageGroup> _groups;
public ObservableCollection<ChatMessage> Source { get; set; }
public CollectionViewSource Cvs { get; set; }
public ChatMessageGroupCollection(ObservableCollection<ChatMessage> messages)
{
Source = messages;
messages.CollectionChanged += Messages_CollectionChanged;
var groups = messages
.GroupBy(GetGroupKey)
.Select(x => new ChatMessageGroup()
{
Key = x.Key,
ChatMessages = x.OrderBy(GetGroupKey).ToObservableOrDefault()
})
.OrderBy(x => x.Key);
_groups = new ObservableCollection<ChatMessageGroup>(groups);
Cvs = new CollectionViewSource() { IsSourceGrouped = true, Source = _groups };
}
...
Everything works fine in here except the changes inside group collection:
_groups.Add(new ChatMessageGroup()); -> this line reflect changes in ListView
but if I do like this: _groups[0].ChatMessages.Add(new ChatMessage()) it doesn't work even though ChatMessageGroup is implementing INotifyCollectionChanged and is raised every time ChatMessages ObservableCollection is changed.
The workaround is to update ChatMessages and remove group from _groups and then add it again but it's not a solution. Refresh() on CollectionViewSource is not available in UWP. Are there any other solutions?
Whilst this doesn't technically qualify as an answer to the question I do think it might qualify as an architectural answer.
The way to make a grouped list view update based on the underlying collection changing in WPF is as simple as setting IsLiveGroupingRequested to true on the CollectionViewSource instance declared in XAML.
As I've been working my way through UWP (after nearly a decade of WPF) I've come to the conclusion that Microsoft are suggesting through omission that this isn't the right approach to the problem for UWP. So going to the lengths of implementing the feature yourself could be construed as missing the point somewhat.
In my particular situation I've decided to change my approach entirely and implement the rendering as multiple instance of ListViews as opposed to forcing an old paradigm onto a new platform.
The result of this actually made me arrive at a solution that improved my UX to boot.
Food for thought ...
Related
I'm fighting it the second day and I'm just fed up.
I'm getting weird exceptions connected with my UI.
First things first.
My model looks basically like that:
Base class:
public class DbItem: ObservableModel
{
public virtual Document ParentDocument { get; set; }
Guid id;
public virtual Guid Id
{
get { return id; }
set
{
if (id != value)
{
id = value;
NotifyPropertyChanged();
}
}
}
string name = string.Empty;
public virtual string Name
{
get { return name; }
set
{
if (value == null || name != value)
{
name = value;
NotifyPropertyChanged();
}
}
}
}
Next we have PeriodBase class:
public enum PeriodType
{
Year,
Sheet
}
public abstract class PeriodBase : DbItem
{
public virtual Period ParentPeriod { get; set; }
public virtual PeriodType PeriodType { get; set; }
}
There are some more properties, but I just deleted them here for clarity.
Next, we have Period class that inherits from PeriodBase:
public class Period : PeriodBase
{
IList<PeriodBase> periods = new ObservableCollection<PeriodBase>();
public virtual IList<PeriodBase> Periods
{
get { return periods; }
set
{
if (periods != value)
{
periods = value;
NotifyPropertyChanged();
}
}
}
}
Now, Period can have other periods and Sheets (which also inherites from PeriodBase):
public class Sheet : PeriodBase
{
DateTimeOffset startDate;
public override DateTimeOffset StartDate
{
get { return startDate; }
set
{
if (startDate != value)
{
startDate = value;
NotifyPropertyChanged();
}
}
}
DateTimeOffset endDate;
public override DateTimeOffset EndDate
{
get { return endDate; }
set
{
if (endDate != value)
{
endDate = value;
NotifyPropertyChanged();
}
}
}
}
And finally we have document class, that is made up of Periods:
public class Document: DbItem
{
IList<Period> periods = new ObservableCollection<Period>();
public virtual IList<Period> Periods
{
get { return periods; }
set
{
if (periods != value)
{
periods = value;
NotifyPropertyChanged();
}
}
}
}
As you may guess, I get a tree hierarchy like that:
- Document
- Period 1
- Sheet 1
My bindings look like this:
public class DocumentMap : DbItemMap<Document>
{
public DocumentMap()
{
Table("documents");
HasMany(x => x.Periods).ForeignKeyConstraintName("ParentDocument_id");
}
}
public class PeriodBaseMap: DbItemMap<PeriodBase>
{
public PeriodBaseMap()
{
UseUnionSubclassForInheritanceMapping();
References(x => x.ParentPeriod);
Map(x => x.Name).Not.Nullable();
Map(x => x.PeriodType).CustomType<PeriodType>();
}
}
public class PeriodMap : SubclassMap<Period>
{
public PeriodMap()
{
Table("periods");
Abstract();
References(x => x.ParentDocument);
HasMany(x => x.Periods).Inverse().Not.LazyLoad();
}
}
public class SheetMap : SubclassMap<Sheet>
{
public SheetMap()
{
Table("sheets");
Abstract();
Map(x => x.StartDate);
Map(x => x.EndDate);
}
}
For now, I just do eager loading everywhere. Just for simplicity.
Now WPF. This is how I create my TreeView (I'm using syncfusion controls):
<sf:TreeViewAdv>
<sf:TreeViewItemAdv
Header="Document"
LeftImageSource="../Resources/database.png"
ItemsSource="{Binding Periods}"
IsExpanded="True"
>
<sf:TreeViewItemAdv.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Periods}"> <!-- Period -->
<TextBlock Text="{Binding Name}"/>
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/> <!-- Sheet -->
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</sf:TreeViewItemAdv.ItemTemplate>
</sf:TreeViewItemAdv>
</sf:TreeViewAdv>
And everything works until I save the records. It's just simple SaveAsync's in one transaction.
Everything gets saved but then I get a weird error. Application crashes with message: Cannot cast TreeViewItemAdv to PeriodBase.
What the heck? I can't even find the place when it's really throws.
This is stacktrace from exception info:
in NHibernate.Collection.Generic.PersistentGenericBag`1.System.Collections.IList.IndexOf(Object value)
in System.Windows.Data.ListCollectionView.InternalIndexOf(Object item)
in Syncfusion.Windows.Tools.Controls.TreeViewItemAdv.Initialize(FrameworkTemplate template)
in Syncfusion.Windows.Tools.Controls.TreeViewItemAdv.TreeViewItemAdv_Loaded(Object sender, RoutedEventArgs e)
in System.Windows.EventRoute.InvokeHandlersImpl(Object source, RoutedEventArgs args, Boolean reRaised)
in System.Windows.UIElement.RaiseEventImpl(DependencyObject sender, RoutedEventArgs args)
in System.Windows.BroadcastEventHelper.BroadcastEvent(DependencyObject root, RoutedEvent routedEvent)
in System.Windows.BroadcastEventHelper.BroadcastLoadedEvent(Object root)
in MS.Internal.LoadedOrUnloadedOperation.DoWork()
in System.Windows.Media.MediaContext.FireLoadedPendingCallbacks()
in System.Windows.Media.MediaContext.FireInvokeOnRenderCallbacks()
in System.Windows.Media.MediaContext.RenderMessageHandlerCore(Object resizedCompositionTarget)
in System.Windows.Media.MediaContext.RenderMessageHandler(Object resizedCompositionTarget)
in System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs)
in System.Windows.Threading.ExceptionWrapper.TryCatchWhen(Object source, Delegate callback, Object args, Int32 numArgs, Delegate catchHandler)
What's important, I get the same error after I start the application and load the document and click on the expander in treeview to expand Period. But everything works fine when I run the app for the first time, until I save the document.
What can be the problem?
In reply to Mark Feldman's post
I decided to reply in an answer as this is too long to comment. This is my first meeting with ORM, so I may have some wrong thoughts about this. I have just one model in my solution. Normally (using SQL) it would work. I would take an object, INSERT it into DB, and the other way also.
So I did the same way here. I just have one business model which has some simple business rules. It is used in ViewModels, and it's stored in db. Is it bad solution? Should I have another model and somewhat break DRY principle?
In my head it was suppose to work like this: User clicks "Create new Sheet". Here you are (this is part of my ViewModel -> method that is called from command):
void CreateNewSheetInActiveDocument()
{
Sheet sh = ActiveDocument.CreateItem<Sheet>();
ActiveDocument.LastPeriod.Periods.Add(sh);
}
This is more like pseudocode but it keeps the idea. Active document creates my sheet. This is done so because document signs to PropertyChanged event just to know if it was modified. Periods is ObservableCollection, so that I can react to adding and removing elements. Thanks to that period can set parentPeriod for my sheet automatically.
And then user saves it to db:
async Task SaveDocument(Document doc)
{
foreach(var item in doc.ModifiedItems)
db.SaveOrUpdate(item);
}
ModifiedItems is simply just a dictionary that keeps items that were modified. Thanks to this I don't have to save the whole document, just modified items.
So as far as I understand you this is not the way it should be. So what would be the PROPER way to do that? Or maybe ORM is not suitable here?
Unless there have been major changes to NHibernate in the years since I've used it you can't just derive your model classes from ObservableModel and expect it to work. It appears that your reasoning for this is to give INPC to your DB models, which some would argue isn't good separation of concerns and suggests that your view model layer hasn't been designed properly.
That said, if you really are adamant about doing it then instead of deriving your entities from ObservableModel try using something like Castle Dynamic Proxy to inject INPC into your entities when NHibernate first creates them. Ayende Rahien's post NHibernate & INotifyPropertyChanged shows how to do this and also provides the code you'll need.
The next problem you'll face is the issue of collections. Again, you can't just assign an ObservableCollection<T> to an IList<T> property and expect it to work, NHibernate replaces the entire list when it deserializes collections back in rather than using add/remove on an existing collection that you've already assigned. It's possible to replace the list with an ObserveableCollection<T> after its been loaded, but if you do that then NHibernate will think the entire list has changed, irrespective of whether it has or not, and serialize the whole thing back out again. You'll get away with it at first, but pretty soon the performance hit is going to start to hurt.
To work around that problem you're going to have to use a convention so that NHibernate creates collection entities that support INotifyCollectionChanged. Unfortunately the page where I originally read about this has long since disappeared, so I'll have to just post the code here (regrettably without attribution). I've only used conventions with NHibernate Fluent, so I'll leave you to find out how to apply them in your own case, but here's what you need...
public class ObservableBagConvention : ICollectionConvention
{
public void Apply(ICollectionInstance instance)
{
Type collectionType = typeof(ObservableBagType<>)
.MakeGenericType(instance.ChildType);
instance.CollectionType(collectionType);
instance.LazyLoad();
}
}
public class ObservableBagType<T> : CollectionType, IUserCollectionType
{
public ObservableBagType(string role, string foreignKeyPropertyName, bool isEmbeddedInXML)
: base(role, foreignKeyPropertyName, isEmbeddedInXML)
{
}
public ObservableBagType()
: base(string.Empty, string.Empty, false)
{
}
public IPersistentCollection Instantiate(ISessionImplementor session, ICollectionPersister persister)
{
return new PersistentObservableGenericBag<T>(session);
}
public override IPersistentCollection Instantiate(ISessionImplementor session, ICollectionPersister persister, object key)
{
return new PersistentObservableGenericBag<T>(session);
}
public override IPersistentCollection Wrap(ISessionImplementor session, object collection)
{
return new PersistentObservableGenericBag<T>(session, (ICollection<T>)collection);
}
public IEnumerable GetElements(object collection)
{
return ((IEnumerable)collection);
}
public bool Contains(object collection, object entity)
{
return ((ICollection<T>)collection).Contains((T)entity);
}
protected override void Clear(object collection)
{
((IList)collection).Clear();
}
public object ReplaceElements(object original, object target, ICollectionPersister persister, object owner, IDictionary copyCache, ISessionImplementor session)
{
var result = (ICollection<T>)target;
result.Clear();
foreach (var item in ((IEnumerable)original))
{
if (copyCache.Contains(item))
result.Add((T)copyCache[item]);
else
result.Add((T)item);
}
return result;
}
public override object Instantiate(int anticipatedSize)
{
return new ObservableCollection<T>();
}
public override Type ReturnedClass
{
get
{
return typeof(PersistentObservableGenericBag<T>);
}
}
}
That's the code for the convention, you use it with this collection class:
public class PersistentObservableGenericBag<T> : PersistentGenericBag<T>, INotifyCollectionChanged,
INotifyPropertyChanged, IList<T>
{
private NotifyCollectionChangedEventHandler _collectionChanged;
private PropertyChangedEventHandler _propertyChanged;
public PersistentObservableGenericBag(ISessionImplementor sessionImplementor)
: base(sessionImplementor)
{
}
public PersistentObservableGenericBag(ISessionImplementor sessionImplementor, ICollection<T> coll)
: base(sessionImplementor, coll)
{
CaptureEventHandlers(coll);
}
public PersistentObservableGenericBag()
{
}
#region INotifyCollectionChanged Members
public event NotifyCollectionChangedEventHandler CollectionChanged
{
add
{
Initialize(false);
_collectionChanged += value;
}
remove { _collectionChanged -= value; }
}
#endregion
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged
{
add
{
Initialize(false);
_propertyChanged += value;
}
remove { _propertyChanged += value; }
}
#endregion
public override void BeforeInitialize(ICollectionPersister persister, int anticipatedSize)
{
base.BeforeInitialize(persister, anticipatedSize);
CaptureEventHandlers(InternalBag);
}
private void CaptureEventHandlers(ICollection<T> coll)
{
var notificableCollection = coll as INotifyCollectionChanged;
var propertyNotificableColl = coll as INotifyPropertyChanged;
if (notificableCollection != null)
notificableCollection.CollectionChanged += OnCollectionChanged;
if (propertyNotificableColl != null)
propertyNotificableColl.PropertyChanged += OnPropertyChanged;
}
private void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
{
PropertyChangedEventHandler changed = _propertyChanged;
if (changed != null) changed(this, e);
}
private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
NotifyCollectionChangedEventHandler changed = _collectionChanged;
if (changed != null) changed(this, e);
}
}
And that's it! Now NHibernate will deserialize your collections as type PersistentObservableGenericBag<T>.
So that's how you inject INPC into entities at runtime, but there are a couple of ways to accomplish what you need without actually having to do that. Apart from being easier to implement they also don't require the use of reflection, which is a factor if you ever need to migrate your code to something that doesn't allow it (e.g. Xamarin.iOS). Adding basic INPC can be achieved by simply adding ProprtyChanged.Fody which will add it to your class properties IL automatically at build time. For change collection you're better off keeping your collections as type IList<T>, representing them with classes of type ObserveableCollection<T> in your view models and then just writing a bit of code, or a helper function, to keep the two synchronized.
UPDATE: I managed to track down the original project where I got that code, it's part of Fabio Maulo's uNhAddIns project.
After Mark Feldman's changes, the error still occures. But when I changed the tree control to standard one, the problem went away. That means there is an error in Syncfusion control. I have reported it.
I'm creating a WinForms application with a DataGridView. The DataSource is a ReactiveList. Adding new items to the list however does not update the UI.
ViewModel
public class HomeViewModel: ReactiveObject
{
public ReactiveCommand<object> AddCmd { get; private set; }
ReactiveList<Model> _models;
public ReactiveList<Model> Models
{
get { return _models; }
set { this.RaiseAndSetIfChanged(ref _models, value); }
}
public HomeViewModel()
{
Models = new ReactiveList<Model>() { new Model { Name = "John" } };
AddCmd = ReactiveCommand.Create();
AddCmd.ObserveOn(RxApp.MainThreadScheduler);
AddCmd.Subscribe( _ =>
{
Models.Add(new Model { Name = "Martha" });
});
}
}
public class Model
{
public string Name { get; set; }
}
View
public partial class HomeView : Form, IViewFor<HomeViewModel>
{
public HomeView()
{
InitializeComponent();
VM = new HomeViewModel();
this.OneWayBind(VM, x => x.Models, x => x.gvData.DataSource);
this.BindCommand(VM, x => x.AddCmd, x => x.cmdAdd);
}
public HomeViewModel VM { get; set; }
object IViewFor.ViewModel
{
get { return VM; }
set { VM = (HomeViewModel)value; }
}
HomeViewModel IViewFor<HomeViewModel>.ViewModel
{
get { return VM; }
set { VM = value; }
}
}
The view always show "John".
Debugging Subscribe show added items.
Tried it with ObservableCollection same result.How to use ReactiveList so UI is updated when new items are added
Tried it with IReactiveDerivedList same result. Does ReactiveUI RaiseAndSetIfChanged fire for List<T> Add, Delete, Modify?
I think what you want is a ReactiveBindingList rather than a ReactiveList. This is a WinForms specific version of the ReactiveList for binding purposes.
You should use BindingList.
reference :
"If you are bound to a data source that does not implement the IBindingList interface, such as an ArrayList, the bound control's data will not be updated when the data source is updated. For example, if you have a combo box bound to an ArrayList and data is added to the ArrayList, these new items will not appear in the combo box. However, you can force the combo box to be updated by calling the SuspendBinding and ResumeBinding methods on the instance of the BindingContext class to which the control is bound."
https://learn.microsoft.com/en-us/dotnet/desktop/winforms/controls/how-to-bind-a-windows-forms-combobox-or-listbox-control-to-data?view=netframeworkdesktop-4.8
Or
ReactiveBindingList
It work fine for me. !!!
I'm working with Catel, MVVM, WPF and am wondering about how to work with nested/hiearchical data.
Let's say from a database I've got a list of Customers, each with a list of Invoices, each with a list of InvoiceItems. Customers own many Invoices which own many InvoiceItems.
I've got a working solution, but I do not like it. My approach was to build a collection of classes that would act a kind of like an ado.net “dataset.” A class would represent each layer of the hiearchy.
This top level class, CustomerModel, would contain a collection of of InvoiceBlocks:
CustomerModel
ObservableCollection of < InvoicesBlocks >
Each InvoceBlock would contain an Invoice and a collection of InvoiceItems:
InvoiceBlock
Invoice
ObservableCollection of < InvoiceItems >
It seemed clever until wading through the databinding path= satements. There are also times when I have to loop through the sets mamaully to update totals, defeating a major selling point of MVVM.
So, I've decided to learn more about grouping with LINQ queries and databinding. Is this the way the pros do it?
What you can do is make each view model responsible for using the right services to retrieve the data.
Note that I did not use Catel properties to make it easy to understand, but you can simply use Catel.Fody or rewrite the properties to get Catel properties.
public class CustomerViewModel
{
private readonly IInvoiceService _invoiceService;
public CustomerViewModel(ICustomer customer, IInvoiceService invoiceService)
{
Argument.IsNotNull(() => customer);
Argument.IsNotNull(() => invoiceService);
Customer = customer;
_invoiceService = invoiceService;
}
public ICustomer Customer { get; private set; }
public ObservableCollection<IInvoice> Invoices { get; private set; }
protected override void Initialize()
{
var customerInvoices = _invoiceService.GetInvoicesForCustomer(Customer.Id);
Invoices = new ObservableCollection<IInvoice>(customerInvoices);
}
}
public class InvoiceViewModel
{
private readonly IInvoiceService _invoiceService;
public InvoiceViewModel(IIinvoice invoice, IInvoiceService invoiceService)
{
Argument.IsNotNull(() => invoice);
Argument.IsNotNull(() => invoiceService);
Invoice = invoice;
_invoiceService = invoiceService;
}
public IInvoice Invoice { get; private set; }
public ObservableCollection<IInvoiceBlock> InvoiceBlocks { get; private set; }
protected override void Initialize()
{
var invoiceBlocks = _invoiceService.GetInvoiceBlocksForInvoice(Invoice.Id);
InvoiceBlocks = new ObservableCollection<IInvoiceBlock>(invoiceBlocks);
}
}
Now you are fully in control what happens when.
I've recently started using the MVVM pattern in silverlight, and i'm not sure if i am using it correctly.
GUI
I currently have a MainView that has combobox of stock market sectors. When the user selects a sector (eg ENERGY) and clicks the Add button a list of stocks for that sector are displayed in a listbox. By the side of each stock in the listbox is a remove button that allows you to remove the individual stock from the listbox.
I have implemented the following ViewModels. (Below is just an indication of the code)
public class MainViewModel
{
public SectorViewModel CurrentSector
{
get;
set;
}
public string SelectedSector
{
get;
set;
}
public void AddSectorClickedCommand()
{
CurrentSector = new SectorViewModel(SelectedSector);
}
}
public class SectorViewModel
{
public ObservableCollection<StockViewModel> Stocks = new ObservableCollection<StockViewModel>();
public SectorViewModel(string sector)
{
List<Stocks> stocklist = StockProvider.GetStocks(sector);
for each (var s in stocklist)
{
StockViewModel svm = new StockViewModel(s);
svm.Remove+= { //Remove svm from Stocks collection logic
Stocks.add(svm);
}
}
}
My question is; in whcih viewmodel is it best to add the code implementation for the Remove button of each row in the listbox?? The Remove button should remove the StockViewModel from the SectorViewModel.Stocks collection.
I have currently added the RemoveClicked method to the StockViewModel(as shown above). This code fires an event back to the SectorViewModel and the RemoveStock method of the SectorViewModel removes the StockViewModel from the Stock collection.
Is there a better way to implement this remove functionality? I'm new to MVVM and am not sure if this is the best approach to develop this functionility, since the SectorViewModel needs to register to events of a StockViewModel.
Personally I don't like events because you should unsubscribe from them and also they can be used where it isn't appropriate.
I would use the constructor parameter to handle the remove command, something like this:
public class StockViewModel
{
public StockViewModel(Stock stock, Action<StockViewModel> removeCommandAction)
{
//...
this.RemoveCommand = new DelegateCommand(() => removeCommandAction(this));
}
}
public class SectorViewModel
{
public SectorViewModel()
{
//...
StockViewModel svm = new StockViewModel(s, this.RemoveStock);
Stocks.add(svm);
}
private void RemoveStock(StockViewModel stock)
{
//...
}
}
Another approach is to use some kind of the EventAggregator pattern, for example, the Messenger class from the MVVM light Toolkit. But I think that it is an overkill for such simple task:
public StockViewModel(Stock stock, IMessenger messenger)
{
//...
this.RemoveCommand = new DelegateCommand(() =>
messenger.Send(new NotificationMessage<StockViewModel>(this, RemoveItemNotification)));
}
public SectorViewModel(IMessenger messenger)
{
//...
messenger.Register<NotificationMessage<StockViewModel>>(this, msg =>
{
if (msg.Notification == StockViewModel.RemoveItemNotification)
{
this.RemoveStock(msg.Content);
}
}
}
Also I heard that Silverlight 5 supports binding to a relative source.
So there is the 3rd approach. I'm not sure whether this example works, but at least it should:
<Button Content="Remove"
Command="{Binding DataContext.RemoveCommand RelativeSource={RelativeSource AncestorType=ListBox}}"
CommandParameter="{Binding}" />
public class SectorViewModel
{
public SectorViewModel()
{
this.RemoveCommand = new DelegateCommand(obj => this.RemoveStock((StockViewModel)obj));
}
public ICommand RemoveCommand { get; set; }
}
The last example is the most preferred by the way and is used in WPF applications because WPF has always had RelativeSource binding.
Is there a best practice or widely accepted way of structuring and validating data using MVVM in conjunction with RIA services in Silverlight?
Here's the crux of my problem. Let's say I have an EmployeeView, EmployeeViewModel and some Employee entity. In regular RIA applications I will expose that Employee entity on the view and I get validation "for free", because Entities implement INotifyDataErrorInfo and IDataErrorInfo (correct?).
Now if I want to expose some Employee properties through a ViewModel instead of directly through an Entity then it becomes more complicated. I could expose the bits that I need directly and hook them into the entity on the backend, like this:
private Employee _employee;
public EmployeeViewModel()
{
_employee = new Employee();
}
public string Name
{
get { return _employee.Name; }
set
{
_employee.Name = value;
// fire property change, etc.
}
}
... but I lose the tasty "free" validation of entities. Otherwise, I could expose the entity directly in the view model, like so
private Employee _employee;
public Employee Employee
{
get { return _employee; }
}
public EmployeeViewModel()
{
_employee = new Employee();
}
In this case, the view will bind directly to the Employee entity and find its properties in there, like so:
<StackPanel DataContext="{Binding Employee}">
<TextBox Text="{Binding Name}" />
</StackPanel>
Using this method we get "free" validation, but it's not exactly a clean implementation of MVVM.
A third option would be to implement INotifyDataErrorInfo and IDataErrorInfo myself in the VMs, but this seems like an awful lot of plumbing code, considering how easy it would be for me to use the above solution and have something slightly less "clean" but a heck of a lot easier at the end of the day.
So I guess my question is, which of these approaches are appropriate in which situation? Is there a better approach I am missing?
In case it's relevant I'm looking at the Caliburn.Micro MVVM framework, but I would be keen to see answers that apply generically.
I am using RIA with Caliburn.Micro and am pretty happy with my solution for client side validation.
What I have done is put a ValidationBaseViewModel between Screen (provided by Caliburn.Micro) and my actual application VMs (EmployeeViewModel in your case). ValidationBaseViewModel implements INotifyDataErrorInfo so that plumbing code your talking about is only written once. I then add/remove/notify of errors via ValidationBaseViewModel from an override of the (Caliburn.Micro) PropertyChangedBase.NotifyOfPropertyChange with the following code:
public override void NotifyOfPropertyChange(string property)
{
if (_editing == null)
return;
if (HasErrors)
RemoveErrorFromPropertyAndNotifyErrorChanges(property, 100);
if (_editing.HasValidationErrors)
{
foreach (var validationError in
_editing.ValidationErrors
.Where(error => error.MemberNames.Contains(property)))
{
AddErrorToPropertyAndNotifyErrorChanges(property, new ValidationErrorInfo() { ErrorCode = 100, ErrorMessage = validationError.ErrorMessage });
}
}
base.NotifyOfPropertyChange(property);
}
This is actually in another VM (between ValidationBaseViewModel and EmployeeViewModel) with the following definition:
public abstract class BaseEditViewModel<TEdit> :
ValidationBaseViewModel where TEdit : Entity
where Entity is RIAs System.ServiceModel.DomainServices.Client.Entity and the _editing class member is an instance of this type TEdit which is being edited by the current VM.
In combination with Caliburn coroutines this allows me to do some cool stuff like the following:
[Rescue]
public IEnumerable<IResult> Save()
{
if (HasErrors)
{
yield return new GiveFocusByName(PropertyInError);
yield break;
}
...
}
If you don't want to use external resources or frameworks, then I you could have a ViewModelBase that implement INotifyDataErrorInfo.
That class will have ValidateProperty(string propertyName, object value) to validate a speciic property, and Validate() method to validate the entire object. Internally use the Validator class to return the ValidationResults.
If you use reflector, it can be pretty easy to achieve by mimicking the validation process in the Entity class itself to the ViewModelBase.
Although it's no "free", is still relatively cheap tho.
Here is a sample implementation of IDataErrorInfo. Although not tested, will give you the idea.
public class ViewModelBase : INotifyPropertyChanged, INotifyDataErrorInfo
{
/*
* InotifyPropertyChanged implementation
* Consider using Linq expressions instead of string names
*/
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
public IEnumerable GetErrors(string propertyName)
{
if (implValidationErrors == null) return null;
return ImplValidationErros.Where(ve =>
ve.MemberNames.Any(mn => mn == propertyName));
}
public bool HasErrors
{
get
{
return implValidationErrors == null || ImplValidationErros.Any();
}
}
private List<ValidationResult> implValidationErrors;
private List<ValidationResult> ImplValidationErros
{
get
{
return implValidationErrors ??
(implValidationErrors = new List<ValidationResult>());
}
}
private ReadOnlyCollection<ValidationResult> validationErrors;
[Display(AutoGenerateField = false)]
protected ICollection<ValidationResult> ValidationErrors
{
get
{
return validationErrors ??
(validationErrors =
new ReadOnlyCollection<ValidationResult>(ImplValidationErros));
}
}
protected void ValidateProperty(string propertyName, object value)
{
ValidationContext validationContext =
new ValidationContext(this, null, null);
validationContext.MemberName = propertyName;
List<ValidationResult> validationResults =
new List<ValidationResult>();
Validator.TryValidateProperty(
value,
validationContext,
validationResults);
if (!validationResults.Any()) return;
validationResults
.AddRange(ValidationErrors
.Where(ve =>
!ve.MemberNames.All(mn =>
mn == propertyName)));
implValidationErrors = validationResults;
if (ErrorsChanged != null)
ErrorsChanged(this, new DataErrorsChangedEventArgs(propertyName));
}
}
you can use a partial class to extend your entitty and add data validation there via idataerrorinfo.