Binding ComboBox and ObservableCollection<KeyValue> in wpf - wpf

in My OpenViewModel i collect data:
private ObservableCollection<KeyValue> availableData;
public ObservableCollection<KeyValue> AvailableDatas
{
get { return availableData; }
set
{
if (value != availableData)
{
availableData= value;
NotifyOfPropertyChange("AvailableDatas");
}
}
}
method for collecting data:
public ObservableCollection<KeyValue> CollectData()
{
ConnectorClient client = null;
try
{
client = webservice.GetClient();
AvailableDatas = client.GetDatas();
client.Close();
}
catch (Exception ex)
{
webservice.HandleException(ex, client);
}
return AvailableDatas;
}
How to call the method CollectData in wpf and fill my COmboBox?
thx

You might simply call the method the first time the AvailableDatas property is accessed (e.g. from a binding in XAML):
private ObservableCollection<KeyValue> availableData;
public ObservableCollection<KeyValue> AvailableDatas
{
get
{
if (availableData == null)
{
availableData = CollectData();
}
return availableData;
}
set
{
if (value != availableData)
{
availableData = value;
NotifyOfPropertyChange("AvailableDatas");
}
}
}
Then you should change the CollectData method in a way that is does not also set the property:
public ObservableCollection<KeyValue> CollectData()
{
ConnectorClient client = null;
ObservableCollection<KeyValue> data = null;
try
{
client = webservice.GetClient();
data = client.GetDatas();
client.Close();
}
catch (Exception ex)
{
webservice.HandleException(ex, client);
}
return data;
}

