使用GAN进行动漫风格迁移

本文通过介绍两种GAN的模型,来完成实景风格迁移,和AI纯艺术创作没有任何关系,寄希望于AI创作原画的请绕行。

先进行一个简短的科普,以便对后续的观点有一定认知,避免闹出笑话

GAN(对抗生成网络)是解决风格迁移的深度学习方法,本文将做出简要介绍。 GAN由生成网络G和对抗网络D组成,G用于接收一个噪声z,从而生成图片G(z);D是一个判别网络,判别一张图片x是不是“真的”我们所需得到的目的图片或者是由G生成的(此时图片是“假的”),即D(x)。D(x)在一些识别领域使用的非常广泛,而且推理过程快,系统开销低。手机和嵌入式设备可以轻松胜任,但是D(x)在GAN的整体当中,只是一小部分。GAN的训练过程是对G(z)和D(x)的不断优化的过程,如果用CNN来表达的话,就是DCGAN。

以上是对GAN的一个简单描述,深度的我就不介绍了,因为我也不会,我只会使用。这里有一篇进阶科普文,有兴趣的可以看看《GAN实战生成对抗网络》

回到本文的目标,动漫风格迁移,我在这里推荐2款GAN模型,一款是开源的AnimeGAN,这里有github的传送门,另一款是根据清华大学的论文做出的非开源AI,叫做CartoonGAN,github的传送门在这里

一、AnimeGAN

AnimeGAN是通过towsorflow-gpu来开发的,依赖cuda和cudnn,tf最大的坑是和cnn、cudnn的版本组合问题,我把我本地的版本列出来作为参考。

tensorflow-gpu 1.14 cuda 10.0 cudnn 7.4

从github下载后,还不能使用,需要下载模型文件vgg19权重文件。有条件的上个梯子,速度会快很多。模型文件可以保存在checkpoint/AnimeGAN_Hayao_lsgan_300_300_1_3_10,权重文件保存在vgg19_weight目录下

一切就绪后,你可以通过以下方式进行训练,训练到100代在T4级别的计算卡下, 大概需要40多个小时。所以考虑好自己的钱包和需求。

python3 main.py –phase train –dataset Hayao –epoch 101–init_epoch 1

在训练之前需要做一个小修改,main.py里os.environ[“CUDA_VISIBLE_DEVICES”] = “1”是设置GPU的,单卡设置就是0,双卡就设置0,1。否则GPU无法生效。–dataset参数是制定训练集的,github上拉下来的有多个风格,可以都训练一下,代数这里设置的是101,随时查看随时调整即可,为了方便查看,可以做个web服务器指向到训练目录,能随时查看训练的情况。

如果不喜欢这个耗时又耗力的过程,那么直接使用你下载好的一个模型就能用

python3 test.py –checkpoint_dir checkpoint/saved_model –test_dir dataset/test/real –style_name H

这里同样需要修改刚才的环境变量,让GPU设置为符合你自己的情况,checkpoint_dir是设置训练模型路径的,test_dir是设置输入路径,如果要生成单个文件,需要修改test.py,注意一下,模型载入以后可以反复推理,不要每次推理每次装载模型,

总结:AnimeGAN是对算力要求很高,显存大小要求很低的网络模型,或者理解为时间复杂度很高,空间复杂度很低,可以胜任2k 4k图形的画质迁移。按照当下的科技水平,很难实现实时推理,V100这种计算卡对于1080p的图片来说,也需要1s多,至于K80那种,奔着20秒去了。至于CPU推理,这个算法我没试过,但还是不建议去试。

二、CartoonGAN

未完待续

DeskBand 开发摸索过程

以下按照已知流程来梳理,随时更新

首先,我感觉从已知资料来看DeskBand的开发是需要通过COM组件来实现的,也就是说最终会注册一个进程内COM组件,被任务栏调起

接下来就是要做的这个COM组件具体的实现

1.完成这个特定的COM组件需要实现一些接口,否则任务栏无法调起你

接口是多个,我都列出来:

一、IOleWIndow <-IDockingWIndow <- IDeskBand

箭头指向的是爹,从爹到儿子都要做实现,I前缀就是接口,类似IUNKNOWN,不懂的自己去科普windows的接口

二、 IDeskBand<- IDeskBand2(可选)

这个好像是为了做各种显示特效的

说一下这一家四个接口需要实现的方法:

1、IDeskBand

GetBandInfo 得到窗体的大小,标题,背景色等信息

2、 IOleWindow

GetWindow  拿到Band对象的窗体句柄(Handle)

