MVVMLight Message firing multiple times in WP7 app - silverlight

I am new to MVVMLight and have started to use it in my WP7 app. I have a View/page which registers for MessageDialogs and then my VM sends the message to show it. This works great. However, when you go back to the previous screen (with WP7 back button) and then enter the page again (using AppBar menu item) then the message fires twice (and increments every time you view the page). I assume it the View is registering every time and old versions are subscribing to the message, but I am not sure of how it should work.
I tried to call VM.Cleanup in my NavigatedFrom event to ensure the old messages are unregistered when they leave the page, but this did not help. Here is my code:
View:
public AboutPage()
{
InitializeComponent();
Messenger.Default.Register<DialogMessage>(this, msg => { var result = MessageBox.Show(msg.Content, msg.Caption, msg.Button); });
}
protected override void OnNavigatedFrom(NavigationEventArgs args)
{
ViewModelLocator.AboutViewModelStatic.Cleanup();
base.OnNavigatedFrom(args);
}
AboutViewModel: (Code gets fired by a command)
var message = new DialogMessage("Why does this fire multiple times?", DialogMessageCallback) { Button = MessageBoxButton.OK, Caption = "" };
Messenger.Default.Send(message);
That's all there is too it, but each time you come to this page it fires once more... I assume it is something to do with Cleanup but I am not sure how it is supposed to work in WP7... any tips appreciated...

A view is created & destroyed as you navigate through the application. Therefore, in your AboutPage view's constructor, the view is registering for the message every time it is created.
A better approach is to setup the registration in the ViewModel's constructor, use a ViewModelLocator and databind the View to the ViewModel. The ViewModel is created once and used throughout the lifetime of the application. Jonas Follesoe's FlightsNorway is good WP7 application to learn about MVVMLight, you can find the MVVMLight Messenger class being used very nicely.
HTH, indyfromoz

Related

MVVM Light: how to send a parameter to ViewModel (when opening new window)?

I am trying to learn MVVM with MVVM Light Toolkit in WPF. But I am stuck on one simple problem.
I have an AddEditProfileWindow which basically has a textbox for profile name and a confirm button. It adds new profile to database table or updates name of existing profile.
In MainWindow/MainViewModel I have a list of profiles and two buttons: "Add Profile" and "Edit Selected Profile". They both open this window via commands + messages. For example here is command for the "Add Profile" button
public RelayCommand OpenAddProfileWindowCommand
{
get
{
return _openAddProfileWindowCommand ?? (_openAddProfileWindowCommand = new RelayCommand(
() => { Messenger.Default.Send(new NotificationMessage("OpenAddProfile")); }));
}
}
and it's receiver in MainWindow code behind
private void MessageReceived(NotificationMessage msg)
{
if (msg.Notification == "OpenAddProfile")
{
var window = new AddEditProfileWindow();
window.Owner = this;
window.ShowDialog();
}
}
So the problem is that I need to somehow pass a parameter to the AddEdit... Window/ViewModel (set IsEditing bool property in ViewModel for example) to change window behavior and customize it a bit (change title and the confirm button text to "Add" or "Update"). Also for updating I need Profile object (or at least Id) of selected record.
For creating ViewModels I use ViewModelLocator and Unity
public ViewModelLocator()
{
var container = new UnityContainer();
ServiceLocator.SetLocatorProvider(() => new UnityServiceLocator(container));
container.RegisterType<MainViewModel>(new ContainerControlledLifetimeManager()); // singleton
container.RegisterType<AddEditProfileViewModel>();
}
public AddEditProfileViewModel AddEditProfile
{
get
{ return ServiceLocator.Current.GetInstance<AddEditProfileViewModel>(); }
}
I have read a lot of similar threads and examples but still don't understand how should I pass parameters to view models. Some answers suggest creating view models on app startup (and make them singletons) in the ViewModelLocator and then I can send message before opening. But looks like not very clean and also I will need to reset view models before opening (via Cleanup() probably).
Is there any better/easier/cleaner approach?
In my opinion, Messenger and getting AddEditProfileViewModel from IoC are not suitable in this scenario. First you send message from a UI's DataContext to UI. Messenger works between loosely coupled components and usually on the same level, view model and view model for example. If you want view model to notify view, you can use InteractionRequest from Prism. Second, AddEditProfileViewModel can be considered as a temporary, based on its view is a modal dialog, so its creation might depend on the environment that creates it.
One approach, using shared service, maybe called IDialogService, which has a method might called ShowAddEditDialog. Your main view model gets this service from IoC and calls it when executing command, add/edit. When calling the method, main view model also creates AddEditProfileViewModel and passing states, such as add/edit, existing profile, etc.
Another approach, using application controller, if you still want to keep Messenger and IoC. You still can use Messenger here but it is not the view who listens to messages, instead it is an application controller. Now, application controller, main view model, AddEditProfileViewModel and AddEdit window are all resolved from IoC container. The application controller holds both view models and listen to the message. When it got message from main view model, it updates states on AddEditProfileViewModel, resolve dialog, set DataContext and show the dialog. You can put the application controller instance in MainWindow code behind or anywhere since once it gets resolved from IoC, it is autonomous.

Make View Models activation aware in Prism/Composite MVVM WPF application

