![]() |
Platforms, Frameworks & Libraries »
Windows Presentation Foundation »
General
Intermediate
License: The Code Project Open License (CPOL)
WPF: If Carlsberg did MVVM Frameworks Part 4 of nBy Sacha BarberIt would probably be like Cinch a MVVM framework for WPF |
C# (C#3.0, C#4.0), .NET (.NET3.5, .NET4.0), WPF, Architect, Dev, Design
|
||||||||||||
|
Advanced Search Add to IE Search |
|
|
|
||||||||||||||||
Last time we started looking at the 2nd part of the Cinch internals, and this time we are going to look at what a typical Model and ViewModel might contain when using Cinch.
In this article I will be looking at the following:
The demo app makes use of :
So I guess the only way to do this is to just start, so lets get going shall we, but before we do that I just need to repeat the special thanks section, with one addition, Paul Stovell who I forgot to include last time
Before I start I would specifically like to say a massive thanks to the following people, without whom this article and the subsequent series of articles would never have been possible. Basically what I have done with Cinch is studied most of these guys, seen what's hot, what's not, and come up with Cinch. Which I hope adresses some new ground not covered in other frameworks.
Mark Smith (Julmar Technology), for his excellent MVVM Helper Library, which has helped me enormously. Mark I know I asked your persmission to use some of your code, which you most kindly gave, but I just wanted to say a massive thanks for your cool ideas, some of which I genuinely had not thought of. I take my hat off to you mate.
Josh Smith / Marlon Grech (as an atomic pair) for their excellent Mediator Implementation. You boys rock, always a pleasure
Karl Shifflett / Jaime Rodriguez (Microsoft boys) for their excellent MVVM Lob tour, which I attended, well done lads
Bill Kempf, for just being Bill and being a crazy wizard like programmer, who also has a great MVVM framework called Onyx, which I wrote an article about some time ago. Bill always has the answers, to tough questions, cheers Bill.
Paul Stovell for his excellent delegate validation idea, which Cinch uses for validation of business objects
ALL of the WPF Disciples, for being the best online group to belong to IMHO
Thanks guys/girl, you know who you are
Now I know that there are some readers that will find my next statement controversial and some may even say it is downright wrong, but I will go into all of this in a minute, for now let me just say what I want to say, which is this:
The way I do MVVM is to have an instance of a particular Model (say currentPerson) inside my ViewModel (say PeopleViewModel) which is exposed to the View (say PeopleView). The View binds and edits the Model directly.
This definitely flies in the face of what most people consider to be the holy grail of MVVM pattern, but it's a fairly new pattern, so people are still finding their way with it every day, and this works for me very well. The reason I do what I do, is for the following reasons:
INotifyPropertyChanged/DataContract etc etcSo now that I have told you this, let's say I did not have the luxury of being able to create my own Model layer. Let's say you had another team developing the Model layer, and they did things their own way. Well that's fine what you would do in that case, is treat this section of this article, as if I was actually talking about doing this work to the ViewModel that wraps the Model rather than directly to the Model. Basically apply what I am going to talk about next to your ViewModel (say PeopleViewModel) and leave the other teams provided Model (which presumably you have no control over) alone.
Ok argument over (hopefully). What we now need to decide is what base class you want to use for your Model (or maybe ViewModel if you have no control over your Model). Cinch has 2 choices.
Cinch.EditableValidatingObject (or EditableValidatingViewModel if you need to do this work in ViewModel) : A editable (IEditableObject), validatable (IDataErrorInfo) object. You would need to supply, logic to support both the editing and the validation within your own Model (or ViewModel if you have no control over your Model)
Cinch.ValidatingObject (or ValidatingViewModel if you need to do this work in ViewModel) : A validatable (IDataErrorInfo) object. You would need to supply, logic to support to do validation within your own Model (or ViewModel if you have no control over your Model)
So what I am going to do now is show you en example Model (but this code
could be applied directly to a ViewModel, if you need to do that using either
ValidatingViewModel or EditableValidatingViewModel).
This is the hardest Cinch base class to inherit from (though it's not that hard) as Cinch does most of the work. You will need to do the following:
DataWrapper<T> helpers that allow your
ViewModel to place every individual property into edit mode independent of
any other property. This is you decision YOU must decide. If you choose not
to use a DataWrapper<T> object for your properties, instead of something like
this private Cinch.DataWrapper<Int32> orderId = new DataWrapper<Int32>(),
you would declare the property as normal like private Int32 orderId = 0IDataErrorInfo interface
does its magic and shows the errors on the View.BeginEdit()/OnEndEdit() and OnCancelEdit() methods
inherited from the IEditableObject interface that we get by inheriting from
Cinch.EditableValidatingObject. Again the code below shows you how to do
this.using System;
using Cinch;
using MVVM.DataAccess;
namespace MVVM.Models
{
/// <summary>
/// Respresents a UI Order Model, which has all the
/// good stuff like Validation/INotifyPropertyChanged/IEditableObject
/// which are all ready to use within the base class.
///
/// This class also makes use of <see cref="Cinch.DataWrapper">
/// Cinch.DataWrapper</see>s. Where the idea is that the ViewModel
/// is able to control the mode for the data, and as such the View
/// simply binds to a instance of a <see cref="Cinch.DataWrapper">
/// Cinch.DataWrapper</see> for both its data and its editable state.
/// Where the View can disable a control based on the
/// <see cref="Cinch.DataWrapper">Cinch.DataWrapper</see> editable state.
/// </summary>
public class OrderModel : Cinch.EditableValidatingObject
{
#region Data
//Any data item is declared as a Cinch.DataWrapper, to allow the ViewModel
//to decide what state the data is in, and the View just renders
//the data state accordingly
private Cinch.DataWrapper<Int32> orderId = new DataWrapper<Int32>();
private Cinch.DataWrapper<Int32> customerId = new DataWrapper<Int32>();
private Cinch.DataWrapper<Int32> quantity = new DataWrapper<Int32>();
private Cinch.DataWrapper<Int32> productId = new DataWrapper<Int32>();
private Cinch.DataWrapper<DateTime> deliveryDate = new DataWrapper<DateTime>();
private IEnumerable<DataWrapperBase> cachedListOfDataWrappers;
//rules
private static SimpleRule quantityRule;
#endregion
#region Ctor
public OrderModel()
{
#region Create DataWrappers
OrderId = new DataWrapper<Int32>(this, orderIdChangeArgs);
CustomerId = new DataWrapper<Int32>(this, customerIdChangeArgs);
ProductId = new DataWrapper<Int32>(this, productIdChangeArgs);
Quantity = new DataWrapper<Int32>(this, quantityChangeArgs);
DeliveryDate = new DataWrapper<DateTime>(this, deliveryDateChangeArgs);
//fetch list of all DataWrappers, so they can be used again later without the
//need for reflection
cachedListOfDataWrappers =
DataWrapperHelper.GetWrapperProperties<OrderModel>(this);
#endregion
#region Create Validation Rules
quantity.AddRule(quantityRule);
#endregion
//I could not be bothered to write a full DateTime picker in
//WPF, so for the purpose of this demo, DeliveryDate is
//fixed to DateTime.Now
DeliveryDate.DataValue = DateTime.Now;
}
static OrderModel()
{
quantityRule = new SimpleRule("DataValue", "Quantity can not be < 0",
(Object domainObject)=>
{
DataWrapper<Int32> obj = (DataWrapper<Int32>)domainObject;
return obj.DataValue <= 0;
});
}
#endregion
#region Public Properties
public Cinch.DataWrapper<Int32> OrderId
{
get { return orderId; }
private set
{
orderId = value;
OnPropertyChanged(() => OrderId);
}
}
public Cinch.DataWrapper<Int32> CustomerId
{
get { return customerId; }
private set
{
customerId = value;
OnPropertyChanged(() => CustomerId);
}
}
public Cinch.DataWrapper<Int32> ProductId
{
get { return productId; }
private set
{
productId = value;
OnPropertyChanged(() => ProductId);
}
}
public Cinch.DataWrapper<Int32> Quantity
{
get { return quantity; }
private set
{
quantity = value;
OnPropertyChanged(() => Quantity);
}
}
public Cinch.DataWrapper<DateTime> DeliveryDate
{
get { return deliveryDate; }
private set
{
deliveryDate = value;
OnPropertyChanged(() => DeliveryDate);
}
}
#endregion
#region Overrides
/// <summary>
/// Override hook which allows us to also put any child
/// EditableValidatingObject objects IsValid state into
/// a combined IsValid state for the whole Model
/// </summary>
public override bool IsValid
{
get
{
//return base.IsValid and use DataWrapperHelper, if you are
//using DataWrappers
return base.IsValid &&
DataWrapperHelper.AllValid(cachedListOfDataWrappers);
}
}
#endregion
#region EditableValidatingObject overrides
/// <summary>
/// Override hook which allows us to also put any child
/// EditableValidatingObject objects into the BeginEdit state
/// </summary>
protected override void OnBeginEdit()
{
base.OnBeginEdit();
//Now walk the list of properties in the CustomerModel
//and call BeginEdit() on all Cinch.DataWrapper<T>s.
//we can use the Cinch.DataWrapperHelper class for this
DataWrapperHelper.SetBeginEdit(cachedListOfDataWrappers);
}
/// <summary>
/// Override hook which allows us to also put any child
/// EditableValidatingObject objects into the EndEdit state
/// </summary>
protected override void OnEndEdit()
{
base.OnEndEdit();
//Now walk the list of properties in the CustomerModel
//and call CancelEdit() on all Cinch.DataWrapper<T>s.
//we can use the Cinch.DataWrapperHelper class for this
DataWrapperHelper.SetEndEdit(cachedListOfDataWrappers);
}
/// <summary>
/// Override hook which allows us to also put any child
/// EditableValidatingObject objects into the CancelEdit state
/// </summary>
protected override void OnCancelEdit()
{
base.OnCancelEdit();
//Now walk the list of properties in the CustomerModel
//and call CancelEdit() on all Cinch.DataWrapper<T>s.
//we can use the Cinch.DataWrapperHelper class for this
DataWrapperHelper.SetCancelEdit(cachedListOfDataWrappers);
}
#endregion
}
}
This is the a slightly easier Cinch base class to inherit
from (its
basically almost the same as what we just saw without the BeginEdit() / OnEndEdit() and OnCancelEdit() methods
inherited from the IEditableObject) , as there is no support
for editability. There is of course still the availability to allow the
individual Model (or ViewModel) fields to be editable/read only by using the
DataWrapper<T> helpers, but that is your choice. Basically BeginEdit()
copies the objects current values to a temporary storage, which has nothing to
do with the DataWrapper<T> helpers. The DataWrapper<T>
helpers simply allow the ViewModel to tell the View (through bindings) that a particular Model/ViewModel
field should not be editable. They are 2 different things. There is some work to
do to store the state of the DataWrapper<T> helpers if you
choose to use EditableValidatingObject
the but that is mainly taken care of for you, as long as you use the static
methods on the DataWrapperHelper class as demonstrated above.
You will need to do the following:
DataWrapper<T> helpers that allow your
ViewModel to place every individual property into edit mode independent of
any other property. This is you decision YOU must decide. If you choose not
to use a DataWrapper<T> object for your properties instead of something like
this private Cinch.DataWrapper<Int32> orderId = new DataWrapper<Int32>()
you would declare the property as normal like private Int32 orderId = 0IDataErrorInfo interface
does its magic and shows the errors on the View.
using System;
using Cinch;
using MVVM.DataAccess;
namespace MVVM.Models
{
/// <summary>
/// Respresents a UI Order Model, which has all the
/// good stuff like Validation/INotifyPropertyChanged
/// which are all ready to use within the base class.
///
/// This class also makes use of <see cref="Cinch.DataWrapper">
/// Cinch.DataWrapper</see>s. Where the idea is that the ViewModel
/// is able to control the mode for the data, and as such the View
/// simply binds to a instance of a <see cref="Cinch.DataWrapper">
/// Cinch.DataWrapper</see> for both its data and its editable state.
/// Where the View can disable a control based on the
/// <see cref="Cinch.DataWrapper">Cinch.DataWrapper</see> editable state.
/// </summary>
public class OrderModel : Cinch.ValidatingObject
{
#region Data
//Any data item is declared as a Cinch.DataWrapper, to allow the ViewModel
//to decide what state the data is in, and the View just renders
//the data state accordingly
private Cinch.DataWrapper<Int32> orderId = new DataWrapper<Int32>();
private Cinch.DataWrapper<Int32> customerId = new DataWrapper<Int32>();
private Cinch.DataWrapper<Int32> quantity = new DataWrapper<Int32>();
private Cinch.DataWrapper<Int32> productId = new DataWrapper<Int32>();
private Cinch.DataWrapper<DateTime> deliveryDate = new DataWrapper<DateTime>();
private IEnumerable<DataWrapperBase> cachedListOfDataWrappers;
//rules
private static SimpleRule quantityRule;
#endregion
#region Ctor
public OrderModel()
{
#region Create DataWrappers
OrderId = new DataWrapper<Int32>(this, orderIdChangeArgs);
CustomerId = new DataWrapper<Int32>(this, customerIdChangeArgs);
ProductId = new DataWrapper<Int32>(this, productIdChangeArgs);
Quantity = new DataWrapper<Int32>(this, quantityChangeArgs);
DeliveryDate = new DataWrapper<DateTime>(this, deliveryDateChangeArgs);
//fetch list of all DataWrappers, so they can be used again later without the
//need for reflection
cachedListOfDataWrappers =
DataWrapperHelper.GetWrapperProperties<OrderModel>(this);
#endregion
#region Create Validation Rules
quantity.AddRule(quantityRule);
#endregion
//I could not be bothered to write a full DateTime picker in
//WPF, so for the purpose of this demo, DeliveryDate is
//fixed to DateTime.Now
DeliveryDate.DataValue = DateTime.Now;
}
static OrderModel()
{
quantityRule = new SimpleRule("DataValue", "Quantity can not be < 0",
(Object domainObject)=>
{
DataWrapper<Int32> obj = (DataWrapper<Int32>)domainObject;
return obj.DataValue <= 0;
});
}
#endregion
#region Public Properties
public Cinch.DataWrapper<Int32> OrderId
{
get { return orderId; }
private set
{
orderId = value;
OnPropertyChanged(() => OrderId);
}
}
public Cinch.DataWrapper<Int32> CustomerId
{
get { return customerId; }
private set
{
customerId = value;
OnPropertyChanged(() => CustomerId);
}
}
public Cinch.DataWrapper<Int32> ProductId
{
get { return productId; }
private set
{
productId = value;
OnPropertyChanged(() => ProductId);
}
}
public Cinch.DataWrapper<Int32> Quantity
{
get { return quantity; }
private set
{
quantity = value;
OnPropertyChanged(() => Quantity);
}
}
public Cinch.DataWrapper<DateTime> DeliveryDate
{
get { return deliveryDate; }
private set
{
deliveryDate = value;
OnPropertyChanged(() => DeliveryDate);
}
}
#endregion
#region Overrides
/// <summary>
/// Override hook which allows us to also put any child
/// EditableValidatingObject objects IsValid state into
/// a combined IsValid state for the whole Model
/// </summary>
public override bool IsValid
{
get
{
//return base.IsValid and use DataWrapperHelper, if you are
//using DataWrappers
return base.IsValid &&
DataWrapperHelper.AllValid(cachedListOfDataWrappers);
}
}
#endregion
}
}
NOTE : You can do all of this directly in your ViewModels. There are a few simple things to follow to do this work directly in your ViewModel
ValidatingViewModelBase
or from EditableValidatingViewModelBase which gives you
the functionality outlined above. Both of these classes also inherit from ViewModelBase
so you will have things like services and View lifecycle events in there
already.There is actually not much you need to think about if you want to use Cinch
to develop ViewModels with. Most of the functionality can be done via inheriting
from one of the Cinch ViewMode base classes, and using the exposed
services, which is described below. If you want the validation
functionality or the editing functionality, just follow the advice I
gave above for Developing Models Using Cinch
but this time inherit from ValidatingViewModelBase or
EditableValidatingViewModelBase.
As discussed in the Developing Models Using Cinch section. What I typically do is have a Model that I bind my View to directly, where the Model is exposed through a currentXXX property in my ViewModel. I realise that not everyone will be able to do this, so Cinch provides a number of base classes which are:
ViewModelBase : The main base class which is also the base class to the 2 other ViewModel base classes mentioned below. That provides all the services and Window lifecycle events etc etc. This is discussed in more detail below.
ValidatingViewModelBase : Which basically inherits from ViewModelBase
and provides support for validation via the same rules you saw demonstrated
above (Developing Models Using Cinch) for the Model I showed being developed.
EditableValidatingViewModelBase : Which basically inherits
from ValidatingViewModelBase and provides support for editing via
the same rules IEditableObject support you saw demonstrated above (Developing
Models Using Cinch) for the Model I showed being developed.
The decision as to which one to use is up to you.
The easiest way to get started with Cinch is to use inherit from the
Cinch ViewModelBase
class, as this gives you lots of pre made support for things like
This class is also the base class for the other 2 possible base classes you could use for your ViewModels in Cinch. That decision is up to you.
This section will highlight how to use the services exposed by the
ViewModelBase (if you inherited
from Cinch ViewModelBase that is) class. You
should also read how the services are designed which was covered in
Part 3 of the Cinch
article series
NOTE:
I am acutely aware there is going to be quite a bit of repetition here from the last article, but maybe the way I explain it this time may be a little different, there may also be some of you that did not read the previous article, that may actually find this description of the services better/more useful, whilst others may go back to the previous article, but at least you now have options. I leave it up to you. Think of it as reinforced learning.
As we saw in the previous article there is an Simple Logging Facade (SLF) that is part of Cinch. So how do we go about using this logging facade, well it's very easy (if you inherited from Cinch ViewModelBase
that is), here is what you do:
private void LogSomething()
{
var logger = this.Resolve<ILogger>();
if (logger != null)
logger.Error("Some error occurred");
}
Or as the SLF.ILogger is also exposed as a public property on the ViewModelBase class you could do this in your custom ViewModels, providing they inherit from Cinch.ViewModelBase.
private void LogSomething()
{
Logger.Error("Some error occurred");
}
It is really just a matter of using the ViewModelBase.Resolve()
method (which is really using the ServiceProvider
class as mentioned in
Part 3) to locate
the service and then just use the service.
One thing to note is that the logger is a special case AND MUST be configured through an App.Config, where as if you are happy with all the other Cinch default services you do not need to do anything to configure them as the defaults will be used in the case of no configuration details.
Here is an example of an App.Config that configures the Cinch logging.
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler,log4net"/>
<section name="slf" type="Slf.Config.SlfConfigurationSection, slf"/>
</configSections>
<slf>
<factories>
<!-- configure single log4net factory, which will get all logging output -->
<!-- Important: Set a reference to the log4net facade library to make sure it will be available at runtime -->
<factory type="SLF.Log4netFacade.Log4netLoggerFactory, SLF.Log4netFacade"/>
</factories>
</slf>
<!-- configures log4net to write into a local file called "log.txt" -->
<log4net>
<!-- log4net uses the concept of 'appenders' to indicate where log messages are written to.
Appenders can be files, the console, databases, SMTP and much more
-->
<appender name="MainAppender" type="log4net.Appender.FileAppender">
<param name="File" value="log.txt" />
<param name="AppendToFile" value="true" />
<!-- log4net can optionally format the logged messages with a pattern. This pattern string details what information
is logged and the format it takes. A wide range of information can be logged, including message, thread, identity and more,
see the log4net documentation for details:
http://logging.apache.org/log4net/release/sdk/log4net.Layout.PatternLayout.html
-->
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%logger: %date [%thread] %-5level - %message %newline" />
</layout>
</appender>
<root>
<level value="ALL" />
<appender-ref ref="MainAppender" />
</root>
</log4net>
</configuration>
As we saw in the previous article there is an MessageBox service that is
part of the core Cinch services. So how do we go about using this
MessageBox
service, well it's very easy (if you inherited from Cinch ViewModelBase
that is), here is what you do:
private void ShowMessageBox()
{
var messager = this.Resolve<IMessageBoxService>();
if (messager != null)
{
//EXAMPLE 1 : Simple MessageBox's, expecting NO return value
messager.ShowError("There was an error");
messager.ShowInformation("Nice day for MVVVM");
messager.ShowWarning("Something could be going down");
//EXAMPLE 2 : Simple MessageBox's, expecting return value
if (messager.ShowYesNo("Do it?",
CustomDialogIcons.Question) == CustomDialogResults.Yes)
{
//YES : Do it
}
if (messager.ShowYesNoCancel("Do it?",
CustomDialogIcons.Exclamation) == CustomDialogResults.Cancel)
{
//Cancel : Unwinnd operation
}
}
}
It is really just a matter of using the ViewModelBase.Resolve()
method (which is really using the ServiceProvider
class as mentioned in
Part 3) to
locate the service and then just use the service.
As we saw in the previous article there is an OpenFile service that is
part of the core Cinch services. So how do we go about using this
OpenFile
service, well it's very easy (if you inherited from Cinch ViewModelBase
that is), here is what you do:
private void OpenFile()
{
var openFileServ = this.Resolve<IOpenFileService>();
if (openFileServ != null)
{
openFileServ.InitialDirectory = @"C:\";
openFileServ.Filter = ".txt | Text Files";
var result = openFileServ.ShowDialog(null);
if (result.HasValue && result.Value == true)
{
//use IOpenFileService.FileName
FileInfo file = new FileInfo(openFileServ.FileName);
//do something with the file
}
}
}
It is really just a matter of using the ViewModelBase.Resolve()
method (which is really using the ServiceProvider
class as mentioned in
Part 3) to
locate the service and then just use the service.
As we saw in the previous article there is an SaveFile service that is
part of the core Cinch services. So how do we go about using this
SaveFile
service, well it's very easy (if you inherited from Cinch ViewModelBase
that is), here is what you do:
private void SaveFile()
{
var saveFileServ = this.Resolve<ISaveFileService>();
if (saveFileServ != null)
{
saveFileServ.InitialDirectory = @"C:\";
saveFileServ.Filter = ".txt | Text Files";
saveFileServ.OverwritePrompt = true;
var result = saveFileServ.ShowDialog(null);
if (result.HasValue && result.Value == true)
{
var messagerServ = this.Resolve<IMessageBoxService>();
if (messagerServ != null)
messagerServ.ShowInformation(
String.Format("Successfully saved file {0}",
saveFileServ.FileName));
}
}
}
It is really just a matter of using the ViewModelBase.Resolve()
method (which is really using the ServiceProvider
class as mentioned in
Part 3) to
locate the service and then just use the service.
As discussed in Part 3 the handling of popups is
a bit of strange case, as Cinch doesn't know about the actual
popup types as these are typically not within the Cinch project,
they are in the WPF UI project. So the WPF UI project must supply the pop windows
to the Cinch contained IUIVisualizerService service within the
WPF apps main window say. Part 3 talked about this
in more details.
However, providing the WPF UI project does it's job and provides the popup
instance and types somewhere to the IUIVisualizerService service,
it really is very easy to show a Popup from a ViewModel using Cinch.
All we do is something like the following.
var uiVisualizerService = this.Resolve<IUIVisualizerService>();
bool? result = uiVisualizerService.ShowDialog("AddEditOrderPopup",
addEditOrderVM);
if (result.HasValue && result.Value)
{
CloseActivePopUpCommand.Execute(true);
}
In the code above the "AddEditOrderPopup" popup that is available
within the WPF UI project is shown and passed a ViewModel instance variable
called "addEditOrderVM" which will be used as the DataContext for
the popup Window. Using this technique and the IEditableObject
technique discussed in Part 2, it is very easy to
have popups that are able to save state (OK button) and also causes an object
that is editable (that presumably was edited by the popup) to be rolled back
to its previous state using the popup Cancel button, which would cause the IEditableObject
to have its CancelEdit() method called to restore the previous state.
When this all works, it is rather lovely. There is a nice example of how this works in the demo apps "AddEditCustomerViewModel" code.
Threading is hard there is no doubt about it. There are also loads of different
ways of doing things, but if you have read the Cinch article
series so far you will be aware that I have introduced a convenient threading
helper class called BackgroundTaskManager<T> that does
help out (at least I think so). This class is discussed in Part
3). So I will not go over how it works in more detail as that has already
been done in the previous article.
This time I just wanted to show you how you might use the BackgroundTaskManager<T>
class inside a ViewModel of you own to do some background task. So without further
ado, here is an example. This example is highly fictitious and simply gets a
range of future dates. The point is that it is done in the background using
the BackgroundTaskManager<T> class inside a ViewModel, so
that is what to take away from the following code.
using System;
using ystem.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Threading;
using System.Windows.Threading;
using System.Windows.Data;
using Cinch;
using System.IO;
namespace MVVM.ViewModels
{
/// <summary>
/// Provides ALL logic for the AddEditCustomerView
/// </summary>
public class ExampleViewModelWithBgWorker : Cinch.WorkspaceViewModel
{
#region Data
private BackgroundTaskManager<DispatcherNotifiedObservableCollection<DateTime>>
bgWorker = null;
private DispatcherNotifiedObservableCollection<DateTime> futureDates
= new DispatcherNotifiedObservableCollection<DateTime>();
private SimpleCommand getFutureDatesCommand;
#endregion
#region Ctor
public ExampleViewModelWithBgWorker()
{
//Get futures Command
getFutureDatesCommand = new SimpleCommand
{
CanExecuteDelegate = x => CanExecuteGetFutureDatesCommand,
ExecuteDelegate = x => ExecuteGetFutureDatesCommand()
};
}
#endregion
#region Public Properties
/// <summary>
/// getFutureDatesCommand : Gets future dates in background thread
/// </summary>
public SimpleCommand GetFutureDatesCommand
{
get { return getFutureDatesCommand; }
}
/// <summary>
/// Background worker which fetches
/// Date Ranges
/// </summary>
public BackgroundTaskManager<DispatcherNotifiedObservableCollection
<DateTime>> BgWorker
{
get { return bgWorker; }
set
{
bgWorker = value;
OnPropertyChanged(() => BgWorker);
}
}
/// <summary>
/// Future Dates
/// </summary>
public DispatcherNotifiedObservableCollection<DateTime> FutureDates
{
get { return futureDates; }
set
{
futureDates = value;
OnPropertyChanged(() => FutureDates);
}
}
#endregion
#region Private Methods
/// <summary>
/// Setup backgrounder worker Task/Completion action
/// to fetch Orders for Customers
/// </summary>
private void SetUpBackgroundWorker()
{
bgWorker = new BackgroundTaskManager
<DispatcherNotifiedObservableCollection<DateTime>>(
() =>
{
var dateRanges = new
DispatcherNotifiedObservableCollection<DateTime>();
for (int i = 0; i < 10000; i++)
{
dateRanges.Add(DateTime.Now.AddDays(i));
}
return dateRanges;
},
(result) =>
{
FutureDates = result;
});
}
#endregion
#region Command Implementation
#region GetFutureDatesCommand
/// <summary>
/// Logic to determine if GetFutureDatesCommand can execute
/// </summary>
private Boolean CanExecuteGetFutureDatesCommand
{
get
{
return true;
}
}
/// <summary>
/// Executes the GetFutureDatesCommand
/// </summary>
private void ExecuteGetFutureDatesCommand()
{
try
{
GetFutureDatesCommand.CommandSucceeded = false;
bgWorker.RunBackgroundTask();
GetFutureDatesCommand.CommandSucceeded = true;
}
catch (Exception ex)
{
Logger.Log(LogType.Error, ex);
var messager = this.Resolve<IMessageBoxService>();
if (messager != null)
{
messager.ShowError(
"Failed to fetch future dates");
}
}
}
#endregion
#endregion
}
}
Some of you may recall from Part2 that there was
a little helper class called DataWrapper<T> which allows
the ViewModel to put each piece of bindable data within either the Current
Model (the way I do it) or the actual ViewModel bindable properties
into an edit/read only state. This was done using the DataWrapper<T>
helpers, which you may or may not decide to use. That is entirely up to you.
If you do here is what I suggest you do. I am assuming you have a CurrentXXX
Model object exposed as a bindable property from the current ViewModel (basically
the way I do it), but read on even if this is not what you
do as I have some advice even if you repeat all you Model properties in the
ViewModel.
NOTE : This code only applies if you are using the Cinch DataWrapper<T> helpers. If you using standard types such as Int32/Double etc etc please ignore this section entirely.
/// <summary>
/// The current ViewMode, when changed will loop
/// through all nested DataWrapper objects and change
/// their state also
/// </summary>
public ViewMode CurrentViewMode
{
get { return currentViewMode; }
set
{
currentViewMode = value;
switch (currentViewMode)
{
case ViewMode.AddMode:
CurrentCustomer = new CustomerModel();
this.DisplayName = "Add Customer";
break;
case ViewMode.EditMode:
CurrentCustomer.BeginEdit();
this.DisplayName = "Edit Customer";
break;
case ViewMode.ViewOnlyMode:
this.DisplayName = "View Customer";
break;
}
//Now change all the CurrentCustomer.CachedListOfDataWrappers
//Which sets all the Cinch.DataWrapper<T>s to the correct IsEditable
//state based on the new ViewMode applied to the ViewModel
//we can use the Cinch.DataWrapperHelper class for this
DataWrapperHelper.SetMode(
CurrentCustomer.CachedListOfDataWrappers,
currentViewMode);
OnPropertyChanged(() => CurrentViewMode);
}
}
I just wanted to say I have spent a lot of time thinking of things that could go wrong and how to make things better and to think of good approaches, but things get missed, ideas are not as good as they were thought to be etc etc.
Anyway to cut a long story short...The idea behind Cinch, is if it works for you in its entirety, great, if not just pick the parts that work for you. That's the way I tried to write it all.
So please just use the bits you like.
In the subsequent articles I will be showcasing it roughly like this
That is actually all I wanted to say right now, but I hope from this article you can see where Cinch is going and how it could help you with MVVM. As we continue our journey we will be covering the remaining items within Cinch and then we will move on to show you how to develop an app with Cinch.
As always votes / comments are welcome.
General
News
Question
Answer
Joke
Rant
Admin
Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads.
|
PermaLink |
Privacy |
Terms of Use
Last Updated: 23 Dec 2009 Editor: |
Copyright 2009 by Sacha Barber Everything else Copyright © CodeProject, 1999-2010 Web17 | Advertise on the Code Project |