编译CEF, 让其支持音视频播放

因为项目需要,需要让CEF支持播放h.264和mp3视频,但从CEF官网上下载得到的并不支持, 需要自己进行重新编译;在经常大量的搜索后发现, 没有人经常性的编译CEF,有的只是之前的比较老的版本,所以决定自己编译。下面展示成果:

在决定要自己进行编译后,真的是一脸茫然,不知道该如何下手,在花了大量的时间,阅读了大量的材料后,把我的经验写下来,让后来者能少走弯路。

开始

在CEF的官方仓库上有关于如何进行编译的WIKI, 当前的地址是这里,有做了详细的说明。

编译CEF的过程可以手动进行,也有自动化的编译脚本,作为一个只要结果,不要过程的人,我选择了自动化编译 。但在进行自动化编译前,需要把编译系统搭建好,windows下编译的要求:

需要windows 7 以上的系统(我选择了windows server 2016数据中心版), Visual Studio 2017 以上,安装windows SDK 10.0.18362 版本 (千万别要安装其他版本,我就是浪费了好几个小时的时间),Ninja( 这个就不用我们安装,自动化工具会自动安装);

硬件要求:最少8G内存(推荐32G以上),至少90G以上的剩余硬盘空间,要有足够快的网速(100Mbps以上), 足够快的CPU(2.4Ghz 16线程),最好是SSD。

这个要求太高了, 我自己没这么好的电脑,而且只要编译一次,最多3、4天时间,我决定购买阿里云服务器(建议买香港的服务器,因为你知道的一些原因,国内很多文件下载不下来), 本来想用DigitalOcean的,因为没有流量费,还便宜, 但看了下来, 他们不提供windows系统,徒耐何?

等系统购买好后,在document目录下创建一个cef文件夹,先下载安装python(3点几版本就ok, 记得安装的时候要勾上把python加入到系统环境变量), 从这里下载自动化编译脚本,如下放置,并在该目录下创建一个files文件夹,用于存放之后的文件

然后打开cmd, 运行以下命令:

C:\Users\Administrator\Documents\cef>python automate-git.py --download-dir=C:\Users\Administrator\Documents\cef\files --branch=4044 --no-debug-build

其中–download-dir表示用哪个文件夹来存储下载的文件, –brand表示要编译的哪一个cef的分支,这个编号可以从这里获取

然后等待脚本下载各种工具(包括git, Ninja)和源码(chroium源码非常大, 十多个G, 这个是下载时间最长的), 之后的目录如下:

上面的out_打头的文件夹是最后编译生成的

在这之前如果你没有安装好visual studio 2019(文档要求2017以上,我选择了2019), 等源码下载完成后编译时会报错,不用怕, 这个时候安装它,安装完成之后,要修改几个文件,修改(建议安装Notepad++来修改,好用)这几个文件的目的是把音视频播放的功能加进去:

第一个文件:C:\Users\Administrator\Documents\cef\files\chromium\src\third_party\ffmpeg\chromium\scripts\build_ffmpeg.py

找到 configure_flags['Chrome'] 修改成以下内容:

configure_flags['Chrome'].extend([
'--enable-decoder=aac,h264,mp3,mpeg4,amrnb,amrwb,flv',
'--enable-demuxer=aac,mp3,mov,avi,amr,flv',
'--enable-parser=aac,h264,mpegaudio,mpeg4video,h263',
])

第二个要修改的文件是一个头文件

C:\Users\Administrator\Documents\cef\files\chromium\src\third_party\ffmpeg\chromium\config\Chrome\win\ia32\config.h

我这里修改的是32位的目录(ia32), 如果你是要编译64位的, 也要把x64的一起修改了

要第4行的注释去掉,修改下面这样:

