As I see error in my code when I did Binding with DelegateCommand. in its Method I called 1 asyc service which is calling arcgis server to find the City names on using Task Service.
I'm bothered about Prism 4.1 Support async? If not is there any work around?
public DelegateCommand SearchCitiesCommand;
private PlaceFinderService placeFinderService;
public GenericMapViewModel()
{
HelloMapMessage = "Generic Map Pow Pow !!";
placeFinderService = new PlaceFinderService();
SearchCitiesCommand = DelegateCommand. //new DelegateCommand(Search);
}
public virtual async Task Search()
{
List<Graphic> graphics=await placeFinderService.FindAsync(SearchText);
SearchResults = graphics;
}
ASYN service
public class PlaceFinderService
{
TaskCompletionSource<List<Graphic>> tcs;
public Task<List<Graphic>> FindAsync(String searchText)
{
FindParameters findParams = new FindParameters();
findParams.LayerIds.AddRange(new int[] { 0 }); // cities layer
findParams.SearchFields.AddRange(new string[] { "CITY_NAME" });
findParams.SpatialReference = new SpatialReference(4326);
findParams.SearchText = searchText;
FindTask findTask = new FindTask("http://sampleserver1.arcgisonline.com/ArcGIS/rest/services/Specialty/ESRI_StatesCitiesRivers_USA/MapServer");
tcs = new TaskCompletionSource<List<Graphic>>();
findTask.ExecuteCompleted += FindTaskExecuteCompleted;
findTask.Failed += findTaskFailed;
return tcs.Task;
}
private void findTaskFailed(object sender, TaskFailedEventArgs e)
{
tcs.TrySetResult(new List<Graphic>());
}
private void FindTaskExecuteCompleted(object sender, FindEventArgs e)
{
List<Graphic> graphics = new List<Graphic>();
foreach (var result in e.FindResults)
{
graphics.Add(result.Feature);
}
tcs.TrySetResult(graphics);
}
}
I see few http://prismwindowsruntime.codeplex.com/discussions/535816 but I think its Prism 5.
To my knowledge, no MVVM framework supports "async commands". IMO, this is because there are lots of different semantic possibilities.
I have an MSDN article that you may find helpful. It provides a few ideas (and example implementations) but at the end of the day you'll have to craft your own "AsyncCommand" because the exact semantics will depend on your application's needs.
Related
I have a rather complex project existing of multiple sub projects.
Application
WPF Application
Windows Store App
Windows Phone App
Contracts
Service Contract (WCF for WPF)
Service Controller (WebAPi for windows phone)
Infrastructure (EventAggregator/Prism)
ViewModels (Preferably usable by WPF, Windows Phone and Windows App)
Views (WPF Specific)
As long I concentrate on WPF, I have no problems. In my viewmodel I can call the webservice and fill it with the required data. For Windows Phone/Windows Store App I can't always use WCF. However the viewmodel remains the same. How would I "send" the correct service call to my viewmodel?
public async override Task<object> RetrieveItems()
{
if ((Customer != null))
{
return await Task.Run(() => Current.ApplicationService.Schedule_Appointments_GetItems_By_Relation(Customer.Relation_ID));
}
else
{
return null;
}
}
This function works fine as long its a wcf service.
Is there some possibility to change this function depending on the view that uses this viewmodel?
Here's the full code of one viewmodel:
namespace ISynergy.Modules.Relations
{
public class Customer_Activities_ViewModel : Customer_Base_ViewModel
{
public Customer_Activities_ViewModel()
: base()
{
}
public override void Add()
{
throw new NotImplementedException();
}
public override Task Delete(object vItem)
{
throw new NotImplementedException();
}
public override void Edit(object vItem)
{
throw new NotImplementedException();
}
public async override Task<object> RetrieveItems()
{
if ((Customer != null))
{
return await Task.Run(() => Current.ApplicationService.Schedule_Appointments_GetItems_By_Relation(Customer.Relation_ID));
}
else
{
return null;
}
}
}
}
Of course the same applies to the other overridden procedures/functions (Add, Delete and Edit)
Ok, I think I got the answer.
I anyone has a suggestion or comment please be free to provide one.
Maybe this answer helps others with the same problem.
The solution consists of two adjustments.
public async override Task<object> RetrieveItems()
{
if ((Customer != null))
{
return await Task.Run(GetItems_Action);
//return await Task.Run(() => Current.ApplicationService.Schedule_Appointments_GetItems_By_Relation(Customer.Relation_ID));
}
else
{
return null;
}
}
and adding the next lines to allow adjustment from the view.
public Action Add_Action;
public Action<object> Edit_Action;
public Func<object, Task> Delete_Action;
public Func<Task<object>> GetItems_Action;
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.
Over the past 5 months we have been prototyping GWT and setting up the infrastructure. WE are using GXT for the widgets with MVP and Command Pattern implementations. However, we are currently looking to do a spike on a ComboBox with autosuggest from a live Database. I would like to do this in the framework of the MVP and Command pattern implementations. Any one out there have any ideas how to go about doing this?
I solved that using a generic DispatchDataProxy modelled over the Command Pattern. Thanks for the link, but GXT documentation leaves a lot to be desired, though the framework is really nice and cool.
I will post the code here `public class DispatchDataProxy implements DataProxy> {
#Inject
private DispatchAsync dispatch ;//= new StandardDispatchAsync(new DefaultExceptionHandler());
#Override
public void load(DataReader<ListLoadResult<X>> reader, Object loadConfig, final AsyncCallback<ListLoadResult<X>> callback) {
if (loadConfig instanceof BasePagingLoadConfig) {
BasePagingLoadConfig a = (BasePagingLoadConfig) loadConfig;
Map<String, Object> map = a.getProperties();
Object data = map.get("query");
XCommand action = new XCommand();
action.setX((String) data);
dispatch.execute(action, new AsyncCallback<XResult>() {
#Override
public void onFailure(Throwable arg0) {
//Log.debug("Some error:" + arg0.getMessage());
callback.onFailure(arg0);
}
#Override
public void onSuccess(XResult arg0) {
ListLoadResult<X> list = arg0.getList();
callback.onSuccess(list);
}
});
}
}
public DispatchAsync getDispatch() {
return dispatch;
}
public void setDispatch(DispatchAsync dispatch) {
this.dispatch = dispatch;
}
}`
Hope its useful. Will appreciate some comments as well
Have you looked here?
http://www.sencha.com/examples-2/explorer.html#advancedcombobox
They show something similar. The issue with GXT is you are better off using their DataProxy because you need to set a ModelData instance.
I found solution for simple combo box, override getValue method:
public SimpleComboBox<String> createEditableSimpleComboBox() {
return new SimpleComboBox<String>() {
#Override
public SimpleComboValue<String> getValue() {
SimpleComboValue<String> v = super.getValue();
String raw = getRawValue();
if ((v == null || v.getValue() == null) && raw != null && !raw.isEmpty()) {
v = new SimpleComboValue<String>(raw){
private static final long serialVersionUID = 1L;
};
}
return v;
}
};
}
Now when you add to combo box default value (not defined in store) method getValue returns this value - not null.
I am working on a 'proof of concept' Silverlight 4 project and am learning the way of THE ASYNC. I have stopped fighting the urge to implement some pseudo-synchronous smoke and mirrors technique. I am going to learn to stop worrying and love THE ASYNC.
Most of the time I just use a BusyIndicator while async methods are running and all is good but I have run into a few situations where I need to call methods sequentially. I put together this example and it works. But in my experience... if it works... there is something wrong with it.
When is this going to blow up in my face or steal my wife or date one of my daughters?
Is there a better way to do this?
The Code:
public class CustomPage : Page
{
static readonly object _AsyncMethodChain_Lock = new object();
private Dictionary<Action<object>, string> _AsyncMethodChain = new Dictionary<Action<object>, string>();
public Dictionary<Action<object>, string> AsyncMethodChain
{
get { lock (_AsyncMethodChain_Lock) { return this._AsyncMethodChain; } }
set { lock (_AsyncMethodChain_Lock) { this._AsyncMethodChain = value; } }
}
private void CustomPage_Loaded(object sender, RoutedEventArgs e)
{
if (!System.ComponentModel.DesignerProperties.GetIsInDesignMode(this))
{
var user = this.SecurityProvider.UserObject as TimeKeeper.UserServiceReference.User;
if (user == null)
return;
this.AsyncMethodChain.Add(
data =>
{
var userServiceClient = new UserServiceClient();
userServiceClient.GetCompleted +=
(send, arg) =>
{
var userViewSource = this.Resources["userViewSource"] as CollectionViewSource;
userViewSource.Source = new List<UserServiceReference.User>(new UserServiceReference.User[1] { arg.Result });
userViewSource.View.MoveCurrentToPosition(0);
this.AsyncMethodChain.ExecuteNext(arg.Result.UserID, this.BusyIndicator);
};
userServiceClient.GetAsync(user.UserID);
},
"Loading user..."
);
this.AsyncMethodChain.Add(
data =>
{
var userID = (int)data;
var timeLogServiceClient = new TimeLogServiceClient();
timeLogServiceClient.FindByUserIDCompleted +=
(send, arg) =>
{
var timeLogViewSource = this.Resources["timeLogViewSource"] as CollectionViewSource;
timeLogViewSource.Source = arg.Result;
this.AsyncMethodChain.ExecuteNext(null, this.BusyIndicator);
};
timeLogServiceClient.FindByUserIDAsync(userID);
},
"Loading time logs..."
);
this.AsyncMethodChain.ExecuteNext(null, this.BusyIndicator);
}
}
}
public static class Extensions
{
public static void ExecuteNext(this Dictionary<Action<object>, string> methods, object data, BusyIndicator busyIndicator)
{
if (methods.Count <= 0)
{
busyIndicator.BusyContent = "";
busyIndicator.IsBusy = false;
return;
}
else
{
var method = methods.Keys.ToList<Action<object>>()[0];
busyIndicator.BusyContent = methods[method];
busyIndicator.IsBusy = true;
}
methods.ExecuteNext(data);
}
public static void ExecuteNext(this Dictionary<Action<object>, string> methods, object data)
{
var method = methods.Keys.ToList<Action<object>>()[0];
methods.Remove(method);
method(data);
}
}
What you have sine looks pretty good, but if you are still worried about the callsequence i would sugest that you create a new method in your webservice which will call the other four in whatever sequence you need them to and call this new method from your silverlight application.
I dont see the need for you to do that as your current implemenation is pretty good as well.
Any ideas on how i get MVP working with Silverlight? How Do I get around the fact there is no load event raised?
This the view I have:
public partial class Person: IPersonView
{
public event RoutedEventHandler Loaded;
public Person()
{
new PersonPresenter(this);
InitializeComponent();
}
public Person Person
{
set { Person.ItemsSource = value; }
}
}
And my presenter:
public class PersonPresenter
{
private readonly IPersonView _view;
private readonly ServiceContractClient _client;
public PersonPresenter(IPersonView view)
{
_client = new ServiceContractClient();
_view = view;
WireUpEvents();
}
private void WireUpEvents()
{
_view.Loaded += Load;
}
private void Load(object sender, EventArgs e)
{
_client.GetPersonCompleted += Client_GetPerson;
_client.GetPersonAsync();
}
private void Client_GetPerson(object sender, GetPersonCompletedEventArgs e)
{
_view.Person= e.Result;
}
}
Nothing happened for me as the Loaded event dont seem to get called, how do i get around this?
Tim Ross has a good introduction to Silverlight MVP implementation, with source code.
I believe the loaded event gets called when the control has been initialized, loaded, rendered and ready for use. This means that as long as you don't place it inside a visible container (so that it is rendered), the loaded event won't be risen.
You may consider using MVC# - a Model View Presenter framework with Silverlight 2.0 support.
Oleg Zhukov