参加实习(WPF)已经有两个多周的时间了,踩了一些坑,也算积累了一些小东西,准备慢慢拿出来分享一下。(●'◡'●)

这次呢就讲讲一个简单的电子签名板的实现。

先上张图(PS:字写得比较丑,不要太在意哈):

1.任务目标

最基本的需求:1.签名功能 2.清除签名 3.保存签名(让用户选择文件夹、签名保存为PNG格式的图片)

尝试额外功能:1.Ctrl + Z实现撤销功能 2.Ctrl + Y实现重做功能 3.保存签名后打开文件位置并选中文件

2.搞事情

1)UI方面

如图,总体来说,一个InkCanvas加上两个Button就解决问题了。

A. InkCanvas

<InkCanvas Grid.Column="1" Grid.Row="1" Background="White" Height="240" Name="ink">
<InkCanvas.DefaultDrawingAttributes>
<DrawingAttributes Color="#FF000000" StylusTip="Ellipse" Height="6" Width="6" IgnorePressure="False" FitToCurve="False">
<!--调整画笔形状-->
<DrawingAttributes.StylusTipTransform>
<!--https://msdn.microsoft.com/library/system.windows.media.matrix(v=vs.110).aspx-->
<Matrix M11="1" M12="0" M21="0" M22="1" OffsetX="0" OffsetY="0"/>
</DrawingAttributes.StylusTipTransform>
</DrawingAttributes>
</InkCanvas.DefaultDrawingAttributes>
</InkCanvas>

关于调整画笔形状的部分(对,就是那个矩阵),就我个人来说并不是很了解,所以就不作什么解释了,感兴趣的童鞋可以访问对应的微软官方文档查看相关资料。

B. Button

<Button x:Name="btnClearSign" HorizontalAlignment="Left" VerticalAlignment="Top" Width="100" Padding="0" Margin="12,6,0,0" Click="btnClearSign_Click">
<Button.Template>
<ControlTemplate>
<Grid>
<Label Cursor="Hand" Foreground="Red" FontFamily="Microsoft YaHei UI" FontSize="20">
<Underline>
<Run Text="清除签名"></Run>
</Underline>
</Label>
</Grid>
</ControlTemplate>
</Button.Template>
</Button>

图中的两个按钮都是同一个套路,所以就只展示一个按钮的代码。(PS:为了让按钮显得不要太俗,我们为按钮弄一个类似于超链接的样式)

2)逻辑代码

签名功能我们就不用操心了,InkCanvas会处理好的。

A. 清除签名

ink.Strokes.Clear();

这么一行代码就足够了。说明一下,这里的ink就是我们在UI部分写的那个InkCanvas。

B.将签名保存为PNG图片

// 判断签名板内是否有内容
if (ink.Strokes.Any())
{
// 让用户自己选择文件夹保存
// 需要在工程中添加对System.Windows.Forms的引用
// References => Add Reference => 勾选 System.Windows.Forms 项 => OK
var folderPicker = new FolderBrowserDialog();
var res = folderPicker.ShowDialog(); // 判断用户有没有选中文件夹
if (res == System.Windows.Forms.DialogResult.Cancel) return; // 文件保存路径
var folderPath = folderPicker.SelectedPath;
var fileName = DateTime.Now.ToString("yyyyMMddHHmmss");
var fileUri = folderPath + "\\" + fileName + ".png"; // windows系统下默认dpi貌似为96,但目前本机测试认为dpi设置为72较为合适
// dpi的大小会直接影响签名保存结果是否完整,关于dpi的知识网上还是比较多的,请各位自行了解
// 下一行代码的第三个参数用于确定位图的横向dpi,第四个参数为纵向dpi
var renderBitmap = new RenderTargetBitmap((int)ink.ActualWidth, (int)ink.ActualHeight, 72d, 72d, PixelFormats.Pbgra32);
renderBitmap.Render(ink); using (var stream = new FileStream(fileUri, FileMode.Create))
{
var encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(renderBitmap));
encoder.Save(stream);
} undoList.Clear(); // 打开签名文件所在位置
FileUtil.LocateFile(fileUri);
}
else
{
System.Windows.MessageBox.Show("尚未进行签名,不能执行保存操作!");
}

注:A.这个部分存在一定的问题,请容许我在另一篇的博客中进行相关解释。

B.代码中的undoList.Clear() 以及FileUtil.LocateFile(fileUri) 各位暂时不用理睬,稍后我会进行相关解释。

C.下方图片讲解的是如何添加对System.Windows.Forms的引用。

C.实现撤销和重做功能

由于InkCanvas自身实现貌似并没这样的方法,所以,我们就自己动动手吧。方法其实还是比较简单的:首先我们需要明白的是,InkCanvas将每一个笔划都以一个Stroke类的对象保存在一个集合里边(InkCanvas的Strokes属性,StrokeCollection类型)。所以,实现撤销/重做功能就变成了对一个Collection的操作,撤销即移除顶部的元素(当然我们需要将移除的元素暂存一下,以便后续的重做操作),重做即向Collection顶部增添一项。下面来看看代码:

Stack<Stroke> undoList = new Stack<Stroke>();

声明一个全局变量(Stroke的一个栈),用于存储进行撤销操作时移除的Stroke,也用于在进行重做功能时提供资源。

private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
this.KeyDown += (s, args) =>
{
// Undo => 检测 Ctrl + Z
if((Keyboard.Modifiers & ModifierKeys.Control) == ModifierKeys.Control && args.Key == Key.Z)
{
if (ink.Strokes.Any())
{
undoList.Push(ink.Strokes[ink.Strokes.Count - ]);
ink.Strokes.RemoveAt(ink.Strokes.Count - );
}
} // Redo => 检测 Ctrl + Y
if ((Keyboard.Modifiers & ModifierKeys.Control) == ModifierKeys.Control && args.Key == Key.Y)
{
if (undoList.Any())
{
ink.Strokes.Add(undoList.Pop());
}
}
};
}

在Window的Loaded事件里加上对Ctrl + Z以及Ctrl + Y的检测,具体套路就如上方代码中显示的那样。

D.打开签名所在位置

先扯点题外话,这个地方我使用的时P/Invoke的方式,调用C++的方法进行实现的。由于我自己对跨语言调用这一块知之甚少,所以无法做出多少解释,只是在运气作用下一番摸索后达到了目的而已。如果以后感觉对这一块了解更多一些东西后,再单独写一篇博客进行相关解释。

回到正题,先上代码:

public static class FileUtil
{
/// <summary>
/// 依据给定文件路径,打开文件位置并选中
/// </summary>
/// <param name="path">文件完全路径</param>
public static void LocateFile(string path)
{
/* // 此方法会导致每次新开一个文件资源管理器窗口,不喜欢
* string domain = "";
* var psi = new ProcessStartInfo("Explorer.exe");
* psi.Arguments = "/c,/select," + path;
* domain = psi.Domain;
* var p = Process.Start(psi);
*/ IntPtr ppidl = IntPtr.Zero;
uint psfgaoOut;
FileManager.SHParseDisplayName(path, IntPtr.Zero, out ppidl, , out psfgaoOut); var res = FileManager.OpenFolderAndSelectItems(ppidl, , IntPtr.Zero, ); } class FileManager
{
[DllImport("shell32.dll", EntryPoint = "SHOpenFolderAndSelectItems")]
public static extern long OpenFolderAndSelectItems(IntPtr pidlFolder, UInt32 cidl, IntPtr apidl, UInt32 dwFlags); [DllImport("shell32.dll", EntryPoint = "SHParseDisplayName")]
public static extern void SHParseDisplayName([MarshalAs(UnmanagedType.LPWStr)] string name, IntPtr bindingContext, [Out()] out IntPtr pidl, uint sfgaoIn, [Out()] out uint psfgaoOut);
}
}

这个家伙又要开始偏(哔)题(哔)了,请不用理睬:

正如代码中所说的,注释的部分也可以在一定程度上实现我们的需求,但它存在一定的问题。所以我就果断寻求另一个解决方案,终于打探到shell32.dll(位于Windows\System32目录下)里的SHOpenFolderAndSelectItems方法可以满足我的需求。在经历了一段时间的搜索相关资料,又看了看这位哥的经验分享后,我终于用C#的方式把SHOpenFolderAndSelectItems方法怼成了上方代码中的模样。但是我悲催的发现,只有OpenFolderAndSelectItems方法貌似依旧不行(根本没有正确的定位到对应的文件/文件夹),在经过一番资料查阅[msdn, pinvoke.net]后,总算是搞出了个可用的版本。

3.Demo

http://files.cnblogs.com/files/lary/UserSignatureDemo.rar