You could override the OnActivated() event assuming you are using an IScreen implementation and load data in there, or just do it in the constructor or a custom Initialise method if you want to roll your own (or in the property accessor as someone has already said).
You can also use coroutines if you want some visual context for the user and a better tie in with CM actions
There is a nice simple implementation of a Loader class here which helps provide visual context to the user:
https://caliburnmicro.codeplex.com/wikipage?title=IResult%20and%20Coroutines&referringTitle=Documentation
This searches the visual tree for a BusyIndicator control and activates it whilst the content is loading e.g. ...
public class SomeViewModel : Screen
{
protected override void OnActivate()
{
RefreshData();
}
public void RefreshData()
{
Coroutine.BeginExecute(LoadData(), new ActionExecutionContext() { Target = this });
}
public IEnumerable<IResult> LoadData()
{
yield return Loader.Show("Loading Data...");
yield return new LoadSomeDataRoutine(client.GetDatas);
yield return Loader.Hide();
}
}
The reason to have a RefreshData method is that this also allows you to bind CM actions and allows the coroutine can grab more contextual information.
Obviously you have less need to worry about the async->sync benefits this gives in Silverlight because you are using WPF (but it still applies to async web service calls), however it still has many benefits and it also helps you to write reusable routines which become part of your application framework (e.g. some level of error handling/logging encapsulated in the IResult implementation etc)
You also mentioned filling the combobox... all you would need to do in CM is place a combobox on your control, and set it's Name property to the name of the property on your VM:
public class SomeViewModel : Screen
{
public ObservableCollection<MyObject> MyProperty { //blah blah... }
}
<UserControl .... blah>
<ComboBox x:Name="MyProperty" />
</UserControl>
This will fill the combobox with the items. You will still need to set the binding for SelectedItem/SelectedValue
I assume you know this already though - if not CM has some decent documentation:
https://caliburnmicro.codeplex.com/wikipage?title=Basic%20Configuration%2c%20Actions%20and%20Conventions&referringTitle=Documentation

Related

UI not calling INotifyDataErrorInfo.GetErrors()

I have a model implementing both INotifyPropertyChanged and INotifyDataErrorInfo. The Property changed event fires when ever I have a property modified, but for some reason when I raise the Error event handler, the UI does ever invoke the GetErrors method. This results in the validation error not being rendered to the UI.
Can someone take a look at how I have the INotifyDataErrorInfo set up and tell me if I'm doing something wrong?
Base model implementation
public class BaseChangeNotify : INotifyPropertyChanged, INotifyDataErrorInfo
{
private bool isDirty;
private Dictionary<string, List<string>> errors = new Dictionary<string, List<string>>();
public BaseChangeNotify()
{
}
public event PropertyChangedEventHandler PropertyChanged;
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
public bool IsDirty
{
get
{
return this.isDirty;
}
set
{
this.isDirty = value;
this.OnPropertyChanged();
}
}
public bool HasErrors
{
get
{
return this.errors.Count(e => e.GetType() == typeof(ErrorMessage)) > 0;
}
}
public IEnumerable GetErrors(string propertyName)
{
if (string.IsNullOrEmpty(propertyName) ||
!this.errors.ContainsKey(propertyName))
{
return null;
}
return this.errors[propertyName];/*.Where(e => (e is ErrorMessage));*/
}
protected virtual void AddError(string propertyName, string error, bool isWarning = false)
{
if (!this.errors.ContainsKey(propertyName))
{
this.errors[propertyName] = new List<string>();
}
if (!this.errors[propertyName].Contains(error))
{
if (isWarning)
{
this.errors[propertyName].Add(error);
}
else
{
this.errors[propertyName].Insert(0, error);
}
this.OnErrorsChanged(propertyName);
}
}
protected virtual void RemoveError(string propertyName, string error)
{
if (this.errors.ContainsKey(propertyName) &&
this.errors[propertyName].Contains(error))
{
this.errors[propertyName].Remove(error);
if (this.errors[propertyName].Count == 0)
{
this.errors.Remove(propertyName);
}
this.OnErrorsChanged(propertyName);
}
}
public virtual void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
// Perform the IsDirty check so we don't get stuck in a infinite loop.
if (propertyName != "IsDirty")
{
this.IsDirty = true; // Each time a property value is changed, we set the dirty bool.
}
if (this.PropertyChanged != null)
{
// Invoke the event handlers attached by other objects.
try
{
// When unit testing, this will always be null.
if (Application.Current != null)
{
try
{
Application.Current.Dispatcher.Invoke(() =>
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName)));
}
catch (Exception)
{
throw;
}
}
else
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
catch (Exception)
{
throw;
}
}
}
/// <summary>
/// Called when an error has changed for this instance.
/// </summary>
/// <param name="propertyName">Name of the property.</param>
public virtual void OnErrorsChanged([CallerMemberName] string propertyName = "")
{
if (string.IsNullOrWhiteSpace(propertyName))
{
return;
}
if (this.ErrorsChanged != null)
{
this.ErrorsChanged(this, new DataErrorsChangedEventArgs(propertyName));
}
}
}
Model using the implementation
public class PayItem : BaseChangeNotify
{
private Section section;
public Section Section
{
get
{
return this.section;
}
set
{
this.section = value;
this.ValidateSection();
this.OnPropertyChanged();
}
}
private void ValidateSection([CallerMemberName] string propertyName = "")
{
const string sectionError = "You must select a Section.";
if (this.Section == null || this.Section.Name.Length > 1)
{
this.AddError(propertyName, sectionError);
}
else
{
this.RemoveError(propertyName, sectionError);
}
}
The View trying to use it
<ComboBox Name="SectionComboBox"
ItemsSource="{Binding Path=ProjectSections}"
SelectedItem="{Binding Path=SelectedPayItem.Section,
NotifyOnValidationError=True,
UpdateSourceTrigger=PropertyChanged}">
The app is being wrote in WPF, and the WPF docs are pretty scarce. I've read through the Silverlight documentation on it along with a few other blog posts I found on the internet and have implemented in each of the different ways the blog authors suggest. Each time the result is the same, the GetErrors() method never gets hit by the Binding engine.
Can anyone see something that I'm doing wrong? When my model has its property set, I can step through the debugger and ultimately end up within the OnErrorsChanged event handler, and the event gets invoked. Nothing happens when it gets invoked though, so I'm stumped.
Thanks in advance for any help.
Johnathon
EDIT
Also I would like to note that I had been using IDataErrorInfo in the base class for the last couple of months without any issues. The binding worked, the errors were reported to the View and everything was happy. When I changed from IDataErrorInfo to INotifyDataErrorInfo, the validation appeared to stop communicating with the View.
The INotifyDataErrorInfo.HasErrors property must return true when raising the ErrorsChanged event. Otherwise the binding engine ignores the errors. Your HasErrors property will return false all the time. This happens because you are checking for items of type ErrorMessage but your dictionary contains items of type KeyValuePair<string, List<string>>. Besides that it is highly inefficent to count all the items. You should use .Any() instead.
By the way, the MSDN documentation of INotifyDataErrorInfo says the following:
Note that the binding engine never uses the HasErrors property,
although you can use it in custom error reporting.
This is plain wrong and it took me hours to find that out.