In my MVVMC application I have a process which contains multiple steps, basically a wizard.
My controller resolves my outer View (call it WizardView) and adds it to the main region.
WizardView contains a breadcrumb trail to show the progress through the wizard and a sub Region to load other views into (call this WizardRegion). Step1View is the first View loaded into WizardRegion.
Each view has it's ViewModel injected into the constructor using the Unity container.
WizardViewModel subscribes to several event aggregation events which are published by the step view models.
As each step is completed the View Model publishes an event which WizardViewModel uses to store the state, this means WizardViewModel is collecting the data from each step as we progress. The step ViewModel also calls the controller to load the next step into WizardRegion.
At the final step WizardViewModel saves the result of the wizard and the MainRegion is navigated back to some other screen.
The next time we enter the wizard, we create a new instance of all the Views and ViewModels but the event subscriptions from the previous wizard still exist.
How can I make my view models aware that they're de-activated so I can unsubscribe my events?
Another option would be to unsubscribe from the event in the event handler. This will probably work but will add complexity when I step back in the wizard and need to re-subscribe to the events again.
The solution is to implement Microsoft.Practices.Prism.IActiveAware in my View Model.
public bool IsActive
{
get { return _isActive; }
set
{
if (_isActive != value)
{
_isActive = value;
DoActiveChangedWork(value);
}
}
}
public event EventHandler IsActiveChanged;
It's also possible to implement this in the View but it's not a requirement.
If you are talking about Prism EventAggregator - you can set keepSubscriberReferenceAlive parameter to false so it will use a weak reference under the hood so everything will be "unsubscribed" automatically by GC when an object will "die".
fundAddedEvent.Subscribe(FundAddedEventHandler, ThreadOption.UIThread, true);
otherwise you have to do it yourself by explicitly unsubscribing:
fundAddedEvent.Unsubscribe(FundAddedEventHandler);

UI freezed instead of showing BusyIndicator in Silverlight

This is a side-related question to this other question:
BackgroundWorker in Silverlight ViewModel
I have a TabControl where I load many TabItems when the user selects menu options. I load this Tabs by binding the TabControl ItemsSource to an ObservableCollection. When I add a new TabItem to this Collection, it is shown perfectly.
The problem is I've realized that since user press a button until the tab is created (ViewModel and View creation takes a couple of seconds), the screen is freezed.
I've tried to set "IsBusy" before calling the "loadTab" but it doesn't shows up... I've tried almost everything with async calls but the UI thread is in use and it throws an exception when I create the new tab control.
Is there any trick I'm loosing??? Any ideas??? Thanks in advance.
have you seen this post?
http://www.dotnetspark.com/kb/3524-doesnt-your-girlfriend-deserves-more-time.aspx
It helps when you avoid heavy stuff in the load event and make Visible=true after you finish to load all your resources, so in that sense you avoid the user feeling tempted to click something that is not ready yet.
Not sure if it helps, but how about this idea?
public void DoStuff(Object values)
{
//your values object could be anything,
//they might even be some objects from your form
//as long as you dont modify them in the other thread
imgLoading.Visible=true;
var client = new Proxy();
client.OnWorkCompletedAsync +=client_OnCompleted() ;
client.Work(values);
}
void client_OnCompletedAsync(object sender, EventArgs e)
{
imgLoading.Visible=false;
//now you can update the UI with other stuff
}

How to complete async call before loading silverlight app

I have a silverlight application that uses WCF, and I would like to make a WCF call to load some data before the usercontrol is loaded so that I could use the data with an autocompletebox. Any suggestions as to how to accomplish this?
Not sure if your user interface will be suitable to use a loading indicator or a progress bar. If you can use loading indicator, then it might be a good option to display the busy / loading indicator while the async call is in progress. That would disable the user from clicking on the dropdown or any other control while the data is being retrieved from the WCF service.
You can do the async call in the Application_Startup() method of your App.xaml.cs file, and set RootVisual in your async callback instead of in Application_Startup().
All WCF service calls in Silverlight are asynchronous. I've learned to use Lambdas and a very useful class called Action (which is a wrapper for an event and a delegate).
Using the application startup as RobSiklos suggested would work great for getting it before showing the control. This shows the code that could also work inside of the userControl loaded event, incorporating a loading overlay (you could use a border with centered text that goes over the whole app or a Silverlight toolkit control). This approach will give more immediate feedback to the user, especially if your data service call will take a longer time.
public MyUserControl : UserControl
{
public MyUserControl()
{
this.Loaded += new RoutedEventHandler(View_Loaded);
}
void View_Loaded(object sender, RoutedEventArgs e)
{
// start showing loading overlay
MyService service = new Service(...);
service.GetDataCompleted += (o, args) =>
{
var data = args.Results;
// hide loading overlay
}
}
}

Silverlight listbox itemsource change not updating list

Working with a listbox in windows phone 7 I am trying to make an async web service call then update the listbox on success.
The method that calls the webservice looks like this:
public void GetReadingList(Action<ObservableCollection<MiniStoryViewModel>> success, Action<string> failure)
I am calling the method with this code:
api.GetReadingList(
(items) => Dispatcher.BeginInvoke(() =>
{
lsbNewest.ItemsSource = items;
}),
(error) =>
{
MessageBox.Show(error);
});
Using this code nothing happens ui wise until I click or scroll on the listbox - then its contents is updated correctly. I am assuming that the code is not being run on the correct thread, how can i fix this?
No errors in your code, you are right regarding the spec...
Try : flush the ItemsSource, then fill it with the items with addRange, or anything else.
Check if your UI is automatically updated.
If not, create a basic silverlight app on Windows, and compare the two behaviours... maybe a bug ;=)
I solved the issue I was having by implementing inotifypropertychanged on my view model and raising the property changed event on the ui thread.

Resources