ContextSensitiveHelp

3、 IDockingWindow

ShowDW 显示隐藏窗体

CloseDW 关闭窗体

ResizeBorderDW 可能是做特效用的,一般不用

4、 IDeskBand2 做毛玻璃特效的接口

CanRenderComposited

SetCompositionState

GetCompositionState

5、IInputObject 输入类的接口(可选)

6、 IContextMenu (上下文菜单)

下文是步骤:

1、首先我们要明确现在做的是一个Band(一种专属于任务栏外挂的COM对象),那就要说明两件事a.我要注册一个Band的COM b.他还得注册自己的类别是放在任务栏上的,因此这个COM对象需要注册2个地方,

a.Band类COM的线程模型必须为“Apartment” ,所以他必须是个dll,你要说不做Band的话,那你进来看这篇文章干啥

b.

====================我是华丽的分割线=============================

以下是我抄的,还是没搞明白

 执行顺序

Windows(桌面band用于资源管理器,浏览栏用于IE)通过查找实现了CATID_DeskBand,,CATID_InfoBand,或 CATID_CommBand的COM对象来发现band对象,并将band的名字添加到工具栏菜单。

1、当用户从菜单选中band时,Windows调用CoCreateInstance或它的同等函数。COM则调用DLL中的 DllGetClassObject输出函数,COM用正确的 ID搜索到一个类工厂并将它返回。然后COM调用IClassFactory::CreateInstance进行一系列的COM常规处理。

关键就是类工厂能正确找到我们自己写的COM,那说明我们要重写IClassFactory::CreateInstance,让它能正确创建出我们写的COM的对象。

2、Windows调用IObjectWithSite::SetSite给出一个(IUnknown*)类型的指针指向对象容器,在这个函数里面我们就要创建BAND对象的窗口了。注意,此时创建的窗口一定要是不可见的,即保证不要使用WS_VISIBLE来创建窗口。

3、接下来,Windows调用IOleWindow::GetWindow来获得窗口的HWND。(这就是为什么你必须在SetSite中创建窗口的原因)。

4、接着,Windows调用IDeskBand::GetBandInfo请求关于band的信息,如大小,可变高度或者定高,以及背景颜色和标题。

5、接着,Windows调用IDockingWindow::ShowDW来显示窗体。

6、当用户关闭Band时,Windows调用IDockingWindow::CloseDW。

通常,IObjectWithSite::SetSite实现应该完成下列步骤:

1. 释放当前所把持的任何现场指针。

2. 如果传递到SetSite的指针被置为NULL,此则区对象被删除。SetSite可以返回S_OK。

3. 如果传递到SetSite的指针被置为非NULL,则建立新的现场。SetSite应该做以下的事情:

   a)调用现场QueryInterface方法请求IOleWindow接口。

   b)调用IOleWindow::GetWindow获取父窗口句柄,并存储它,以便以后使用。如果不再使用的话,就释放IOleWindow接口。

   c)创建此band对象的窗口为一个子窗口,其父窗口就是上一步获得的那个窗口。注意在此不能将它创建成可见窗口。

   d)如果此band对象实现IInputObject,调用现场QueryInterface方法请求IInputObjectSite接口,存储这个接口的指针以备后用。

   e)如果所有步骤都成功,则返回S_OK,否则返回OLE定义的错误代码以指示错误类型。

Python中使用lua续,让lupa调用原生模块

前文曾经讲过python中使用lua的方法,目前用的最多的还是lupa模块,但是通过pip安装的lupa存在一个问题,无法支持lua原生模块,因此本文介绍一下如何用lupa调用原生模块。

首先需要卸载掉原来的lupa

pip uninstall lupa

接下来安装lua,这个不多做介绍了,下载->解压->编辑,本人用的是lua5.1.5,linux和macos都没问题

安装lua之后,需要把lua源码目录里的./etc/lua.pc拷贝到/usr/local/lib/pkg-config/,这里的/usr/local/lib是你lua库安装的位置,这个很重要,否则后边进行不下去

接下来从pip官网下载lupa源码,解压后修改setup.py,搜索–exists 把他替换为–libs,因为lupa的安装程序这里有个逻辑错误,是通过pkg-config的exists参数来判断库是否存在,修改为–libs参数以后可以绕过这个检查,让lupa直接使用你本机安装的lua库。

接下来就是

python setup.py --no-bundle
sudo python setup.py install

为了验证原生模块是否能够编译使用,可以下载一个lua的json扩展,叫lua-cjson,下载地址

下载解压后,如果是linux,直接sudo make install