define FFMPEG_CONFIGURATION "--disable-everything --disable-all --disable-doc --disable-htmlpages --disable-manpages --disable-podpages --disable-txtpages --disable-static --enable-avcodec --enable-avformat --enable-avutil --enable-fft --enable-rdft --enable-static --enable-libopus --disable-debug --disable-bzlib --disable-error-resilience --disable-iconv --disable-lzo --disable-network --disable-schannel --disable-sdl2 --disable-symver --disable-xlib --disable-zlib --disable-securetransport --disable-faan --disable-alsa --disable-autodetect --enable-decoder='vorbis,libopus,flac' --enable-decoder='pcm_u8,pcm_s16le,pcm_s24le,pcm_s32le,pcm_f32le,mp3' --enable-decoder='pcm_s16be,pcm_s24be,pcm_mulaw,pcm_alaw' --enable-demuxer='ogg,matroska,wav,flac,mp3,mov' --enable-parser='opus,vorbis,flac,mpegaudio,vp9' --extra-cflags=-I/usr/local/google/home/jrummell/chromium/src/third_party/opus/src/include --disable-linux-perf --x86asmexe=nasm --optflags='\"-O2\"' --enable-decoder='theora,vp8' --enable-parser='vp3,vp8' --toolchain=msvc --extra-cflags=-I/usr/local/google/home/jrummell/chromium/src/third_party/ffmpeg/chromium/include/win --enable-cross-compile --cc=clang-cl --ld=lld-link --nm=llvm-nm --ar=llvm-ar --extra-cflags=-O2 --extra-cflags=-m32 --extra-cflags=-imsvc/usr/local/google/home/jrummell/chromium/src/third_party/ffmpeg/../depot_tools/win_toolchain/vs_files/9ff60e43ba91947baca460d0ca3b1b980c3a2c23/win_sdk/Include/10.0.18362.0/um --extra-cflags=-imsvc/usr/local/google/home/jrummell/chromium/src/third_party/ffmpeg/../depot_tools/win_toolchain/vs_files/9ff60e43ba91947baca460d0ca3b1b980c3a2c23/win_sdk/Include/10.0.18362.0/shared --extra-cflags=-imsvc/usr/local/google/home/jrummell/chromium/src/third_party/ffmpeg/../depot_tools/win_toolchain/vs_files/9ff60e43ba91947baca460d0ca3b1b980c3a2c23/win_sdk/Include/10.0.18362.0/winrt --extra-cflags=-imsvc/usr/local/google/home/jrummell/chromium/src/third_party/ffmpeg/../depot_tools/win_toolchain/vs_files/9ff60e43ba91947baca460d0ca3b1b980c3a2c23/win_sdk/Include/10.0.18362.0/ucrt --extra-cflags=-imsvc/usr/local/google/home/jrummell/chromium/src/third_party/ffmpeg/../depot_tools/win_toolchain/vs_files/9ff60e43ba91947baca460d0ca3b1b980c3a2c23/VC/Tools/MSVC/14.23.28105/include --extra-cflags=-imsvc/usr/local/google/home/jrummell/chromium/src/third_party/ffmpeg/../depot_tools/win_toolchain/vs_files/9ff60e43ba91947baca460d0ca3b1b980c3a2c23/VC/Tools/MSVC/14.23.28105/atlmfc/include --extra-ldflags='-libpath:/usr/local/google/home/jrummell/chromium/src/third_party/depot_tools/win_toolchain/vs_files/9ff60e43ba91947baca460d0ca3b1b980c3a2c23/VC/Tools/MSVC/14.23.28105/atlmfc/lib/x86' --extra-ldflags='-libpath:/usr/local/google/home/jrummell/chromium/src/third_party/depot_tools/win_toolchain/vs_files/9ff60e43ba91947baca460d0ca3b1b980c3a2c23/win_sdk/Lib/10.0.18362.0/ucrt/x86' --extra-ldflags='-libpath:/usr/local/google/home/jrummell/chromium/src/third_party/depot_tools/win_toolchain/vs_files/9ff60e43ba91947baca460d0ca3b1b980c3a2c23/win_sdk/Lib/10.0.18362.0/um/x86' --extra-ldflags='-libpath:/usr/local/google/home/jrummell/chromium/src/third_party/depot_tools/win_toolchain/vs_files/9ff60e43ba91947baca460d0ca3b1b980c3a2c23/VC/Tools/MSVC/14.23.28105/lib/x86' --enable-decoder='aac,h264' --enable-demuxer=aac --enable-parser='aac,h264' -enable-decoder=’rv10,rv20,rv30,rv40,cook,h263,h263i,mpeg4,msmpeg4v1,msmpeg4v2,msmpeg4v3,amrnb,amrwb,ac3,flv’ -enable-demuxer=’rm,mpegvideo,avi,avisynth,h263,aac,amr,ac3,flv,mpegts,mpegtsraw’ -enable-parser=’mpegvideo,rv30,rv40,h263,mpeg4video,ac3′"

