65.9K
CodeProject 正在变化。 阅读更多。
Home

Twain for WPF Applications - Look Ma, No Handles

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.94/5 (30投票s)

2011年3月22日

CPOL

5分钟阅读

viewsIcon

151188

downloadIcon

9184

托管的接口允许在 WPF 应用程序中简单使用 Twain。演示项目提供了从相机和扫描仪扫描、显示多张图像以及通过 Web 服务上传到服务器的功能。

引言

该项目提供了一个干净的接口,可以在 WPF 项目中使用 Twain。

代码包含两部分

  1. 一个 Twain 抽象层 
  2. 一个演示应用程序

抽象层公开了一个干净的 C# 接口(没有句柄,IntPtrs 等)。它包含一些 CS 文件,用于处理必要的接口和 Twain 的激活。

该应用程序演示了在典型的 WPF 应用程序中使用 Twain 扫描

  • 带扫描仪 UI 和不带扫描仪 UI 的扫描
  • 在 WPF 中显示多个扫描的图像
  • 使用 Web 服务将图像上传到服务器

背景

Twain 是一个广泛使用的软件标准,常见于从扫描仪和相机获取图像。

Twain 接口使用低级窗口定义,大量使用非托管指针、窗口句柄和消息。

2001 年,一篇 CodeProject 文章介绍了 TwainLib - Twain 接口的 C# 包装器。这段代码一直是许多已发布作品的参考。不幸的是,TwainLib 接口仍然依赖于句柄、显式消息以及广泛使用 GDI。

希望隐藏这些实现细节,使其不被 WPF 应用程序所感知。抽象出低级窗口内容可以使生成的代码更干净,并简化 WPF 功能的使用。

当前的接口类 WpfTwain 构建在经典的 TwainLib 接口之上。它处理 Twain 消息循环与 WPF 系统的集成,并使用托管的 BitmapSource 而不是 TwainLib 中使用的 GDI+。

Using the Code

WFP 要使用的接口类是 WpfTwain

获取图像的步骤如下

  1. 创建接口对象,通常在 MainWindow Load 事件中
  2. 选择 Twain 源(如果系统中只有一个 Twain 源,则不是必需的)
  3. 启动采集
  4. 处理采集结果事件

所有这些步骤都直接且直观。请参阅演示应用程序中的 MainWindow.cs 代码以获取完整示例。

1. 创建接口对象

private void Window_Loaded(object sender, RoutedEventArgs e)
{
    TwainInterface = new WpfTwain();
    TwainInterface.TwainTransferReady += new TwainTransferReadyHandler
					(TwainWin_TwainTransferReady);

    ...
}		

创建后,Twain 接口将内部挂钩消息并负责实现细节。

请注意,我们注册了一个事件 - TwainTransferReady。这将使我们能够处理获取的图像。

也可以挂钩到其他事件,但对于简单的采集来说不是必需的。如果您需要对过程进行更精细、更低级别的控制,请参阅 WpfTwain 类的实现。在大多数情况下可能不需要。

2. 选择源

private void SelecctButton_Click(object sender, RoutedEventArgs e)
{
    TwainInterface.Select();
}	

这不言自明,不是吗?调用 Twain 接口的 Select 方法将弹出 Twain 源选择对话框。这是一个 Windows 对话框,由 Twain 系统控制。

3. 开始扫描

private void ScanButton_Click(object sender, RoutedEventArgs e)
{
    TwainInterface.Acquire(false /*show UI*/);
}		

唯一需要提及的是 showUI 参数。将其设置为 true 将导致采集窗口显示。此窗口特定于设备(由设备驱动程序安装),因此其外观和行为因设备而异。

在某些情况下(例如,需要自动化时),最好隐藏此选项,然后直接运行扫描。演示应用程序提供了这两种选项。

4. 处理扫描结果

private void TwainWin_TwainTransferReady(WpfTwain sender, List<ImageSource> imageSources)
{
    foreach (ImageSource ims in imageSources)
        AddImageThumbnail(ims); // process the image by the application

    // alternatively if the program should only support one-image scans
    // you can use imageSources[0]
    this.Activate();
}		

此事件处理程序接收采集到的图像列表,并执行应用程序所需的任何操作。

就是这样。使用 WPF 成功运行扫描仪并获取图像只需要这些。

演示应用程序

演示应用程序利用了一些很棒的 WPF 功能,只是为了提醒我们当初为什么想使用 WPF。

1. 多图像支持

Scan Application

程序显示图像缩略图列表和一个选定的放大图像。当新图像被采集时,它会被添加到缩略图列表中。

每个缩略图实际上是一个 WPF 按钮。单击按钮会在全屏视图中打开相应的图像。

缩略图和全屏图像之间的边界可以移动。这样做会改变缩略图区域的宽度。缩略图和全屏图像的大小会自动调整。所有这些都由 WPF 引擎完成 - 无需编码。

这是自动调整大小的缩略图背后的 XAML