如果是macos,需要修改Makefile,把macos相关部分的CFLAG配置拷贝过来即可。否则编译参数是不对的,会找不到库

最后就是写一个lua脚本,引用cjson库,通过python去执行这个脚本就可以了

微信小程序发红包发零钱双向验证攻略

首先需要明确几个概念,小程序、公众号各自的能力范围。

本文涉及到的功能均需要TLS的双向验证机制,因此证书的处理是开发中最容易遇到的问题,而且这些肯定是运行在服务器上的,绝大多数情况下都是证书使用错误导致,其次就是IP白名单的问题。关于TLS双向验证,这不是传统意义上的http多次交互握手,而是基于TLS协议的一个长连接,在握手、协议选择、校验私钥之后才进行数据传输。

设计双向验证的,需要3个pem文件,其中2个是微信商户平台自行下载的,或者通过商户平台的p12文件来生成。而最后一个pem则是双向验证必须的文件,即根证书,根证书下载地址:https://pay.weixin.qq.com/wiki/doc/api/micropay.php?chapter=23_4

这个搞错了,势必会报出非认证机构颁发的证书。

接下来,就是涉及功能。小程序体系(参数指定的appid为小程序)可以调用企业红包服务,但是不能掉起微信红包功能,微信红包功能依赖的wxappi是指一个公众号的appid,这个坑千万不要踩。

先讲这么多

Python和lua互相调用

本帖中用到的py模块是lupa,目前版本是1.7,通过pip可以直接安装。说明一下,由于这个模块的文档非常少,只有几个代码例子,所以以下所说都是根据已有的例子自己测出来的。如果有更好的办法请回帖。

一、python调用lua,照搬lupa在python社区的所有例子都可以,但要注意一点,整段的lua文件代码,不能使用eval方法,一定会报错。原因不多讲了。整段lua代码的调用直接execute()

二、lua调用python,这就是亮点,也是一个容易踩的坑。首先你不能在lua代码里直接使用py的任何变量,如果要使用,需要通过eval来执行一个匿名定义,然后通过eval返回值(他是py的一个function)来当做py本地函数来调用。

下文是例子:

import lupa
from lupa import LuaRuntime
lua = LuaRuntime(unpack_returned_tuples=True)

def py_func(p):
    print p
    return 'hello '+str(p)

#execute无返回,打印出来是None
lua.execute('var_before=1;print(var_before);')

#eval返回的是一个lua call到py call的映射
pycall = lua.eval('function(pyfunc,param1) var_before=pyfunc(param1) end')

pycall(py_func,'world')

lua.execute('print(var_before)')

通过以上例子应该能看明白二者互相调用的方式。重要的事情多说几遍,eval内部只能是匿名过程。所以要互相调用,目前我所知道的办法就是频繁的切换eval和execute来控制lua执行

以上执行过程都是在lua这个luaruntime实例里运行的,所以属于一个上下文,这点极其重要,直接关乎到性能问题,同时共享上下文变量的值得以继承,真正实现了无缝切换,多实例的lua vm没有测试过,理论上是不影响的。

Node.js原生模块开发之N-API开发教程

长话短说,N-API在node8.0以后开始加入支持,目的在于解决node跨版本之间原生模块的兼容问题,Nan原生模块无法兼容不同版本的Node,需要重新编译(看脸和google的心情)。N-API做了一层抽象,实现了跨版本支持。

需要注意一点,10.0之前,N-API是试验模式,使用该模块的时候会在stderr也可能是stdout输出一段警告,程序不依赖控制外可以无视此警告。10.0以后默认支持,警告不再复现

准备工作:

node环境:node 10.X

node开发环境:https://github.com/nodejs/node

需要安装的模块:npm/node-gyp

本人开发环境:debian 9/Linux kernel 4.9/gcc 6.3.0

最简易的开发方式,进入源码目录下的~/node-master/test/addon-napi/1_hello_world/

直接编译:#node-gyp configure &&node-gyp build

结果呢,当然是一堆错误,参照第二个例子修改这个hello world即可

ANSI C的库可以直接使用,三方库需要在binding.gpy里配置,相当于-l -L这些参数去搞gpy文件即可,google搜node-gpy使用

下边简单介绍一下API调用

NAPI_CALL(env, napi_throw_error(env,”timeout 001″,”time out”));

通过NAPI_CALL宏来调用N-API,N-API帮助去官网看,各种函数,上边这个例子是调用了一个抛出异常的API。

napi_value val1;

通过napi_value声明一个nodejs变量。

