首页 > 代码库 > 第一个 WP 程序 : 手机条码扫描枪

第一个 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 }
Bootstrapper.cs
 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>
App.xaml

 

界面我弄的也很简单,因为到目前为止,我还没来得及搞懂“中心应用程序”和“透视应用程序”有什么区别,不过,他们有一个共性:挺丑的。我的应用就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     }
View Code

这个方法还有待优化,应该是先截取,再旋转的,我把这个过程弄翻了。

 

另外,在扫描的时候,采用了“循环” 的 对焦/拍照/扫描,而不是手动的去点一下拍照并扫描一下,因为识别率并不高。

 

扫描成功后,本来是想通过 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