在WPF中实现数据验证实现自定义的Popup提示
传统的WPF中集成了数据有效性的验证,不过我们在使用中发现使用这个功能在WPF中并不能出现像Silverlight中那样的弹出式的Popup提示,只能以红色的边框提示有错误的数据发生,不能以人性化的方式给予你提示,这似乎给了我们一个小小的遗憾。不过我们可以利用WPF强大的自定义图形功能弥补我们的遗憾。下面就以自定义的Popup为例来说明如果给数据添加验证提示功能。这是如下的效果图:
重要提示:
1、为了对特定的信息进行验证,我在这儿使用了数据类从IDataErrorInfo 接口来实现错误的设置工作。
public interface IDataErrorInfo{
// Properties
string this[string columnName] { get; }//错误详情 }
2、为了对使用WPF对发生的数据改变作出及时的反应,我们还得使用INotifyPropertyChanged接口以实现数据更新通知事件。
public interface INotifyPropertyChanged{
// Events
event PropertyChangedEventHandler PropertyChanged;}
3、最后我们还得对数据发生验证错误时,我们还得附加相应的错误事件处理,这里就得使用Validation类来附加处理事件了,相应的类型定义如下:
public static class Validation{
public static void AddErrorHandler(DependencyObject element, EventHandler<ValidationErrorEventArgs> handler);//附加事件处理
public static bool GetHasError(DependencyObject element);//相应的对象是否有错误发生 }
具体实现:
1、 定义一个Popup图形化对象,由于为了使用可视化的设计,我们这儿没有采用硬编码的方式来使用Poppup对象,而是采用了硬编码控制Popup的IsOpen的属性来实现的。在主窗体的Xaml代码中我们定义了它的相应的样式,如下所示:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<Popup x:Name="emailPopup" PopupAnimation="Scroll" Placement="Relative" Margin="20,0,0,40" AllowsTransparency="True" Width="175" HorizontalAlignment="Right" VerticalAlignment="Top" HorizontalOffset="200" VerticalOffset="-10">
<Grid Height="47" Width="175" SnapsToDevicePixels="True">
<Path Data="M12.166999,0.5 L143.167,0.5 C145.92843,0.50000003 148.167,2.7385762 148.167,5.5 L148.167,30.5 C148.167,33.261425 145.92843,35.5 143.167,35.5 L12.166999,35.5 11.81918,35.482437 11.834001,35.5 C10.619751,36.16069 10.9795,36.5625 8.1912529,37.482075 5.4030061,38.401646 3.063751,40.272026 0.5,41.667 2.2780275,39.667053 4.7294998,37 5.8340837,35.66716 6.662521,34.66753 6.9629115,32.753006 7.1582331,31.348595 L7.1991105,31.052473 7.1928148,31.011221 C7.1757442,30.843136 7.1669998,30.672588 7.1669995,30.5 L7.1669995,17.25 7.1669995,5.5 C7.1669998,2.7385762 9.405576,0.50000003 12.166999,0.5 z" Margin="1.333,0,0,1.833" Stretch="Fill" Stroke="#FF32E767" UseLayoutRounding="False">
<Path.Fill>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="#FF5F5DB6" Offset="1"/>
<GradientStop Color="#FF3F3BCA"/>
<GradientStop Color="#FFACABE9" Offset="0.747"/>
</LinearGradientBrush>
</Path.Fill>
</Path>
<Path Data="M67.5,15.25 C70.750538,10.312179 121.33333,14.916667 148.25,14.75 148.41667,11.083333 149,6.0625 148.75,3.75 148.5,1.4375 145.43711,0.0625 144.5,0.25 149.46607,0.22253689 13.874996,-0.062023075 11.75,0.25 9.6250039,0.56202307 8.3747773,3.5006011 8.25,4.75 8.1252227,5.9993989 8.159157,10.23778 8.25,14.5 28,14.75 64.249462,20.187821 67.5,15.25 z" Margin="8,0,0,28.686" Stretch="Fill" Stroke="Black" UseLayoutRounding="False" StrokeThickness="0">
<Path.Fill>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="#B2FFFFFF"/>
<GradientStop Color="#33FFFFFF" Offset="1"/>
</LinearGradientBrush>
</Path.Fill>
</Path>
<TextBlock x:Name="errorTextBlock_Copy" TextWrapping="Wrap" Text="{Binding .}" Margin="11,3,3,11" HorizontalAlignment="Center" VerticalAlignment="Center" Foreground="#FFF11818" Width="157" TextAlignment="Center"/>
</Grid>
</Popup>
这儿的TextBlock中的文本采用了动态绑定的方式来实现,以实现在后置代码中设置DataContext属性来实现数据的传递。
2、 这儿我们实现了一个公共的实现前面两个接口的类:ViewModelBase
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
public class ViewModelBase:INotifyPropertyChanged,IDataErrorInfo
{
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
public Dictionary<string, string> errors = new Dictionary<string, string>();
#region IDataErrorInfo Members
public string Error
{
get { return null; }
}
public string this[string columnName]
{
get {
if (errors.ContainsKey(columnName))
return errors[columnName];
else return string.Empty;
}
}
#endregion
public void SetError(string propertyName,string errorMessage)
{
errors[propertyName] = errorMessage;
NotifyPropertyChanged(propertyName);
}
public void ClearError(string propertyName)
{
errors.Remove(propertyName);
NotifyPropertyChanged(propertyName);
}
}
3、 接着我们从这个基类派生我们自己的数据处理类MembershipViewModel,这里面我们定义了三个数据成员,在Email中我们会验证它的有效性,而在Phone中我们也会验证它的有效性,我们在这儿使用了正则表达式的验证,相应的验证逻辑如下:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
private void ValidateEmail(string email)
{
if(!Regex.IsMatch (email,emailPatten ))
SetError("Email","Email 格式有误");
else
ClearError ("Email");
}
private void ValidatePhone(string phone)
{
if(!Regex.IsMatch (phone,phonePatten ))
SetError ("Phone","电话格式有误");
else
ClearError ("Phone");
}
4、 在MainWindow中实现相应的绑定,在绑定中我们得实现相应的绑定属性,为了实现数据错误发生时提供提示功能,我们得设置如下形式:
Text="{Binding Email, Mode=TwoWay, NotifyOnValidationError=True, ValidatesOnDataErrors=True}
5、 在后置代码文件中的构造函数中加入错误处理事件:
private void ValidationDatas()
{
Validation.AddErrorHandler(emailTxt, OnEmailError);//附加事件处理方法
Validation.AddErrorHandler(phoneTxt, OnPhoneError);
}
6、 在错误处理逻辑中设置要显示的数据及如何显示Popup.
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
private void OnEmailError(object sender, ValidationErrorEventArgs e)
{
Popup pop = FindName("emailPopup") as Popup;
//如果验证到有错误发生则显示错误框否则消除它
if (Validation.GetHasError(sender as FrameworkElement))
{
pop.DataContext = e.Error.ErrorContent;
pop.IsOpen = true;//打开Popup窗口
}
else {
pop.IsOpen = false;
}
}
总结:在这儿利用WPF内置的错误处理机制来实现了一个个性化的提示框功能,这只能WPF框架下的一个比较小的功能,其实WPF及Silverlight 中的设计模式很有意思,它封装了大部分的逻辑,所以我们只要稍微改动一下,就可以取得很酷的效果。