修改config.h文件

然后修改最后一个文件:

C:\Users\Administrator\Documents\cef\files\out_master\Debug_GN_x86\args.gn

在文件的末尾加上两行

proprietary_codecs=true
ffmpeg_branding="Chrome"

然后再次执行编译命令:

C:\Users\Administrator\Documents\cef>python automate-git.py --download-dir=C:\Users\Administrator\Documents\cef\files --branch=4044 --no-debug-build

然后坐着等吧, 如果一切顺利,几个小时后就可以拿到编译的文件了, 最后的libcef.dll比没有视频的大了好几M。

这也是我第一次体验,一个代码的编译要花5、6个小时的,之前我写delphi, 写go, 那都是秒级的。

最后,如果你不想自己也走一次这个过程(实在是时间长, 还不便宜), 我附上我的成果:

点这里下载:cef_binary_81.3.10+gb223419+chromium-81.0.4044.138_windows32.zip

对了,我们公司招人, 如有兴趣,联系我-

链接: https://pan.baidu.com/s/1VVQ2SANZ28VdW0cv6aWFfw 密码: h73a

教你如何配置Ubuntu用于高效、高质量的发送邮件

 

       在网站上线后,经常会遇到的一个需求就是发送邮件, 比如注册验证邮件,密码找回邮件等。我尝试过好多方法,其中最简单的方法就是花钱买别人的服务, 比如说国外的mailgun, 国内的sendcloud等, 相比而言, 他们的服务好,只是要花钱, mailgun一个月内小于1w封是免费的, sendcloud一天只能免费发200封, 很多时候这些数字很容易就超了。还有一种方法是注册一个免费邮件,然后通过这个免费邮箱提供的smtp服务发邮件。这种方法的缺点也很明显, 分信量照样限制, 而且,你还得设置发信间隔, 如果一分钟内你发送的量超过一个免费邮箱设定的限值, 你的邮件也发不出去。 所以, 就得找一个更好的, 最好就是能免费的发邮件的方法。

      免费的发邮件的方法,现成的有一种, 就是sendmail。 今天要我教大家的也是如何配置,使得我们用sendmail发出的邮件也可以被邮件服务商认为是正常的邮件。

      通常通过使用sendmail发出的邮件会有以下几个缺点

  1. 在邮件收到时,会显示www-data代发的字样,如图所示localhost.localdomian, 很容易被当做垃圾邮件,使得我们的发信质量大大降低
  2. 没有配置的好的情况下, 发送一封信的时间会很长,可达4-5秒的时间,简直不能忍受啊
  3. 被拒绝或者邮件被评为垃圾邮件的可能性很大

因为上面的问题,今天我就教大家如何通过一些配置和操作来使得我们用sendmail发出的邮件速度快, 接收率高。 ps: 本教程是基于 ubuntu 14

1. 先注册一个域名邮箱

要使我们的发信可受信任,就需要用我们的域名做发信箱, 比如说i#mengxi.me这样子的。设置这样的邮箱, 我推荐使用腾讯企业邮箱 http://exmail.qq.com.  虽然他们没有给我钱, ^_^。

可以注册一个免费试用账号:http://exmail.qq.com/signupfree#signup/free

具体的怎么注册,就按他们的提示信息走就好,然后 设置好你的域名解析项,就可以直接使用了。

 

