解读WPF中的Binding

1.Overview

基于MVVM实现一段绑定大伙都不陌生,Binding是wpf整个体系中最核心的对象之一这里就来解读一下我花了纯两周时间有哪些秘密。这里我先提出几个问题应该是大家感兴趣的,如下:

(1)INotifyPropertyChanged是如何被加载、触发的(Binding如何完成数据更新的)?

(2)为什么需要开发者手动实现INotifyPropertyChanged接口来为每个成员实现数据通知,为什么不集成在wpf框架里?

(3)藏在WPF体系里的观察者模式在哪里?

2.Detail

想了解以上问题,我们先补充以下前置知识点。

我们带着以上几个问题来看本文的后续内容,首先我们通过下面这张图来了解绑定的过程。

2964380a83fbc309d5398aa007a3ff7a.png

根据以上过程我们可以基于MVVM模式下,在Xaml中写出这样的语句来表示绑定。

<TextBoxName="mytextbox"Height="25"Width="150"Text="{BindingPath=Name,Mode=**TwoWay**,UpdateSourceTrigger=**PropertyChanged**}"></TextBox>

那么如果把他转换成c#代码,将会是如下表示。

public string BeachName{ get; set; }private void Test()
{BeachName="BikiniBeach";TextBoxtextBox = newTextBox();textBox.Name = "myTextBox";Binding binding = new Binding();binding.Source = BeachName;binding.Path = new PropertyPath("BeachName");textBox.SetBinding(TextBox.TextProperty, binding);
}

                                                                                                  (1-1)

上面这段代码,包含了两个关键对象Textbox和Binding它们里面大有文章首先我们逐个拆解这两个对象里都有什么。

Textbox

在(1-1)的代码中初始化一个Textbox对象,它会创建一个依赖属性TextProperty用于绑定要素之一。

public static readonly DependencyProperty TextProperty = DependencyProperty.Register(nameof (Text), typeof (string), typeof (TextBox), (PropertyMetadata) new FrameworkPropertyMetadata((object) string.Empty, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault | FrameworkPropertyMetadataOptions.Journal, new PropertyChangedCallback(TextBox.OnTextPropertyChanged), new CoerceValueCallback(TextBox.CoerceText), true, UpdateSourceTrigger.LostFocus));

Binding

53ea8f068c40a3f8afcb8592ba520f40.png

当我们在日常开发实现绑定过程当中,WPF的体系会默默帮你创建Binding对象,这里我们来看看Binding包含了哪些定义(为了观看体验删除了大部分不相关代码)。

