加载中

Introduction

In this article I'll cover a simple method to create a QR Code inside a standard control, wrapping up everything inside a UserControl for future reference. There  are many libraries which could help us in operations like that, but in this post I will use Google Charts (https://developers.google.com/chart/), together with System.Net and System.IO namespaces.

Background

Google Charts can be queried though POST request (see here for details: https://developers.google.com/chart/image/docs/post_requests?csw=1), so we must: a) query remote server, with particular POST parameters (more on this later), b) retrieve the server's response (a PNG image), c) use it for our means, namely paint it on our control.

So, open a new project in Visual Studio, then add a new User Control. I've set BorderStyle property to Fixed3D, and DoubleBuffered to True (in order to avoid flickering when the control refresh itself).

介绍

在这篇文章里我将会介绍一个简单的方法(使用内部的标准空间)用来创建二维码(QR Code), 记录关于UserControl的一切用以备查。有许多的库帮助我们做类似的操作,但是这次我要使用谷歌图表( Google Charts) (https://developers.google.com/chart/),同时还有System.Net和 System.IO命名空间。

背景

谷歌图表(Google Charts)可以通过POST被查询请求(细节看这里: https://developers.google.com/chart/image/docs/post_requests?csw=1),因此,我们必须: a)查询远程服务器,尤其是POST的参数(稍后叙述), b)获取服务器的应答 (一张PNG图片), c) 使用它对我们而言,就是把它(图片)绘成我们想要的样子。

因此, 在 Visual Studio中打开一个工程, 之后添加一个新的用户控件(User Control)。 把 BorderStyle属性设置成Fixed3D, 把 DoubleBuffered 设成 True (当控件自己刷新时,避免闪烁)。

Using the code

The standard URL we will query is the following: http://chart.googleapis.com/chart?chs={WIDTH}x{HEIGHT}&cht=qr&chl={DATA} (parameters in brackets will be replaced with real parameters). chs will specify QR Code resolution (width x height), while chl will contain the data to be represented through barcode. The parameter related to the barcode size could be easily deduced from our control's property (since a standard control naturally have a width and a height), but we will create a new property to store a certain amount of text, representing the data our QR Code will show.

In our UserControl, we start declaring the standard URI as a constant, our Data Property, and an internal variable to store data in the local context:

Const _GOOGLE_URL As String = "http://chart.googleapis.com/chart?chs= [This link is external to TechNet Wiki. It will open in a new window.] {WIDTH}x{HEIGHT}&cht=qr&chl={DATA}"
Dim _DATA As String = String.Empty
 
Property Data As String
  Get
    Return _DATA
  End Get
  Set(value As String)
    _DATA = value
  End Set
End Property

When we will use our Control, Data Property will be available in both Code View and Design Mode:

使用代码

我们将要查询的标准URL如下:http://chart.googleapis.com/chart?chs={WIDTH}x{HEIGHT}&cht=qr&chl={DATA}  (大括号里的参数将由实际的参数代替)。chs 由特定的二维码决定 (width x height)chl 包含条形码所表示的数据。与条形码大小相关的参数很容易从我们的控制属性上得出(标准的控制自然是有宽和高的),但是我们还要新建一个变量来存储一个定长的文本,即表示我们的二维码所展示的数据。

在UserControl中,我们将标准URI定义为一个常量,定义Data Property以及一个用来存储本地上下文数据的内部变量:

Const _GOOGLE_URL As String = "http://chart.googleapis.com/chart?chs= [This link is external to TechNet Wiki. It will open in a new window.] {WIDTH}x{HEIGHT}&cht=qr&chl={DATA}"
Dim _DATA As String = String.Empty
 
Property Data As String
  Get
    Return _DATA
  End Get
  Set(value As String)
    _DATA = value
  End Set
End Property

当我们使用Control时,Data Property在代码视图和设计视图里都是可用的:

Now that we can compose a URI with all the requested parameters, we must forge Data Property in order to encode its content before web request. This way, we can be sure no special character will come to break our query. I've implemented a private function for this. Calling on it return an URI with replaced parameters, the least of which will be encoded (thanks to the WebUtility.UrlEncode function)

Private Function getQRURI() As String
    Dim _qrAddr As String = _GOOGLE_URL.Replace("{WIDTH}", Me.Width.ToString).Replace("{HEIGHT}", Me.Height.ToString)
    _qrAddr = _qrAddr.Replace("{DATA}", WebUtility.UrlEncode(_DATA))
 
    Return _qrAddr
End Function

We'll replace the first two tag parameters, {WIDTH} and {HEIGHT}, with our control's size, while the data's parameter will be the content of our Data Property, encoded (for details on WebUtility.UrlEncode, please refer here).

现在可以用请求参数来构造一个网址URI,这需要在页面请求之前通过编码的方式将数据组装。要确保没有特别的字符来干扰我们的查询。我编写了一个私有方法来完成这个任务。调用它可以得到一个参数编码的URI(多亏有WebUtility.UrlEncode函数)。

Private Function getQRURI() As String
    Dim _qrAddr As String = _GOOGLE_URL.Replace("{WIDTH}", Me.Width.ToString).Replace("{HEIGHT}", Me.Height.ToString)
    _qrAddr = _qrAddr.Replace("{DATA}", WebUtility.UrlEncode(_DATA))
 
    Return _qrAddr
End Function

一旦有数据参数需要被包含,将用我们的尺寸替换掉代码中的两个参数{WIDTH}和{HEIGHT}(有关WebUtility.UrlEncode,请看 这里

We're now ready to fetch our image from remote servers, and use the returning buffer to paint our QR Code on our control. Since i want QR Code to be painted at the standard OnPaint Event (taking advantage of the PaintEventArgs argument it exposes), i will override it, adding my code:

Protected Overrides Sub OnPaint(e As PaintEventArgs)
    MyBase.OnPaint(e)
    If _DATA Is Nothing Then Exit Sub
 
    Dim client As New WebClient()
    Dim bytes() As Byte = client.DownloadData(getQRURI())
    client.Dispose()
 
    Dim memStream As New IO.MemoryStream(bytes)
    Dim bmp As Bitmap = Bitmap.FromStream(memStream)
    memStream.Dispose()
 
    e.Graphics.DrawImage(bmp, 0, 0)
End Sub

MyBase.OnPaints calls on standard paint operations. Next, we check if there is data to query (exiting method otherwise), and proceeding querying remote server with a new instance of WebClient. Through the call at DownloadData, to which we pass the result of our URI-formatting function, we'll fill an array of bytes, which is the server response, i.e. the PNG image representing our QR Code.

现在可以从远程服务器获取二维码图片了,因为我们已经在服务器上利用QRCode控件产生好二维码图片缓存等待您的请求。由于想直接用标准OnPaint绘制方法(可以更好使用PaintEventArgs),我将进行重载,添加我自己的代码:

Protected Overrides Sub OnPaint(e As PaintEventArgs)
    MyBase.OnPaint(e)
    If _DATA Is Nothing Then Exit Sub
 
    Dim client As New WebClient()
    Dim bytes() As Byte = client.DownloadData(getQRURI())
    client.Dispose()
 
    Dim memStream As New IO.MemoryStream(bytes)
    Dim bmp As Bitmap = Bitmap.FromStream(memStream)
    memStream.Dispose()
 
    e.Graphics.DrawImage(bmp, 0, 0)
End Sub

OnPaints调用标准的绘制操作。下一步,如果有数据请求,我们会进行检查(另外存在方法),我们用一个新的WebClient实例来处理远程请求。通过格式化URI方法处理过的下载数据的调用,我们填充一个bytes数组,进而构建一个诸如PNG格式的QR Code二维码图片

An image-type variable can be initialized through reading a Stream (like when we wish to open an image existing on our hard-drive, a stream to our local copy of it). Since we'll have our bytes in memory, we can declare a MemoryStream based on our array, and use it as a Bitmap source. At this point, having a perfectly working bitmap, we can exploit the variable e, which the OnPaint event give access to us, to draw the image on our control, at [0;0] location.

After compiling our project, the QRBox will be available in the ToolBox, ready to be used on our Forms.

Using it is simple, it's sufficient to set it's Data Property, and call for a control's refresh.
The following example Form shows how it works: i've added to my Form a QrBox control, together with a standard TextBox ad Button.

图片类型的变量可以通过读取流的方式进行初始化(就像我们打开本地的一张图片,会有一份本地流的副本)。既然在内存中我们有自己的字节,我们可以声明一个基于数组的MemoryStream,并且使用它作为位图的源。在这一点上,为了实现完美的工作位图,我们可以利用变量 e,其中的OnPaint事件可以访问自己,以此在我们控制的位置[0;0]绘制图像。

编译我们的项目后,QRBox将出现在工具箱里,准备在Form上使用。


 

使用它非常简单,只需要设置它的数据和属性,还有控制刷新的回调。

接下来的简单Form例子会展示它是如何工作的。我已经向我的Form中添加了QrBox,一个标准的TextBox和Button。

When the user press the "Make" Button, we'll read the TextBox content, passing it to the QrBox Data Property, and invoking the Refresh() method, in order to start the remote query towards Google Charts. The code for the Button's click will be simply like that:

Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
    QrBox1.Data = TextBox1.Text
    QrBox1.Refresh()
End Sub

UserControl's complete source code

The complete code for QrBox UserControl is as follows:

Imports System.Net
 
Public Class QRBox
    Const _GOOGLE_URL As String = "http://chart.googleapis.com/chart?chs={WIDTH}x{HEIGHT}&cht=qr&chl={DATA}"
    Dim _DATA As String = String.Empty
 
    Property Data As String
        Get
            Return _DATA
        End Get
        Set(value As String)
            _DATA = value
        End Set
    End Property
 
    Private Function getQRURI() As String
        Dim _qrAddr As String = _GOOGLE_URL.Replace("{WIDTH}", Me.Width.ToString).Replace("{HEIGHT}", Me.Height.ToString)
        _qrAddr = _qrAddr.Replace("{DATA}", WebUtility.UrlEncode(_DATA))
 
        Return _qrAddr
    End Function
 
    Protected Overrides Sub OnPaint(e As PaintEventArgs)
        MyBase.OnPaint(e)
        If _DATA Is Nothing Then Exit Sub
 
        Dim client As New WebClient()
        Dim bytes() As Byte = client.DownloadData(getQRURI())
        client.Dispose()
 
        Dim memStream As New IO.MemoryStream(bytes)
        Dim bmp As Bitmap = Bitmap.FromStream(memStream)
        memStream.Dispose()
 
        e.Graphics.DrawImage(bmp, 0, 0)
    End Sub
 
    Public Sub New()
        InitializeComponent()
    End Sub
End Class

I hope this could be useful for your projects.
Happy coding, and good luck with your work!

当用户按下“制作”按钮时,我们将会读到一段TextBox文本,并将其传送到QrBox Data Property中,并且触发刷新方法。为了开始针对谷歌Charts的远程查询,按按钮产生的代码将会简单如下:

Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
    QrBox1.Data = TextBox1.Text
    QrBox1.Refresh()
End Sub

UserControl的完整代码

QrBox UserControl的完整代码如下:

Imports System.Net
 
Public Class QRBox
    Const _GOOGLE_URL As String = "http://chart.googleapis.com/chart?chs={WIDTH}x{HEIGHT}&cht=qr&chl={DATA}"
    Dim _DATA As String = String.Empty
 
    Property Data As String
        Get
            Return _DATA
        End Get
        Set(value As String)
            _DATA = value
        End Set
    End Property
 
    Private Function getQRURI() As String
        Dim _qrAddr As String = _GOOGLE_URL.Replace("{WIDTH}", Me.Width.ToString).Replace("{HEIGHT}", Me.Height.ToString)
        _qrAddr = _qrAddr.Replace("{DATA}", WebUtility.UrlEncode(_DATA))
 
        Return _qrAddr
    End Function
 
    Protected Overrides Sub OnPaint(e As PaintEventArgs)
        MyBase.OnPaint(e)
        If _DATA Is Nothing Then Exit Sub
 
        Dim client As New WebClient()
        Dim bytes() As Byte = client.DownloadData(getQRURI())
        client.Dispose()
 
        Dim memStream As New IO.MemoryStream(bytes)
        Dim bmp As Bitmap = Bitmap.FromStream(memStream)
        memStream.Dispose()
 
        e.Graphics.DrawImage(bmp, 0, 0)
    End Sub
 
    Public Sub New()
        InitializeComponent()
    End Sub
End Class

我希望本文对你的项目有用。

祝你编码愉快,工作愉快!

返回顶部
顶部