2. 设置sendmail

先在ubuntu上运行命令 sudo apt-get install sendmail 安装sendmail安装sendmail

安装好sendmail后,再修改hosts文件

修改hosts

确保如下图所示在红圈里加入你的主机名,主机名可以通过命令hostname来获取

填写主机名

查看主机名

如果要修改主机名, 请使用下面的命令:

sudo vi /etc/hostname

然后, 重启网络服务

重新加载网络

重启网络服务后,使用命令 sendmailconfig来配置sendmail, 在配置过程中, 对任何的询问都输入Y

配置sendmail

配置sendmail

 

到这里我们的sendmail就配置好了,但是,你觉得到这里就完了?不不不!没完, 我们还得设置我们的php

3. 设置php.ini

其实现在php已经能够通过mail函数正常发送邮件了,但是这个时候你发的邮件要很长时间才能到达你的收件箱,且, 邮件会被放到垃圾箱,会有这样的文字, “邮件由[email protected]代发

20151205131213

相信这也不是你所想要的,接下来我们就通过一个配置来,解决这个问题。

配置你的php.ini. 如果你使用的是nginx的话,配置文件在 /etc/php5/fpm/php.ini

nginx配置php.ini

如果是apache2的话,配置文件在/etc/php5/apache2/php.ini

apache2配置php.ini

我们找到sendmail_path这个key

修改sendmail参数

在我圈出来的红框里写上你自己的邮箱, 最好是你自己的域名邮箱, 这样更加容易保证发信的成功率。

然后重启php5-fpm ( Nginx ) 或apache2

对于Nginx服务器:

sudo service php5-fpm restart

对于apache2

sudo service apache2 restart

 

好了, 你现在就可以快速、高效的发送不限数量的邮件了

 

本文为原创文章, 转载请注明出处, 谢谢!

As3 Matrix 理解与应用

TheMatrixWallpaper800

一、介绍

Matrix 字面意思是矩阵, 是一个数学概念。这个词在本科线性代数里有详细说明,它是数的一个排列,支持好多种数学运算!在flash里,在3D运算中,矩阵 真的是无处不在。flash中,你要使一个可显示对象(DisplayObject )旋转,倾斜,调位置你都可以通过矩阵来完成,虽然你还有其他更简单的方法,就是通过修改rotation, x, y 等值来达到同样的目的。在每个显示对象中,都有一个属性:transform. 这里就包含了显示对象的各种变换,其中一项,就是matrix! 可见matrix在flash中也有重要的作用。

二、为什么是矩阵

矩阵对于很多数学恐惧者来说,很让人无法直视, 真的很让人头痛!但是它却在计算机图形学里起着很重要的作用。我们从简单说起!

2.1 坐标系统

在我们的学习中,就已经深刻的学习了坐标系统的概念,要分析空间物体及其对应关系,就离不开坐标系这个概念!我们最熟悉的是笛卡尔坐标系, 直角坐标系!在坐标系里又可以找到很多概念,这些概念对于我们理解这矩阵有着很重要的作用。 比如说,物体坐标系, 旋转坐标系,世界坐标系等。

世界坐标系,简单来说,它是空间里处于全局位置,永远不变的坐标系。举个例子,我们要研究小猫在房间里的运动路径,这时我们可以选择房间里的一个角落来作为我们的世界坐标系。 物体坐标系,就是以处于世界坐标系下的一个物体上的某一点构成的坐标系,假如我们选择小猫正常站立形态下后脚作为原点建立一个直角坐标系,那么,这个坐标系就是小猫的物体坐标系,这个物体坐标系相对世界坐标系而言,它会随着猫的运动而运动!

2.2 物体变换

我们选定了物体坐标系, 小猫一天中会在房间里各处走动,或跑,或跳,或睡觉等,那么,这时的小猫就相对世界坐标系而言,它发生了一些变换,可能包括:平移,旋转,当然还可能包括缩放(缩着身子)。 假如小猫在进食时在身上沾了一粒米,那么,在小猫在世界坐标系里进行变换时,这粒米相对相对世界坐标系又是怎么一个情形呢? 这就相当于在数学上一个物体从一个坐标系变换到另一个坐标系下,如何确定其位置的问题,对于这个问题,用矩阵可以很好的来解决这个问题!