namespace System.Windows.Data
{public class Binding : BindingBase{//....其它代码省略public static void AddSourceUpdatedHandler(DependencyObject element,EventHandler<DataTransferEventArgs> handler){UIElement.AddHandler(element, Binding.SourceUpdatedEvent, (Delegate) handler);}public static void RemoveSourceUpdatedHandler(DependencyObject element,EventHandler<DataTransferEventArgs> handler){UIElement.RemoveHandler(element, Binding.SourceUpdatedEvent, (Delegate) handler);}public static void AddTargetUpdatedHandler(DependencyObject element,EventHandler<DataTransferEventArgs> handler){UIElement.AddHandler(element, Binding.TargetUpdatedEvent, (Delegate) handler);}public static void RemoveTargetUpdatedHandler(DependencyObject element,EventHandler<DataTransferEventArgs> handler){UIElement.RemoveHandler(element, Binding.TargetUpdatedEvent, (Delegate) handler);}public Binding(string path){if (path == null)return;if (Dispatcher.CurrentDispatcher == null)throw new InvalidOperationException();this.Path = new PropertyPath(path, (object[]) null);}public PropertyPath Path{get => this._ppath;set{this.CheckSealed();this._ppath = value;this._attachedPropertiesInPath = -1;this.ClearFlag(BindingBase.BindingFlags.PathGeneratedInternally);if (this._ppath == null || !this._ppath.StartsWithStaticProperty)return;if (this._sourceInUse == Binding.SourceProperties.None || this._sourceInUse == Binding.SourceProperties.StaticSource || FrameworkCompatibilityPreferences.TargetsDesktop_V4_0)this.SourceReference = Binding.StaticSourceRef;elsethrow new InvalidOperationException(SR.Get("BindingConflict", (object) Binding.SourceProperties.StaticSource, (object) this._sourceInUse));}}[DefaultValue(BindingMode.Default)]public BindingMode Mode{get{switch (this.GetFlagsWithinMask(BindingBase.BindingFlags.PropagationMask)){case BindingBase.BindingFlags.OneTime:return BindingMode.OneTime;case BindingBase.BindingFlags.OneWay:return BindingMode.OneWay;case BindingBase.BindingFlags.OneWayToSource:return BindingMode.OneWayToSource;case BindingBase.BindingFlags.TwoWay:return BindingMode.TwoWay;case BindingBase.BindingFlags.PropDefault:return BindingMode.Default;default:Invariant.Assert(false, "Unexpected BindingMode value");return BindingMode.TwoWay;}}set{this.CheckSealed();BindingBase.BindingFlags flags = BindingBase.FlagsFrom(value);if (flags == BindingBase.BindingFlags.IllegalInput)throw new InvalidEnumArgumentException(nameof (value), (int) value, typeof (BindingMode));this.ChangeFlagsWithinMask(BindingBase.BindingFlags.PropagationMask, flags);}}[DefaultValue(UpdateSourceTrigger.Default)]public UpdateSourceTrigger UpdateSourceTrigger{get{switch (this.GetFlagsWithinMask(BindingBase.BindingFlags.UpdateDefault)){case BindingBase.BindingFlags.OneTime:return UpdateSourceTrigger.PropertyChanged;case BindingBase.BindingFlags.UpdateOnLostFocus:return UpdateSourceTrigger.LostFocus;case BindingBase.BindingFlags.UpdateExplicitly:return UpdateSourceTrigger.Explicit;case BindingBase.BindingFlags.UpdateDefault:return UpdateSourceTrigger.Default;default:Invariant.Assert(false, "Unexpected UpdateSourceTrigger value");return UpdateSourceTrigger.Default;}}set{this.CheckSealed();BindingBase.BindingFlags flags = BindingBase.FlagsFrom(value);if (flags == BindingBase.BindingFlags.IllegalInput)throw new InvalidEnumArgumentException(nameof (value), (int) value, typeof (UpdateSourceTrigger));this.ChangeFlagsWithinMask(BindingBase.BindingFlags.UpdateDefault, flags);}}[DefaultValue(false)]public bool NotifyOnSourceUpdated{get => this.TestFlag(BindingBase.BindingFlags.NotifyOnSourceUpdated);set{if (this.TestFlag(BindingBase.BindingFlags.NotifyOnSourceUpdated) == value)return;this.CheckSealed();this.ChangeFlag(BindingBase.BindingFlags.NotifyOnSourceUpdated, value);}}[DefaultValue(false)]public bool NotifyOnTargetUpdated{get => this.TestFlag(BindingBase.BindingFlags.NotifyOnTargetUpdated);set{if (this.TestFlag(BindingBase.BindingFlags.NotifyOnTargetUpdated) == value)return;this.CheckSealed();this.ChangeFlag(BindingBase.BindingFlags.NotifyOnTargetUpdated, value);}}[DefaultValue(null)]public IValueConverter Converter{get => (IValueConverter) this.GetValue(BindingBase.Feature.Converter, (object) null);set{this.CheckSealed();this.SetValue(BindingBase.Feature.Converter, (object) value, (object) null);}}public object Source{get{WeakReference<object> weakReference = (WeakReference<object>) this.GetValue(BindingBase.Feature.ObjectSource, (object) null);if (weakReference == null)return (object) null;object target;return !weakReference.TryGetTarget(out target) ? (object) null : target;}set{this.CheckSealed();if (this._sourceInUse == Binding.SourceProperties.None || this._sourceInUse == Binding.SourceProperties.Source){if (value != DependencyProperty.UnsetValue){this.SetValue(BindingBase.Feature.ObjectSource, (object) new WeakReference<object>(value));this.SourceReference = (ObjectRef) new ExplicitObjectRef(value);}else{this.ClearValue(BindingBase.Feature.ObjectSource);this.SourceReference = (ObjectRef) null;}}elsethrow new InvalidOperationException(SR.Get("BindingConflict", (object) Binding.SourceProperties.Source, (object) this._sourceInUse));}}internal override BindingExpressionBase CreateBindingExpressionOverride(DependencyObject target,DependencyProperty dp,BindingExpressionBase owner){return (BindingExpressionBase) BindingExpression.CreateBindingExpression(target, dp, this, owner);}}
}

Binding对象继承自BindingBase,在Binding类中我们可以看到CreateBindingExpressionOverride这个方法,这个方法来自父类BindingBase。代码中的BindingExpression是“绑定表达式”的意思,在CreateBindingExpression中入参完美的阐述了绑定关系;

internal override BindingExpressionBase CreateBindingExpressionOverride(DependencyObject target,DependencyProperty dp,BindingExpressionBase owner)
{return (BindingExpressionBase) BindingExpression.CreateBindingExpression(target, dp, this, owner);
}internal static BindingExpression CreateBindingExpression(DependencyObject d,DependencyProperty dp,Binding binding,BindingExpressionBase parent)
{if (dp.GetMetadata(d.DependencyObjectType) is FrameworkPropertyMetadata metadata && !metadata.IsDataBindingAllowed || dp.ReadOnly)throw new ArgumentException(System.Windows.SR.Get("PropertyNotBindable", (object) dp.Name), nameof (dp));BindingExpression bindingExpression = new BindingExpression(binding, parent);bindingExpression.ResolvePropertyDefaultSettings(binding.Mode, binding.UpdateSourceTrigger, metadata);if (bindingExpression.IsReflective && binding.XPath == null && (binding.Path == null || string.IsNullOrEmpty(binding.Path.Path)))throw new InvalidOperationException(System.Windows.SR.Get("TwoWayBindingNeedsPath"));return bindingExpression;
}

(1)DependencyObject,是所有控件的基类这里我们在当前环境中可以理解为Textbox。

(2)DependencyProperty,是我们要绑定的控件中的TextProperty依赖属性。

(3)Binding,表达了数据源、绑定目标、绑定模式、更新通知触发类型等信息。

5bed8a93d0c994299613fdd9a098b69b.png