变量的初始化通过NAPI_CALL来调用对应的API来实现,可以创建

这么多类型的js变量。

NAPI_CALL(env, napi_create_string_utf8(env, str, str_len, &val1));

这里创建了一个val1的js字符串,还是utf8版本的

如果你的缓存区是个字节流,NAPI能自动给你转UTF-8,大写的赞。

没了,就这么多,不会的看例子。

有心情继续写。

我刚发布的npm模块叫xd-synchttp

执行的功能是同步获取http请求结果。解决http模块全异步的局限性。有些场景必须使用同步。模块内加了超时设定,超时了就会抛出异常。欢迎使用

const sync = require('xd-synchttp');
let content = "";
try{
    content = sync.http_get('http://www.csdn.net',0);//0为不超时
}
catch(err)
{
console.log(err);
}

C# Socket的Send实现完全阻塞

C#中Socket的Send方法即使是在阻塞模式下也会立即返回,查了不少资料,都没什么结果,最后在MSDN找到了答案。
Send的发送默认是不带参数的,其实是写入了本地缓存区,然后基础系统拆分后分批次发送。如果想要实现真正的阻塞,需要使用SocketFlag参数
但SocketFlag参数参考MSDN的例子,现在把代码贴上来,一看便知




// Displays sending with a connected socket 
// using the overload that takes a buffer, message size, and socket flags. public static int SendReceiveTest3(Socket server) 
{ 
byte[] msg = Encoding.UTF8.GetBytes("This is a test"); 
byte[] bytes = new byte[256]; 
try {
 // Blocks until send returns. 
int i = server.Send(msg, msg.Length, SocketFlags.None); Console.WriteLine("Sent {0} bytes.", i); 
// Get reply from the server. 
int byteCount = server.Receive(bytes, server.Available, SocketFlags.None); if (byteCount > 0)
 Console.WriteLine(Encoding.UTF8.GetString(bytes)); 
} 
catch (SocketException e) 
{ 
Console.WriteLine("{0} Error code: {1}.", e.Message, e.ErrorCode); 
return (e.ErrorCode); 
} 
return 0; } 

文本比较算法Ⅰ——LD算法

文本比较算法Ⅰ——LD算法

  在日常应用中,文本比较是一个比较常见的问题。文本比较算法也是一个老生常谈的话题。

  文本比较的核心就是比较两个给定的文本(可以是字节流等)之间的差异。目前,主流的比较文本之间的差异主要有两大类。一类是基于编辑距离(Edit Distance)的,例如LD算法。一类是基于最长公共子串的(Longest Common Subsequence),例如Needleman/Wunsch算法等。

  LD算法(Levenshtein Distance)又成为编辑距离算法(Edit Distance)。他是以字符串A通过插入字符、删除字符、替换字符变成另一个字符串B,那么操作的过程的次数表示两个字符串的差异。

  例如:字符串A:kitten如何变成字符串B:sitting。

    第一步:kitten——》sitten。k替换成s

    第二步:sitten——》sittin。e替换成i

    第三步:sittin——》sitting。在末尾插入g

  故kitten和sitting的编辑距离为3

  定义说明:

  LD(A,B)表示字符串A和字符串B的编辑距离。很显然,若LD(A,B)=0表示字符串A和字符串B完全相同

  Rev(A)表示反转字符串A

  Len(A)表示字符串A的长度

  A+B表示连接字符串A和字符串B

  有下面几个性质:

  LD(A,A)=0

  LD(A,””)=Len(A)

  LD(A,B)=LD(B,A)

  0≤LD(A,B)≤Max(Len(A),Len(B))

  LD(A,B)=LD(Rev(A),Rev(B))

  LD(A+C,B+C)=LD(A,B)

  LD(A+B,A+C)=LD(B,C)

  LD(A,B)≤LD(A,C)+LD(B,C)(注:像不像“三角形,两边之和大于第三边”)

  LD(A+C,B)≤LD(A,B)+LD(B,C)

  为了讲解计算LD(A,B),特给予以下几个定义

  A=a1a2……aN,表示A是由a1a2……aN这N个字符组成,Len(A)=N

  B=b1b2……bM,表示B是由b1b2……bM这M个字符组成,Len(B)=M

  定义LD(i,j)=LD(a1a2……ai,b1b2……bj),其中0≤i≤N,0≤j≤M

  故:  LD(N,M)=LD(A,B)

      LD(0,0)=0

      LD(0,j)=j

      LD(i,0)=i

  对于1≤i≤N,1≤j≤M,有公式一

  若ai=bj,则LD(i,j)=LD(i-1,j-1)

  若ai≠bj,则LD(i,j)=Min(LD(i-1,j-1),LD(i-1,j),LD(i,j-1))+1

  举例说明:A=GGATCGA,B=GAATTCAGTTA,计算LD(A,B)

  第一步:初始化LD矩阵  



