当前位置: 移动技术网 > IT编程>开发语言>.net > WPF图形解锁控件ScreenUnLock使用详解

WPF图形解锁控件ScreenUnLock使用详解

2017年08月16日  | 移动技术网IT编程  | 我要评论

升技主板,根据以下图标猜成语,战槌仪祭血刃

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>

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持移动技术网。

如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复

相关文章:

验证码:
移动技术网