I have control in SL4. I want data validation on button click. Big problem is normally SL4 give validation using binding property.
like example given shown in this example
http://weblogs.asp.net/dwahlin/archive/2010/08/15/validating-data-in-silverlight-4-applications-idataerrorinfo.aspx
<TextBox Text="{Binding Name,Mode=TwoWay,ValidatesOnDataErrors=true}"
Height="23"
Width="120"
HorizontalAlignment="Left"
VerticalAlignment="Top" />
BUT I WANT TO SHOW ERROR MESSAGE LIKE THIS ....
using my own code like on button click i check
(textbox1.text == null ) then set this style of error to textbox1
One way of deferring validation is to set the property UpdateSourceTrigger=Explicit in the bindings. If you do this, the bindings won't update the source objects, and hence won't cause validation errors, until you explicitly tell the bindings to do so. When your button is clicked, you force an update on the bindings, using a line such as the following for each control:
someTextBox.GetBindingExpression(TextBox.TextProperty).UpdateSource();
You then have your property setters throwing exceptions for invalid data.
This approach can be a bit of a pain if there are quite a lot of controls to force binding updates on.
Also, forcing an update on the bindings has to be done in the code-behind of a control. If you're using a Command with the button as well then you might run in to an issue. Buttons can have both a Command and a Click event handler, and both will execute when the button is clicked on, but I don't know the order in which this happens or even if an order can be guaranteed. A quick experiment suggested that the event handler was executed before the command, but I don't know whether this is undefined behaviour. There is therefore the chance that the command will be fired before the bindings have been updated.
An approach to programmaticaly creating validation tooltips is to bind another property of the textbox and then deliberately cause an error with this binding.
'sapient' posted a complete solution, including code on the Silverlight forums (search for the post dated 07-08-2009 4:56 PM). In short, he/she creates a helper object with a property whose getter throws an exception, binds the Tag property of the textbox to this helper object and then forces an update on the binding.
'sapient's code was written before Silverlight 4 was released. We'll 'upgrade' his/her code to Silverlight 4. The class ControlValidationHelper becomes the following:
public class ControlValidationHelper : IDataErrorInfo
{
public string Message { get; set; }
public object ValidationError { get; set; }
public string Error
{
get { throw new NotImplementedException(); }
}
public string this[string columnName]
{
get { return Message; }
}
}
It's easy enough to knock up a quick demo application to try this out. I created the following three controls:
<TextBox x:Name="tbx" Text="{Binding Path=Text, ValidatesOnDataErrors=True, NotifyOnValidationError=True, Mode=TwoWay}" />
<Button Click="ForceError_Click">Force error</Button>
<Button Click="ClearError_Click">Clear error</Button>
The Text property and the event handlers for the two buttons live in the code-behind and are as follows:
public string Text { get; set; }
private void ForceError_Click(object sender, RoutedEventArgs e)
{
var helper = new ControlValidationHelper() { Message = "oh no!" };
tbx.SetBinding(Control.TagProperty, new Binding("ValidationError")
{
Mode = BindingMode.TwoWay,
NotifyOnValidationError = true,
ValidatesOnDataErrors = true,
UpdateSourceTrigger = UpdateSourceTrigger.Explicit,
Source = helper
});
tbx.GetBindingExpression(Control.TagProperty).UpdateSource();
}
private void ClearError_Click(object sender, RoutedEventArgs e)
{
BindingExpression b = tbx.GetBindingExpression(Control.TagProperty);
if (b != null)
{
((ControlValidationHelper)b.DataItem).Message = null;
b.UpdateSource();
}
}
The 'Force error' button should make a validation error appear on the textbox, and the 'Clear error' button should make it go away.
One potential downside of this approach occurs if you are using a ValidationSummary. The ValidationSummary will list all validation errors against ValidationError instead of against the name of each property.
Although my answer wasn't regarded as preferable, I'm still sure that the MVVM pattern is the best choice to perform validation.
In my code you should use the model validator from this post about validation and any mvvm framework, for example MVVM Light.
It is much easier to add validation rules using the view model and model validator classes:
public class PersonViewModel : ViewModelBase, INotifyDataErrorInfo
{
private ModelValidator _validator = new ModelValidator();
public PersonViewModel()
{
this._validator.AddValidationFor(() => this.Age)
.Must(() => this.Age > 0)
.Show("Age must be greater than zero");
}
}
And you can validate the model if and only if a user explicitly clicks a button:
#region INotifyDataErrorInfo
public IEnumerable GetErrors(string propertyName)
{
return this._validator.GetErrors(propertyName);
}
public bool HasErrors
{
get { return this._validator.ErrorMessages.Count > 0; }
}
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged = delegate { };
protected void OnErrorsChanged(string propertyName)
{
ErrorsChanged(this, new DataErrorsChangedEventArgs(propertyName));
this.RaisePropertyChanged("HasErrors");
}
#endregion
public bool Validate()
{
var result = this._validator.ValidateAll();
this._validator.PropertyNames.ForEach(OnErrorsChanged);
return result;
}
As everyone can see, there is nothing difficult here, just 20-30 lines of code.
Moreover, the MVVM approach is much more flexible and you can reuse some common validation scenaries among several view models.
Related
I have Car.xaml:
<CheckBox Name="carA" IsChecked="{Binding CarACheck, Mode=TwoWay, FallbackValue=true}" />
Then in Car.xaml.cs:
public Car()
{
//...
DataContext = this;
}
public bool CarACheck
{
get => carA.IsChecked;
set => carA.IsChecked = value;
}
When I run it and click on CheckBox, the app crashes with the error below:
set => carA.IsChecked = value;
System.StackOverflowException
An unhandled exception of type 'System.StackOverflowException' occurred in mscorlib.dll
Cause of Error
The reason is that your setter is called over and over.
The CheckBox is clicked.
The IsChecked property of the CheckBox is set.
The binding sets value on the bound property CarACheck.
The setter of CarACheck sets the IsChecked property of the CheckBox.
Go to 3.
In the long run this causes the stack to overflow and the application crashes.
An MVVM Solution
It seems that you are trying to build a view that displays data about cars. What I see from your sample is that you mix your data and most likely business logic with the user interface code. You should not do that, because it harms code quality and maintainability in the long run. A better and sustainable approach is to separate your view from data and business logic. This is what the MVVM pattern is about.
In your sample code, we have a view, which we would call CarView. This is where the CheckBox is defined along with the rest of the user interface to represent a car. The data is exposed to the view through a separate view model type called CarViewModel. This view model contains properties that are bound from the view.
When using simple properties, regardless of having a backing field or not, the bindings are not able to determine changes to properties from the view model side. This is why you have to implement the INotifyPropertyChanged interface, which provides an event for this purpose. The event is raised, whenever a property is changed. Usually this is in done in the setter.
public class CarViewModel : INotifyPropertyChanged
{
private bool _carACheck;
public bool CarACheck
{
get => _carACheck;
set
{
if (_carACheck == value)
return;
_carACheck = value;
OnPropertyChanged(nameof(_carACheck));
}
}
// ...other code, maybe even setting CarACheck.
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
In the view, simply assign an instance of this view model to the DataContext in XAML or code-behind, e.g.:
public Car()
{
// ...other code.
DataContext = new CarViewModel();
}
In your view, you do not need a name for the CheckBox, as there is no explicit reference to it. The Mode=TwoWay declaration can be removed, too, because the IsChecked property binds two-way by default.
<CheckBox IsChecked="{Binding CarACheck, FallbackValue=true}"/>
Remarks about your original code: I hope that you can see the benefits of the MVVM pattern. Of course, after reviewing this solution, you could simply add a backing field and INotifyPropertyChanged to your current code to make it work, too, but the lesson to learn here is that this separation is valuable and worth investing although it might seem more verbose. See also:
Data binding overview (WPF .NET)
How to: Implement Property Change Notification
I understand that ViewModel shouldn't have any knowledge of View, but how can I call MediaElement.Play() method from ViewModel, other than having a reference to View (or directly to MediaElement) in ViewModel?
Other (linked) question: how can I manage View's controls visibility from ViewModel without violating MVVM pattern?
1) Do not call Play() from the view model. Raise an event in the view model instead (for instance PlayRequested) and listen to this event in the view:
view model:
public event EventHandler PlayRequested;
...
if (this.PlayRequested != null)
{
this.PlayRequested(this, EventArgs.Empty);
}
view:
ViewModel vm = new ViewModel();
this.DataContext = vm;
vm.PlayRequested += (sender, e) =>
{
this.myMediaElement.Play();
};
2) You can expose in the view model a public boolean property, and bind the Visibility property of your controls to this property. As Visibility is of type Visibility and not bool, you'll have to use a converter.
You can find a basic implementation of such a converter here.
This related question might help you too.
For all the late-comers,
There are many ways to achieve the same result and it really depends on how you would like to implement yours, as long as your code is not difficult to maintain, I do believe it's ok to break the MVVM pattern under certain cases.
But having said that, I also believe there is always way to do this within the pattern, and the following is one of them just in case if anyone would like to know what other alternatives are available.
The Tasks:
we don't want to have direct reference from the ViewModel to any UI elements, i.e. the the MediaElement and the View itself.
we want to use Command to do the magic here
The Solution:
In short, we are going to introduce an interface between the View and the ViewModel to break the dependecy, and the View will be implementing the interface and be responsible for the direct controlling of the MediaElement while leaving the ViewModel talking only to the interface, which can be swapped with other implementation for testing purposes if needed, and here comes the long version:
Introduce an interface called IMediaService as below:
public interface IMediaService
{
void Play();
void Pause();
void Stop();
void Rewind();
void FastForward();
}
Implement the IMediaService in the View:
public partial class DemoView : UserControl, IMediaService
{
public DemoView()
{
InitializeComponent();
}
void IMediaService.FastForward()
{
this.MediaPlayer.Position += TimeSpan.FromSeconds(10);
}
void IMediaService.Pause()
{
this.MediaPlayer.Pause();
}
void IMediaService.Play()
{
this.MediaPlayer.Play();
}
void IMediaService.Rewind()
{
this.MediaPlayer.Position -= TimeSpan.FromSeconds(10);
}
void IMediaService.Stop()
{
this.MediaPlayer.Stop();
}
}
we then do few things in the DemoView.XAML:
Give the MediaElement a name so the code behind can access it like above:
<MediaElement Source="{Binding CurrentMedia}" x:Name="MediaPlayer"/>
Give the view a name so we can pass it as a parameter, and
import the interactivity namespace for later use (some default namespaces are omitted for simplicity reason):
<UserControl x:Class="Test.DemoView"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ia="http://schemas.microsoft.com/expression/2010/interactivity"
x:Name="MediaService">
Hookup the Loaded event through Trigger to pass the view itself to the view model through a Command
<ia:Interaction.Triggers>
<ia:EventTrigger EventName="Loaded">
<ia:InvokeCommandAction Command="{Binding LoadedCommand}" CommandParameter="{Binding ElementName=MediaService}"></ia:InvokeCommandAction>
</ia:EventTrigger>
</ia:Interaction.Triggers>
last but not least, we need to hookup the media controls through Commands:
<Button Command="{Binding PlayCommand}" Content="Play"></Button>
<Button Command="{Binding PauseCommand}" Content="Pause"></Button>
<Button Command="{Binding StopCommand}" Content="Stop"></Button>
<Button Command="{Binding RewindCommand}" Content="Rewind"></Button>
<Button Command="{Binding FastForwardCommand}" Content="FastForward"></Button>
We now can catch everything in the ViewModel (I'm using prism's DelegateCommand here):
public class AboutUsViewModel : SkinTalkViewModelBase, IConfirmNavigationRequest
{
public IMediaService {get; private set;}
private DelegateCommand<IMediaService> loadedCommand;
public DelegateCommand<IMediaService> LoadedCommand
{
get
{
if (this.loadedCommand == null)
{
this.loadedCommand = new DelegateCommand<IMediaService>((mediaService) =>
{
this.MediaService = mediaService;
});
}
return loadedCommand;
}
}
private DelegateCommand playCommand;
public DelegateCommand PlayCommand
{
get
{
if (this.playCommand == null)
{
this.playCommand = new DelegateCommand(() =>
{
this.MediaService.Play();
});
}
return playCommand;
}
}
.
. // other commands are not listed, but you get the idea
.
}
Side note: I use Prism's Auto Wiring feature to link up the View and ViewModel. So at the View's code behind file there is no DataContext assignment code, and I prefer to keep it that way, and hence I chose to use purely Commands to achieve this result.
I use media element to play sounds in UI whenever an event occurs in the application. The view model handling this, was created with a Source property of type Uri (with notify property changed, but you already know you need that to notify UI).
All you have to do whenever source changes (and this is up to you), is to set the source property to null (this is why Source property should be Uri and not string, MediaElement will naturally throw exception, NotSupportedException I think), then set it to whatever URI you want.
Probably, the most important aspect of this tip is that you have to set MediaElement's property LoadedBehaviour to Play in XAML of your view. Hopefully no code behind is needed for what you want to achieve.
The trick is extremely simple so I won't post a complete example. The view model's play function should look like this:
private void PlaySomething(string fileUri)
{
if (string.IsNullOrWhiteSpace(fileUri))
return;
// HACK for MediaElement: to force it to play a new source, set source to null then put the real source URI.
this.Source = null;
this.Source = new Uri(fileUri);
}
Here is the Source property, nothing special about it:
#region Source property
/// <summary>
/// Stores Source value.
/// </summary>
private Uri _Source = null;
/// <summary>
/// Gets or sets file URI to play.
/// </summary>
public Uri Source
{
get { return this._Source; }
private set
{
if (this._Source != value)
{
this._Source = value;
this.RaisePropertyChanged("Source");
}
}
}
#endregion Source property
As for Visibility, and stuff like this, you can use converters (e.g. from bool to visibility, which you can find on CodePlex for WPF, SL, WP7,8) and bind your control's property to that of the view model's (e.g. IsVisible). This way, you control parts of you view's aspect. Or you can just have Visibility property typed System.Windows.Visibility on your view model (I don't see any pattern breach here). Really, it's not that uncommon.
Good luck,
Andrei
P.S. I have to mention that .NET 4.5 is the version where I tested this, but I think it should work on other versions as well.
I have a silverlight application that uses WCF RIA services.
I want to submit changes every time a property is changed.
However if a user tries to change a another property in the same entity before the submit is complete I get the following error:
"This entity is currently read-only. One of the following conditions exist: a custom method has been invoked, a submit operation is in progress, or edit operations are not supported for the entity Type"
Scenario:
User changes property A in Entity 1
Application catches change and submits WCF DataContext
Soon after user changes property B in Entity 1. However this property is read-only due to submit in progress.
Is it possible change the behaviour of the DataContext so that I can update an entity during the submit process?
PLEASE NOTE - I MIGHT BE WAYYYY OFF ON THIS RESPONSE:
Let me try to get what your saying:
If i'm correct, on the interface, values are being changed which are bound to some entity. This entity, is what you want to be updated in real time on the server. If this is the case, I would recommend this:
Let's say I have a text box, and as the user types the value in the text box, it updates the binding entity, and i want that entity to be updated on the server at the same time - define a behaviour to take the keyup method and trigger e.g. (AssociatedObject.GetBindingExpression(TextBox.TextProperty).UpdateSource();)
Also, in this behaviour, I would give the text box the ability to define a method to bind to for the OnKeyUp event allowing me, to say, once the user presses "Enter" then actually do the udpate on the server. In addition, i would create a loading screen/child window to say "saving" - that would then cause "blocking" to occur to prevent any further user intervention during the save. On the call back. I would close the window that said loading.
Now, like i said, i'm not sure if this is the answer your are looking for, but the theory behind the idea, especially the blocking only on enter or on a final change seems to be your best bet. An example of the behaviour for the text box is below:
Include your library: using CustomControlsUI.Extensions;
Define the class for the text box: public class TTextBoxKeyUpBehavior:Behavior<TextBox>
Define the method pointer for the ontextkeyup that you want to dynamically bind to: protected MethodInfo OnTextBoxKeyUp { get; set; }
Define the public string that names the OnTextKeyUpMethod to bind to: OnTextBoxKeyUpMethod
Define the TextboxKeyUp event in the behavior:
/// <summary>
/// Method impl. for the onkey up event
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void TextBoxKeyUp(object sender, KeyEventArgs e)
{
if (!object.ReferenceEquals(AssociatedObject.GetBindingExpression(TextBox.TextProperty), null))
{
AssociatedObject.GetBindingExpression(TextBox.TextProperty).UpdateSource();
}
if (!string.IsNullOrEmpty(OnTextBoxKeyUpMethod) && !object.ReferenceEquals(AssociatedObject,null) &&
!object.ReferenceEquals(AssociatedObject.DataContext,null))
{
try
{
//use reflection to try and find the method being pointed to.
if (object.ReferenceEquals(OnTextBoxKeyUp, null))
{
MethodInfo _m = null;
_m = AssociatedObject.DataContext.GetType().GetMethod(OnTextBoxKeyUpMethod, new Type[] { typeof(object), typeof(KeyEventArgs) });
if (!object.ReferenceEquals(_m, null))
//set the pointer to the on text box key up method
OnTextBoxKeyUp = _m;
}
OnTextBoxKeyUp.Invoke(AssociatedObject.DataContext, new object[] { sender, e });
}
catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex.ToString()); }
}
}
Override the On Attached and On Detached methods:
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.KeyUp += TextBoxKeyUp;
}
protected override void OnDetaching()
{
base.OnDetaching();
AssociatedObject.KeyUp -= TextBoxKeyUp;
}
Usage:
Include the namespace where the behavior is stored: xmlns:ccui="clr-namespace:CustomControlsUI.Behaviors;assembly=CustomControlsUI"
Include the namespaces for System.Windows.Interactivity and Microsoft.Expression.Interactions:
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
Attach the OnKeyUpTo the desired method in the viewmodel:
<TextBox Grid.Column="1" Grid.Row="1" Height="23" HorizontalAlignment="Left" Margin="6,4,0,0"
Name="txtFirstName" VerticalAlignment="Top" Width="200" Text="{Binding FirstName, Mode=TwoWay}">
<i:Interaction.Behaviors>
<ccui:TTextBoxKeyUpBehavior OnTextBoxKeyUpMethod="OnSearchKeyUp" />
</i:Interaction.Behaviors>
</TextBox>
Implement the method and property in the view model that should implement INotifyPropertyChanged found in System.ComponentModel
public string FirstName
{
get
{
return __fFirstName;
}
set
{
__fFirstName = value;
//this is a custom extension on INotifyPropertyChanged
this.NotifyPropertyChanged("FirstName", PropertyChanged);
}
}
Finally define the key up method to notify the server of the change:
#region "Helper Method"
public void OnSearchKeyUp(object sender, KeyEventArgs e)
{
if (e.Key == Key.Enter && IsSearchStateValid==true)
{
OnFindCustomers(sender);
}
}
#endregion
Please Note: I could be WAYYYYYYYYYYY Off on this answer. But it's just based on what i think you're looking for....
This is a follow up question to a previous question, wich didn't really get me anywhere:
deterministic and asynchronous field validation in WPF
Since WPF doesn't support INotifyDataErrorInfo it seems, that I need to implement something like that myself (please correct me if I am wrong here). I need this because I want the ViewModel to trigger when to display special ErrorTemplates for certain fields (e.g. after the click of a button or after the end of a long running async validation operation or when the internal state changes in a way that certain fields suddenly become invalid).
I am considering to write a custom markup extension or behavior for this. It listens to my version of INotifyDataErrorInfo implemented by the ViewModel and creates a VisualTree from a special wellknown ErrorTemplate defined in XAML once the ErrorsChanged event was raised.
Once I have defined that template in XAML, how do I find it from my behavior/expression, materialize an actual visual tree out of it and then display it (probably somehow on an adorner layer) at the right field entry on my form?
You don't need a markup extension. I recently found myself wishing for the same behavior, so I created a solution that works for my needs. Hopefully this helps you as well.
The IDataErrorInfo interface actually contains everything we need in order to do asynchronous signaling. What it lacks is an event system to trigger notifications automatically. There is a relationship between that interface and the INotifyPropertyChanged interface. The combination of the two actually allows you to signal a change, somewhat indirectly.
First the control:
<TextBox
Grid.Column="1"
Width="100"
Text="{Binding UpdateSourceTrigger=LostFocus,
Path=Id,
ValidatesOnDataErrors=true}" />
Pretty straightforward. The value of UpdateSourceTrigger is not important, and NotifyOnValidationError is not required, but won't hurt anything if you add it.
Next, the view model, which is just a contrived example. The important part is in the IDataErrorInfo indexer.
public class WindowViewModel : INotifyPropertyChanged, IDataErrorInfo
{
public event PropertyChangedEventHandler PropertyChanged;
private int _id;
public int Id
{
get{ return _id; }
set
{
_id = value;
this.PropertyChanged( this, new PropertyChangedEventArgs( "Id" ) );
}
}
public string this[ string columnName ]
{
get
{
object result = AsynchValidationCoordinator.GetError( columnName );
if ( result != null )
{
return result.ToString();
}
return null;
}
}
AsynchValidationCoordinator is a class that tracks properties and any associated error information. For illustrative purposes, the key being used is just the property name, but you could as easily create a compound key to differentiate potential property collisions in scenarios with multiple view models.
public static class AsynchValidationCoordinator
{
private static readonly ConcurrentDictionary<string, object> ErrorList =
new ConcurrentDictionary<string, object>();
public static void CancelError( string propertyName, object error )
{
object value;
ErrorList.TryRemove( propertyName, out value );
}
public static object GetError( string propertyName )
{
object error = null;
if ( ErrorList.ContainsKey( propertyName ) )
{
ErrorList.TryRemove( propertyName, out error );
}
return error;
}
public static void RegisterError( string propertyName, object error )
{
ErrorList[propertyName] = error;
}
}
Tracking property names is necessary, but you could create an entirely different way of tracking them, including tracking the names within the view model. This was just an easy way for me to apply a structured form quickly to an existing project.
So tying this all together, I added the following ICommand property to the test view model and bound it to a Button. (RelayCommand is from Josh Smith's MSDN MVVM article.)
public ICommand ValidateCommand
{
get
{
return new RelayCommand( Validate );
}
}
private void Validate( object value )
{
Thread thread = new Thread( RaiseChanged );
thread.Start();
}
private void RaiseChanged()
{
Thread.Sleep( 3000 );
AsynchValidationCoordinator.RegisterError( "Id", "Error Message Goes Here" );
this.PropertyChanged( this, new PropertyChangedEventArgs( "Id" ) );
}
The source of the call is irrelevant. The important point that ties all of this together is the fact that once PropertyChanged is called, the IDataErrorInfo indexer follows in its tracks. Returning the error information that was registered in AsynchValidationCoordinator triggers the Validation.ErrorTemplate of the control with the relevant error message.
INotifyDataErrorInfo is now included in WPF 4.5 along with a lot of other features. See the following links
What's New in WPF Version 4.5 Developer Preview
INotifyDataErrorInfo Interface
Here is the link to the Visual Studio 11 Developer Preview:
http://msdn.microsoft.com/en-us/vstudio/hh127353
I have something like it will pop to the user for getting confirmation of changes. If he clicks no I am setting the selectedValue in view model to the previous selection. But its not getting displayed correctly in view. Please help.
Very simple solution for .NET 4.5.1+:
<ComboBox SelectedItem="{Binding SelectedItem, Delay=10}" ItemsSource="{Binding Items}" />
It's works for me in all cases.
Just fire NotifyPropertyChanged without value assignment to rollback.
If the user clicks no and you try to revert the value and then call OnPropertyChanged, WPF will swallow the event since it is already responding to that event. One way to get around this is to use the dispatcher and call the event outside of the current event context.
System.Windows.Threading.Dispatcher.CurrentDispatcher.BeginInvoke(new Action(() => { OnPropertyChanged("ComSelectedValue"); }), null);
WPF seems to validate that the bound property has changed before updating the UI. So simply invoking an NotifyPropertyChanged()/OnPropertyChanged() doesn't do the trick.
The problem is that since the property hasn't changed, WPF doesn't think it needs to refresh the combo box.
here is the incredibly hackish way I handle it in my ViewModels;
private int _someProperty = -1;
public int SomeProperty
{
if (_someProperty != -1 && doYesNo() == Yes)
{
_someProperty = value;
}
else if (_someProperty != -1)
{
int oldValue = _someProperty;
_someProperty = -1; // Set to a different/invalid value so WPF sees a change
Dispatcher.BeginInvoke(new Action(() => { SomeProperty = oldValue; }));
}
else
{
_someProperty = value;
}
NotifyPropertyChanged("SomeProperty");
}
Not pretty but it works reliably.
Assumptions:
- You show a dialog box (with a message and OKCancel buttons) when a user selects some value from ComboBox.
- If user presses OK, everything is OK. :)
- If user presses Cancel, you say vmPropSelectedValue=previousValue.
This won't work. Why?
Don't have exact answer, but I believe when you show the dialog the system has just changed the selected value and has just notified the Source (via binding infrastructure) about the changed value . If at this moment (when source has control) you now change the value of ViewModel property from your VM code, which you expect would trigger OnPropertyChanged of INotifyPropertyChanged, which you expect would ask the WPF to update the target with your requested value. However, the WPF has not yet completed the cycle - its still waiting for the Source to return the control back to it. So it simply rejects your request (otherwise it would go in infinite loop).
If this was confusing, try this:
Cycle starts:
1. User changes value on UI. WPF changes target.
2. binding infrastructure requests Source to update itself.
3. Source updates itself (VM property).
4. Source returns control back to binding infra.
Cycle End.
Experts: Can't find some documentation in this regard. Above is my belief how things work. Please rectify if incorrect.
Short Answer:
AFAIK, this can't be done via pure VM code alone. You will have to put some code-behind code.
Here's one way: http://www.amazedsaint.com/2008/06/wpf-combo-box-cancelling-selection.html
In most WPF applications you bind a view model to the user interface with a TwoWay mode and then you're set to go.
However this goes against the typical user experience, where when you edit something and you don't save, you don't see that editing reflected throughout your entire application, even if you don't save your changes to the Database.
The mechanism available in WPF is the UpdateSourceTrigger property of the Binding. With this property you can control when the User Interface updates the ViewModel that it is bound to. This allows you to update only when the user saves what he's editing or something similar.
An example of a XAML Binding with the UpdateSourceTrigger set to Explicit:
"{Binding Path=Privado, UpdateSourceTrigger=Explicit, Mode=TwoWay}"
And when you want to really save to the ViewModel you call:
UpdateSource();
What if you tried to raise the property changed event asynchronously? This is similar the examples from shaun and NebulaSleuth.
public int SomeProperty
{
get { return m_someProperty; }
set
{
if (value == m_someProperty)
return;
if (doYesNo() == No)
{
// Don't update m_someProperty but let the UI know it needs to retrieve the value again asynchronously.
Application.Current.Dispatcher.BeginInvoke((Action) (() => NotifyOfPropertyChange("SomeProperty")));
}
else
{
m_someProperty = value;
NotifyOfPropertyChange("SomeProperty");
}
}
}
Here is the general flow that I use:
I just let the change pass through the ViewModel and keep track of whatever's passed in before.
(If your business logic requires the selected item to not be in an invalid state, I suggest moving that to the Model side). This approach is also friendly to ListBoxes that are rendered using Radio Buttons as making the SelectedItem setter exit as soon as possible will not prevent radio buttons from being highlighted when a message box pops out.
I immediately call the OnPropertyChanged event regardless of the value passed in.
I put any undo logic in a handler and call that using SynchronizationContext.Post()
(BTW: SynchronizationContext.Post also works for Windows Store Apps. So if you have shared ViewModel code, this approach would still work).
public class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public List<string> Items { get; set; }
private string _selectedItem;
private string _previouslySelectedItem;
public string SelectedItem
{
get
{
return _selectedItem;
}
set
{
_previouslySelectedItem = _selectedItem;
_selectedItem = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("SelectedItem"));
}
SynchronizationContext.Current.Post(selectionChanged, null);
}
}
private void selectionChanged(object state)
{
if (SelectedItem != Items[0])
{
MessageBox.Show("Cannot select that");
SelectedItem = Items[0];
}
}
public ViewModel()
{
Items = new List<string>();
for (int i = 0; i < 10; ++i)
{
Items.Add(string.Format("Item {0}", i));
}
}
}
I realize this is an old post but it seems no one has done this the right way. I used System.Interactivity.Triggers and Prism to process the SelectionChanged event and manually trigger the SelectedItem. This will prevent undesired Selected Item Changes in both the UI and the View-Model.
My view:
<Window x:Class="Lind.WPFTextBlockTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:Lind.WPFTextBlockTest"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:prism="clr-namespace:Microsoft.Practices.Prism.Interactivity;assembly=Microsoft.Practices.Prism.Interactivity"
Title="MainWindow" Height="649" Width="397">
<Window.DataContext>
<vm:MainWindowViewModel/>
</Window.DataContext>
<StackPanel>
<ComboBox ItemsSource="{Binding Data}" SelectedItem="{Binding SelectedData, Mode=OneWay}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<prism:InvokeCommandAction Command="{Binding TryChangeSelectedData}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</ComboBox>
</StackPanel>
My View-Model (BindeableBase and DelegateCommand from Prism 5):
public class MainWindowViewModel : BindableBase
{
public ObservableCollection<string> Data { get; private set; }
private string selectedData;
public string SelectedData
{
get { return selectedData; }
set
{
SetProperty(ref selectedData, value);
}
}
public DelegateCommand<SelectionChangedEventArgs> TryChangeSelectedData { get; private set; }
public MainWindowViewModel()
{
Data = new ObservableCollection<string>() { "Foo", "Bar", "Dick", "Head" };
SelectedData = Data.First();
TryChangeSelectedData = new DelegateCommand<SelectionChangedEventArgs>(args =>
{
var newValue = args.AddedItems.Cast<string>().FirstOrDefault();
if (newValue == "Dick")
this.OnPropertyChanged(() => this.SelectedData);
else
SelectedData = newValue;
});
}
}