GAATTCAGTTA

01234567891011
G1










G2










A
3










T4










C5










G6










A7










  第二步:利用上述的公式一,计算第一行



GAATTCAGTTA

01234567891011
G1012345678910
G2










A3










T4










C5










G6










A7










  第三步,利用上述的公示一,计算其余各行 



GAATTCAGTTA

01234567891011
G1012345678910
G211234566789
A321123456788
T432212345678
C543322234567
G654433333456
A765444434455

  则LD(A,B)=LD(7,11)=5

  下面是LD算法的代码,用的是VB2005。代码格式修正于2012年1月6日。Public Class clsLD
  Private Shared mA() As Char
  Private Shared mB() As Char

  Public Shared Function LD(ByVal A As String, ByVal B As String) As Integer

    mA = A.ToCharArray
    mB = B.ToCharArray

    Dim L(A.Length, B.Length) As Integer
    Dim i As Integer, j As Integer

    For i = 1 To A.Length
      L(i, 0) = i
    Next
    For j = 1 To B.Length
      L(0, j) = j
    Next

    For i = 1 To A.Length
      For j = 1 To B.Length
        If mA(i – 1) = mB(j – 1) Then
          L(i, j) = L(i – 1, j – 1)
        Else
          L(i, j) = Min(L(i – 1, j – 1), L(i – 1, j), L(i, j – 1)) + 1
        End If
      Next
    Next

    Return L(A.Length, B.Length)
  End Function

  Public Shared Function Min(ByVal A As Integer, ByVal B As Integer, ByVal C As Integer) As Integer
    Dim I As Integer = A
    If I > B Then I = B
    If I > C Then I = C
    Return I
  End Function
End Class

  这个LD算法时间复杂度为O(MN),空间复杂度为O(MN),如果进行优化的话,空间复杂度可以为O(M),优化的代码这里不再详述了。参看“计算字符串的相似度(VB2005)

  我们往往不仅仅是计算出字符串A和字符串B的编辑距离,还要能得出他们的匹配结果。

  以上面为例A=GGATCGA,B=GAATTCAGTTA,LD(A,B)=5

  他们的匹配为:

    A:GGA_TC_G__A

    B:GAATTCAGTTA

  如上面所示,蓝色表示完全匹配,黑色表示编辑操作,_表示插入字符或者是删除字符操作。如上面所示,黑色字符有5个,表示编辑距离为5。

  利用上面的LD矩阵,通过回溯,能找到匹配字串

  第一步:定位在矩阵的右下角  



GAATTCAGTTA

01234567891011
G1012345678910
G211234566789
A321123456788
T432212345678
C543322234567
G654433333456
A765444434455

  第二步:回溯单元格,至矩阵的左上角

    若ai=bj,则回溯到左上角单元格



GAATTCAGTTA

01234567891011
G1012345678910
G211234566789
A321123456788
T432212345678
C543322234567
G654433333456
A765444434455

    若ai≠bj,回溯到左上角、上边、左边中值最小的单元格,若有相同最小值的单元格,优先级按照左上角、上边、左边的顺序



GAATTCAGTTA

01234567891011
G1012345678910
G211234566789
A321123456788
T432212345678
C543322234567
G654433333456
A765444434455

    若当前单元格是在矩阵的第一行,则回溯至左边的单元格

    若当前单元格是在矩阵的第一列,则回溯至上边的单元格



GAATTCAGTTA

01234567891011
G1012345678910
G211234566789
A321123456788
T432212345678
C543322234567
G654433333456
A765444434455

    依照上面的回溯法则,回溯到矩阵的左上角

  第三步:根据回溯路径,写出匹配字串

    若回溯到左上角单元格,将ai添加到匹配字串A,将bj添加到匹配字串B

    若回溯到上边单元格,将ai添加到匹配字串A,将_添加到匹配字串B

    若回溯到左边单元格,将_添加到匹配字串A,将bj添加到匹配字串B

    搜索晚整个匹配路径,匹配字串也就完成了

  从上面可以看出,LD算法在不需要计算出匹配字串的话,时间复杂度为O(MN),空间复杂度经优化后为O(M)

  不过,如果要计算匹配字符串的话,时间复杂度为O(MN),空间复杂度由于需要利用LD矩阵计算匹配路径,故空间复杂度仍然为O(MN)。这个在两个字符串都比较短小的情况下,能获得不错的性能。不过,如果字符串比较长的情况下,就需要极大的空间存放矩阵。例如:两个字符串都是20000字符,则LD矩阵的大小为20000*20000*2=800000000Byte=800MB。呵呵,这是什么概念?故,在比较长字符串的时候,还有其他性能更好的算法。留待后文详述。

