我不像有些大牛能出书做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。