<Border BorderThickness="1" BorderBrush="#FF6E789A" Margin="12,70,12,12" >
<Grid  Name="imageGrid"><Grid.ColumnDefinitions><ColumnDefinition Width="75" />
<ColumnDefinition Width="5" /><ColumnDefinition Width="*" />
</Grid.ColumnDefinitions><GridSplitter HorizontalAlignment="Stretch"
                  VerticalAlignment="Stretch"
                  Grid.Column="1" ResizeBehavior="PreviousAndNext"
                  Width="Auto" Background="#FF787896" Height="Auto" />
                  <ScrollViewer VerticalScrollBarVisibility="Auto">
                  <StackPanel Margin="0,0,0,0" Name="ThumbnailStackPanel" >
                        <button
    height="Auto" width="Auto"><Button.Content>
                                <Image HorizontalAlignment="Left" Stretch="Uniform"
                                VerticalAlignment="Top"
                                Source="/scan2web;component/Resources/
				free-drink-pictures-espresso-coffee.jpg" />
                            </Button.Content>
                        </button></StackPanel>
                </ScrollViewer>
                <Image  Grid.Column="2" HorizontalAlignment="Left" Name="image1"
                Stretch="Uniform" VerticalAlignment="Top"
                Source="/scan2web;component/Resources/
			free-drink-pictures-espresso-coffee.jpg" />
            </Grid>
        </Border>

扫描会添加图像,按下清除会清除所有缩略图。

以下是滑块向右移动的屏幕截图。请注意,缩略图按钮被拉伸以适应宽度,此外,还会自动添加一个垂直滚动条。

App - wider thumbnails

注意:以上图像是使用 Teac mx-10 网络摄像头采集的。

2. 将图像上传到 Web 服务器

在许多应用程序中,需要提供扫描仪自动化,其中扫描和上传到 Web 服务器一键完成。

代码用于上传图像以及相应的服务器端,以完成此示例。请注意,C# 4 更改了服务代理,这里客户端使用 C# 2 风格的 Web 服务代理以获得更广泛的兼容性。

客户端如下

public void UploadImage()
{
    MemoryStream stream = new MemoryStream();
    try {
        JpegBitmapEncoder encoder = new JpegBitmapEncoder();
        // TBD: encoding parameters (quality etc.)

        TextBlock myTextBlock = new TextBlock();
        BitmapSource bs = image1.Source as BitmapSource;
        BitmapFrame bf = BitmapFrame.Create(bs);
        //encoder.Frames.Add(BitmapFrame.Create(image1.Source));
        encoder.Frames.Add(bf);
        encoder.Save(stream);
        stream.Flush();

        // upload
        scan2web.ScanServer.Scanner scanerServerProxy =
        			new scan2web.ScanServer.Scanner();
        string result = scanerServerProxy.UploadScan
        		(stream.GetBuffer(), "test 1");
        UploadResultLabel.Content = result;
    } catch (Exception ex) {
    UploadResultLabel.Content = "Error: " + ex.Message;
    }
    stream.Close();
    // This will come handy if we want to annotate the image
    //RenderTargetBitmap rendered = new RenderTargetBitmap
    ( (int)bs.Width, (int)bs.Height, bs.DpiX, bs.DpiY, bs.Format);
    //rendered.Render(image1);
} 

代码使用 Web 方法将当前图像作为字节数组上传。可以发送其他参数来完成信息 - 根据您的应用程序的相关性。

以及相应的服务器端是

[WebMethod]
public string UploadScan(byte[] data, string scanKey)
{
    Guid fileID = Guid.NewGuid();
    string path = Server.MapPath("~/Documents");
    string filePath = path + "/" + fileID.ToString() + ".jpg";
    try {
        // TBD: folder by date of upload to prevent too many files in the uploads folder
        FileStream traget = new FileStream
        	(filePath, FileMode.Create); // the jpg extension is for debug
        traget.Write(data, 0, data.Length);
        traget.Flush();
        traget.Close();
        // TBD: register in the DB (fileID, scanKey, person, date etc)
    } catch (Exception ex) {
    // TBD: cleanup - delete file, clear DB atc.
    // TBD: register the error and alert operators
    return "Error: " + ex.Message;
    }
    return "Saved";
}

关注点

该项目是一次关于抽象的实践练习,特别是旨在通过从客户端代码中移除低级细节来简化 Twain 的使用。

大部分工作不是花在应用程序上,而是花在弄清楚如何激活旧代码、转换位图等方面。我希望这项工作可以节省那些对 Twain 感兴趣的开发者的时间和精力。

此代码已在几台具有 32 位和 64 位操作系统以及几种类型源的计算机上进行了测试

  1. Teac MX-10 网络摄像头(使用 twain 接口)
  2. HP LaserJet 3055 多功能设备(使用 Twain 和 Twain over WIA)
  3. Brother MFC-6490W 多功能扫描仪
  4. Canon Lide 100

我们不知道有任何问题,但测试是有限的。

限制

当前代码并未利用许多更高级的 Twain 功能。使用这些功能(例如多页扫描)超出了本项目范围。

反馈

任何反馈、请求、问题、建议、修复和改进都将受到高度欢迎。

如有请求和评论,请联系 Baruch:rnd@ibn-labs.com

历史

  • 2011 年 3 月 21 日:初始版本
© . All rights reserved.