Windows编程之滚动条—滚动条消息

在用鼠标单击滚动条或者拖动卷动方块时,Windows给窗口消息处理程序发送WM_VSCROLL(供上下移动)和WM_HSCROLL(供左右移动)消息。在滚动条上的每个鼠标动作都至少产生两个消息,一条在按下鼠标按钮时产生,一条在释放按钮时产生。

和所有的消息一样,WM_VSCROLL和WM_HSCROLL也带有wParam和lParam消息参数。对于来自作为窗口的一部分而建立的滚动条消息,您可以忽略lParam;它只用于作为子窗口而建立的滚动条(通常在对话框内)。

wParam消息参数被分为一个低字组和一个高字组。wParam的低字组是一个数值,它指出了鼠标对滚动条进行的操作。这个数值被看作一个「通知码」。通知码的值由以SB(代表「scroll bar(滚动条)」)开头的标识符定义。以下是在WINUSER.H中定义的通知码:

#define SB_LINEUP       0
        
#define SB_LINELEFT           0
        
#define SB_LINEDOWN           1
        
#define SB_LINERIGHT          1
        
#define SB_PAGEUP         2
        
#define SB_PAGELEFT           2
        
#define SB_PAGEDOWN           3
        
#define SB_PAGERIGHT          3
        
#define SB_THUMBPOSITION   4
        
#define SB_THUMBTRACK         5
        
#define SB_TOP                6
        
#define SB_LEFT           6
        
#define SB_BOTTOM        7
        
#define SB_RIGHT          7
        
#define SB_ENDSCROLL          8

包含LEFT和RIGHT的标识符用于水平滚动条,包含UP、DOWN、TOP和BOTTOM的标识符用于垂直滚动条。鼠标在滚动条的不同区域单击所产生的通知码如图4-7所示。

 ​

如果在滚动条的各个部位按住鼠标键,程序就能收到多个滚动条消息。当释放鼠标键后,程序会收到一个带有SB_ENDSCROLL通知码的消息。一般可以忽略这个消息,Windows不会去改变卷动方块的位置,而您可以在程序中呼叫SetScrollPos来改变卷动方块的位置。

当把鼠标的光标放在卷动方块上并按住鼠标键时,您就可以移动卷动方块。这样就产生了带有SB_THUMBTRACK和SB_THUMBPOSITION通知码的滚动条消息。在wParam的低字组是SB_THUMBTRACK时,wParam的高字组是使用者在拖动卷动方块时的目前位置。该位置位于卷动列范围的最小值和最大值之间。在wParam的低字组是SB_THUMBPOSITION时,wParam的高字组是使用者释放鼠标键后卷动方块的最终位置。对于其它的卷动列操作,wParam的高字组应该被忽略。

为了给使用者提供回馈,Windows在您用鼠标拖动卷动方块时移动它,同时您的程序会收到SB_THUMBTRACK消息。然而,如果不通过呼叫SetScrollPos来处理SB_THUMBTRACK或SB_THUMBPOSITION消息,在使用者释放鼠标键后,卷动方块会迅速跳回原来的位置。

程序能够处理SB_THUMBTRACK或SB_THUMBPOSITION消息,但一般不同时处理两者。如果处理SB_THUMBTRACK消息,在使用者拖动卷动方块时您需要移动显示区域的内容。而如果处理SB_THUMBPOSITION消息,则只需在使用者停止拖动卷动方块时移动显示区域的内容。处理SB_THUMBTRACK消息更好一些(但更困难),对于某些型态的数据,您的程序可能很难跟上产生的消息。

WINUSER.H表头文件还包括SB_TOP、SB_BOTTOM、SB_LEFT和SB_RIGHT通知码,指出滚动条已经被移到了它的最小或最大位置。然而,对于作为应用程序窗口一部分而建立的滚动条来说,永远不会接收到这些通知码。

在滚动条范围使用32位的值也是有效的,尽管这不常见。然而,wParam的高字组只有16位的大小,它不能适当地指出SB_THUMBTRACK和SB_THUMBPOSITION操作的位置。在这种情况下,需要使用GetScrollInfo函数(在下面描述)来得到信息。

