`
vfl40vfl
  • 浏览: 17143 次
社区版块
存档分类
最新评论

Silverlight, Validation and MVVM - Part II 提交时验证

 
阅读更多

Silverlight, Validation and MVVM - Part II 提交时验证
2011年02月15日
  The new Validation states for controls in Silverlight 3 sure look nice but there are a number of limitations. For starters, you can only invoke them through:  You can't even use the new ValidationAttributes from System.ComponentModel.DataAnnotations. Well, actually you can but you'd have to this inside the setter: 
  [Range(18, int .MaxValue, ErrorMessage="Must be 18 or over" )]
  publicint Age
  {
  get { return _age; }
  set
  {
  Validator.ValidateProperty(value, new ValidationContext(this , null , null ) 
  { 
  MemberName ="Age" ,
  });
  _age = value;
  OnPropertyChanged("Age" );
  }
  }
  (You can imagine an enhancement to this pattern using the SetValue concept I shared with my new snippets the other day). What's particularly tricky is to display the invalid state if the value is never changed by the user. For example, you have a name field that is required: 
  [Reguired(ErrorMessage="Name is required" )]
  publicstring Name
  This is bound to a TextBox: 
  
  
  The problem is, the user might click save without ever entering the a name. Oh no! Sure, we can catch that in our code (using the Validator type again, for example). However, there's no easy way of forcing the control to display the error. The only way to achieve this is to force the binding to update programmatically, like so: 
  BindingExpression binding = NameTextBox.GetBindingExpression(TextBox.TextPrope rty);
  binding.UpdateSource();
  Now, to get this working on any scale is going to require code-behind. Lots of code-behind. And everybody knows I hate this. The whole validation story at the moment isn't going to play at all well with Model-View-ViewModel (MVVM). What we need is an easy way to Update all bindings from our ViewModel... And so, I set about finding a more declarative way of achieving solving. My approach builds on my previous post Silverlight Validation and ViewModel .  It now becomes an integral part of your ViewModel. Let's walk through a scenario. Here we'll have a ViewModel that exposes a Person property of type Person: 
  // properties snipped down for brevity
  publicclass Person
  {
  [Required(ErrorMessage= "Name is required" )]
  publicstring Name {}
  [Required(ErrorMessage ="Salutation is required" )]
  publicstring Salutation {}
  [Range(0, int .MaxValue, ErrorMessage ="Must be over 18" )]
  publicint Age {}
  }
  Nice and easy. Now the validation scope comes into play - we add an instance to our ViewModel because we'll access it via binding. To be honest, this could go almost anywhere you like provided it's accessible in a Binding (resources, in the Person class itself, anywhere you like!). 
  // properties snipped down for brevity
  publicclass MainViewModel : INotifyPropertyChanged
  {
  public ObservableCollection Salutations {}
  public Person Person {}
  public ValidationScope PersonValidationScope {}
  }
  So there's our model and our ViewModel. Now for some view - our Xaml (again, simplified for brevity): 
  
  
  
  ComboBox
  ItemsSource="{Binding Salutations}"     
  SelectedItem="{Binding Person.Salutation, Mode=TwoWay, NotifyOnValidationError=True, ValidatesOnExceptions=True}"
  local:ValidationScope.ValidateBoundProperty="Selec tedItem" />
  
  
  And the code-behind: 
  //Note - I'd normally use Prism's DelegateCommand and commanding support to avoid this code-behind but don't want to muddy the example
  privatevoid SaveButtonClick(object sender, RoutedEventArgs e)
  {
  _personViewModel.Save();
  }
  Finally, the Save method on the ViewModel 
  publicvoid Save()
  {
  // This causes all registered bindings to be updated
  PersonValidationScope.ValidateScope();
  if (PersonValidationScope.IsValid())
  {
  // Save changes!
  }
  }
  First, we use an attached behavior to pass the FrameworkElement we want to be the conceptual 'validation scope' within the VisualTree to our actual ValidationScope instance: 
  
  Then we specify the property who is bound and might need a refresh for each control : 
  
  ComboBox local:ValidationScope.ValidateBoundProperty="Selec tedItem" />
  Sadly, this is really a violation of the DRY principle anyway but it does have the added advantage of having to opt in your Bindings to the ValidationScope. Finally, when we're ready, we tell the ValidationScope to update all the bindings: 
  PersonValidationScope.ValidateScope();
  This kicks the process into action with a crawl of the VisualTree inside the FrameworkElement registered as our scope (the StackPanel in this case) and hunts out any attached ValidateBoundProperty properties wired to controls. When it finds them it looks for the appropriate DependencyProperty (Text and SelectedItem in our demo)  UPDATE: Be sure to go on and read Part III in this series! 
  
  
  > Next Post: How to work with PropertyChanged's smelly name string
  ToolTip on each control for my errors.   Posted by josh @ 07 Aug 2009 04:58 
  That's an interesting point and I haven't tried this.
  I guess the display wouldn't need to update if you can't see the control though and the important thing would be to make sure full validation occurs before update.
  e.g. 
  PersonValidationScope.ValidateScope();
  if (PersonValidationScope.IsValid() && Validator.ValidateObject(this.Person))
  {
  // Save changes!
  }
  Make sense?   Posted by tomas.k @ 17 Aug 2009 07:31 
  Great article, 
  I looking for something similar for some time. Thank you for sharing it!   Posted by kanur @ 17 Aug 2009 13:43 
  Other problem I am solving is localization of messages from ValueConverter. I am not able to localize message: "Input is not in correct format".
  I tried my custom ValueConverter but there is not possible to throw exception to be caught by UI.
  Any ideas?
  Thanks   Posted by Mark @ 20 Aug 2009 00:56 
  Hi Josh, I finally got round to implementing this. One question came about fairly quickly: how would you deal with optional fields, that need to be validated only when information is entered? Thanks, Mark   Posted by Mark @ 20 Aug 2009 00:56 
  Hi Josh, I finally got round to implementing this. One question came about fairly quickly: how would you deal with optional fields, that need to be validated only when information is entered? Thanks, Mark   Posted by Paurav @ 27 Aug 2009 14:12 
  Hi Josh,
  Really like this solution. Thanks.
  Mark, you can use a custom validator attribute on the property you are validating.   Posted by roopesh @ 31 Oct 2009 01:35 
  Its really help fulll ...can we do same validation in ria services??????????????? how about custom validation?   Posted by R4cOOn @ 18 Nov 2009 00:05 
  This is the only post that I found that dealt cleanly with the issue of the validation occurring in the ViewModel.
  I share your belief in the "no code-behind" and I was not looking forward to adding a lot of it.
  My only gripe is that I'd rather had the class separated in a Validator and a ValidationScopeBahavior in much the same way as the commands and their associated behavior are done in PRISM. 
  I couldn't get it to work though because I couldn't keep hold of the dependency object if the classes are separated (I could call Validate() but then the DependencyObject containing the ScopeElement wouldn't be there).
  Good job!   Posted by Aaron @ 18 Jan 2010 13:20 
  Nice! But I still think IsValid should be supplied to us out of the box   Posted by Antti Makkonen @ 26 Jan 2010 04:54 
  Thanks for great sample.
  I am now stuck with how to actually localize validation messages using ValueConverter or similar approach. 
  Any ideas?   Posted by Ken @ 18 Feb 2010 15:18 
  Nice! This is the only way to go in silverlight 3. The DataForm is useless in my opinion.   Posted by Dmitriy @ 08 Mar 2010 07:10 
  Thanks for the great article!
  In your example the error message for the salutation combobox suppossed to be (ErrorMessage = "Salutation is required") yet it shows "Input is not in a correct format."
  I ran into similar issue with validating combobox in my application. Any ideas how to overcome that?   Posted by Nirmal @ 27 Mar 2010 06:21 
  Hai,
  i am using radgridview. While editing the cell details i am using radGridView_CellValidating event.
  In that,
  void radGridView_CellValidating (object sender, Telerik.Windows.Controls.GridViewCellValidatingEve ntArgs e)
  {
  e.IsValid = false; 
  e.ErrorMessage ="Invalid Data(Some Message)";
  }
  It shows the error message when mouse over on the right top corner of tht red indication. But i need when focusing on that cell or immediate when error occurs(doesn't need tooltipservice). How can i do this?
  Posted by boldtechie @ 14 Apr 2010 12:46 
  Hi, I was trying the validation with datagrid in silverlight 3. But my error messages was not showing up when i hit save. Is there any thing else i have to do in code?
  I debugged through code, found that just like any normal textbox control in the page the be.UpdateSource(); will be called. but my text box was not showing up the error messages when its inside datagrid.   Posted by Eric @ 05 Jun 2010 05:26 
  I have the same problem as boldtechie . Are there any known problem if we use TemplatePanels and custom controls?   Posted by TM @ 02 Aug 2010 12:31 
  Thank you, Josh. Really good input - just what I needed!   Posted by cwobie @ 06 Oct 2010 13:37 
  Hi Josh, Excellent post and it works great. This is my requirement, I must have a datagrid where by users can enter values for the columns, the add a new row to the grid by adding a new blank object to the ObservableCollection. After implementing your code and hitting the save button it works great. No the problem, the second requirement is to import data from a file into this DataGrid. No big deal, i read each row from the file, populate a new object by assigning the values to the properties and add it to the collection. 
  But when i then hit the save button, none of the fields populated from the file gets validated, only the blank one i created first gets validated. AS long as I dont assign values to the new object being created (as I do when adding a new row) all is fine, but not when i populate the object from the file and adding it to the collection. 
  Will appreciate it greatly if you can shed some light on this or propose a solution.   Posted by Alfonso Paredes @ 04 Nov 2010 19:30 
  unhandled exception in your demo code at
  Validator.ValidateProperty(value, new ValidationContext(this, null, null) { MemberName = propertyName });
  at method
  protected virtual bool SetValue(ref T target, T value, string propertyName)
  from class ModelBase   Posted by Mitesh Patel @ 10 Nov 2010 16:03 
  This is really good example, particularly in MVVM patern for validation its really helpful, but still I am looking for the solution of my problem where if I am using String formatting on my text box, its causing problem for the behavior which is implemented for textbox. We have the behavior which bind the source with textbox on textchange/Keyup event. But if this behavior is ON then cursor in textbox jumping to first posion after each keystroke. So, i am looking for the solution... If you have any suggestion please send me on miteshpatel2011@gmail.com
  Thanks.  Posted by Jag @ 09 Dec 2010 16:39 
  Hey Josh,
  Thanks a lot for this lovely post. It is working brilliantly for me. You have saved me few hours :-)
  Cheers---Jag 
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics