第一个 WP 程序 : 手机条码扫描枪
前言碎语:
前段时间,我第一时间尝试了 Windows 8.1 update1 , 结果把我硬盘搞挂了!
升级之后,硬盘一直是100%,平均响应时间能高达400多毫秒。
我自认为我的配置还不错,AMD的4核推土机,8G金仕顿骇客神条, 1T的希捷单碟,两年多点,以前跑 WIN7 / WIN8 / WIN 8.1 都不带眨眼的,怎么遇到TMD Win 8.1 update 1 就变成渣了呢?基本每次启动都要自动修复一下,开机后在磁盘管理里还提示有危险。用检查工具检查了一下,有二十多个坏道,但是没办法修复!弄的头大!耽误了4天时间,窝了一肚子火!最终还是买了块SSD,完事!
之前写了一个用在 Android 上的小应用:
今天在来一个:手机条码扫描枪 Windows phone 8.1 版,不过还没有研究怎么发布到应用商店中。
直到现在,我还没有弄明白 Windows Phone项目 和 Windows Phone Silverlight 项目到底有什么区别。
一开始,我新建了一个 Windows Phone 项目,但是死活不能用 VideoBrush 这个东西,折腾了两天,才在 SakeOverflow 上找找到答案:VideoBrush 在 Windows Phone Silverlight 项目中才有。
另外,我安装了 VS 2013 UPDATE 2 RC, 可以新建 Windows Phone 8.1 的应用,在 Windows Phone 项目下可以使用那些 Flip 之类的新控件,但是在 Silverlight 里仍然没有搞懂如何才能使用这些新的东西。
和我的第一个WPF桌面程序一样,Windows Phone 程序依然是用 Caliburn.Micro (CM) 框架,不过有些问题, MVVM的MODEL层不过问VIEW上有什么东西,但是我这里需要获取VIEW上的某一块的位置和大小,用数据绑定的方式处理的这一块,非常不理想。
用CM框架,必须要按照约定来命名 VIEW 和 MODEL,当然可以更改默认的规则。如下图:
XXXPageViewModel.cs 对应 XXXPage.xaml 。
一开始我以为MODEL的命名中不需要 Page ,结果无论如何都不能定位到 MODEL上(CM在WINDOWS Phone 上是VIEW优先)
搞成这个样子(Views / ViewModels),还要改两个地方:Package.appxmanifest (入口点) 和 Properties 下面的 WMAppManifest.xaml (导航页),我这里指向 Views/HomePage.xaml
要用 CM,当然还需要用 CM的 Bootstrapper
1 using BarCodeScanner.ViewModels; 2 using Caliburn.Micro; 3 using System; 4 using System.Collections.Generic; 5 using System.Diagnostics; 6 using System.Linq; 7 using System.Text; 8 using System.Threading.Tasks; 9 using System.Windows; 10 11 namespace BarCodeScanner { 12 public class Bootstrapper : PhoneBootstrapper { 13 14 private PhoneContainer Container; 15 protected override void StartRuntime() { 16 base.StartRuntime(); 17 } 18 protected override void Configure() { 19 base.Configure(); 20 21 this.Container = new PhoneContainer(); 22 this.Container.RegisterPhoneServices(this.RootFrame); 23 24 this.Container.PerRequest<HomePageViewModel>(); 25 this.Container.PerRequest<ConnectPageViewModel>(); 26 this.Container.PerRequest<ScannerPageViewModel>(); 27 this.Container.PerRequest<HelpPageViewModel>(); 28 } 29 30 protected override object GetInstance(Type service, string key) { 31 return this.Container.GetInstance(service, key); 32 } 33 34 protected override IEnumerable<object> GetAllInstances(Type service) { 35 return this.Container.GetAllInstances(service); 36 } 37 38 protected override void BuildUp(object instance) { 39 this.Container.BuildUp(instance); 40 } 41 42 protected override void OnUnhandledException(object sender, System.Windows.ApplicationUnhandledExceptionEventArgs e) { 43 base.OnUnhandledException(sender, e); 44 45 if (!Debugger.IsAttached) 46 Debugger.Launch(); 47 } 48 } 49 }
1 <Application 2 x:Class="BarCodeScanner.App" 3 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 4 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 5 xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone" 6 xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone" 7 xmlns:local="clr-namespace:BarCodeScanner" 8 > 9 10 <!--应用程序资源--> 11 <Application.Resources> 12 <!--<local:LocalizedStrings x:Key="LocalizedStrings"/>--> 13 <local:Bootstrapper x:Key="Bootstrapper" /> 14 </Application.Resources> 15 </Application>
界面我弄的也很简单,因为到目前为止,我还没来得及搞懂“中心应用程序”和“透视应用程序”有什么区别,不过,他们有一个共性:挺丑的。我的应用就4个页面,神马中心透视都没有必要。
定义这个六边形按钮的时候,发现 ControlTemplate 没有 Triggers 这个东西,也不知道如何才能给按钮加上响应触摸的效果,和桌面应用还是有很大差别的。
前面说了在 VideoBrush 上耗了两天,是因为我要用它把镜头输出到屏幕上,除了用 VideoBrush 我还不知道有什么其它的办法。
这是不用MVVM的写法:
1 <Rectangle.Fill> 2 <VideoBrush x:Name="vb"> 3 <VideoBrush.RelativeTransform> 4 <CompositeTransform CenterX=".5" CenterY=".5" Rotation="90" /> 5 </VideoBrush.RelativeTransform> 6 </VideoBrush> 7 </Rectangle.Fill>
一目发然, CompositeTransform 那句是说,按照屏幕的中心点旋转90度,因为我的手机 (撸妹925)输出的镜头是90度的,不知道其它手机是不是也是这样。
但是我用了 CM,必须用MVVM,这些东西都要放到 MODEL 里去设置,
1 this.Camera = new PhotoCamera(CameraType.Primary); this.VBrush = new VideoBrush();
2 this.VBrush.RelativeTransform = new CompositeTransform() { 3 CenterX = 0.5, 4 CenterY = 0.5, 5 Rotation = 90 6 }; 7 this.VBrush.SetSource(this.Camera);
然后将这个 VBrush 绑定到 Rectangle的Fill 上。
前面说了要在MODEL里取VIEW上的某块区域的起点和大小,是因为镜头下可能有N个条形码,如果不指定区域的话,就不知道目的条码是哪一个。
ZXing (我用的ZXing) 不像 ZBar, ZBar 有个 scanCrop ,通过它可以限定识别区域(参见:http://www.cnblogs.com/xling/archive/2013/03/21/2972640.html)
ZXing 要限定识图区域,只能自行截取了。
PhotoCaramer的 GetPreviewBufferY 、 GetPreviewBufferArgb32 和 GetPreviewBufferYCbCr 获取到的数据都是一个像素一个像素按行罗列的的一维数组(具体里面存的是什么,我也不懂)比如:
1,2,3,4
5,6,7,8
9,10,11,12
一个高3宽4像素的图片,用上面的方法都是返回诸如:1,2,3,4,5,6,7,8,9,10,11,12 这样一个一维数组。
结合到镜头返回的图像是顺时针旋转90度的,我写了这么个方法:
1 public void Cutout(int x, int y, int w, int h) { 2 3 var routedRaw = new byte[this.luminances.Length]; 4 //1, 2, 3, 4 5 //5, 6, 7, 8 6 //9, 10, 11, 12 7 //13, 14, 15, 16 8 //17 18 19 20 9 //21 22 23 24 10 11 //rh = 6 , rw = 4 12 //21,17,13,9,5,1 22,18,14,10,6,2 23,19,15,11,7,3 24,20,16,12,8,4 13 14 //RW*(RH-0) - RW + 1 4*(6-0)-4+1 = 21 15 //RW*(RH-1) - RW + 1 4*(6-1)-4+1=17 16 //RW*(RH-2) - RW + 1 4*(6-2)-4+1=13 17 //RW*(RH-3) - RW + 1 4*(6-3)-4+1=9 18 //RW*(RH-4) - RW + 1 4*(6-4)-4+1=5 19 //RW*(RH-5) - RW + 1 4*(6-5)-4+1=1 20 21 for (var i = 0; i < routedRaw.Length; i++) { 22 //var add = i / rh + 1; 23 //var idx = rw * (rh - i % rh) - rw + add;//下标从1开始 24 routedRaw[i] = this.luminances[this.Width * (this.Height - i % this.Height) - this.Width + i / this.Height]; 25 } 26 27 var t = routedRaw.Skip(y * this.Width).Take(h * this.Width).ToArray(); 28 29 var datas = routedRaw 30 .Skip(y * this.Height) //y是目标所在的起始列 31 .Take(h * this.Height) 32 .Where((r, i) => { 33 var row = i % this.Height; 34 return x <= row && (x + w) > row; 35 }).ToArray(); 36 37 this.luminances = datas; 38 this.Height = h; 39 this.Width = w; 40 } 41 }
这个方法还有待优化,应该是先截取,再旋转的,我把这个过程弄翻了。
另外,在扫描的时候,采用了“循环” 的 对焦/拍照/扫描,而不是手动的去点一下拍照并扫描一下,因为识别率并不高。
扫描成功后,本来是想通过 TcpClient 传到电脑端的,但是 Windows Phone SDK里并没有这个东西,不过可以直接用 Socket,用了MSDN上的示例代码,做了一点点简单的修改:
http://msdn.microsoft.com/en-us/library/windowsphone/develop/hh202858(v=vs.105).aspx#BKMK_UsingtheSocketClientClassintheApplication
附上源码(没有开发者账户,而且还不想出钱购买,还没有达到那个高度):
http://files.cnblogs.com/xling/BarCodeScanner.7z
电脑端:
http://files.cnblogs.com/xling/TNS2.7z