         创建binding对象,建立绑定表达式CreateBindingExpression将依赖属性和控件、绑定对象关联起来->BindingExpression该方法将Path传给 TraceData.Trace追踪对象。在Binding继承的BindingBase.cs中实现了CreateBindingExpression(创建绑定表达式,它的作用就是用来“描述”绑定的整个过程)

[BindingExpression作用-1]

该对象提供了绑定更新的机制,UpdateSourceTrigger.Explicit 模式用来控制源对象的更新时机。

(1)调用 BindingExpression.UpdateSource()和 UpdateTarget( )方法,触发立即刷新行为。

(2)获取 BindingExpression对象,需要使用 GetBindingExpression( )方法。

BindingExpiression binding =
txtFontSize.GetBindingExpression(TextBox ,TextProperty);binding.UpdateSource();

要完全控制源对象的更新时机,可选择 UpdateSourceTrigger.ExpUdt 模式。如果在文本框示 例中使用这种方法,当文本框失去焦点后不会发生任何事情 反而,由您编写代码手动触发更 新。例如,可添加 Apply 按钮,调用 BindingExpression.UpdateSource()方法,触发立即刷新行为并更新字体尺寸。 当然,在调用 BindingExpressiorLUpdateSource( )之前 ,需要 一 种方法 来获取 BindingExpression 对象。BindingExpressicm 对象仅是将两项内容封装到一起的较小组装包,这 两项内容是:己经学习过的 Binding 对象(通过 BindingExpression.ParentBinding 属性提供)和由 源绑定的对象(BindingExpression.Dataltem)a 此外,BindingExpression 对象为触发立即更新绑定 的-部分提供了两个方法:UpdateSource( )和 UpdateTarget( )方法, 为联取 BindingExpressiori 对象,需要使用 GetBindingExpression( )方法,并传入具有绑定的 目标属性,每个元素都从 FrameworkEkment 类继承了该方法。

