In my first view model (renamed to MainViewModel) I have a list of ActionViewModels.
In my xaml i have a listbox which is bound to the list, in the listbox i have a template which binds to properties from the ActionViewModel.
So far so good and everything works.
When selecting one of the listitems i navigate to an ActionViewModel and pass the id with it.
The ActionViewModel retrieves information from a static list in memory from which the MainViewModel also retrieved the information to create the list of actionviewmodels.
So far still so good, i can edit the properties, all the bindings do work fine and i'm all happy.
By clicking the save button the information is gathered and stored in the static list.
When i hit the back button i go back to the list, but unfortunately the values showing there are still the same, is there some way to send a command to reload the items in the list? To pass a complete viewmodel as reference to a new ActionViewModel? Or some property which tells the parent 'this viewmodel in your list has been updated'?
I am sure the above text is a bit confusing, so here is some code to clarify it a bit (hopefully)
MainViewModel.cs
private List<ActionViewModel> _actionViewModels;
public List<ActionViewModel> ActionViewModels
{
get { return _actionViewModels; }
set { _actionViewModels = value; RaisePropertyChanged(() => ActionViewModels); }
}
private Cirrious.MvvmCross.ViewModels.MvxCommand<int> _navigateToAction;
public System.Windows.Input.ICommand NavigateToAction
{
get
{
_navigateToAction = _navigateToAction ?? new Cirrious.MvvmCross.ViewModels.MvxCommand<int>((action) => NavigateToTheDesiredAction(action));
return _navigateToAction;
}
}
private void NavigateToTheDesiredAction(int action)
{
ShowViewModel<ActionViewModel>(new { id = action });
}
// Get DTOs from server or from cache and fill the list of ActionViewModels
public async Task Load()
{
ActionService actionService = new ActionService();
List<ActionViewModel> actionViewModels = new List<ActionViewModel>();
MyActions = await actionService.GetMyActions();
foreach (ActionDTO action in MyActions)
{
ActionViewModel actionViewModel = new ActionViewModel();
await actionViewModel.Load(action.id);
actionViewModels.Add(actionViewModel);
}
ActionViewModels = actionViewModels;
}
ActionViewModel.cs
public int ID
{
get { return TheAction.id; }
set { TheAction.id = value; RaisePropertyChanged(() => ID); }
}
public string Title
{
get { return TheAction.Title; }
set { TheAction.Title = value; RaisePropertyChanged(() => Title); }
}
public async Task Load(int actionId)
{
ActionDTO TheAction = await actionService.GetAction(actionId);
this.ID = TheAction.id;
this.Title = TheAction.Title;
}
private Cirrious.MvvmCross.ViewModels.MvxCommand _save;
public System.Windows.Input.ICommand Save
{
get
{
_save = _save ?? new Cirrious.MvvmCross.ViewModels.MvxCommand(PreSaveModel);
return _save;
}
}
private void PreSaveModel()
{
SaveModel();
}
private async Task SaveModel()
{
ValidationDTO result = await actionService.SaveAction(TheAction);
}
ActionService.cs
public static List<ActionDTO> AllActions = new List<ActionDTO>();
public async Task<ActionDTO> GetAction(int actionId)
{
ActionDTO action = AllActions.FirstOrDefault(a => a.id == actionId);
if (action == null)
{
int tempActionId = await LoadAction(actionId);
if (tempActionId > 0)
return await GetAction(actionId);
else
return new ActionDTO() { Error = new ValidationDTO(false, "Failed to load the action with id " + actionId, ErrorCode.InvalidActionId) };
}
return action;
}
private async Task<int> LoadAction(int actionId)
{
ActionDTO action = await webservice.GetAction(actionId);
AllActions.Add(action);
return action.id;
}
public async Task<ValidationDTO> SaveAction(ActionDTO action)
{
List<ActionDTO> currentList = AllActions;
ActionDTO removeActionFromList = currentList.FirstOrDefault(a => a.id == action.id);
if (removeActionFromList != null)
currentList.Remove(removeActionFromList);
currentList.Add(action);
AllActions = currentList;
return await webservice.SaveAction(action);
}
There are 3 ways I can think of that would allow you to do this.
The ActionService could send out some sort of notification when data changes. One easy way to do this is to use the MvvmCross Messenger plugin. This is the way the CollectABull service works in CollectionService.cs in the N+1 days of mvvmcross videos (for more info watch N=13 in http://mvvmcross.wordpress.com)
This is the approach I generally use. It has low overhead, uses WeakReferences (so doesn't leak memory), it is easily extensible (any object can listen for changes), and it encourages loose coupling of the ViewModel and Model objects
You could implement some kind of Refresh API on the list ViewModel and could call this from appropriate View events (e.g. ViewDidAppear, OnNavigatedTo and OnResume).
I don't generally use this approach for Refreshing known data, but I have used it for enabling/disabling resource intensive objects - e.g. timers
For certain shape of model data (and especially how often it changes), then I can imagine scenarios where this approach might be more efficient than the messenger approach.
You could extend the use of INotifyPropertyChanged and INotifyCollectionChanged back into your model layer.
I've done this a few times and it's worked well for me.
If you do choose this approach, be careful to ensure that all Views do subscribe to change events using WeakReference subscriptions such as those used in MvvmCross binding - see WeakSubscription. If you didn't do this, then it could be possible for the Model to cause Views to persist in memory even after the UI itself has removed them.
Related
I'm making an app for some research. I'm trying to listen to all of the wifi networks, and save them in a database. But I need to save more information then just the wifi details.
I'm using Xamarin Forms for a cross platform solution.
What I already have that's working:
- A View that is binding with the ViewModel.
- The ViewModel is working as predicted, withe the values of the entries that I've created in the View.
- The project MySolution.Droid has code that accesses the wifi networks.
- I have a Receiver that is firing OnReceive with the information.
What I've done that might not be the best choice:
- Since after starting scan I'm waiting (async) for the OnReceive to fire. I've chosen to send the data to the ViewModel via MessagingCenter.
- I'm sending with success the information. And receiving it in the ViewModel with MessagingCenter.Subscribe
What's wrong:
- I can't get the information out (into my ViewModel), every property that the ViewModel has is null.
- What I'd like is to just get the information to the ViewModel. Or if I could just get the information that is binding to the View in the BeginInvokeOnMainThread, that wold work just fine.
The code that I have:
ScanViewModel.cs:
string locationName;
public string LocationName
{
get
{
return locationName;
}
set
{
if (locationName != value)
{
locationName = value;
OnPropertyChanged();
StartScanCommand.ChangeCanExecute();
}
}
}
public ScanViewModel()
{
MessagingCenter.Subscribe<IScanReceiver, List<ScanResult>>(this, "receiveWiFiNetworks", (sender, arg) =>
{
Device.BeginInvokeOnMainThread(
async () =>
{
var l = LocationName // THIS IS NULL
......
}
);
});
}
App.Droid.Wifi.cs:
public void GetWifiNetworks()
{
WiFiNetworks = new List<ScanResult>();
wifi = (WifiManager)context.GetSystemService(Context.WifiService);
wifiReceiver = new WifiReceiver();
context.RegisterReceiver(wifiReceiver, new IntentFilter(WifiManager.ScanResultsAvailableAction));
wifi.StartScan();
}
class WifiReceiver : BroadcastReceiver, IScanReceiver
{
public override void OnReceive(Context context, Intent intent)
{
List<ScanResult> scanwifinetworks = wifi.ScanResults.ToList();
MessagingCenter.Send<IScanReceiver, List<ScanResult>>(this, "receiveWiFiNetworks", scanwifinetworks);
}
}
For those who didn't read the post above (like, tldr), I'm receiving the list of ScanResult in the OnReceive and I need it to be in the ViewModel, But I also need the rest of the properties...
If there's a simpler way of doing this, I'm all ears!!!
Thanks in advance
Solution I found for my problem.
It might not be the best or cleanest way to do it, but it works for now.
In the Wifi.cs class I fill a List
public List<ScanResult> GetWifiNetworks(){
return WiFiNetworks;
}
class WifiReceiver : BroadcastReceiver, IScanReceiver
{
public override void OnReceive(Context context, Intent intent)
{
WiFiNetworks = wifi.ScanResults.ToList();
}
}
And I periodically get the list with a Get Method from the ViewModel as follows,
void StartScan()
{
Timer _wifiAsyncTimer = new Timer();
_wifiAsyncTimer.Elapsed += GetWifiNetworks;
_wifiAsyncTimer.Interval = ListenPeriodicity * 1000;
_wifiAsyncTimer.Enabled = true;
}
private void GetWifiNetworks(object source, ElapsedEventArgs e)
{
var wifiNetworks = _wifiManager.GetWifiNetworks();
if (wifiNetworks.Count > 0)
{
LocationInformation LocationInformation = new LocationInformation()
{
LocationName = LocationName,
Temperature = Temperature,
Notes = Notes,
Timestamp = DateTime.Now
};
LocationInformations = LocationInformation.AddWifiInformation(wifiNetworks);
}
}
With this, I can access all the Information and the properties aren't null.
If anybody has a better solution, I'm still all ears!!!!!
I am making my first steps with ReactiveUI but I am not able to event get a very basic example to work. I want to execute an task as soon as the property "SearchTerm" changes. I followed the instructions on the github page of ReactiveUI ("a compelling example").
I have a ViewModel with the property SearchTerm which is bind to a TextBox in my view. If I update the content of the TextBox the property is updated as expected (I used UpdateSourceTrigger=PropertyChanged).
The code in my observables never fires:
public class MainWindowViewModel: ReactiveObject
{
public string SearchTerm
{
get { return m_SearchTerm; }
set { this.RaiseAndSetIfChanged(ref m_SearchTerm, value); }
}
private string m_SearchTerm;
public MainWindowViewModel()
{
SearchResults = new List<string>();
var canSearch = this.WhenAny(x => x.SearchTerm, x => !string.IsNullOrWhiteSpace(x.GetValue()));
var search = ReactiveCommand.CreateAsyncTask(canSearch,
async _ => {
// this is never called
return await dosearch(this.SearchTerm);
});
search.Subscribe(results => {
// this is never called too
SearchResults.Clear();
SearchResults.AddRange(results);
});
}
private async Task<List<string>> dosearch(string searchTerm)
{
await Task.Delay(1000);
return new List<string>() { "1", "2", "3" };
}
public List<string> SearchResults { get; private set; }
}
The code inside your command never fires, because you are not invoking the command anywhere. You have to bind your command to any event (like input text changing, button click, etc).
First, expose your command from ViewModel:
public ReactiveCommand<List<string>> Search { get; private set; }
next, assign it in constructor:
this.Search = ReactiveCommand.CreateAsyncTask(canSearch,
async _ => {
return await dosearch(this.SearchTerm);
});
and finally, invoke the command when the input changes (this is the crucial missing part of your code):
this.WhenAnyValue(x => x.SearchTerm)
.InvokeCommand(this, x => x.Search);
Put the abouve code in the constructor.
Note that this will fire searches constantly when the user types. To fix this, you can use an Rx operator called Throttle, as seen in the example you linked to.
I'm using MVVM light Framework with WPF and I have a DataGrid that contain all the customers loaded from my SQLite database, But it take too much time to display the Window so if any one can help me for I can dislpay the window and load the DataGrid separately.I think that the Window is taking time because of the DataGrid Binding.
public ObservableCollection<CustumerModel> customerList
{
get
{
_customerList = new ObservableCollection<CustumerModel>();
IList<CustumerModel> listCustomer = RemplireListCustomer();
_customerList = new ObservableCollection<CustumerModel>(listCustomer);
return _customerList;
}
the method RemplireListCustomer
private IList<CustumerModel> RemplireListCustomer()
{
IList<CustumerModel> listCustomer = new List<CustumerModel>();
foreach (var c in _customerService.GetAllCustomers())
{
listCustomer.Add((CustumerModel)c);
}
return listCustomer;
}
You could load your data async by starting a new Task in e.g. your class' constructor.
public class YourClass
{
public YourClass()
{
TaskEx.Run(() =>
{
var listCustomer = RemplireListCustomer();
CustomerList = new ObservableCollection<CustumerModel>(listCustomer);
});
}
public ObservableCollection<CustumerModel> CustomerList { get; private set; }
}
And just maybe you do not have to iterate over all customers returned by your service using foreach, just return the collection _customerService.GetAllCustomers()?
I am developing a windows 8 store app, using C#/xaml, with the MVVM pattern.
I want to refresh a page automatically every 30 seconds, after a search I came up with this, but how do I implement it into a MVVM page?
Edit: As of Charleh's answer I came up with this:
var dispatcher = CoreApplication.MainView.CoreWindow.Dispatcher;
var period = TimeSpan.FromSeconds(30);
var timer = ThreadPoolTimer.CreatePeriodicTimer((source) =>
{
await dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
{
RefreshOrdersList();
});
}, period);
But the VS compiler marks an 'Error' under the dispatcher.RunAsync() function: "The 'await' operator can only be used within an async lambda expression". When I remove the 'await' keyword the application runs with a warning saying "because this call is not awaited, execution of the current method continues before the call is completed, Consider apply the 'await' operator to the result of the call".
RefreshOrdersList function -gets all orders and details from WCF service:
private async void RefreshOrdersList()
{
var orders = await proxy.GetAllOrdersAsync();
IList<OrderCart> orderModel = new List<OrderCart>();
foreach (var order in orders)
{
OrderCart oc = new OrderCart { Id = order.Id, FullPrice = Convert.ToDouble(order.Total), TableId = order.TableId, IsDone = false, OrderTime = (DateTime)order.StartOrderTime };
order.OrderDetails = await proxy.GetOrderDetailsByOrderIdAsync(order.Id);
oc.OrderCartItems = new ObservableCollection<OrderCartItem>();
foreach (var orderDetail in order.OrderDetails)
{
var course = await proxy.GetCourseByIdAsync(orderDetail.CourseId);
OrderCartItem oci = new OrderCartItem { Quantity = orderDetail.Amount, Course = new Course(course) };
oc.OrderCartItems.Add(oci);
}
orderModel.Add(oc);
}
var sortOrder = orderModel.OrderByDescending(x => x.Id);
Items = new ObservableCollection<OrderCartViewModel>(sortOrder.
Select(o => new OrderCartViewModel(o))
.ToList());
Items property:
public ObservableCollection<OrderCartViewModel> Items
{
get { return _items; }
private set { SetProperty(ref _items, value); }
}
Anyone??
The dispatcher is found on UI types (controls etc) and it's job is to sync to the UI thread so you will only find this in your views.
Is there any reason the whole view needs to reload?
If you are using the MVVM pattern, you can just do your work asyncronously in the viewmodel, and then update your properties as per usual.
e.g.
class SomeViewModel
{
IEnumerable<Results> Results { get; set; } // Obviously this would actually need to raise PropertyChanged from INotifyPropertyChanged in order to refresh any bindings
public SomeViewModel()
{
var period = TimeSpan.FromMinutes(1);
var timer = ThreadPoolTimer.CreatePeriodicTimer((source) =>
{
// do your query/work here
DoSomeWorkAsync();
},
period);
}
void DoSomeWorkAsync()
{
// Get the data
someService.GetResults((result) => { Results = result.Data; });
}
}
The binding system will take care of the UI update. I assume you just want to go off to the datasource (a web service maybe?) and get new results every 30 seconds, do you need more than just a data/binding update?
Disclaimer: Didn't test this, and you'd also need to be aware of exceptions thrown within the thread pool work item and handle appropriately
Edit: In response to your question about the binding system
The WPF/RT/SL binding system looks for data sources that implement certain interfaces - one of these is INotifyPropertyChanged. As long as the datasource you are binding to implements this (and in this case your datasource is the viewmodel), and you raise a PropertyChanged event for the property when it changes, the binding system will know to refresh
I have a code snippet in Visual Studio (well actually Resharper as it works a bit better) that writes the property for me - I think there is one included in VS but it should look something like this:
private int _someInt;
public int SomeInt
{
get { return _someInt; }
set
{
if(_someInt != value)
{
_someInt = value;
// Helper function in the class which checks to see if propertychanged has any subscribers
OnPropertyChanged("_someInt");
}
}
}
Note: There are better implementations of this using Expressions instead of 'magic strings' or using new language features. If you are using the latest c# version the addition of CallerMemberName attribute means you don't need to specify the property name
Edit 2: Ok a complete example may look like this
public class SomeViewModel : INotifyPropertyChanged
{
#region Properties (that support INotifyPropertyChanged)
private IEnumerable<Result> _results;
public IEnumerable<Result> Results
{
get
{
return _results;
}
set
{
if (value != _results)
{
_results = value;
OnPropertyChanged("Results");
}
}
}
#endregion
// A reference to the service that will get some data
private IDataService _dataService;
public SomeViewModel(IDataService dataService)
{
_dataService = dataService;
var period = TimeSpan.FromMinutes(1);
var timer = ThreadPoolTimer.CreatePeriodicTimer((source) =>
{
// do your query/work here
GetData();
},
period);
}
#region Data fetch method
/// <summary>
/// Method to get some data - waits on a service to return some results
/// </summary>
void GetData()
{
// Get the data
_dataService.GetResults((result) =>
{
// Try this instead
Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher.Invoke(() => {
Results = result.Data; }
});
}
#endregion
#region INotifyPropertyChanged
// Note: usually most MVVM frameworks have a base class which implements the INPC interface for you (such as PropertyChangedBase in Caliburn Micro)
// so it might be worth fishing around in your framework for it if you are using one.. If you aren't using a framework then you are a braver man than me
// start using one now!
/// <summary>
/// The property changed event
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// The helper function to raise the event
/// </summary>
/// <param name="propertyName">Name of the property that changed (to tell the binding system which control to update on screen based on bindings)</param>
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
Edit: update code above. Let me know if you can't get hold of the dispatcher still
Just need to add the 'async' keyword var timer = ThreadPoolTimer.CreatePeriodicTimer( async (source)=>, and no errors or warning were detected:
var dispatcher = CoreApplication.MainView.CoreWindow.Dispatcher;
var period = TimeSpan.FromSeconds(30);
var timer = ThreadPoolTimer.CreatePeriodicTimer( async (source) =>
{
await dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
{
RefreshOrdersList();
});
}, period);
hi what is the easiest way to implement asynch operations on WPF and MVVM, lets say if user if user hits enter when on a field i want to launch a command and then return back while a thread will do some search operations and then come back and update the properties so notification can update the bindings.
thanks!
Rob Eisenberg showed a really clean implementation of running async operations in MVVM during his MIX10 talk. He has posted the source code on his blog.
The basic idea is that you implement the command as returning an IEnumerable and use the yield keyword to return the results. Here is a snippet of code from his talk, which does a search as a background task:
public IEnumerable<IResult> ExecuteSearch()
{
var search = new SearchGames
{
SearchText = SearchText
}.AsResult();
yield return Show.Busy();
yield return search;
var resultCount = search.Response.Count();
if (resultCount == 0)
SearchResults = _noResults.WithTitle(SearchText);
else if (resultCount == 1 && search.Response.First().Title == SearchText)
{
var getGame = new GetGame
{
Id = search.Response.First().Id
}.AsResult();
yield return getGame;
yield return Show.Screen<ExploreGameViewModel>()
.Configured(x => x.WithGame(getGame.Response));
}
else SearchResults = _results.With(search.Response);
yield return Show.NotBusy();
}
Hope that helps.
How about a BackgroundWorker instance to call your command on the VM ?
Update:
Scratch the above suggestion.. There's an online video on MVVM by Jason Dolinger.. I recommend you take a look at that. It's a cleaner way where the view is thin/ does not hold any threading code.
To summarize:
the VM ctor caches the Dispatcher.CurrentDispatcher object (main thread).
when updating the backing store (results), use
_dispatcher.BeginInvoke( () => _results.AddRange( entries) ) so that the UI is updated correctly.
In Shawn Wildermuth's MSDN Article he did something like this:
check out the article here:
http://msdn.microsoft.com/en-us/magazine/dd458800.aspx
and his more recent blog post here:
http://wildermuth.com/2009/12/15/Architecting_Silverlight_4_with_RIA_Services_MEF_and_MVVM_-_Part_1
public interface IGameCatalog
{
void GetGames();
void GetGamesByGenre(string genre);
void SaveChanges();
event EventHandler<GameLoadingEventArgs> GameLoadingComplete;
event EventHandler<GameCatalogErrorEventArgs> GameLoadingError;
event EventHandler GameSavingComplete;
event EventHandler<GameCatalogErrorEventArgs> GameSavingError;
}
with an implementation like this:
public class GameCatalog : IGameCatalog
{
Uri theServiceRoot;
GamesEntities theEntities;
const int MAX_RESULTS = 50;
public GameCatalog() : this(new Uri("/Games.svc", UriKind.Relative))
{
}
public GameCatalog(Uri serviceRoot)
{
theServiceRoot = serviceRoot;
}
public event EventHandler<GameLoadingEventArgs> GameLoadingComplete;
public event EventHandler<GameCatalogErrorEventArgs> GameLoadingError;
public event EventHandler GameSavingComplete;
public event EventHandler<GameCatalogErrorEventArgs> GameSavingError;
public void GetGames()
{
// Get all the games ordered by release date
var qry = (from g in Entities.Games
orderby g.ReleaseDate descending
select g).Take(MAX_RESULTS) as DataServiceQuery<Game>;
ExecuteGameQuery(qry);
}
public void GetGamesByGenre(string genre)
{
// Get all the games ordered by release date
var qry = (from g in Entities.Games
where g.Genre.ToLower() == genre.ToLower()
orderby g.ReleaseDate
select g).Take(MAX_RESULTS) as DataServiceQuery<Game>;
ExecuteGameQuery(qry);
}
public void SaveChanges()
{
// Save Not Yet Implemented
throw new NotImplementedException();
}
// Call the query asynchronously and add the results to the collection
void ExecuteGameQuery(DataServiceQuery<Game> qry)
{
// Execute the query
qry.BeginExecute(new AsyncCallback(a =>
{
try
{
IEnumerable<Game> results = qry.EndExecute(a);
if (GameLoadingComplete != null)
{
GameLoadingComplete(this, new GameLoadingEventArgs(results));
}
}
catch (Exception ex)
{
if (GameLoadingError != null)
{
GameLoadingError(this, new GameCatalogErrorEventArgs(ex));
}
}
}), null);
}
GamesEntities Entities
{
get
{
if (theEntities == null)
{
theEntities = new GamesEntities(theServiceRoot);
}
return theEntities;
}
}
}