在flash里,矩阵matrix也是一样用于处理坐标变换的问题。相信大家可能曾经会想过一个问题。 一个显示对象有宽,有高,那么对于这些内容是如何分层次的画出来的,又如何确定物体内部每个点在舞台上的位置的呢? 没错,就是通过坐标变换来实现的!

三、矩阵

flash里 矩阵用于将点从一个坐标系转换到另一个坐标系, 使用下面的方程:

clip_image002

其中P代表变换后的坐标点, N代表变换前坐标点(都是以列向量的形式)。

显示对象的transform属性的matrix就是这个M, 它用于将物体坐标系里的点变换到显示对象父坐标中。

image

这个M是一个3维矩阵,如下所示:

Matrix class properties in matrix notation showing    assumed values for u, v, and w

转换过程如下:

clip_image002[4]

其中a, d 影响显示对象的缩放, c, d 影响显示对象的倾斜, a, b, c, d同时影响显示对象的旋转。 tx, ty 代表显示对象相对于父对象的位置偏移!如果clip_image002[6] 都为0 的话,

那么clip_image002[8]就是 clip_image002[10] 所以说, tx, ty 就是显示对象的坐标。

四、应用

1. 获取对象里某一点在父对象中的位置。

var p:Point = new Point( x0, y0 );
p = displayObject.transform.matrix.transformPoint( p );

2. 绘制渐变图

代码示例略

3. 中心旋转

public function rotateAroundCenter (ob:DisplayObject, angleDegrees:Number):void
{
    var matrix:Matrix = ob.transform.matrix; 

    var rect:Rectangle = ob.getBounds(ob.parent); 

    matrix.translate(- (rect.left + (rect.width/2)), - (rect.top + (rect.height/2))); 

    matrix.rotate((angleDegrees/180)*Math.PI); 

    matrix.translate(rect.left + (rect.width / 2), rect.top + (rect.height / 2));

    ob.transform.matrix = matrix;
}

4. 绕点旋转

// get matrix object from your MovieClip (mc)
var m:Matrix = mc.transform.matrix;

// set the point around which you want to rotate your MovieClip (relative to the MovieClip position)
var point:Point = new Point(10, 10);

// get the position of the MovieClip related to its origin and the point around which it needs to be rotated
point = m.transformPoint(point);
// set it
m.translate( -point.x, -point.y);

// rotate it of 30°
m.rotate(30 * (Math.PI / 180));

// and get back to its "normal" position
m.translate(point.x, point.y);

// finally, to set the MovieClip position, use this
mc.transform.matrix = m;

// or this
mc.x = m.tx;
mc.y = m.ty;
mc.rotation += 30;

值得注意的是, 你通过Matrix的rotate方法旋转得到的效果与你通过显示对象的rotation属性得到的是不样的!这是因为 matrix.rotate 以父对象的原点为旋转中心,旋转的是物体坐标系, 所以,通过rotate是会改变tx, ty的。 如下图所示:

image

希望本文章能够帮到你!转载请注明梦溪笔记!

flash 动态嵌入字体, 嵌入你要的字符

一、简单

在使用As3 开发软件的过程,我们会遇到的比较头痛的一个问题是字体的处理!特别是针对中文这样的语言!

我们都知道嵌入式的字体比非嵌入的字体在效果上好很多, 所以我们希望在很多项目上能够使用嵌入的字体。

通常的嵌入字体的做法是通过flash cs 这些工具 来进行字体生成,如果你要生成一个字体,而这个字体可能的符号是所有字符集的时候,这个字体文件将会变得特别大,如果你要放在网络环境中,那直接就是一个噩梦!要在网络上加载1M文件会让用户在等待中发狂。。。

 