        可为每个属性引发事件。对于这种情况,事件必须以 的形式迸行命 名(如 UnitCostChanged)当属性变化时,由您负责引发事件。 可实现 System.ComponentModel.INotifyPropertyChanged 接口,该接口需要名为 PropertyChanged 的事件。无论何时属性发生变化,都必须引发 PropertyChanged 事件,并 且通过将属性名称作为字符串提供来指示哪个属性发生了变化。当属性发生变化时,仍 由您负责引发事件,但不必为每个属性定义单独的事件& 第一种方法依赖于 WPF 的依赖项属性基础架构,而第二种和第三种方法依赖于事件,通 常,当创建数据对象时,会使用第三种方法。对于非元素类而言,这是最简单的选择。实际上,还可使用另一种方法如果怀疑绑定对象已经发生变化,并且绑定对象不支持任 何恰当方 式的更改通知,这时可检索 BindingExpression 对象(使用 FrameworkElement. GetBmdingExpression()方法),并调用 BindingExpresskm.UpdateTarget()方法来触发更新, 这是最憨的解决方案。

[BindingExpression作用-2]

BindingExpression继承自BindingExpressionBase除了表述绑定关系以外,还创建了BindingWorker对象(下面为关键代码)这里所要讲的就是INotifyPropertyChanged是如何被加载、触发的。

private BindingWorker _worker;private void CreateWorker()
{Invariant.Assert(this.Worker == null, "duplicate call to CreateWorker");this._worker = (BindingWorker) new ClrBindingWorker(this, this.Engine);
}

上面代码将_worker初始化为ClrBindingWorker,它里面又包含PropertyPathWorker对象,PropertyPathWorker这个对象中有一个方法UpdateSourceValueState,它会从上层引用中拿到ViewModel的引用(引用会逐层从Binding类的层面逐层传递进来)然后会判断这个ViewModel是否继承了INotifyPropertyChanged如果继承了则找到public event PropertyChangedEventHandler PropertyChanged;的引用并进行管理。

else if (newO is INotifyPropertyChanged source15)PropertyChangedEventManager.AddHandler(source15, new EventHandler<PropertyChangedEventArgs>(this.OnPropertyChanged), this.SVI[k].propertyName);

ViewModel.PropertyChangedEventHandler的我们开发者定义好的通知事件,添加进入到PropertyChangedEventManager中进行管理,这个时候我们在给ViewModel里的变量Set值能通知界面更改就这么来的;下面为PropertyChangedEventManager.cs部分源码(这里的Manager类似于观察者模式)。

//将ViewModel里的PropertyChangedEventHandler PropertyChanged;添加监听
private void AddListener(INotifyPropertyChanged source,string propertyName,IWeakEventListener listener,EventHandler<PropertyChangedEventArgs> handler){using (this.WriteLock){HybridDictionary hybridDictionary = (HybridDictionary) this[(object) source];if (hybridDictionary == null){hybridDictionary = new HybridDictionary(true);this[(object) source] = (object) hybridDictionary;this.StartListening((object) source);}WeakEventManager.ListenerList list = (WeakEventManager.ListenerList) hybridDictionary[(object) propertyName];if (list == null){list = (WeakEventManager.ListenerList) new WeakEventManager.ListenerList<PropertyChangedEventArgs>();hybridDictionary[(object) propertyName] = (object) list;}if (WeakEventManager.ListenerList.PrepareForWriting(ref list))hybridDictionary[(object) propertyName] = (object) list;if (handler != null)list.AddHandler((Delegate) handler);elselist.Add(listener);hybridDictionary.Remove((object) PropertyChangedEventManager.AllListenersKey);this._proposedAllListenersList = (WeakEventManager.ListenerList) null;this.ScheduleCleanup();}}//这里每次在ViewModel里给变量Set值的之后就是通过OnPropertyChanged通知界面更改值,sender是ViewModel对象
private void OnPropertyChanged(object sender, PropertyChangedEventArgs args){string propertyName = args.PropertyName;WeakEventManager.ListenerList list;using (this.ReadLock){HybridDictionary hybridDictionary = (HybridDictionary) this[sender];if (hybridDictionary == null)list = WeakEventManager.ListenerList.Empty;else if (!string.IsNullOrEmpty(propertyName)){WeakEventManager.ListenerList<PropertyChangedEventArgs> listenerList1 = (WeakEventManager.ListenerList<PropertyChangedEventArgs>) hybridDictionary[(object) propertyName];WeakEventManager.ListenerList<PropertyChangedEventArgs> listenerList2 = (WeakEventManager.ListenerList<PropertyChangedEventArgs>) hybridDictionary[(object) string.Empty];if (listenerList2 == null)list = listenerList1 == null ? WeakEventManager.ListenerList.Empty : (WeakEventManager.ListenerList) listenerList1;else if (listenerList1 != null){list = (WeakEventManager.ListenerList) new WeakEventManager.ListenerList<PropertyChangedEventArgs>(listenerList1.Count + listenerList2.Count);int index1 = 0;for (int count = listenerList1.Count; index1 < count; ++index1)list.Add(listenerList1.GetListener(index1));int index2 = 0;for (int count = listenerList2.Count; index2 < count; ++index2)list.Add(listenerList2.GetListener(index2));}elselist = (WeakEventManager.ListenerList) listenerList2;}else{list = (WeakEventManager.ListenerList) hybridDictionary[(object) PropertyChangedEventManager.AllListenersKey];if (list == null){int capacity = 0;foreach (DictionaryEntry dictionaryEntry in hybridDictionary)capacity += ((WeakEventManager.ListenerList) dictionaryEntry.Value).Count;list = (WeakEventManager.ListenerList) new WeakEventManager.ListenerList<PropertyChangedEventArgs>(capacity);foreach (DictionaryEntry dictionaryEntry in hybridDictionary){WeakEventManager.ListenerList listenerList = (WeakEventManager.ListenerList) dictionaryEntry.Value;int index = 0;for (int count = listenerList.Count; index < count; ++index)list.Add(listenerList.GetListener(index));}this._proposedAllListenersList = list;}}list.BeginUse();}try{this.DeliverEventToList(sender, (EventArgs) args, list);}finally{list.EndUse();}if (this._proposedAllListenersList != list)return;using (this.WriteLock){if (this._proposedAllListenersList != list)return;HybridDictionary hybridDictionary = (HybridDictionary) this[sender];if (hybridDictionary != null)hybridDictionary[(object) PropertyChangedEventManager.AllListenersKey] = (object) list;this._proposedAllListenersList = (WeakEventManager.ListenerList) null;}}

[BindingExpression作用-3]

c30da698fb196d0108a3ea9723e497f2.png

这里主要讲述,如果直接在文本框内直接修改数据Binding是如何更新通知的(View->ViewModel)。

1.创建Binding对象,建立绑定表达式CreateBindingExpression将依赖属性和控件、绑定对象关联起来->BindingExpression该方法将Path传给 TraceData.Trace追踪Path。

2.手动在Textbox中输入内容则会被控件中的OnPreviewTextInput事件捕捉到,最后由BindingExpressionBase.OnPreviewTextInput触发Drity方法。

[特别分享:这里的Dirty命名我觉得很有造诣,这里分享一下我的理解Dirty直接翻译为‘脏’这个字如何去理解,举例:下雨天雨点落在了车窗玻璃上,这时候雨刷器把落在玻璃上的雨点视为‘脏’东西然后雨刷器刷一下把所有雨点清理干净了。借喻到代码中就是当有数据需要更新调用Dirty方法解决所有的更新需求。]

internal void Dirty()
{if (ShouldReactToDirty()){NeedsUpdate = true;if (!HasValue(Feature.Timer)){ProcessDirty();}else{// restart the timerDispatcherTimer timer = (DispatcherTimer)GetValue(Feature.Timer, null);timer.Stop();timer.Start();}NotifyCommitManager();}
}

Drity方法会检测是否有数据改动没有改动则退出更新机制。如果在绑定表达式中用了Delay属性,则会触发BindingExpressionBase中的DispatcherTimer来达到数据延迟更新的效果。可见每创建一个绑定表达式里都会包含一个定时器只是大部分时间不会启动而已。内部会有bool的标记来判断更新过程是否开始或结束。

private void DetermineEffectiveUpdateBehavior()
{if (!this.IsReflective)return;for (BindingExpressionBase bindingExpressionBase = this.ParentBindingExpressionBase; bindingExpressionBase != null; bindingExpressionBase = bindingExpressionBase.ParentBindingExpressionBase){if (bindingExpressionBase is MultiBindingExpression)return;}int delay = this.ParentBindingBase.Delay;if (delay <= 0 || !this.IsUpdateOnPropertyChanged)return;DispatcherTimer dispatcherTimer = new DispatcherTimer();this.SetValue(BindingExpressionBase.Feature.Timer, (object) dispatcherTimer);//这里的Interval就是根据我们在设置Binding对象Delay属性来设置的。如果写Delay=1000;那么就是1秒后触发更新dispatcherTimer.Interval = TimeSpan.FromMilliseconds((double) delay);dispatcherTimer.Tick += new EventHandler(this.OnTimerTick);
}

3.这时候访问依赖属性Text的内容去修改绑定在ViewModel的属性BindingExpression.UpdateSource(object value)。

4.BindingExpressionBase.UpdateValue()里的object rawProposedValue = this.GetRawProposedValue();会去拿到依赖属性的值这时候取到的内容是没有被验证是否合法的内容,然后会做两件事情

           (1)会判断值是否合法能否通过验证规则。

           (2)如果在绑定表达式里写了Convert转换器,则进行值转换。

完成以上两步的值将会object obj = this.UpdateSource(convertedValue)来触发更新;最终由依赖属性中PropertyMetadata注册的PropertyChangedCallback来落实值的修改。

internal bool UpdateValue(){ValidationError oldValidationError = BaseValidationError;if (StatusInternal == BindingStatusInternal.UpdateSourceError)SetStatus(BindingStatusInternal.Active);object value = GetRawProposedValue();if (!Validate(value, ValidationStep.RawProposedValue))return false;value = ConvertProposedValue(value);if (!Validate(value, ValidationStep.ConvertedProposedValue))return false;value = UpdateSource(value);if (!Validate(value, ValidationStep.UpdatedValue))return false;value = CommitSource(value);if (!Validate(value, ValidationStep.CommittedValue))return false;if (BaseValidationError == oldValidationError){// the binding is now valid - remove the old errorUpdateValidationError(null);}EndSourceUpdate();NotifyCommitManager();return !HasValue(Feature.ValidationError);}

看到这里大家应该会明白设计者为什么不把ViewModel的每个字段默认集数据通知机制,我个人的理解是数据通知会带来一定的性能损耗所以开放给开发者“按需”添加通知的成员。

3.Reference

dotnet/wpf: WPF is a .NET Core UI framework for building Windows desktop applications. (github.com)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/291661.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

maven 导入数据库

2019独角兽企业重金招聘Python工程师标准>>> 一.mysql 配置 基本代码 1. pom.xml 文件配置:jeesite.property jdbc.typemysql jdbc.drivercom.mysql.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/yaoshi?useUnicodetrue&characterEncodingutf-8 jdbc.user…

下载matlab安装包太慢_MATLAB 2020a商业数学中文版软件下载安装教程

【软件语言】&#xff1a;简体中文 【支持系统】&#xff1a;Win7/Win8/Win10【软件类别】&#xff1a;安装版【更新时间】&#xff1a;2020年5月15日【下载地址】&#xff1a;www.rjazbs.me/t-2945.html客服微信&#xff1a;rjazbsMATLAB是一款商业数学软件&#xff0c;用于算…

phpstorm+Xdebug断点调试PHP

运行环境&#xff1a; PHPSTORM版本 : 8.0.1 PHP版本 : 5.6.2 xdebug版本&#xff1a;php_xdebug-2.2.5-5.6-vc11-x86_64.dll ps : php版本和xdebug版本一定要相对应 1. PHP安装xdebug扩展 php.ini的配置&#xff0c;下面的配置仅供参考&#xff0c;路径要换成自己的&#xff0…

EF Core 6 新功能汇总(三)

在这篇文章中&#xff0c;我将重点介绍 EF Core 6 中 LINQ 查询功能的增强。这是 EF Core 6 新功能汇总的第三篇文章&#xff1a;EF Core 6 新功能汇总&#xff08;一&#xff09;EF Core 6 新功能汇总&#xff08;二&#xff09;EF Core 6 新功能汇总&#xff08;三&#xff0…

SpringMvc项目中使用GoogleKaptcha 生成验证码

前言&#xff1a;google captcha 是google生成验证码的一个工具类&#xff0c;其原理是将随机生成字符串保存到session中&#xff0c;同时以图片的形式返回给页面&#xff0c;之后前台页面提交到后台进行对比。 1、jar包准备 官方提供的pom应该是 <dependency> <grou…

wpsppt流程图联系效果_风险隐患排查的手段—HAZOP 与检查表的区别及应用效果

HAZOP 与检查表的区别HAZOP 分析可以在工厂运行周期内的任何时间段进行&#xff0c;既适用于设计阶段&#xff0c;也适用于在役的工艺装置。在化工项目的设计阶段采用HAZOP 方法进行分析&#xff0c;能识别设计、设备及操作程序中的潜在危险&#xff0c;比如装置设备是否装有安…

软件测试实验报告下载 实验一到实验五

实验一&#xff1a; 传送门在此&#xff1a;https://download.csdn.net/download/qq_44872173/15559951 目录如下&#xff1a;

linux网络编程之并发服务器的三种实现模型 (超级经典)

转载 &#xff1a; http://blog.csdn.net/tennysonsky/article/details/45671215 服务器设计技术有很多&#xff0c;按使用的协议来分有 TCP 服务器和 UDP 服务器&#xff0c;按处理方式来分有循环服务器和并发服务器。 循环服务器与并发服务器模型 在网络程序里面&#xff0c…

IT人的自我导向型学习:学习的4个层次

[原文链接] 谈起软件开发一定会想到用什么技术、采用什么框架&#xff0c;然而在盛行的敏捷之下&#xff0c;人的问题逐渐凸显出来。不少企业请人来培训敏捷开发技术&#xff0c;却发现并不能真正运用起来&#xff0c;其中一个主要原因就是大家还没有很好的学习能力。没有学习&…

动态ram依靠什么存储信息_ROM、RAM、DRAM、SRAM和FLASH傻傻分不清

ROM、RAM、DRAM、SRAM和FLASH各类储存器在电脑、手机、电子设备、嵌入式设备及相应的开发中普遍应用的&#xff0c;但是很多还是傻傻分不清楚。下面就简单介绍下这几个吧&#xff01;ROM和RAMROM&#xff1a;只读存储器或者固化存储器&#xff1b;RAM&#xff1a;随机存取存储器…

软件项目管理课后题下载【共5个章(1、3、4、5、6)】

都整理好了&#xff0c;链接在此&#xff1a;https://download.csdn.net/download/qq_44872173/15560093 目录如下&#xff1a;

linux c之snprintf()和sprintf()区别

1、snprintf函数 int snprintf(char *str, size_t size, const char *format, ...); 将可变个参数(...)按照format格式化成字符串,然后将其复制到str中 (1) 如果格式化后的字符串长度 < size,则将此字符串全部复制到str中,并给其后添加一个字符串结束符(/0); (2) 如果…

Chrome中输入框默认样式移除

Chrome中输入框默认样式移除 在chrome浏览器中会默认给页面上的输入框如input、textarea等渲染浏览器自带的边框效果 IE8中效果如下&#xff1a; Chrome中效果如下&#xff1a; 这在我们未给输入框设置获取焦点时改变边框颜色时&#xff0c;chrome浏览器解析页面中默认将输入框…

MBR解析

先推广一下QQ群&#xff1a;61618925。欢迎各位爱好编程的朋友加入。 一、程序界面 二、关键部分代码&#xff1a; 1.数据结构定义 #define MBRSIZE 512 #define BOOTRECORDSIZE 440 #define DISKSIGNEDSIZE 4 #define RESERVESIZE 2 #define DPTNUMBER 4 #define DPTSIZE 16 #…

css sprite讲解与使用实例

转自&#xff1a;http://www.manongjc.com/article/886.html 一、什么是css sprites css sprites直译过来就是CSS精灵。通常被解释为“CSS图像拼合”或“CSS贴图定位”。其实就是通过将多个图片融合到一张图里面&#xff0c;然后通过CSS background背景定位技术技巧布局网页背景…

实验 2 关键字驱动测试(2 学时)实验报告--软件功能测试与性能测试实验

以下是实验要求部分&#xff1a;&#xff08;小伙伴们根据自己需求决定是否下载哈&#xff09; 下载位置链接&#xff1a;https://download.csdn.net/download/qq_44872173/20031354

小手工纸盒机器人_亲子手工 | DIY弹珠迷宫小黑手自制玩具系列

上周用鞋盒做的迷你桌球受到很多男孩子的喜欢今天小黑手又做了一个瓦楞纸手工「弹珠迷宫」也是喜欢的爱不释手呢暑假小黑手系列&#xff0c;希望也能攒出个自己的小游乐场自己动手做自己的玩具&#xff0c;买玩具的钱可以省了快速手工教程Let’s do it我们一起做起来吧&#xf…

Linux screen 常用命令

想必&#xff0c;只要接触过Linux一段时间的人&#xff0c;一定知道screen这个神奇的工具了&#xff0c;它主要有如下些优势&#xff1a; 1. 后台运行&#xff1a;当你在ssh terminal执行shell时&#xff0c;如果网络这时断开&#xff0c;你的程序会怎样&#xff1f;TERMINATED…

实验 3 检查点和参数化实验报告--软件功能测试与性能测试实验

实验报告下载链接&#xff1a; https://download.csdn.net/download/qq_44872173/20031539 实验要求部分&#xff1a;