How to Cancel windowManager.ShowDialog() from ViewModel

I have a ShellViewModel which loads a Modal Dialog. The Dialog's ViewModel has its OnActivate() override, where it gathers the data to be displayed on the Dialog. I would like to know how can we ask the WindowManager to cancel its ShowDialog based on a condition in OnActivate of the ViewModel backing the dialog.
For example, lets say that I have following code in ShellViewModel which tries to load a modal dialog based on StationOpenViewModel
public class ShellViewModel : Conductor<object>, IShell, IHandle<ConnectionChangedEvent> {
public void ShowOpenStationPage() {
StationOpenViewModel viewModel = container.GetExportedValue<StationOpenViewModel>();
windowManager.ShowDialog(viewModel);
}
...
}
and here is to code of OnActivate override of the StationOpenViewModel
public class StationOpenViewModel : Screen {
...
protected override void OnActivate() {
try {
using (StationRepository stationRepository = new StationRepository()) {
//code to get Station Data
}
catch (Exception ex) {
//Here I have no data, so there is no point in showing the window.
//How to cancel showDialog() for this viewModel
}
...
}
So in the above code, if I get Exception in OnActivate override, I don't have any Station data to show and I would like to cancel the showDialog() for the StationOpenViewModel. I tried using TryClose(), but if I do so, the WindowManager.ShowDialog() throws exception saying that the operation is invalid.
In summary, if I call WindowManager.ShowDialog() for a dialog backed by some ViewModel, then in that ViewModel how do I cancel the ShowDialog() operation.
The ShowDialog() implementation in CM source is:
public virtual void ShowDialog(object rootModel, object context = null, IDictionary<string, object> settings = null)
{
var view = EnsureWindow(rootModel, ViewLocator.LocateForModel(rootModel, null, context));
ViewModelBinder.Bind(rootModel, view, context);
var haveDisplayName = rootModel as IHaveDisplayName;
if(haveDisplayName != null && !ConventionManager.HasBinding(view, ChildWindow.TitleProperty)) {
var binding = new Binding("DisplayName") { Mode = BindingMode.TwoWay };
view.SetBinding(ChildWindow.TitleProperty, binding);
}
ApplySettings(view, settings);
new WindowConductor(rootModel, view);
view.Show();
}
full source here:
http://caliburnmicro.codeplex.com/SourceControl/changeset/view/ae25b519bf1e46a506c85395f04aaffb654c0a08#src/Caliburn.Micro.Silverlight/WindowManager.cs
It doesn't look like there is a good way to do this with the default implementation. You should probably implement your own WindowManager and subclass the original implementation
The WindowConductor in the above code file is responsible for the lifecycle of the window, therefore and additional interface which your VMs can implement would work well:
public interface ICancelActivate
{
public bool ActivationCancelled { get };
}
Then just change your MyWindowConductor implementation to something like:
public MyWindowConductor(object model, ChildWindow view)
{
// Added this field so the window manager can query the state of activation (or use a prop if you like)
public bool ActivationCancelled;
this.model = model;
this.view = view;
var activatable = model as IActivate;
if (activatable != null)
{
activatable.Activate();
}
// Added code here, check to see if the activation was cancelled:
var cancelActivate = model as ICancelActivate;
if(cancelActivate != null)
{
ActivationCancelled = cancelActivate.ActivationCancelled;
if(ActivationCancelled) return; // Don't bother handling the rest of activation logic if cancelled
}
var deactivatable = model as IDeactivate;
if (deactivatable != null) {
view.Closed += Closed;
deactivatable.Deactivated += Deactivated;
}
var guard = model as IGuardClose;
if (guard != null) {
view.Closing += Closing;
}
}
then to stop the view from showing:
// This is in 'ShowDialog' - you can override the default impl. as the method is marked virtual
ApplySettings(view, settings);
// Get a ref to the conductor so you can check if activation was cancelled
var conductor = new MyWindowConductor(rootModel, view);
// Check and don't show if we don't need to
if(!conductor.ActivationCancelled)
view.Show();
Obviously I've just thrown this together so it might not be the best way, and I'd look carefully at where this leaves the state of your application
Your VMs just implement this:
public class StationOpenViewModel : Screen, ICancelActivation {
private bool _activationCancelled;
public bool ActivationCancelled { get { return _activationCancelled; } }
...
protected override void OnActivate() {
try {
using (StationRepository stationRepository = new StationRepository()) {
//code to get Station Data
}
catch (Exception ex) {
_activationCancelled = true;
}
...
}
... of course there may be better ways for you to check if you need to open a VM in the first place - I'm not sure what they would be but still, worth thinking about
Edit:
The reason I didn't just do this in the WindowManager...
new WindowConductor(rootModel, view);
var cancel = rootModel as ICancelActivation;
if(cancel == null || !cancel.ActivationCancelled) // fixed the bug here!
view.Show();
Is twofold - 1: you are still letting the WindowConductor add Deactivate and GuardClose hooks even though they should never be used, which may lead to some undesirable behaviour (not sure about reference holding either - probably ok with this once since nothing holds a ref to the conductor/VM)
2: it seems like the WindowConductor which activates the VM should be responsible for handling the cancellation of activation - ok it does mean that the WindowManager needs to know whether to show the VM or not, but it seemed a more natural fit to me
Edit 2:
One idea might be to move view.Show() into the conductor - that way you can cancel the activation without needing to expose details to the manager. Both are dependent on each other though so it's the same either way to me

silverlight: How to implement dynamic object creation in POCO class properties?

We're porting our data model from WPF over to Silverlight. On the WPF side, every element of the data model supports dynamic initialization so that we don't have to worry about any bookkeeping. For example:
public UnitNameClass UnitName
{
get
{
if ((this.unitNameField == null))
{
this.unitNameField = new UnitNameClass();
}
return this.unitNameField;
}
set
{
if (((this.unitNameField == null)
|| (this.unitNameField.Equals(value) != true)))
{
this.unitNameField = value;
this.OnPropertyChanged("UnitName"));
}
}
}
The majority of the classes are generated for us, from an XSD representing the data model. There are some other extension classes we've added which are not in the XSD files.
Silverlight generates properties of the following form for its proxy classes:
public UnitNameClass UnitName
{
this.OnCreated();
}
public UnitNameClass UnitName
{
get
{
return this._unitName;
}
set
{
if ((this._unitName != value))
{
this.OnUnitNameChanging(value);
this.RaiseDataMemberChanging("UnitName");
this.ValidateProperty("UnitName", value);
this._unitName = value;
this.RaiseDataMemberChanged("UnitName");
this.OnUnitNameChanged();
}
}
}
Obviously, we're meant to put initialization of all the properties in the OnCreated partial function, but we'd much rather have the ability to add some logic to the get property.
Has anyone ever had this problem before?

making async calls using mvvm in silverlight

I am trying to make a call to a wcf service with my silverlight application and I am having some trouble understanding how the model returns the result back to the view model. Within my view model I have the following command:
public DelegateCommand GetSearchResultCommand
{
get
{
if (this._getSearchResultCommand == null)
this._getSearchResultCommand = new DelegateCommand(GetSearchResultCommandExecute, CanGetSearchResultsCommandExecute);
return this._getSearchResultCommand;
}
}
private void GetSearchResultCommandExecute(object parameter)
{
this.SearchResults = this._DataModel.GetSearchResults(this.SearchTerm);
}
/// <summary>
/// Bindable property for SearchResults
/// </summary>
public ObservableCollection<QueryResponse> SearchResults
{
get
{
return this._SearchResults;
}
private set
{
if (this._SearchResults == value)
return;
// Set the new value and notify
this._SearchResults = value;
this.NotifyPropertyChanged("SearchResults");
}
}
then within my model I have the following code
public ObservableCollection<QueryResponse> GetSearchResults(string searchQuery)
{
//return type cannot be void needs to be a collection
SearchClient sc = new SearchClient();
//******
//TODO: stubbed in placeholder for Endpoint Address used to retreive proxy address at runtime
// sc.Endpoint.Address = (clientProxy);
//******
sc.QueryCompleted += new EventHandler<QueryCompletedEventArgs>(sc_QueryCompleted);
sc.QueryAsync(new Query { QueryText = searchQuery });
return LastSearchResults;
}
void sc_QueryCompleted(object sender, QueryCompletedEventArgs e)
{
ObservableCollection<QueryResponse> results = new ObservableCollection<QueryResponse>();
results.Add(e.Result);
this.LastSearchResults = results;
}
When I insert breakpoints within the model I see where the query is being executed and a result is returned within the model (this.LastSearchResults = results) however I cannot seem to get this collection to update/ notify the view model of the result. I've generated and run a similar test using just a method and dummy class and it seems to work so I suspect the issue is due to the async call /threading. I have INotifyPropertyChanged within the ViewModel to sync the View and ViewModel. Do I need to also implement INotifyPropChng within the model as well? I'm new to mvvm so any help / example of how I should approach this would be appreciated.
Thank you,
UPDATE
In further testing I added INotifyPropertyChanged to the model and changed the Completed event as follows:
void sc_QueryCompleted(object sender, QueryCompletedEventArgs e)
{
ObservableCollection<QueryResponse> results = new ObservableCollection<QueryResponse>();
results.Add(e.Result);
//this.LastSearchResults = results;
SearchResults = results;
}
Placing a watch on Search Results I now see it is updated with results from teh WCF. My question is still around is this teh correct approach? It seems to work right now however I am curious if I am missing something else or if I should not be placing INotify within the Model.
Thank you,
I've found that it's best to encapsulate my WCF services in an additional layer of Service classes. This allows me to more easily Unit Test my ViewModels. There are several patterns when doing this, though this is the simplest I've used. The pattern is to create a method that matches the definition of the service call, though also contains an Action that can be invoked after the service call completes.
public class Service : IService
{
public void GetSearchResults(string searchQuery, Action<ObservableCollection<QueryResponse>> reply)
{
//return type cannot be void needs to be a collection
SearchClient sc = new SearchClient();
//******
//TODO: stubbed in placeholder for Endpoint Address used to retreive proxy address at runtime
// sc.Endpoint.Address = (clientProxy);
//******
sc.QueryCompleted += (s,e) =>
{
ObservableCollection<QueryResponse> results = new ObservableCollection<QueryResponse>();
results.Add(e.Result);
reply(results);
};
sc.QueryAsync(new Query { QueryText = searchQuery });
}
}
You can also provide an interface that your ViewModel can use. This makes Unit Testing even easier, though is optional.
public interface IService
{
void GetSearchResults(string searchQuery, Action<ObservableCollection<QueryResponse>> reply);
}
Your ViewModel would then look something like this:
public class MyViewModel : INotifyPropertyChanged
{
private IService _service;
public MyViewModel()
: this(new Service())
{ }
public MyViewModel(IService service)
{
_service = service;
SearchResults = new ObservableCollection<QueryResponse>();
}
private ObservableCollection<QueryResponse> _searchResults
public ObservableCollection<QueryResponse> SearchResults
{
get { return _searchResults; }
set
{
_searchResults = value;
NotifyPropertyChanged("SearchResults");
}
}
public void Search()
{
_service.GetSearchResults("abcd", results =>
{
SearchResults.AddRange(results);
});
}
protected void NotifyPropertyChanged(string property)
{
var handler = this.PropertyChanged;
if(handler != null)
handler(new PropertyChangedEventArgs(property));
}
}
An additional reason for encapsulating your service calls into another class like this is that it can provide a single place for such things as logging and error handling. That way your ViewModel itself doesn't need to take care of those things specifically related to the Service.
I would likely use something along the lines of:
public class ViewModel : INotifyPropertyChanged
{
private readonly IModel model;
private readonly DelegateCommand getSearchResultsCommand;
public DelegateCommand GetSearchResultsCommand
{
get { return getSearchResultsCommand; }
}
public ObservableCollection<QueryResponse> SearchResults
{
get { return model.SearchResults; }
}
public ViewModel(IModel model)
{
this.model = model;
this.model.SearchResultsRetrieved += new EventHandler(model_SearchResultsRetrieved);
this.getSearchResultsCommand = new DelegateCommand(model.GetSearchResultCommandExecute, model.CanGetSearchResultsCommandExecute);
}
private void model_SearchResultsRetrieved(object sender, EventArgs e)
{
this.NotifyPropertyChanged("SearchResults");
}
}
public interface IModel
{
event EventHandler SearchResultsRetrieved;
void GetSearchResultCommandExecute(object parameter);
bool CanGetSearchResultsCommandExecute(object parameter);
ObservableCollection<QueryResponse> SearchResults { get; }
}
With the SearchResultsRetrieved event being fired by the Model when its SearchResults collection has been filled with the appropriate data. I prefer to have custom events rather than implement INotifyPropertyChanged on my models, particularly if there are only one, or a few, events that need to be communicated to the viewmodel.

How to validate inputs when click button in silverlight?

Im using DataAnnotation to validate input controls. But ValidatesOnExceptions only works when user type something in textbox and press Tab. (Basically on Lostfocus event).
but if user never enters anything in the textbox and click submit. It does not work. Like ASP.NET Page.IsValid property is there any property or method in Silverlight that i can use, that will validates all the controls on UI?
Taking help from the URL provided by Terence, i have prepared the solution below for you.
This you can use to make sure that all the properties are set before service call.
public class PersonViewModel : EntityBase
{
private readonly RelayCommand saveCommand;
public PersonViewModel(IServiceAgent serviceAgent)
{
saveCommand = new RelayCommand(Save) { IsEnabled = true };
}
public RelayCommand SaveCommand // Binded with SaveButton
{
get { return saveCommand; }
}
public String Name // Binded with NameTextBox
{
get
{
return name;
}
set
{
name = value;
PropertyChangedHandler("Name");
ValidateName("Name", value);
}
}
public Int32 Age // Binded with AgeTextBox
{
get
{
return age;
}
set
{
age = value;
PropertyChangedHandler("Age");
ValidateAge("Age", value);
}
}
private void ValidateName(string propertyName, String value)
{
ClearErrorFromProperty(propertyName);
if (/*SOME CONDITION*/)
AddErrorForProperty(propertyName, "/*NAME ERROR MESSAGE*/");
}
private void ValidateAge(string propertyName, Int32 value)
{
ClearErrorFromProperty(propertyName);
if (/*SOME CONDITION*/)
AddErrorForProperty(propertyName, "/*AGE ERROR MESSAGE*/");
}
public void Save()
{
ValidateName("Name", name);
ValidateAge("Age", age);
if (!HasErrors)
{
//SAVE CALL TO SERVICE
}
}
}
I don't think, that there is a way to validate ALL UserControls which are visible on the page. But I would recommend you to have a look at the INotifyDataErrorInfo. This is, in my opinion, the best way to validate data in silverlight. With the INotifyDataErrorInfo approach you don't have to make changes in the view (like ValidatesOnException, ...) and you are able to validate against a WebService in an easy way (This is not possible with data annotations).
Have a look here: http://mtaulty.com/CommunityServer/blogs/mike_taultys_blog/archive/2009/11/18/silverlight-4-rough-notes-binding-with-inotifydataerrorinfo.aspx
Hope this helps you.

Resources