一个可行的解决方案是,用户用到了某个字体的特定字符,我们就只把这个字符嵌入到swf中,这样生成的文件将会小很多,文件的大小以出现的字符数量定!而且这样的好处就是用户可以使用他机器上的任何一个字体。 那么这个如何在技术上实现呢?

二、技术实现

2.1 Haxe

有一种开源的语言叫Haxe, 这种语言超NB, 她只要你写一次,就可以直接编译成各种语言, 诸如:c++, php, as3, c#, javascript, NekoVM, java 等, 让人觉得不可思议。 她有actionscript 3 的传统,所以,她对flash, as3 有超强的支持能力,使得你可以用她了做很多关于as3,swf等的事情,就比如说我们现在讨论的动态生成嵌入了字体的swf文件。这是一件多么有意思的事情

2.2 关于swf

SWF是一种文件格式,大家都知道的,而有意思的是这个格式是开放标准的,意思是,你可以查到关于SWF这个文件格式的任意一个规范,如果你够NB,你也可以阅读这个规范,并根据这个规范写出你自己的swf播放器,swf生成器,并且编辑swf! 你所知道的一些软件,比如说硕思这个swf反编译工具 就是根据SWF文件格式来进行反编译工具的,你也可以模仿它来写一个自己的swf加密混淆或者是反编译工具。

2.3 动态生成技术

Google Code 上有一个开源项目,她叫hxswfml, 她是基于Haxe 而来的一个项目, 简单的来说,她可以让你写一个xml文件,然后通过她,把这个xml文件编译成swf文件,够COOL吧!因为 Haxe 可以编译成php, js 等语言,所以,这能够让你在你的网站项目中,动态的生成 swf文件, 一下子让你的网站充满了神奇! 我们可以用她来完成动态生成swf字体文件的任务!在下面体验一下吧!

 

在文本区域内输入你需要的字符, 写上字体名( FontName 用于指定 字体名,类似于css中的font-family ), 写上调用时的类名( className 用于指定生成这个字体的类名,用于加载字体时获取字体引用 ) , 写完这些后,点击 Generate SWF 来生成你的swf 字体文件。 Test Font 用于测试字体 文件是不是有效 微笑 快来体验一下吧

 

2.4 在项目中使用

如果你的项目是基于AIR技术, 你可以直接把hxswfml 编译成swc文件,在项目中使用即可,我这里给你提供一个已经编译好的AIR 可用swc 文件flash as3 可以swc 文件.

如果你是基于客户端的技术,这时候你要把hxswfml编译成exe文件, 这个exe文件可以在该项目的下载中找到,不过要注意:

如果你直接使用下载的hxswfml.exe 你会发现运行不了,出现一个加载库失败提示

Error : Could not load module std@parse_xml__2

这是因为hxswfml需要依赖几个动态连接库,你只需要把这几个dll放到hxswfml.exe目录下,就可直接运行了.

如果你不知道去哪里找这些库,我为你准备好了,点击下载就ok

 

 

2.5 编译cpp 配置

如果需要自己编译这个hxswfml.exe文件,那么你需要配置工作环境,你可以根据这篇文章的指导来进行, HXCPP配置

结束语

到这里, 你已经了解了如何去实现动态生成所需要的swf字体, 希望这篇文章能为你解决问题!

如果你有什么问题,可以联系! 至于联系方式,相信你会找到 微笑

 

本文章为原创!转载请注明作者及出处!

smaile

Delphi 实现事件侦听与触发

我最近在Cnblogs上看到有一些朋友在讨论关于事件侦听,他的想法是,给Delphi即有的事件,例如click, mouse加上多播机制,在我认为,这种做法是不可取的,delphi在设计之初,就是把所谓的事件当做是一个回调函数来处理,这就使得每个事件只能有一个响应者,在这之上给组件加上事件多播机制只会让代码更加难懂,更加难用,增加代码量。而且用这种方法增加的事件多播机制只能用于界面上, 不能很好的用于处理数理, 实现数据与界面的分离, 降低耦合度, 在此, 我把我的事件多播实现机制与原理分享给大家. 至于好与坏, 请在实践中进行检验.

 