【WPF】学习笔记(一)——做一个简单的电子签名板的更多相关文章

  1. Linux系统学习笔记之 1 一个简单的shell程序

    不看笔记,长时间不用自己都忘了,还是得经常看看笔记啊. 一个简单的shell程序 shell结构 1.#!指定执行脚本的shell 2.#注释行 3.命令和控制结构 创建shell程序的步骤 第一步: ...

  2. 【opencv学习笔记五】一个简单程序:图像读取与显示

    今天我们来学习一个最简单的程序,即从文件读取图像并且创建窗口显示该图像. 目录 [imread]图像读取 [namedWindow]创建window窗口 [imshow]图像显示 [imwrite]图 ...

  3. UNP学习笔记2——从一个简单的ECHO程序分析TCP客户/服务器之间的通信

    1 概述 编写一个简单的ECHO(回复)程序来分析TCP客户和服务器之间的通信流程,要求如下: 客户从标准输入读入一行文本,并发送给服务器 服务器从网络输入读取这个文本,并回复给客户 客户从网络输入读 ...

  4. Django 学习笔记之六 建立一个简单的博客应用程序

    最近在学习django时建立了一个简单的博客应用程序,现在把简单的步骤说一下.本人的用的版本是python 2.7.3和django 1.10.3,Windows10系统 1.首先通过命令建立项目和a ...

  5. [原创]java WEB学习笔记12:一个简单的serlet连接数据库实验

    本博客为原创:综合 尚硅谷(http://www.atguigu.com)的系统教程(深表感谢)和 网络上的现有资源(博客,文档,图书等),资源的出处我会标明 本博客的目的:①总结自己的学习过程,相当 ...

  6. Ruby学习笔记2 : 一个简单的Ruby网站,搭建ruby环境

    Ruby on Rails website 的基础是 请求-返回 循环. 首先是浏览器请求服务器, 第二步,Second, in our Rails application, the route ta ...

  7. 【Python学习笔记三】一个简单的python爬虫

    这里写爬虫用的requests插件 1.一般那3.x版本的python安装后都带有相应的安装文件,目录在python安装目录的Scripts中,如下:   2.将scripts的目录配置到环境变量pa ...

  8. DuiLib学习笔记2——写一个简单的程序

    我们要独立出来自己创建一个项目,在我们自己的项目上加皮肤这才是初衷.我的新建项目名为:duilibTest 在duilib根目录下面有个 Duilib入门文档.doc 我们就按这个教程开始入门 首先新 ...

  9. avalonjs学习笔记之实现一个简单的查询页

    官网地址:http://avalonjs.coding.me/ 因为是为了学习js,所以对样式没什么要求,先放效果图: 步骤为:初始页面-------条件查询-------编辑员工1-------保存 ...

随机推荐

  1. Mysql主从配置讲解

    一个主机上管理多个mysql实例 资源有限,只能用一台主机备份多个机器上的mysql,所以怎么才能在一台机器上运行多个mysql呢,肯定是要包括不同的端口,搜索一下mysql multi 可以配置管理 ...

  2. CoreAnimation 视觉效果

    CoreAnimation 视觉效果 CoreAnimation 目录 博客园MakeDown支持不佳,如有需要请进GitHub iPhone手机的视觉效果是十分优秀的,因此作为iOS工程师一定要对其 ...

  3. Java完成简单猜数字游戏v2.0

    猜数字游戏v2.0 优化了获取随机数.输入数据超出边界值的代码,并增加了异常处理,能够在玩家输入错误数据错误时给出可靠指引,希望对和我一样的新人有帮助, 最后希望有大神愿意帮我解决代码优化的问题,谢谢 ...

  4. javascript中常用的

    1.javascript中构造equals().trim()方法并应用 String.prototype.Trim = function() { return this.replace(/(^\s*) ...

  5. (转)关闭iptables和SELinux

    1. 关闭SELinux setenforce 0   #临时关闭 编辑/etc/selinux/config,找到SELINUX 行修改成为:SELINUX=disabled:     #永久关闭, ...

  6. CSS.04 -- 浮动float、overflow、定位position、CSS初始化

    标准流:行内/行内块元素横向有序排列 : 块元素纵向有序排列. 浮动:Float 语法:float:left/right :  设置浮动的元素,脱离标准流 浮动的框可以向左或向右移动,直到它的外边缘碰 ...

  7. ASP.NET Core:使用Dapper和SwaggerUI来丰富你的系统框架

    一.概述 1.用VS2017创建如下图的几个.NET Standard类库,默认版本为1.4,你可以通过项目属性进行修改,最高支持到1.6,大概五月份左右会更新至2.0,API会翻倍,很期待! 排名分 ...

  8. 从输入url到页面加载完成发生了什么?——前端角度

    这是一道经典的面试题,这道面试题不光前端面试会问到,后端面试也会被问到.这道题没有一个标准的答案,它涉及很多的知识点,面试官会通过这道题了解你对哪一方面的知识比较擅长,然后继续追问看看你的掌握程度.当 ...

  9. unity插件开发——AssetDatabase

    AssetDatebase也是一个静态类,他的作用是管理整个工程的所有文件(一般成为“资产”).直观地说就是管理整个project窗口中的所有内容,比如,你可以增加.删除.修改文件等等. 这里有几个常 ...

  10. 学习smart gwt 的一些好的网站

    最近在学smart gwt,这个框架和我们比较熟悉的SSH实现思路上有点不一样,因为技术是外国的,所以好多东西都是英文的,正因为是英文的,我们学到的东西才是最多最好的,好了,网站如下: gwt api ...