我不像有些大牛能出书做MVP,有些大牛能卖组件开公司,我只是个小程序员。
今天主要是展示Silverlight的验证和OOB(Out Of Browser)模式,对了,我平时最恨那些写博客只写简写而不注明全称的人,所以这里OOB就是Out Of Browser。顾名思义,浏览器外,也就是Silverlight运行在浏览器外。OK,废话不多说,先上一张图,档案信息的修改界面。
点击修改按钮,弹出档案信息修改界面。我们首先看看修改界面的UI代码。
<controls:ChildWindow x:Class="MISInfoManage.ArchiveInfoModify" xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:controls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls" Width="480" Height="260" xmlns:LocalResource="clr-namespace:MISInfoManage.Resources"> <controls:ChildWindow.Resources> <LocalResource:ArchiveInfoModifyResource x:Key="LocalResource"/> <Style x:Key="TitleColumnStyle" TargetType="TextBlock"> <Setter Property="FontSize" Value="12"/> <Setter Property="HorizontalAlignment" Value="Right"/> </Style> </controls:ChildWindow.Resources> <Grid x:Name="LayoutRoot" Margin="5"> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto"/> <ColumnDefinition Width="Auto"/> <ColumnDefinition Width="Auto"/> <ColumnDefinition Width="Auto"/> </Grid.ColumnDefinitions> <TextBlock Text="{Binding Tb_ArchiveNo,Source={StaticResource LocalResource}}" Style="{StaticResource TitleColumnStyle}" Grid.Row="0" Grid.Column="0"/> <TextBox Text="{Binding ArchiveNo,Mode=TwoWay}" Grid.Row="0" Grid.Column="1" FontSize="12" Width="150"/> <TextBlock Text="{Binding Tb_Name,Source={StaticResource LocalResource}}" Style="{StaticResource TitleColumnStyle}" Grid.Row="0" Grid.Column="2" Margin="20,0,0,0"/> <TextBox Text="{Binding Name,Mode=TwoWay,NotifyOnValidationError=True,ValidatesOnExceptions=True,UpdateSourceTrigger=Explicit}" Grid.Row="0" Grid.Column="3" FontSize="12" Width="170"/> <TextBlock Text="{Binding Tb_IdCardNo,Source={StaticResource LocalResource}}" Style="{StaticResource TitleColumnStyle}" Grid.Row="1" Grid.Column="0" Margin="0,5,0,0"/> <TextBox Text="{Binding IdCardNo,Mode=TwoWay,NotifyOnValidationError=True, ValidatesOnExceptions=True}" Grid.Row="1" Grid.Column="1" Margin="0,5,0,0" FontSize="12"/> <TextBlock Text="{Binding Tb_Sex,Source={StaticResource LocalResource}}" Style="{StaticResource TitleColumnStyle}" Grid.Row="1" Grid.Column="2" Margin="20,5,0,0"/> <ComboBox Grid.Row="1" Grid.Column="3" SelectedValuePath="Tag" SelectedValue="{Binding Sex,Mode=TwoWay}" Margin="0,5,0,0" FontSize="12"> <ComboBoxItem Content="男" Tag="1"></ComboBoxItem> <ComboBoxItem Content="女" Tag="0"></ComboBoxItem> </ComboBox> <TextBlock Text="{Binding Tb_Birth,Source={StaticResource LocalResource}}" Style="{StaticResource TitleColumnStyle}" Margin="0,5,0,0" Grid.Row="2" Grid.Column="0"/> <sdk:DatePicker Grid.Row="2" Grid.Column="1" SelectedDate="{Binding BirthDay,Mode=TwoWay,NotifyOnValidationError=True,ValidatesOnExceptions=True}" Margin="0,5,0,0" FontSize="12"/> <TextBlock Text="{Binding Tb_TelNo,Source={StaticResource LocalResource}}" Style="{StaticResource TitleColumnStyle}" Margin="20,5,0,0" Grid.Row="2" Grid.Column="2"/> <TextBox Text="{Binding TelNumber,Mode=TwoWay}" Grid.Row="2" Grid.Column="3" Margin="0,5,0,0" FontSize="12"/> <TextBlock Text="{Binding Tb_Professional,Source={StaticResource LocalResource}}" Style="{StaticResource TitleColumnStyle}" Margin="0,5,0,0" Grid.Row="3" Grid.Column="0"/> <TextBox Text="{Binding Professional,Mode=TwoWay}" Grid.Row="3" Grid.Column="1" Margin="0,5,0,0" FontSize="12"/> <TextBlock Text="{Binding Tb_Education,Source={StaticResource LocalResource}}" Style="{StaticResource TitleColumnStyle}" Margin="20,5,0,0" Grid.Row="3" Grid.Column="2"/> <ComboBox ItemsSource="{Binding EducationList,Mode=OneWay}" DisplayMemberPath="display_content" SelectedValuePath="data" SelectedValue="{Binding Education,Mode=TwoWay}" FontSize="12" Margin="0,5,0,0" Grid.Row="3" Grid.Column="3"/> <TextBlock Text="{Binding Tb_GraduateSchool,Source={StaticResource LocalResource}}" Style="{StaticResource TitleColumnStyle}" Margin="0,5,0,0" Grid.Row="4" Grid.Column="0"/> <TextBox Text="{Binding GraduateSchool,Mode=TwoWay}" Grid.Row="4" Grid.Column="1" Margin="0,5,0,0" FontSize="12"/> <TextBlock Text="{Binding Tb_GraduateYear,Source={StaticResource LocalResource}}" Style="{StaticResource TitleColumnStyle}" Margin="20,5,0,0" Grid.Row="4" Grid.Column="2"/> <TextBox Text="{Binding GraduateYear,Mode=TwoWay,NotifyOnValidationError=True,ValidatesOnExceptions=True}" Grid.Row="4" Grid.Column="3" Margin="0,5,0,0" FontSize="12"/> <TextBlock Text="{Binding Tb_ArchiveState,Source={StaticResource LocalResource}}" Style="{StaticResource TitleColumnStyle}" Margin="0,5,0,0" Grid.Row="5" Grid.Column="0"/> <ComboBox Grid.Row="5" Grid.Column="1" SelectedValuePath="Tag" SelectedValue="{Binding ArchiveState,Mode=TwoWay}" Margin="0,5,0,0" FontSize="12"> <ComboBoxItem Content="已提" Tag="0"/> <ComboBoxItem Content="在库" Tag="1"/> </ComboBox> <StackPanel Grid.Row="6" Grid.Column="0" Grid.ColumnSpan="4" Orientation="Horizontal" HorizontalAlignment="Center" Margin="0,10,0,0"> <Button Content="{Binding Btn_Modify,Source={StaticResource LocalResource}}" Width="80" Height="25" Margin="0,0,10,0" Click="Button_Click" Tag="m"/> <Button Content="{Binding Btn_Cancel,Source={StaticResource LocalResource}}" Width="80" Height="25" Click="Button_Click" Tag="c"/> </StackPanel> </Grid> </controls:ChildWindow>
大家看到了,也是Grid和StackPanel布局。我们看到里面有这样一段代码
<ComboBox Grid.Row="1" Grid.Column="3" SelectedValuePath="Tag" SelectedValue="{Binding Sex,Mode=TwoWay}" Margin="0,5,0,0" FontSize="12"> <ComboBoxItem Content="男" Tag="1"></ComboBoxItem> <ComboBoxItem Content="女" Tag="0"></ComboBoxItem> </ComboBox>
注意,这里SelectedValuePath="Tag",这里指定了这个下拉列表的SelectedValue值绑定的是自己的ComboBoxItem 的Tag属性。如果选择了男,下拉列表的SelectedValue值就是1,如果选择女,则SelectedValue值是0。也就是这里绑定的SelectedValue="{Binding Sex,Mode=TwoWay}" ,即ViewModel的Sex属性。
再往下看有这么一段代码
<TextBox Text="{Binding Name,Mode=TwoWay,NotifyOnValidationError=True,ValidatesOnExceptions=True}" Grid.Row="0" Grid.Column="3" FontSize="12" Width="170"/>
在这里绑定的是ViewModel的Name属性,NotifyOnValidationError和ValidatesOnExceptions用来捕获验证出现的异常。我们来看看ViewModel的代码
namespace ViewModel { public class ArchiveInfoModifyModel { public string ArchiveNo { get; set; } private string archiveState; public string ArchiveState { get { return archiveState; } set { archiveState = value; NotifyPropertyChange("ArchiveState"); } } private string sex; public string Sex { get { return sex; } set { sex = value; NotifyPropertyChange("Sex"); } } private string education { get; set; } public string Education { get { return education; } set { education = value; NotifyPropertyChange("Education"); } } private DateTime? birthDay; public DateTime? BirthDay { get { return birthDay; } set { birthDay = value; if (value > DateTime.Now.AddYears(-15) || value < DateTime.Now.AddYears(-50)) { throw new ValidationException("出生日期不正确!"); } NotifyPropertyChange("BirthDay"); } } private string professional; public string Professional { get { return professional; } set { professional = value; NotifyPropertyChange("Professional"); } } private int? graduateYear; [Range(1950,2012,ErrorMessage="毕业年份不正确!")] public int? GraduateYear { get { return graduateYear; } set { var validatorContext = new ValidationContext(this, null, null); validatorContext.MemberName = "GraduateYear"; Validator.ValidateProperty(value, validatorContext); graduateYear = value; NotifyPropertyChange("GraduateYear"); } } private string name; [Required(ErrorMessage="姓名不能为空!")] [StringLength(8,ErrorMessage="姓名长度不能超过8!")] public string Name { get { return name; } set { var validatorContext = new ValidationContext(this, null, null); validatorContext.MemberName = "Name"; Validator.ValidateProperty(value, validatorContext); name = value; NotifyPropertyChange("Name"); } } private string idCardNo; public string IdCardNo { get { return idCardNo; } set { idCardNo = value; NotifyPropertyChange("IdCardNo"); } } private string graduateSchool; public string GraduateSchool { get { return graduateSchool; } set { graduateSchool = value; NotifyPropertyChange("GraduateSchool"); } } private string telNumber; public string TelNumber { get { return telNumber; } set { telNumber = value; NotifyPropertyChange("TelNumber"); } } private ObservableCollection<ViewModel.ArchiveInfoService.Codes> educationList; public ObservableCollection<ViewModel.ArchiveInfoService.Codes> EducationList { get { return educationList; } set { educationList = value; NotifyPropertyChange("EducationList"); } } public event PropertyChangedEventHandler PropertyChanged; private void NotifyPropertyChange(string property) { if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(property)); } } } }
我们看到了Name和GraduateYear等加入了验证。Name验证了非空和长度,采用的是标注Attribute的格式,和MVC的验证方式一样,需要引入System.ComponentModel.DataAnnotations命名空间。其中的Birthday属性采用了抛出异常的方式,当抛出异常后,页面UI会接收到异常信息,并显示红色的提示信息,因为Birthday设置了ValidatesOnExceptions=True。我们来看看验证的效果。
UI会提示出生日期不正确。如果毕业年份输入不正确,也会给出提示信息。
因为毕业年份设置了NotifyOnValidationError=True。所以在这里NotifyOnValidationError对应于设置ValidationAttribute的方式,而ValidatesOnExceptions对应于Set访问器中的抛出异常方式。在这里我特别要注意说明的是
<TextBox Text="{Binding Name,Mode=TwoWay,NotifyOnValidationError=True,ValidatesOnExceptions=True,UpdateSourceTrigger=Explicit}" Grid.Row="0" Grid.Column="3" FontSize="12" Width="170"/>
在这里有一个UpdateSourceTrigger,这个属性有两个值,一个是默认的Default,一个是Explicit,有什么区别呢?我们在用MVVM的时候,经常会碰到这样一种场景,在文本框输入一个值,然后直接按回车键查数据或者别的什么操作,但是你会发现回车后取得的文本框得值仍然是文本框上次的值(没有失去焦点前的值)。为什么呢?这就是这里要说的UpdateSourceTrigger,在双向绑定下,大多数控件默认是在PropertyChanged以后就会更改ViewModel的值,但是TextBox则默认是失去焦点以后才会更改ViewModel的值。所以在这里我也不默认了,直接改成UpdateSourceTrigger=Explicit,显式的调用BindingExpression的UpdateSource方法获取页面上的值,将其反映到ViewModel上。这就意味着,只有在在调用了UpdateSource方法以后才会启用验证。上述讲的验证只是在失去焦点以后的验证,大多数情况下我们是要点击提交按钮以后来验证,并且验证不通过,不能再执行下面的逻辑。这个在我们web开发中很容易实现,但是在Silverlight中怎么实现呢?我们看看后台
using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Net; using System.Threading; using System.Windows; using System.Windows.Data; using System.Windows.Controls; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Animation; using System.Windows.Shapes; using Client.Common; using ViewModel; namespace MISInfoManage { public partial class ArchiveInfoModify : ChildWindow { string archiveNo; ArchiveInfoModifyModel viewModel; ViewModel.ArchiveInfoService.ArchiveInfoServiceClient client; public ArchiveInfoModify() { InitializeComponent(); this.Title = "档案信息修改"; } public ArchiveInfoModify(string archiveNo) : this() { this.archiveNo = archiveNo; viewModel = new ArchiveInfoModifyModel(); this.Loaded += delegate(object sender, RoutedEventArgs e) { client = new ViewModel.ArchiveInfoService.ArchiveInfoServiceClient(); client.GetEducationCompleted += delegate(object sender1, ViewModel.ArchiveInfoService.GetEducationCompletedEventArgs e1) { viewModel.EducationList = new System.Collections.ObjectModel.ObservableCollection<ViewModel.ArchiveInfoService.Codes>(e1.Result); this.GetArchiveInfoByNo(); }; client.GetEducationAsync(); }; } private void Button_Click(object sender, RoutedEventArgs e) { Button button = sender as Button; if (button.Tag.ToString().Equals("m")) { if (!this.ValidateData()) { return; } ViewModel.ArchiveInfoService.Person_Info personInfo = new ViewModel.ArchiveInfoService.Person_Info() { birth = viewModel.BirthDay.Value, contact_tel = viewModel.TelNumber, education_level = viewModel.Education, graduate_school = viewModel.GraduateSchool, graduate_year = viewModel.GraduateYear.Value, id_card = viewModel.IdCardNo, name = viewModel.Name, no = viewModel.ArchiveNo, professional = viewModel.Professional, sex = viewModel.Sex, state = viewModel.ArchiveState }; client.ModifyArchiveInfoCompleted += delegate(object modifySender, ViewModel.ArchiveInfoService.ModifyArchiveInfoCompletedEventArgs modifyArgs) { new Thread(() => { UISynchronizationContext.Context.Post((state) => { MessageBox.Show("修改成功!"); }, null); }).Start(); }; client.ModifyArchiveInfoAsync(personInfo); } if (button.Tag.ToString().Equals("c")) { this.Close(); } } private void GetArchiveInfoByNo() { client.GetPersonInfoByIDCompleted += delegate(object sender, ViewModel.ArchiveInfoService.GetPersonInfoByIDCompletedEventArgs e) { ViewModel.ArchiveInfoService.Person_Info personInfo = e.Result; this.viewModel.ArchiveNo = personInfo.no; this.viewModel.ArchiveState = personInfo.state; this.viewModel.BirthDay = personInfo.birth; this.viewModel.Education = personInfo.education_level; this.viewModel.GraduateSchool = personInfo.graduate_school; this.viewModel.GraduateYear = personInfo.graduate_year; this.viewModel.IdCardNo = personInfo.id_card; this.viewModel.Name = personInfo.name; this.viewModel.Professional = personInfo.professional; this.viewModel.Sex = personInfo.sex; this.viewModel.TelNumber = personInfo.contact_tel; this.LayoutRoot.DataContext = viewModel; }; client.GetPersonInfoByIDAsync(archiveNo); } private bool ValidateData() { UIElementCollection UIElments = LayoutRoot.Children; foreach (var element in UIElments) { if (element.GetType() == typeof(TextBox)) { TextBox textbox = element as TextBox; BindingExpression expression = textbox.GetBindingExpression(TextBox.TextProperty); if (expression.ParentBinding.NotifyOnValidationError == true || expression.ParentBinding.ValidatesOnExceptions == true) { expression.UpdateSource(); if (Validation.GetHasError(textbox)) { return false; } } } if (element.GetType() == typeof(ComboBox)) { ComboBox comboBox = element as ComboBox; BindingExpression expression = comboBox.GetBindingExpression(ComboBox.SelectedValueProperty); if (expression.ParentBinding.NotifyOnValidationError == true || expression.ParentBinding.ValidatesOnExceptions == true) { expression.UpdateSource(); if (Validation.GetHasError(comboBox)) { return false; } } } if (element.GetType() == typeof(DatePicker)) { DatePicker datepicker = element as DatePicker; BindingExpression expression = datepicker.GetBindingExpression(DatePicker.SelectedDateProperty); if (expression.ParentBinding.NotifyOnValidationError == true || expression.ParentBinding.ValidatesOnExceptions == true) { expression.UpdateSource(); if (Validation.GetHasError(datepicker)) { return false; } } } } return true; } } }
我们看看这个Button_Click事件,在修改方法中,我们调用了ValidateData方法,该方法循环遍历页面UIElment,并强制更新ViewModel中的值。如果发现验证不通过,直接返回False,在Button_Click事件中,如果没有通过验证,就不再执行修改操作。这样就实现了验证不通过,就不再往下走的逻辑。关于验证,我就说到这里,如有不懂,可以加入.net群205217091,我可以把源代码共享给大家。我们看看服务端代码
public int ModifyArchiveInfo(Person_Info personInfoModel) { Person_Info personInfo = misInfoEntities.person_info.SingleOrDefault(p => p.no.Equals(personInfoModel.no)); Type type = personInfo.GetType(); PropertyInfo[] propertyInfos = type.GetProperties().Where(p=>!p.Name.Equals("id")).ToArray(); foreach (var propertyInfo in propertyInfos) { propertyInfo.SetValue(personInfo, propertyInfo.GetValue(personInfoModel, null)); } return misInfoEntities.SaveChanges(); }
这个后台代码也没什么,循环遍历利用反射赋值,最后调用DBContext的SaveChanges方法完成更新。好了,最后我们看看OOB模式。
在Silverlight 项目上点击右键,打开属性设置界面,如下所示
我们勾选Enable running application out of browser,然后点击Out-of-Browser Settings按钮,弹出设置界面,如下
我们设置了高度和宽度,以及window style等信息。OK,我们再次将程序运行起来。我们发现点击右键多了一项“将MISInformation Application 安装到此计算机...”。我们点击安装后,出现下面的界面
勾选开始和桌面后,点击确定,我们发现桌面上多了一个图标,如下所示
我们双击它,弹出如下界面,即我们的OOB运行模式
OK,看到了吧,Title是我们上面设置的档案信息管理。怎么样,这样的Silverlight运行方式是不是很不错。它同样能够实现在浏览器中的功能,如下
好了,今天就讲这么多,如果大家需要源代码,直接找我要,或者加入.net群205217091。