武稀松的实现方法相似, 本方法在实现上用到了以下几个知识点:

  1. RTTI的 TMethod 结构体, 该结构体可用于比较两个事件处理方法是否相同, 用于移除事件侦听.
  2. 用于了指针, 用于传递事件数据, 需要了解如何获取内存, 释放内存
  3. 泛型. 用于保存事件数据.

下面来具体分析 代码.

unit EventListener;

interface
uses System.Generics.Collections;

type
  TNwEventProc = procedure(const EventType:string;EventData:Pointer) of object;
  INiEventListener = interface
    procedure PerformEvent(EventType: String; EventData: Pointer);
    procedure removeEventListener(EventType: String; EventProc: TNwEventProc);
    procedure addEventListener(EventType: String; EventPro: TNwEventProc);
  end;

  TEventData = record
    EventType:String;
    EventProc:TNwEventProc;
  end;
  TNTEventList = TList<TEventData>;
  TNwEventListener = class(TInterfacedObject, INiEventListener)
  protected
    FEventList:TNTEventList;
  public
    procedure PerformEvent(EventType: String; EventData: Pointer);
    procedure removeEventListener(EventType: String; EventProc: TNwEventProc);
    procedure addEventListener(EventType: String; EventPro: TNwEventProc);

    destructor Destroy; override;
    constructor Create();
  end;

TNwEventProc 是事件处方法, 有两个参数, 一个是事件类型EventType, 一个是事件数据 EventData.  事件类型是字符串, 用于自己定义事件. 事件数据是一个指针, 指向事件的数据所在内存. 这个内存需要你自己手动去管理.

INiEventListener  是事件处理器的接口, 它包括三个就去, 分别是执行事件PerformEvent, 添加事件 AddEventListener 和删除事件RemoveEventListener. 当我们需要在某一事件发生的时候处理某数据时, 就可以调用添加事件AddEventListener来增加某个事件的侦听, 当这个事件发生时, 相应的事件就会被处理. 如果你不想再这个事件发生时处理数据, 就需要调用删除事件RemoveEventListener来移除事件的侦听. 在使用时, 你可以使用一个全局的事件处理器来管理事件; 如果你只想对某一类增加事件处理功能, 这时, 你的类就需要继承自TNwEventListener, 该类已经为你实现了INiEventListener接口, 在需要让某一个事件触发时,你只需要调用PerformEvent就可以. 这个时候的事件数据就需要事先进行约定.

下面附上全部的事件处理器代码.

unit EventListener;

interface
uses System.Generics.Collections;

type
  TNwEventProc = procedure(const EventType:string;EventData:Pointer) of object;
  INiEventListener = interface
    procedure PerformEvent(EventType: String; EventData: Pointer);
    procedure removeEventListener(EventType: String; EventProc: TNwEventProc);
    procedure addEventListener(EventType: String; EventPro: TNwEventProc);
  end;

  TEventData = record
    EventType:String;
    EventProc:TNwEventProc;
  end;
  TNTEventList = TList<TEventData>;
  TNwEventListener = class(TInterfacedObject, INiEventListener)
  protected
    FEventList:TNTEventList;
  public
    procedure PerformEvent(EventType: String; EventData: Pointer);
    procedure removeEventListener(EventType: String; EventProc: TNwEventProc);
    procedure addEventListener(EventType: String; EventPro: TNwEventProc);

    destructor Destroy; override;
    constructor Create();
  end;

var
  IEventListener:INiEventListener;

implementation
uses System.SysUtils, Rtti;
{ TNwEventListener }

procedure TNwEventListener.addEventListener(EventType: String;
  EventPro: TNwEventProc);
var
  EventData:TEventData;
begin
  EventData.EventType := EventType;
  EventData.EventProc := EventPro;
  FEventList.Add(EventData);
end;

constructor TNwEventListener.Create;
begin
  inherited;
  FEventList := TNTEventList.Create();
end;

