2018年01月11日 22:42
原创作品,转载时请务必以超链接形式标明文章原始出处,否则将追究法律责任。

我不像有些大牛能出书做MVP,有些大牛能卖组件开公司,我只是个小程序员。

今天主要是展示Silverlight的验证和OOB(Out Of Browser)模式,对了,我平时最恨那些写博客只写简写而不注明全称的人,所以这里OOB就是Out Of Browser。顾名思义,浏览器外,也就是Silverlight运行在浏览器外。OK,废话不多说,先上一张图,档案信息的修改界面。

153338167.jpg

点击修改按钮,弹出档案信息修改界面。我们首先看看修改界面的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。我们来看看验证的效果。

155307477.jpg

UI会提示出生日期不正确。如果毕业年份输入不正确,也会给出提示信息。

155504148 (1).jpg

因为毕业年份设置了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 项目上点击右键,打开属性设置界面,如下所示

181343703.jpg

我们勾选Enable running application out of browser,然后点击Out-of-Browser Settings按钮,弹出设置界面,如下

181515248.jpg

我们设置了高度和宽度,以及window style等信息。OK,我们再次将程序运行起来。我们发现点击右键多了一项“将MISInformation Application 安装到此计算机...”。我们点击安装后,出现下面的界面

182407438.jpg

勾选开始和桌面后,点击确定,我们发现桌面上多了一个图标,如下所示

182555371.jpg

我们双击它,弹出如下界面,即我们的OOB运行模式

182958321.jpg

 OK,看到了吧,Title是我们上面设置的档案信息管理。怎么样,这样的Silverlight运行方式是不是很不错。它同样能够实现在浏览器中的功能,如下

183236311.jpg

好了,今天就讲这么多,如果大家需要源代码,直接找我要,或者加入.net群205217091。

发表评论
匿名  
用户评论
暂无评论