在SYSMETS中加入卷动功能

前面的说明已经很详尽了,现在,要将那些东西动手做做看了。让我们开始时简单些,从垂直卷动着手,因为我们实在太需要垂直卷动了,而暂时还可以不用水平卷动。SYSMET2如程序4-3所示。这个程序可能是滚动条的最简单的应用。

程序4-3 SYSMETS2.C        

/*------------------------------------------------------------------
        
    SYSMETS2.C -- System Metrics Display Program No. 2
        
            (c) Charles Petzold, 1998
        
------------------------------------------------------------------*/
        
#include <windows.h>
        
#include "sysmets.h"
        
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
        
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
        
                 PSTR szCmdLine, int iCmdShow)
        
{
        
    static TCHAR szAppName[] = TEXT ("SysMets2") ;
        
    HWND   hwnd ;
        
    MSG    msg ;
        
    WNDCLASS wndclass ;
        
    wndclass.style           = CS_HREDRAW | CS_VREDRAW ;
        
    wndclass.lpfnWndProc  = WndProc ;
        
    wndclass.cbClsExtra   = 0 ;
        
    wndclass.cbWndExtra   = 0 ;
        
    wndclass.hInstance       = hInstance ;
        
    wndclass.hIcon        = LoadIcon (NULL, IDI_APPLICATION) ;
        
    wndclass.hCursor         = LoadCursor (NULL, IDC_ARROW) ;
        
    wndclass.hbrBackground        = (HBRUSH) GetStockObject (WHITE_BRUSH) ;
        
    wndclass.lpszMenuName         = NULL ;
        
    wndclass.lpszClassName        = szAppName ;
        

    if (!RegisterClass (&wndclass))
        
    {
        
    MessageBox (NULL, TEXT ("This program requires Windows NT!"),
        
           szAppName, MB_ICONERROR) ;
        
    return 0 ;
        
    }
        

    hwnd = CreateWindow (szAppName, TEXT ("Get System Metrics No. 2"),
        
                   WS_OVERLAPPEDWINDOW | WS_VSCROLL,
        
                   CW_USEDEFAULT, CW_USEDEFAULT,
        
                   CW_USEDEFAULT, CW_USEDEFAULT,
        
                   NULL, NULL, hInstance, NULL) ;
        
    ShowWindow (hwnd, iCmdShow) ;
        
    UpdateWindow (hwnd) ;
        

    while (GetMessage (&msg, NULL, 0, 0))
        
    {
        
       TranslateMessage (&msg) ;
        
        DispatchMessage (&msg) ;
        
    }
        
    return msg.wParam ;
        
}
        
LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
        
{
        
    static int  cxChar, cxCaps, cyChar, cyClient, iVscrollPos ;
        
    HDC         hdc ;    
        
    int         i, y ;   
        
    PAINTSTRUCT ps ;
        
    TCHAR       szBuffer[10] ;   
        
    TEXTMETRIC  tm ;     
        
    switch (message)     
        
    {
        
case WM_CREATE:
        
    hdc = GetDC (hwnd) ;
        
    GetTextMetrics (hdc, &tm) ;
        
    cxChar = tm.tmAveCharWidth ;
        
    cxCaps = (tm.tmPitchAndFamily & 1 ? 3 : 2) * cxChar / 2 ;
        
    cyChar = tm.tmHeight + tm.tmExternalLeading ;
        

    ReleaseDC (hwnd, hdc) ;
        
    SetScrollRange (hwnd, SB_VERT, 0, NUMLINES - 1, FALSE) ;
        
    SetScrollPos   (hwnd, SB_VERT, iVscrollPos, TRUE) ;
        
            return 0 ;
        

    case WM_SIZE:
        
            cyClient = HIWORD (lParam) ;
        
            return 0 ;
        

    case WM_VSCROLL:
        
            switch (LOWORD (wParam))
        
         {
        
    case SB_LINEUP:
        
          iVscrollPos -= 1 ;
        
            break ;
        
   
        
    case SB_LINEDOWN:
        
            iVscrollPos += 1 ;
        
            break ;
        

    case SB_PAGEUP:
        
            iVscrollPos -= cyClient / cyChar ;
        
            break ;
        
   
        
    case SB_PAGEDOWN:
        
            iVscrollPos += cyClient / cyChar ;
        
            break ;
        
   
        
    case SB_THUMBPOSITION:
        
            iVscrollPos = HIWORD (wParam) ;
        
            break ;
        
   
        
    default :
        
            break ;
        
         }
        

    iVscrollPos = max (0, min (iVscrollPos, NUMLINES - 1)) ;
        
    if (iVscrollPos != GetScrollPos (hwnd, SB_VERT))
        
         {
        
            SetScrollPos (hwnd, SB_VERT, iVscrollPos, TRUE) ;
        
            InvalidateRect (hwnd, NULL, TRUE) ;
        
         }
        
            return 0 ;
        
    case WM_PAINT:
        
            hdc = BeginPaint (hwnd, &ps) ;
        
            for (i = 0 ; i < NUMLINES ; i++)
        
            {
        
                   y = cyChar * (i - iVscrollPos) ;
        
                   TextOut (hdc, 0, y,
        
                           sysmetrics[i].szLabel,
        
                           lstrlen (sysmetrics[i].szLabel)) ;
        
   
        
                   TextOut (hdc, 22 * cxCaps, y,
        
                           sysmetrics[i].szDesc,
        
                           lstrlen (sysmetrics[i].szDesc)) ;
        
   
        
                   SetTextAlign (hdc, TA_RIGHT | TA_TOP) ;
        
                   TextOut (hdc, 22 * cxCaps + 40 * cxChar, y, szBuffer,
        
                           wsprintf (szBuffer, TEXT ("%5d"),
        
                                          GetSystemMetrics (sysmetrics[i].iIndex))) ;
        
                   SetTextAlign (hdc, TA_LEFT | TA_TOP) ;
        
        }
        
            EndPaint (hwnd, &ps) ;
        
            return 0 ;
        

    case WM_DESTROY:
        
            PostQuitMessage (0) ;
        
            return 0 ;
        
    }
        
    return DefWindowProc (hwnd, message, wParam, lParam) ;
        
}
        