destructor TNwEventListener.Destroy;
begin
  FEventList.Clear;
  FreeAndNil(FEventList);
  inherited;
end;

procedure TNwEventListener.PerformEvent(EventType: String; EventData: Pointer);
var
  I:Integer;
begin
  for I := FEventList.Count - 1 downto 0 do
  begin
    if FEventList.Items[I].EventType = EventType then
      FEventList.Items[I].EventProc(EventType, EventData);
  end;
end;

procedure TNwEventListener.removeEventListener(EventType: String;
  EventProc: TNwEventProc);
var
  I: Integer;
  EventData:TEventData;
  ItemProc:TNwEventProc;

  function SameMethod(S, T:TNwEventProc):Boolean;
  var
    MethodS, MethodT:TMethod;
  begin
    MethodS := TMethod(S);
    MethodT := TMethod(T);
    Result := ( MethodS.Code = MethodT.Code ) and ( MethodS.Data = MethodT.Data );
  end;

begin
  for I := FEventList.Count - 1 downto 0 do
  begin
    EventData := FEventList.Items[I];
    ItemProc := EventData.EventProc;
    if (EventData.EventType = EventType) and SameMethod(ItemProc, EventProc) then
    begin
      FEventList.Delete(I);
    end;
  end;

end;

end.

你可能会关心如何使用的问题, 下面,附上使用代码

//使用全局对象的方法

type
  TUseEventListener = class
  protected
    procedure OnEvent( EventType:String; EventData:Pointer );
  public
    procedure InitEvents;
  end;

var
  UseEvent:TUseEventListener;
begin
    { TUseEventListener }
  procedure TUseEventListener.InitEvents;
  begin
    IEventListener.addEventListener( 'OnMessage', OnEvent );
  end;
  
  procedure TUseEventListener.OnEvent( EventType:String; EventData: Pointer );
  begin
    ShowMessage( String( PChar( EventData ) ) );
  end;

  IEventListener := TNwEventListener.Create;
  UseEvent := TUseEventListener.create;
  UseEvent.InitListener;
  
  IEventListener.PerformEvent( 'OnMessage', PChar( 'The Message should be shown.' ));
end.

 

使用类对象的方法:

const
  evtDataChanged = 'evtDataChanged';

Type
  PData = ^TData;
  TData = record
    Name: String;
    City: String;
    CellPhone: String;
    Age: Integer;
  end;

  //数据处理类, 用于提供数据
  TNwDataClass = class( TNwEventListener )
  public
    procedure AddData( Name, City, CellPhone:String; Age: Integer );
  end;

  //界面显示类
  TNwInterface = class( TForm )
    procedure FormCreate( Sender: TObject );
  protected
    procedure OnEvent( EventType:String; EventData:Pointer );
  public
    procedure AddDataToList( Data: TData );
  end;

  // TNwDataClass 应该有一个全局的实例, 用于提供数据. 在下面的代码中, 就以
  // instanceDataClass 为这个实例
implementation
 
 { TNwDataClass  }
 procedure TNwDataClass.AddData( Name, City, CellPhone:String; Age: Integer );
 var
   Data: PData;
 begin
   //数据处理代码.

   GetMem( Data, SizeOf( TData ) );
   try
     Data^.Name := Name;
     Data^.City := City;
     Data^.CellPhone := CellPhone;
     Data^.Age := Age;
     Self.PerformEvent( evtDataChanged, Data );
   finally
     FreeMem( Data );
   end;
 end;

  { TNwInterface }
  procedure TNwInterface.FormCreate( Sender: TObject );
  begin
    instanceDataClass.addEventListener( evevtDataChanged, OnEvent );
  end;
  
  procedure TNwInterface.OnEvent( EventType:String; EventData: Pointer );
  begin
    AddDataToList( PData( EventData )^ );
  end;

  procedure TNwInterface.AddDataToList( Data: TData );
  begin
    //用于处理显示数据的代码.
  end;

 

总体来看, 这种做法方便简单实用. 欢迎在使用过程中有什么问题与我交流!