WPF图形解锁控件ScreenUnLock使用详解
程序员文章站
2022-03-27 08:34:10
screenunlock 与智能手机上的图案解锁功能一样。通过绘制图形达到解锁或记忆图形的目的。
本人突发奇想,把手机上的图形解锁功能移植到wpf中。也应用到了公司的项目...
screenunlock 与智能手机上的图案解锁功能一样。通过绘制图形达到解锁或记忆图形的目的。
本人突发奇想,把手机上的图形解锁功能移植到wpf中。也应用到了公司的项目中。
在创建screenunlock之前,先来分析一下图形解锁的实现思路。
1.创建九宫格原点(或更多格子),每个点定义一个坐标值
2.提供图形解锁相关扩展属性和事件,方便调用者定义。比如:点和线的颜色(color),操作模式(check|remember),验证正确的颜色(rightcolor), 验证失败的颜色(errorcolor), 解锁事件 oncheckedpoint,记忆事件 onrememberpoint 等;
3.定义mousemove事件监听画线行为。 画线部分也是本文的核心。在画线过程中。程序需判断,线条从哪个点开始绘制,经过了哪个点(排除已经记录的点)。是否完成了绘制等等。
4.画线完成,根据操作模式处理画线完成行为。并调用相关自定义事件
大致思路如上,下面开始一步一步编写screenunlock吧
创建screenunlock
public partial class screenunlock : usercontrol
定义相关属性
/// <summary> /// 验证正确的颜色 /// </summary> private solidcolorbrush rightcolor; /// <summary> /// 验证失败的颜色 /// </summary> private solidcolorbrush errorcolor; /// <summary> /// 图案是否在检查中 /// </summary> private bool ischecking; public static readonly dependencyproperty pointarrayproperty = dependencyproperty.register("pointarray", typeof(ilist<string>), typeof(screenunlock)); /// <summary> /// 记忆的坐标点 /// </summary> public ilist<string> pointarray { get { return getvalue(pointarrayproperty) as ilist<string>; } set { setvalue(pointarrayproperty, value); } } /// <summary> /// 当前坐标点集合 /// </summary> private ilist<string> currentpointarray; /// <summary> /// 当前线集合 /// </summary> private ilist<line> currentlinelist; /// <summary> /// 点集合 /// </summary> private ilist<ellipse> ellipselist; /// <summary> /// 当前正在绘制的线 /// </summary> private line currentline; public static readonly dependencyproperty operationporperty = dependencyproperty.register("operation", typeof(screenunlockoperationtype), typeof(screenunlock), new frameworkpropertymetadata(screenunlockoperationtype.remember)); /// <summary> /// 操作类型 /// </summary> public screenunlockoperationtype operation { get { return (screenunlockoperationtype)getvalue(operationporperty); } set { setvalue(operationporperty, value); } } public static readonly dependencyproperty pointsizeproperty = dependencyproperty.register("pointsize", typeof(double), typeof(screenunlock), new frameworkpropertymetadata(15.0)); /// <summary> /// 坐标点大小 /// </summary> public double pointsize { get { return convert.todouble(getvalue(pointsizeproperty)); } set { setvalue(pointsizeproperty, value); } } public static readonly dependencyproperty colorproperty = dependencyproperty.register("color", typeof(solidcolorbrush), typeof(screenunlock), new frameworkpropertymetadata(new solidcolorbrush(colors.white), new propertychangedcallback((s, e) => { (s as screenunlock).refresh(); }))); /// <summary> /// 坐标点及线条颜色 /// </summary> public solidcolorbrush color { get { return getvalue(colorproperty) as solidcolorbrush; } set { setvalue(colorproperty, value); } } /// <summary> /// 操作类型 /// </summary> public enum screenunlockoperationtype { remember = 0, check = 1 }
初始化screenunlock
public screenunlock() { initializecomponent(); this.loaded += screenunlock_loaded; this.unloaded += screenunlock_unloaded; this.mousemove += screenunlock_mousemove; //监听绘制事件 } private void screenunlock_loaded(object sender, routedeventargs e) { ischecking = false; rightcolor = new solidcolorbrush(colors.green); errorcolor = new solidcolorbrush(colors.red); currentpointarray = new list<string>(); currentlinelist = new list<line>(); ellipselist = new list<ellipse>(); createpoint(); } private void screenunlock_unloaded(object sender, routedeventargs e) { rightcolor = null; errorcolor = null; if (currentpointarray != null) this.currentpointarray.clear(); if (currentlinelist != null) this.currentlinelist.clear(); if (ellipselist != null) ellipselist.clear(); this.canvasroot.children.clear(); }
创建点
/// <summary> /// 创建点 /// </summary> private void createpoint() { canvasroot.children.clear(); int row = 3, column = 3; //三行三列,九宫格 double onecolumnwidth = (this.actualwidth == 0 ? this.width : this.actualwidth) / 3; //单列的宽度 double onerowheight = (this.actualheight == 0 ? this.height : this.actualheight) / 3; //单列的高度 double leftdistance = (onecolumnwidth - pointsize) / 2; //单列左边距 double topdistance = (onerowheight - pointsize) / 2; //单列上边距 for (var i = 0; i < row; i++) { for (var j = 0; j < column; j++) { ellipse ellipse = new ellipse() { width = pointsize, height = pointsize, fill = color, tag = string.format("{0}{1}", i, j) }; canvas.setleft(ellipse, j * onecolumnwidth + leftdistance); canvas.settop(ellipse, i * onerowheight + topdistance); canvasroot.children.add(ellipse); ellipselist.add(ellipse); } } }
创建线
private line createline() { line line = new line() { stroke = color, strokethickness = 2 }; return line; }
点和线都创建都定义好了,可以开始监听绘制事件了
private void screenunlock_mousemove(object sender, system.windows.input.mouseeventargs e) { if (ischecking) //如果图形正在检查中,不响应后续处理 return; if (e.leftbutton == system.windows.input.mousebuttonstate.pressed) { var point = e.getposition(this); hittestresult result = visualtreehelper.hittest(this, point); ellipse ellipse = result.visualhit as ellipse; if (ellipse != null) { if (currentline == null) { //从头开始绘制 currentline = createline(); var ellipsecenterpoint = getcenterpoint(ellipse); currentline.x1 = currentline.x2 = ellipsecenterpoint.x; currentline.y1 = currentline.y2 = ellipsecenterpoint.y; currentpointarray.add(ellipse.tag.tostring()); console.writeline(string.join(",", currentpointarray)); currentlinelist.add(currentline); canvasroot.children.add(currentline); } else { //遇到下一个点,排除已经经过的点 if (currentpointarray.contains(ellipse.tag.tostring())) return; onafterbypoint(ellipse); } } else if (currentline != null) { //绘制过程中 currentline.x2 = point.x; currentline.y2 = point.y; //判断当前line是否经过点 ellipse = isonline(); if (ellipse != null) onafterbypoint(ellipse); } } else { if (currentpointarray.count == 0) return; ischecking = true; if (currentlinelist.count + 1 != currentpointarray.count) { //最后一条线的终点不在点上 //两点一线,点的个数-1等于线的条数 currentlinelist.remove(currentline); //从已记录的线集合中删除最后一条多余的线 canvasroot.children.remove(currentline); //从界面上删除最后一条多余的线 currentline = null; } if (operation == screenunlockoperationtype.check) { console.writeline("playanimation check"); var result = checkpoint(); //执行图形检查 //执行完成动画并触发检查事件 playanimation(result, () => { if (oncheckedpoint != null) { this.dispatcher.begininvoke(oncheckedpoint, this, new checkpointargs() { result = result }); //触发检查完成事件 } }); } else if (operation == screenunlockoperationtype.remember) { console.writeline("playanimation remember"); rememberpoint(); //记忆绘制的坐标 var args = new rememberpointargs() { pointarray = this.pointarray }; //执行完成动画并触发记忆事件 playanimation(true, () => { if (onrememberpoint != null) { this.dispatcher.begininvoke(onrememberpoint, this, args); //触发图形记忆事件 } }); } } }
判断线是否经过了附近的某个点
/// <summary> /// 两点计算一线的长度 /// </summary> /// <param name="pt1"></param> /// <param name="pt2"></param> /// <returns></returns> private double getlinelength(double x1, double y1, double x2, double y2) { return math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2)); //根据两点计算线段长度公式 √((x1-x2)²x(y1-y2)²) } /// <summary> /// 判断线是否经过了某个点 /// </summary> /// <param name="ellipse"></param> /// <returns></returns> private ellipse isonline() { double lineab = 0; //当前画线的长度 double lineca = 0; //当前点和a点的距离 double linecb = 0; //当前点和b点的距离 double dis = 0; double deciation = 1; //允许的偏差距离 lineab = getlinelength(currentline.x1, currentline.y1, currentline.x2, currentline.y2); //计算当前画线的长度 foreach (ellipse ellipse in ellipselist) { if (currentpointarray.contains(ellipse.tag.tostring())) //排除已经经过的点 continue; var ellipsecenterpoint = getcenterpoint(ellipse); //取当前点的中心点 lineca = getlinelength(currentline.x1, currentline.y1, ellipsecenterpoint.x, ellipsecenterpoint.y); //计算当前点到线a端的长度 linecb = getlinelength(currentline.x2, currentline.y2, ellipsecenterpoint.x, ellipsecenterpoint.y); //计算当前点到线b端的长度 dis = math.abs(lineab - (lineca + linecb)); //线ca的长度+线cb的长度>当前线ab的长度 说明点不在线上 if (dis <= deciation) //因为绘制的点具有一个宽度和高度,所以需设定一个允许的偏差范围,让线靠近点就命中之(吸附效果) { return ellipse; } } return null; }
检查点是否正确,按数组顺序逐个匹配之
/// <summary> /// 检查坐标点是否正确 /// </summary> /// <returns></returns> private bool checkpoint() { //pointarray:正确的坐标值数组 //currentpointarray:当前绘制的坐标值数组 if (currentpointarray.count != pointarray.count) return false; for (var i = 0; i < currentpointarray.count; i++) { if (currentpointarray[i] != pointarray[i]) return false; } return true; }
记录经过点,并创建一条新的线
/// <summary> /// 记录经过的点 /// </summary> /// <param name="ellipse"></param> private void onafterbypoint(ellipse ellipse) { var ellipsecenterpoint = getcenterpoint(ellipse); currentline.x2 = ellipsecenterpoint.x; currentline.y2 = ellipsecenterpoint.y; currentline = createline(); currentline.x1 = currentline.x2 = ellipsecenterpoint.x; currentline.y1 = currentline.y2 = ellipsecenterpoint.y; currentpointarray.add(ellipse.tag.tostring()); console.writeline(string.join(",", currentpointarray)); currentlinelist.add(currentline); canvasroot.children.add(currentline); }
/// <summary> /// 获取原点的中心点坐标 /// </summary> /// <param name="ellipse"></param> /// <returns></returns> private point getcenterpoint(ellipse ellipse) { point p = new point(canvas.getleft(ellipse) + ellipse.width / 2, canvas.gettop(ellipse) + ellipse.height / 2); return p; }
当绘制完成时,执行完成动画并触发响应模式的事件
/// <summary> /// 执行动画 /// </summary> /// <param name="result"></param> private void playanimation(bool result, action callback = null) { task.factory.startnew(() => { this.dispatcher.invoke((action)delegate { foreach (line l in currentlinelist) l.stroke = result ? rightcolor : errorcolor; foreach (ellipse e in ellipselist) if (currentpointarray.contains(e.tag.tostring())) e.fill = result ? rightcolor : errorcolor; }); thread.sleep(1500); this.dispatcher.invoke((action)delegate { foreach (line l in currentlinelist) this.canvasroot.children.remove(l); foreach (ellipse e in ellipselist) e.fill = color; }); currentline = null; this.currentpointarray.clear(); this.currentlinelist.clear(); ischecking = false; }).continuewith(t => { try { if (callback != null) callback(); } catch (exception ex) { console.writeline(ex.message); } finally { t.dispose(); } }); }
图形解锁的调用
<local:screenunlock width="500" height="500" pointarray="{binding pointarray, mode=twoway, updatesourcetrigger=propertychanged}" operation="check"> <!--或remember--> <i:interaction.triggers> <i:eventtrigger eventname="oncheckedpoint"> <custom:eventtocommand command="{binding oncheckedpoint}" passeventargstocommand="true"/> </i:eventtrigger> <i:eventtrigger eventname="onrememberpoint"> <custom:eventtocommand command="{binding onrememberpoint}" passeventargstocommand="true"/> </i:eventtrigger> </i:interaction.triggers> </local:screenunlock>
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
上一篇: .NET中接口与类的区别浅析
下一篇: 你想过我的感受吗