新的CreateWindow呼叫在第三个参数中包含了WS_VSCROLL窗口样式,从而在窗口中加入了垂直滚动条,其窗口样式为:

WS_OVERLAPPEDWINDOW | WS_VSCROLL
       

WndProc窗口消息处理程序在处理WM_CREATE消息时增加了两条叙述,以设置垂直滚动条的范围和初始位置:

SetScrollRange (hwnd, SB_VERT, 0, NUMLINES – 1, FALSE) ;
       
SetScrollPos (hwnd, SB_VERT, iVscrollPos, TRUE) ;
       

sysmetrics结构具有NUMLINES行文字,所以滚动条范围被设定为0至NUMLINES-1。滚动条的每个位置对应于在显示区域顶部显示的一个文字行。如果卷动方块的位置为0,则第一行会被放置在显示区域的顶部。如果位置大于0,其它行就会出现在显示区域的顶部。当位置为NUMLINES-1时,则最后一行文字出现在显示区域的顶部。

为了有助于处理WM_VSCROLL消息,在窗口消息处理程序中定义了一个静态变量iVscrollPos,这一变量是滚动条内卷动方块的目前位置。对于SB_LINEUP和SB_LINEDOWN,只需要将卷动方块调整一个单位的位置。对于SB_PAGEUP和SB_PAGEDOWN,我们想移动一整面的内容,或者移动cyClient /cyChar个单位的位置。对于SB_THUMBPOSITION,新的卷动方块位置是wParam的高字组。SB_ENDSCROLL和SB_THUMBTRACK消息被忽略。

在程序依据收到的WM_VSCROLL消息计算出新的iVscrollPos值后,用min和max宏来调整iVscrollPos,以确保它在最大值与最小值之间。程序然后将iVscrollPos与呼叫GetScrollPos取得的先前位置相比较,如果卷动位置发生了变化,则使用SetScrollPos来进行更新,并且呼叫InvalidateRect使整个窗口无效。

InvalidateRect呼叫产生一个WM_PAINT消息。SYSMETS1在处理WM_PAINT消息时,每一行的y坐标计算公式为:

cyChar * i
       

在SYSMETS2中,计算公式为:

cyChar * (i – iVscrollPos)
       

循环仍然显示NUMLINES行文字,但是对于非零值的iVscrollPos是负数。程序实际上在显示区域以外显示这些文字行。当然,Windows不会显示这些行,因此屏幕显得干净和漂亮。

前面说过,我们一开始不想弄得太复杂,这样的程序代码很浪费,效率很低。下面我们对此加以修改,但是先要考虑在WM_VSCROLL消息之后更新显示区域的方法。

<br />本文来自【C语言中文网】:<a href=”http://see.xidian.edu.cn/cpp/html/1112.html” target=”_blank”>http://see.xidian.edu.cn/cpp/html/1112.html</a>