I am developing WP7 app. I met some unexpected behavior. I use PerformanceProgressBar from the SilverLight toolktit in my app in several pages. These PerformanceProgressBars are binded to ViewModel property called IsBusy. Each page has its own ViewModel.
....<toolkit:PerformanceProgressBar
VerticalAlignment="Top"
HorizontalAlignment="Left"
IsIndeterminate="{Binding IsBusy}"
Visibility="{Binding IsBusy, Converter={StaticResource BoolToVisibilityConverter}}"
/>......
public bool IsBusy
{
get
{
return this._isBusy;
}
set
{
if (value == this._isBusy)
{
return;
}
this._isBusy = value;
RaisePropertyChanged("IsBusy");
}
}
When I change IsBusy value, I get "Invalid cross-thread access" exception.
Any ideas?
Any change to the visual-tree, i.e. the UI of your application, must be performed from the UI thread. This includes changes to properties that occur via bindings. My guess is that you are updating this property via a background-thread?
In this case, you need to marshal the property change onto the UI thread via the Dispatcher.
public bool IsBusy
{
get
{
return this._isBusy;
}
set
{
if (value == this._isBusy)
{
return;
}
Application.Current.Dispatcher.BeginInvoke(() => {
this._isBusy = value;
RaisePropertyChanged("IsBusy");
});
}
}
This exposes the view to your view-model, so is not very good MVVM! In this case I would 'hide' the dispatcher behind a single method interface IMarshalInvoke, that you provide to the ViewModel.
Or consider using BackgroundWorker which can fire ProgressChanged events onto the UI thread for you.
Related
Now I know properties do not support async/await for good reasons. But sometimes you need to kick off some additional background processing from a property setter - a good example is data binding in a MVVM scenario.
In my case, I have a property that is bound to the SelectedItem of a ListView. Of course I immediately set the new value to the backing field and the main work of the property is done. But the change of the selected item in the UI needs also to trigger a REST service call to get some new data based on the now selected item.
So I need to call an async method. I can't await it, obviously, but I also do not want to fire and forget the call as I could miss exceptions during the async processing.
Now my take is the following:
private Feed selectedFeed;
public Feed SelectedFeed
{
get
{
return this.selectedFeed;
}
set
{
if (this.selectedFeed != value)
{
this.selectedFeed = value;
RaisePropertyChanged();
Task task = GetFeedArticles(value.Id);
task.ContinueWith(t =>
{
if (t.Status != TaskStatus.RanToCompletion)
{
MessengerInstance.Send<string>("Error description", "DisplayErrorNotification");
}
});
}
}
}
Ok so besides the fact I could move out the handling from the setter to a synchronous method, is this the correct way to handle such a scenario? Is there a better, less cluttered solution I do not see?
Would be very interested to see some other takes on this problem. I'm a bit curious that I was not able to find any other discussions on this concrete topic as it seems very common to me in MVVM apps that make heavy use of databinding.
I have a NotifyTaskCompletion type in my AsyncEx library that is essentially an INotifyPropertyChanged wrapper for Task/Task<T>. AFAIK there is very little information currently available on async combined with MVVM, so let me know if you find any other approaches.
Anyway, the NotifyTaskCompletion approach works best if your tasks return their results. I.e., from your current code sample it looks like GetFeedArticles is setting data-bound properties as a side effect instead of returning the articles. If you make this return Task<T> instead, you can end up with code like this:
private Feed selectedFeed;
public Feed SelectedFeed
{
get
{
return this.selectedFeed;
}
set
{
if (this.selectedFeed == value)
return;
this.selectedFeed = value;
RaisePropertyChanged();
Articles = NotifyTaskCompletion.Create(GetFeedArticlesAsync(value.Id));
}
}
private INotifyTaskCompletion<List<Article>> articles;
public INotifyTaskCompletion<List<Article>> Articles
{
get { return this.articles; }
set
{
if (this.articles == value)
return;
this.articles = value;
RaisePropertyChanged();
}
}
private async Task<List<Article>> GetFeedArticlesAsync(int id)
{
...
}
Then your databinding can use Articles.Result to get to the resulting collection (which is null until GetFeedArticlesAsync completes). You can use NotifyTaskCompletion "out of the box" to data-bind to errors as well (e.g., Articles.ErrorMessage) and it has a few boolean convenience properties (IsSuccessfullyCompleted, IsFaulted) to handle visibility toggles.
Note that this will correctly handle operations completing out of order. Since Articles actually represents the asynchronous operation itself (instead of the results directly), it is updated immediately when a new operation is started. So you'll never see out-of-date results.
You don't have to use data binding for your error handling. You can make whatever semantics you want by modifying the GetFeedArticlesAsync; for example, to handle exceptions by passing them to your MessengerInstance:
private async Task<List<Article>> GetFeedArticlesAsync(int id)
{
try
{
...
}
catch (Exception ex)
{
MessengerInstance.Send<string>("Error description", "DisplayErrorNotification");
return null;
}
}
Similarly, there's no notion of automatic cancellation built-in, but again it's easy to add to GetFeedArticlesAsync:
private CancellationTokenSource getFeedArticlesCts;
private async Task<List<Article>> GetFeedArticlesAsync(int id)
{
if (getFeedArticlesCts != null)
getFeedArticlesCts.Cancel();
using (getFeedArticlesCts = new CancellationTokenSource())
{
...
}
}
This is an area of current development, so please do make improvements or API suggestions!
public class AsyncRunner
{
public static void Run(Task task, Action<Task> onError = null)
{
if (onError == null)
{
task.ContinueWith((task1, o) => { }, TaskContinuationOptions.OnlyOnFaulted);
}
else
{
task.ContinueWith(onError, TaskContinuationOptions.OnlyOnFaulted);
}
}
}
Usage within the property
private NavigationMenuItem _selectedMenuItem;
public NavigationMenuItem SelectedMenuItem
{
get { return _selectedMenuItem; }
set
{
_selectedMenuItem = val;
AsyncRunner.Run(NavigateToMenuAsync(_selectedMenuItem));
}
}
private async Task NavigateToMenuAsync(NavigationMenuItem newNavigationMenu)
{
//call async tasks...
}
In order to get PropertyChanged to fire in NUnit tests, I had to set ShouldAlwaysRaiseInpcOnUserInterfaceThread(false). Are there any repercussions to this when I later use the class as a ViewModel? Maybe I should be setting up a user interface thread in NUnit? Help!
public interface ISomething : INotifyPropertyChanged
{
}
public class Something : MvxNotifyPropertyChanged, ISomething
{
public Something()
{
ShouldAlwaysRaiseInpcOnUserInterfaceThread(false);
}
private int _num;
public int Num
{
get { return _num; }
set { if (_num != value) { _num = value; RaisePropertyChanged(() => Num); }
}
}
By default MvvmCross marshals calls like RaisePropertyChanged onto the UI thread for the convenience of developers.
If you want to disable this on an individual object, you can call ShouldAlwaysRaiseInpcOnUserInterfaceThread(false); for that object (this is a method call rather than a property as properties on ViewModel objects are generally reserved for INotifyPropertyChanged use)
If you want to disable this by default on all objects then you can use Mvx.Resolve<IMvxSettings>().AlwaysRaiseInpcOnUserInterfaceThread = false;
If during testing you want to provide a mock implementation for the UI thread marshalling, then see for example the N=29 video in http://mvvmcross.blogspot.co.uk/ - with some MockDispatcher code inside https://github.com/MvvmCross/NPlus1DaysOfMvvmCross/tree/master/N-29-TipCalcTest/TipCalcTest.Tests
I have two viewmodel, on the first viewmodel i have a listbox:
<ListBox x:Name="MainMenu" toolkits:TiltEffect.IsTiltEnabled="True"
SelectedItem="{Binding SelectedItem, Mode=TwoWay}"
ItemTemplate="{StaticResource MainMenu}"
ItemsSource="{Binding Categories}" Margin="0,97,0,0"
Tap="MainMenu_Tap">
In the second page, i have a listpicker
<toolkit:ListPicker Margin="0,153,0,0" Background="{StaticResource PhoneAccentBrush}" VerticalAlignment="Top"
ItemsSource="{Binding Categories}"
SelectedItem="{Binding Item}"
ItemTemplate="{StaticResource CategorySelector}"
FullModeHeader="Category"
FullModeItemTemplate="{StaticResource FullCategorySelector}"
BorderBrush="{StaticResource PhoneAccentBrush}"/>
What i want is when I navigate to second page, the selected item in the first page will be selected in the second page. But I always get the selected item must always set to a valid value when I navigate to second page.
first viewmodel
private CategoryModel _selectedItem = null;
public CategoryModel SelectedItem
{
get { return _selectedItem; }
set
{
if (_selectedItem == value)
{
return;
}
var oldValue = _selectedItem;
_selectedItem = value;
RaisePropertyChanged("SelectedItem", oldValue, value, true);
}
}
second viewmodel
private CategoryModel _item = null;
public CategoryModel Item
{
get { return _item; }
set
{
if (_item == value)
{
return;
}
var oldValue = _item;
_item = value;
// Update bindings, no broadcast
RaisePropertyChanged("Item");
}
}
EDIT
When I change the listpicker in the second page to Listbox, it works pretty well.
So this is an issue enter link description here. How should I do to get this thing work with the listpicker?
I think you're confusing views and viewmodels.
Because you're binding the selected item in XAML, when the XAML is parsed and the page created it's trying to bind to an item in a collection which hasn't been created yet. This is why the comments on the bug suggest a work around when setting this in code behind.
In your Tap handler on the first page, I assume that you're passing some details of the selected item to the second page. You could, therefore, remove the XAML binding of the selected item and in the OnNavigatedTo event handler on the second page set the binding in code, once you know the ItemsSource has been populated.
Alternatively, you could consider having the two pages share the same viewmodel instance.
ListPicker uses Items.IndexOf to get the index of item instance that should select.
If the instance does not match (it is not an object instance from the collection) the IndexOf will return -1 and the InvalidOperationException is thrown with the message: "SelectedItem must always be set to a valid value".
Override Equals method of the type in the collection and it will work as expected.
Example:
public override bool Equals(object obj)
{
var target = obj as ThisTarget;
if (target == null)
return false;
if (this.ID == target.ID)
return true;
return false;
}
Hope it helps
I was just reading Rx HOL NET. Upon finding (example uses Windows Forms):
var moves = Observable.FromEvent<MouseEventArgs>(frm, "MouseMove");
I wonder how can I instantiate and pass the reference to moves to ViewModel in some WPF MVVM setup? In my understanding it does make sense to try and filter this stream of data inside ViewModel.
Or, how to do something similar for keyboard input into TextBox? In this scenario you wouldn't, for example, attach some text masking behavior to a control in XAML but would, instead, let Observer in VM filter and validate keyboard input.
Am I completely off the track?
Here is an example of how you could implement the web service dictionary in a MVVM fashion. It has three parts:
The ObservablePropertyBacking class, a backing for properties (of T) that also implements IObservable
The MyViewModel class. It contains a property CurrentText which uses an ObservablePropertyBacking as backing storage. It also observes the value of this property and uses it to call the dictionary web service.
The MainView.xaml which contains a TextBox. Its Text property is two-way bound to the CurrentText property on the view model.
MyViewModel.cs:
class MyViewModel: INotifyPropertyChanged
{
#region INotifyPropertyChanged implementation
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string p)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(p));
}
#endregion
public MyViewModel()
{
SetupProperties();
}
#region CurrentText
/* We use a special class for backing of the CurrentText property. This object
* holds the value of the property and also dispatches each change in an observable
* sequence, i.e. it implements IObservable<T>.
*/
private ObservablePropertyBacking<string> _textInput;
public string CurrentText
{
get { return _textInput.Value; }
set
{
if (value == _textInput.Value) { return; }
_textInput.Value = value;
RaisePropertyChanged("CurrentText");
}
}
#endregion
/* Create property backing storage and subscribe UpdateDictionary to the observable
* sequence. Since UpdateDictionary calls a web service, we throttle the sequence.
*/
private void SetupProperties()
{
_textInput = new ObservablePropertyBacking<string>();
_textInput.Throttle(TimeSpan.FromSeconds(1)).Subscribe(UpdateDictionary);
}
private void UpdateDictionary(string text)
{
Debug.WriteLine(text);
}
}
ObservablePropertyBacking.cs:
public class ObservablePropertyBacking<T> : IObservable<T>
{
private Subject<T> _innerObservable = new Subject<T>();
private T _value;
public T Value
{
get { return _value; }
set
{
_value = value;
_innerObservable.OnNext(value);
}
}
public IDisposable Subscribe(IObserver<T> observer)
{
return _innerObservable
.DistinctUntilChanged()
.AsObservable()
.Subscribe(observer);
}
}
MainPage.xaml:
<Window
x:Class="RxMvvm_3435956.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<TextBox
Text="{Binding CurrentText, UpdateSourceTrigger=PropertyChanged}" />
</Grid>
</Window>
This might help: Reactive Extensions (Rx) + MVVM = ?
The easiest way of doing the keyboard sample would be to two-way bind the text to a property of the ViewModel. The Text setter could then write to a private Subject that the rest of your code uses as a basis of IObservable<string>. From there, you can complete the HOL sample.
Mouse movements are generally considered too "view" to put in the ViewModel, but if the logic that came off it was complex enough, you could have it execute an ICommand or perhaps put the logic into a behavior. If it were an ICommand, you could have the command have a WhenExecuted IObservable property that you could pick up in your ViewModel.`
My application has a menu option to allow the creation of a new account. The menu option's command is bound to a command (NewAccountCommand) in my ViewModel. When the user clicks the option to create a new account, the app displays a "New Account" dialog where the user can enter such data as Name, Address, etc... and then clicks "Ok" to close the dialog and create the new account.
I know my code in the ViewModel is not correct because it creates the "New Account" dialog and calls ShowDialog(). Here is a snippet from the VM:
var modelResult = newAccountDialog.ShowDialog();
if (modelResult == true)
{
//Create the new account
}
how do i avoid creating and showing the dialog from within my VM so I can unit test the VM?
I like the approach explained in this codeproject article:
http://www.codeproject.com/KB/WPF/XAMLDialog.aspx
It basically creates a WPF Dialog control that can be embedded in the visual tree of another window or usercontrol.
It then uses a style trigger that causes the dialog to open up whenever there is content in the dialog.
so in you xaml all you have to do is this(where DialogViewModel is a property in you ViewModel):
<MyControls:Dialog Content = {Binding DialogViewModel}/>
and in you ViewModel you just have to do the following:
DialogViewModel = new MyDialogViewModel();
so in unit testing all you have to do is:
MyViewModel model = new MyViewModel();
model.DialogViewModel = new MyDialogViewModel();
model.DialogViewModel.InputProperty = "Here's my input";
//Assert whatever you want...
I personally create a ICommand property in my ViewModel that sets the DialogViewModel property, so that the user can push a button to get the dialog to open up.
So my ViewModel never calls a dialog it just instantiates a property. The view interprets that and display a dialog box. The beauty behind this is that if you decide to change your view at all and maybe not display a dialog, your ViewModel does not have to change one bit. It pushes all the User interaction code where it should be...in the view. And creating a wpf control allows me to re-use it whenever I need to...
There are many ways to do this, this is one I found to be good for me. :)
In scenarios like this, I typically use events. The model can raise an event to ask for information and anybody can respond to it. The view would listen for the event and display the dialog.
public class MyModel
{
public void DoSomething()
{
var e = new SomeQuestionEventArgs();
OnSomeQuestion(e);
if (e.Handled)
mTheAnswer = e.TheAnswer;
}
private string mTheAnswer;
public string TheAnswer
{
get { return mTheAnswer; }
}
public delegate void SomeQuestionHandler(object sender, SomeQuestionEventArgs e);
public event SomeQuestionHandler SomeQuestion;
protected virtual void OnSomeQuestion(SomeQuestionEventArgs e)
{
if (SomeQuestion == null) return;
SomeQuestion(this, e);
}
}
public class SomeQuestionEventArgs
: EventArgs
{
private bool mHandled = false;
public bool Handled
{
get { return mHandled; }
set { mHandled = value; }
}
private string mTheAnswer;
public string TheAnswer
{
get { return mTheAnswer; }
set { mTheAnswer = value; }
}
}
public class MyView
{
private MyModel mModel;
public MyModel Model
{
get { return mModel; }
set
{
if (mModel != null)
mModel.SomeQuestion -= new MyModel.SomeQuestionHandler(mModel_SomeQuestion);
mModel = value;
if (mModel != null)
mModel.SomeQuestion += new MyModel.SomeQuestionHandler(mModel_SomeQuestion);
}
}
void mModel_SomeQuestion(object sender, SomeQuestionEventArgs e)
{
var dlg = new MyDlg();
if (dlg.ShowDialog() != DialogResult.OK) return;
e.Handled = true;
e.TheAnswer = dlg.TheAnswer;
}
}
The WPF Application Framework (WAF) shows a concrete example how to accomplish this.
The ViewModel sample application shows an Email Client in which you can open the “Email Account Settings” dialog. It uses dependency injection (MEF) and so you are still able to unit test the ViewModel.
Hope this helps.
jbe
There are different approaches to this. One common approach is to use some form of dependency injection to inject a dialog service, and use the service.
This allows any implementation of that service (ie: a different view) to be plugged in at runtime, and does give you some decoupling from the ViewModel to View.