自定义struts2标签

网上有人指出基本步骤如下:
One of the developers recently mentioned they is writing a document about how to do this and I think there was talk about creating a maven archetype.
In the mean-time, try following some of existing code. The current version of the struts2 yui plugin provides a nice simple example as it only contains two tags and is packaged as a a plugin (http://code.google.com/p/struts2yuiplugin/source).【见附件:】
I find it's easiest to follow that example completely - use the maven plugin to generate the TLD and package your tags as a struts2 plugin. This isn't mandatory though as you can write the TLD by hand and include the classes in your webapp directly.
In general:
1. create a bean that extends UIBean or ClosingUIBean (for open & close tags), identifies the templates and populates the template context
2. create a tag class that extends AbstractUITag that creates the bean instance and sets the properties of the bean
3. create the templates referenced by the bean
After completing these 3 steps you can start using your tag if you write a TLD and include it in your classpath. If you copy the pom.xml from the example able you can use maven to generate the TLD (if you've used the appropriate annotations).
The remaining steps allowing your tags to be uses within freemarker and velocity templates (rather than only JSP)
4. Create a TagLibrary and TagModels for Freemarker and Directive's for velocity
5. Create struts-plugin.xml that declares your TagLibrary bean
----------------------------------------------------------------------------------------------------------------------------------------------------------------------
plugin配置中:
<bean type="org.apache.struts2.views.TagLibrary" name="tagtest" class="test.s2.plugin.views.TestTagLibrary" />
freemarker models中:
    public AncorModel getAncor() {
        if (this.ancor == null) {
            this.ancor = new AncorModel(this.stack, this.req, this.res);
        }
        return this.ancor;
    }
那么应该这样使用:
<@tagtest.ancor ... />
注意前缀tagtest和后缀ancor分别对应蓝色斜体字。【shit!】
然而velocity的用法又不一样:
AbstractDirective:
    public String getName()
    {       
        return "testa" ;
    }
#testa (... )
这个与配置无关,是由getName返回的来决定的。

Continue reading 自定义struts2标签

strut2笔记

1:plugin-tiles在jdk1.4下面有问题,ServletContextListener重写一下可以解决问题。代码如下:(注意使用 RETROTRANSLATOR转换jdk5 编译的包时需要指明classpath="jdk1.4/rt.jar的路径")
https://issues.apache.org/struts/browse/WW-2897
package co.ntelagent.client.web.ps.application;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.struts2.tiles.ConfiguredServletContext;
import org.apache.struts2.tiles.StrutsTilesContainerFactory;
import org.apache.tiles.TilesContainer;
import org.apache.tiles.TilesException;
import org.apache.tiles.access.TilesAccess;
import org.apache.tiles.factory.TilesContainerFactory;
public class MyStrutsTilesListener implements ServletContextListener
{
    /**
     * Log instance.
     */
    protected static final Log LOG =
        LogFactory.getLog(MyStrutsTilesListener.class);
    private static final Map INIT;
    static
    {
        INIT = new HashMap();
        INIT.put(TilesContainerFactory.CONTAINER_FACTORY_INIT_PARAM,
                 StrutsTilesContainerFactory.class.getName());
    }
    /**
     * Initialize the TilesContainer and place it
     * into service.
     *
     * @param event The intercepted event.
     */
    public void contextInitialized(ServletContextEvent event)
    {
        ServletContext servletContext = event.getServletContext();
        try
        {
            TilesContainer container = createContainer(servletContext);
            TilesAccess.setContainer(servletContext, container);
        }
        catch (TilesException e)
        {
            throw new IllegalStateException("Unable to instantiate container.");
        }
    }
    /**
     * Remove the tiles container from service.
     *
     * @param event The intercepted event.
     */
    public void contextDestroyed(ServletContextEvent event) {
        ServletContext servletContext = event.getServletContext();
        try
        {
            TilesAccess.setContainer(servletContext, null);
        }
        catch (TilesException e) {
            LOG.warn("Unable to remove tiles container from service.");
        }
    }
    /**
     * Creates a Tiles container.
     *
     * @param context The servlet context to use.
     * @return The created container
     * @throws TilesException If something goes wrong during creation.
     */
    protected TilesContainer createContainer(ServletContext context)
        throws TilesException
        {
        if(context.getInitParameter(TilesContainerFactory.CONTEXT_FACTORY_INIT_PARAM) == null) {
            context = decorate(context);
        }
        else
        {
            LOG.warn("Tiles container factory is explicitly set.  Not injecting struts configuration.");
        }
        TilesContainerFactory factory =
            TilesContainerFactory.getFactory(context);
        return factory.createContainer(context);
    }
    protected ServletContext decorate(ServletContext context)
    {
        return new ConfiguredServletContext(context, INIT);
    }
}
2:plugin-tiles此时还不支持velocity视图,但支持freemarker(必须以ftl做扩展名)和jsp混用
3:action 配置后不需要明确指明method,使用时直接指向这个action,再指定method名称即可,调用时是只调用method方法。注意如果form指明了action,那么这个form的excute会执行,但是不会影响其内部元素的method的最终结果(而且会抛异常),这样不好,一般不要再 form上指明action

Continue reading strut2笔记

关于Java中GridBagLayout布局管理器的用法。

    最近要写一个界面,我却发现一般的布局管理器都不那么好用。上网百度了一下,有人推荐GridBagLayout,却有很多人说GridBagLayout不好用,看不懂。

    于是我仔细查了一下java API,感觉掌握GridBagLayout最简单的用法还是蛮简单的,也是很有必要的。因为个人不喜欢绝对定位,而使用相对定位的话就必须用到GridBagLayout,主要是由于其它的几个布局管理器太简单,可操作性差。
总结了GridBagLayout的用法中的关键点如下:

1.要明确一点概念:每个 GridBagLayout 对象维持一个动态的矩形单元网格,每个组件占用一个或多个这样的单元,称为显示区域。
  网格的总体方向取决于容器的 ComponentOrientation 属性。对于水平的从左到右的方向,网格坐标 (0,0) 位于容器的左上角,其中 X 向右递增,Y 向下递增。

2.要使用GidBagLayout要先定义一个GridBagConstraints对象。
  java API说明如下:“每个由 GridBagLayout 管理的组件都与 GridBagConstraints 的实例相关联。Constraints 对象指定组件在网格中的显示区域以及组件在其显示区域中的放置方式。”
  例如,如下几行代码就可以添加其它组件:
         GridBagLayout gridbag = new GridBagLayout();
         GridBagConstraints c = new GridBagConstraints();
         JFrame   f=new JFrame();
         f.setLayout(gridbag);
         Button button = new Button(name);
         gridbag.setConstraints(button, c);
         f.add(jButton);

3.为了有效使用网格包布局,必须自定义与组件相关联的一个或多个 GridBagConstraints 对象。
即须设置GridBagConstraints 对象的属性。我认为只要能掌握以下四种参数就能很好的使用GidBagLayout:
(1)GridBagConstraints.gridwidthGridBagConstraints.gridheight
    指定组件的显示区域行(针对 gridwidth)或列(针对 gridheight)中的单元数。默认值为 1。如下向窗口中添加一个占两个单元格(两行一列)的按钮的例子:
         JFrame   f=new JFrame();
         GridBagLayout gridbag = new GridBagLayout();
         GridBagConstraints c = new GridBagConstraints();
         f.setLayout(gridbag);
         c.gridheight=2;
         c.gridwidth=1;
         JButton jButton = new JButton("按钮1");
         gridbag.setConstraints(button, c);
         f.add(jButton);
(2)GridBagConstraints.fill
    当组件的显示区域大于组件的所需大小时,用于确定是否(以及如何)调整组件。
    可能的值为 GridBagConstraints.NONE(默认值)、
              GridBagConstraints.HORIZONTAL(加宽组件直到它足以在水平方向上填满其显示区域,但不更改其高度)、               

              GridBagConstraints.VERTICAL(加高组件直到它足以在垂直方向上填满其显示区域,但不更改其宽度)和                  

            GridBagConstraints.BOTH(使组件完全填满其显示区域)。
    使用情景举例:在一个很大的窗口(如300*300)中添加一个按钮(原始大小40*30)。

(3)GridBagConstraints.anchor
    当组件小于其显示区域时,用于确定将组件置于何处(在显示区域中)。可能的值有两种:相对和绝对。相对值的解释是相对于容器的ComponentOrientation 属性,而绝对值则不然。个人觉得只使用绝对值就可以。有效值有:
    绝对值
    GridBagConstraints.NORTH
    GridBagConstraints.SOUTH
    GridBagConstraints.WEST
    GridBagConstraints.EAST
    GridBagConstraints.NORTHWEST
    GridBagConstraints.NORTHEAST
    GridBagConstraints.SOUTHWEST
    GridBagConstraints.SOUTHEAST
    GridBagConstraints.CENTER (the default)
(4)GridBagConstraints.weightx、GridBagConstraints.weighty   (************最重要的属性)
  用于确定分布空间的方式,这对于指定调整行为至关重要。例如:在一个很大的窗口(如300*300)中添加两个按钮(也可以是面板)(原始大小 40*30),默认的,你会发现两个按钮分别处于上下两个等大小的区域中,且只占用了一小部分,没有被按钮占用的区域就被称为额外区域。该额外区域会随着参数weightx、weighty而被分配。

   完整的示例代码如下:

import javax.swing.*;
import java.util.*;
import java.awt.*;

public class Example{

     public Example() {
     }

     public static void main(String args[]) {
        JFrame f = new JFrame("GridBag Layout Example");

        GridBagLayout gridbag = new GridBagLayout();
        GridBagConstraints c = new GridBagConstraints();
        f.setLayout(gridbag);
//添加按钮1
        c.fill = GridBagConstraints.BOTH;
        c.gridheight=2;
        c.gridwidth=1;
        c.weightx=0.0;//默认值为0.0
        c.weighty=0.0;//默认值为0.0
        c.anchor=GridBagConstraints.SOUTHWEST;
        JButton jButton1 = new JButton("按钮1");
        gridbag.setConstraints(jButton1, c);
        f.add(jButton1);
//添加按钮2       
        c.fill = GridBagConstraints.NONE;
        c.gridwidth=GridBagConstraints.REMAINDER;
        c.gridheight=1;
        c.weightx=1.0;//默认值为0.0
        c.weighty=0.8;
        JButton jButton2 = new JButton("按钮2");
        gridbag.setConstraints(jButton2, c);
        f.add(jButton2);
//添加按钮3
        c.fill = GridBagConstraints.BOTH;
        c.gridwidth=1;
        c.gridheight=1;
        c.weighty=0.2;
        JButton jButton3 = new JButton("按钮3");
        gridbag.setConstraints(jButton3, c);
        f.add(jButton3);

        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.setSize(500,500);
        f.setVisible(true);
     }
}

    在上述代码中添加按钮2时c.weighty=0.8,而在添加按钮3时c.weighty=0.2,这就会导致按钮2所占区域的高大约是按钮3所占区域的高的0.8/0.2=4倍。

以下是我修改的代码:

package co.im.client.component.dialog;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.LookAndFeel;
import javax.swing.UIManager;
public class GridBagLayoutTest
{
    public static void main(String[] args)
    {
        JFrame jFrame = new JFrame();
//        UIManager.setLookAndFeel();
        jFrame.setSize(400, 300);
        GridBagConstraints c = new GridBagConstraints();
        GridBagLayout gridbag = new GridBagLayout();
        jFrame.setLayout(gridbag);
        c.fill = GridBagConstraints.BOTH;
        c.gridheight = 2;
        c.gridwidth = 1;
        c.weightx = 1;//简单的方法就是这里设置为一就可以了(填充效果)
        c.weighty = 1;//
        c.anchor = GridBagConstraints.SOUTHWEST;
        JButton jButton1 = new JButton("1");
        gridbag.setConstraints(jButton1, c);
        jFrame.add(jButton1);
//        c.fill = GridBagConstraints.NONE;
        c.gridwidth = GridBagConstraints.REMAINDER;
        c.gridheight = 1;
//        c.weightx = 1.0;
//        c.weighty = 0.8;
        JButton jButton2 = new JButton("2");
        gridbag.setConstraints(jButton2, c);
        jFrame.add(jButton2);
        c.fill = GridBagConstraints.BOTH;
        c.gridwidth = 1;
        c.gridheight = 1;
//        c.weighty = 0.2;
        JButton jButton3 = new JButton("3");
        gridbag.setConstraints(jButton3, c);
        jFrame.add(jButton3);
        jFrame.setVisible(true);
    }
}

 

更多例子参见:

http://docs.oracle.com/javase/tutorial/uiswing/layout/gridbag.html

http://docs.oracle.com/javase/tutorial/uiswing/layout/visual.html

Continue reading 关于Java中GridBagLayout布局管理器的用法。

swt结合httpclient实现自动更新 [autoupdate in swt with httpclient]

/**
* 本地程序应当自知版本号已与更新站点相比较,
* plugin的版本号是在plugin.xml里面设置的,导出的jar包会依据此版本号命名,
* 而eclipse会自动依据此版本信息选择加载最新版本
* 此程序的测试:不能直接在eclipse里面运行,应将此rcp打包,然后将version改大,又打个plugin包,将plugin包放在站点上
* 启动原来的rcp程序则会出现下载重启动作
  * @date Nov 4, 2008
* @see
*/
public class AutoUpdate
{
    private Display display;
    private Shell shell;
    private boolean needReload;
    private String version = "1.0.0";
    public AutoUpdate()
    {
        display = new Display();
        shell = new Shell(display);   
        shell.setText(version);
        shell.setSize(500, 200);
    }
    private byte[] httpGetRequest(String url)
    {
        HttpClient httpClient = new HttpClient();
        GetMethod getMethod = new GetMethod(url);
        try
        {
            int statusCode = httpClient.executeMethod(getMethod);
            if (statusCode != HttpStatus.SC_OK)
            {
                System.err.println("Method failed: " + getMethod.getStatusLine());
                return null;
            }
            byte[] responseBody = getMethod.getResponseBody();
            return responseBody;           
        }
        catch (HttpException e)
        {
            e.printStackTrace();
            return null;
        }
        catch (IOException e)
        {
            e.printStackTrace();
            return null;
        }       
        finally
        {
            getMethod.releaseConnection();
        }
    }
    public void checkAndDown()
    {       
        try
        {
            byte[] responseBody = httpGetRequest("http://localhost:10010/SmileScriptWeb/autoupdate_config.xml");
            if (null == responseBody)
            {
                System.err.println("can't get config.");
                return;
            }
            String strXml = new String(responseBody);
            System.out.println(strXml);
            Document doc = DocumentHelper.parseText(strXml);
            Element el = doc.getRootElement().element("update");
            String sversion = el.attributeValue("version");
            String fileName = el.attributeValue("filename");
            String url = el.attributeValue("url");
            int curversion = Integer.valueOf(version.replaceAll("\\.", "")).intValue();
            int serverversion = Integer.valueOf(sversion.replaceAll("\\.", "")).intValue();
            if (curversion < serverversion)
            {
                System.out.println("need download new version.");
            }else{
                return;               
            }
            System.out.println("got config successfully: version=" + sversion);
//            download           
            url = "http://localhost:10010/SmileScriptWeb/" + url;
            responseBody = httpGetRequest(url);
            if (null == responseBody)
            {
                System.err.println("can't download update.");
                return;
            }
            fileName = getAppRootPath() + "plugins/" + fileName;
            System.out.println("the download file full name=" + fileName);
            File storeFile = new File(fileName); 
            FileOutputStream output = new FileOutputStream(storeFile);
            output.write(responseBody);
            output.close();
            System.out.println("download successfully.");
            needReload = true;
        }
        catch (DocumentException e)
        {
            e.printStackTrace();
        }
        catch (IOException e)
        {
            e.printStackTrace();
        }
    }
    private static String installPath;
    public static String getAppRootPath() {
        if (null == installPath)
        {
            Location installLoc = Platform.getInstallLocation();       
            URL installURL = installLoc.getURL();
            installPath = installURL.getPath();           
        }       
        return installPath;
    }
    public Object run()
    {
        final int totalColumn = 1 ;
        GridLayout layout = new GridLayout(totalColumn,false);
        shell.setLayout(layout);
        checkAndDown();
        if (needReload)
        {
            return IPlatformRunnable.EXIT_RESTART;       
        }
//        shell.pack();
        shell.open();
        while (!shell.isDisposed())
        {
            if (!display.readAndDispatch())
                display.sleep();
        }
        display.dispose();
        return IPlatformRunnable.EXIT_OK;           
    }
}
------------------------------------------------------------------------autoupdate_config.xml-------------------------------
<?xml version="1.0" encoding="UTF-8"?>
<config>
    <update version="1.0.1" filename="rcp_swt_1.0.1.jar" url="rcp_swt_1.0.1.jar" />
</config>

Continue reading swt结合httpclient实现自动更新 [autoupdate in swt with httpclient]

关于javascript和css的宿主环境以及资源引用相对路径等问题实践总结!

1.javascript引用资源(比如图片)相对路径是以宿主环境(所被引用的网页比如user.html)所处位置为基准

2.css引用资源(比如图片)相对路径是以.css文件所处位置为基准!

已经实践证明过!

--2009aicheaizi
------images
---------index_02.jpg
------test.htm

--css
------css
----------test.css

--js
------js
----------test.js

引用代码如下:
test.css

#imgtest 
{ 
background-image:url(。。/。。/2009aicheaizi/images/index_02.jpg);//.换成。以防cnblogs转换 
width:500px; 
height:50px; 
border:solid 1px red; 
}

test.js

function writeimg() 
{ 
  document.write("<img src='images/index_02.jpg' />"); 
}

test.html

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 
<html xmlns="http://www.w3.org/1999/xhtml" > 
<head> 
    <title>无标题页</title> 
<script type="text/javascript" src="../js/js/test.js"></script> 
  <link href="../css/css/test.css" rel="stylesheet" type="text/css" /> 
</head> 
<body> 
<script type="text/javascript"> 
writeimg(); 
</script> 
<div id="imgtest"></div> 
</body> 
</html>

实践是检验真理的唯一途径.

 

另外//d1.abc.com/test.js 这种用法没试过吧,这也是绝对路径,但是没有写前面的http协议头,这样写有什么好处呢,对于需要引用外部脚本的站点来说,如果是https请求,那么上面的地址会请求https://d1.abc.com/test.js  如果是http请求,上面地址则会请求http://d1.abc.com/test.js 这样就避免了跨域安全问题,参见:http://blog.ureshika.com/archives/402.html

Continue reading 关于javascript和css的宿主环境以及资源引用相对路径等问题实践总结!

flex缺点与比较

flex漫谈

若干年前,我还在CitiBank工作的时候,全球包括国内IT界就有很多WEB开发者热论Rich Internet思想,众多IT媒体纷纷把RIA当作一个时髦的词汇挂在嘴边,时不时地品评一番。而各网络技术供应商也在暗地里思考着是不是新的机会到来了。当时正是全球java企业级技术开发高速发展的时期,OpenSource运动也在如火如荼的进行着。有一天,头召集我们说Macromedia(当时还没有被Adobe兼并)已经实现了一个实验版本的解决方案,名字叫Flex,让我们关注一下云云,因为下一个项目要用到它。于是从那时起便开始了 Flex之旅了。我想我们是Macromedia Flex最早的客户。Flex1.5精彩的UI组件给我留下了深深的印象. 一直到现在都密切的关注着它的发展,用它实现过很多让很多人激动人心的功能,同时也经历着由于Flex先天的不足所带来的困惑,以及每个困惑后面的解决方案。从第一个Beta版本的脆弱到现在2.0的进步,我能明显地感觉到它在成长和发展。我个人也在成长和发展。想以一个过来人的视角告诉国内这些被 Flex地魅力所打动地人们一些经验和教训。以及怎么发挥Flex最强悍的地方,巧妙地避开它相对不稳定和脆弱的地方。只要注意足够的细节,我的实践和经验告诉我,Flex总能让人激动人心!

现在,我想首先随便举例介绍一些Flex的短处。从最早的版本1.5开始。

1 不支持鼠标双击事件(解决方案是用两次时间间隔100毫秒的单击事件来模拟),2.0有所改观。

2 Flex的FlashSession和IE的标准HttpSession在Tomcat,WebLogic上部署时没有问题,但是在Unix的JRUN上时,Post请求参数不同步(解决方案是在Remote Object实现中手动同步必要的参数,比如如果你把Spring中的Facade Object作为Remote Object)2.0有同样的问题。在WebService作为Server端的通信方式时没有类似问题。

3  前期绑定和官方声称的不一致。组件绑定在Flex中分为前期绑定和后期绑定。当Client端装载编译过的mxml或as码流时首先出发的事件是 Initialize然后是CreationComplete,当我们分别写两个handler相应这两个事件,如果后期绑定可能用到前期绑定时装载的数据,你就会发现数据有可能会没有转载。原因有若干,在下一篇文章中详细介绍。

4 FlashPlayer 9以前的实现,类层次结构太深,实现很机械死板,存在很多重构的可能,比如一个应用中包括若干容器和组件,每个组件的初始化都要调flash类根结点相同的构造器。你配置好Flex自带的Profiler,就可以在Console中看到整个应用在底层的对象创建初始化的所有过程。只要在flex- config.xml中打开开关然后再做些额外的配置就可以了。(解决方案是写一个底层flash类Singleton的类工厂cache所有底层 flash类,此方案非常见效,flex并不是想象中的那么慢!)

5 本身没有好的pattern可以直接利用,容易造成mxml和as混在一起,可以选择cairngorm,这个不错。我将单独写一篇关于CAIRNGORM架构的文章。不过2.0时已经被集成进来。

6 CellRender存在数据类型DataType不匹配的Bug等等。

以上说了这些,有些朋友可能有些失望,其实大可不必。我们要保持一种客观的态度,等我把Flex的优点列出来以后,你又会充满信心了,其实,任何产品都有优势和劣势。另外,以上我所列的东西都是我在实际的开发中碰到的,到后来你会发现,等你做完项目必须的优化动作后,反而是数据传输所消耗的时间比 Flex组件初始化的时间要长一些,那时可以采用在Server端做个Cache以及Client端Lazy Load的策略。想一下Flex都可以做全球各大股票市场行情实时显示和分析,还有什么它不可以做的呢!
==============================================================================================================

支持的:
在Action Script的历代版本中, 没有任何一个UI是支持HTML所有的标签的, 它们只支持HTML一部分的标签.
TextArea这个UI也是一样的, 只支持HTML里少数标签, 比如加粗(<B>内容</B>), 或者斜体等等这些基本标签.
表格标签<table>是不被支持的, 要用表格的话, 我建议你用DataGrid组件, Flex 2里的DG比Flash 8的漂亮很多, bug很少.
不是。java is a programing language!
Flex技术是Micromedia推出的,此公司相续推出了两个版本Flex1.0和Flex1.5,但是由于他们所设计的IDE和生成的flash文件太过于臃肿,所以一直声誉不隆。
2005年,Micromedia被Adobe公司收购,Flex2.0和Flex3.0相继推出,Adobe改良了FlexBuilder和SDk,使得开发Flex变得简单,而且生成的.swf文件变的更小。于是这门技术才开始在国外盛行起来。
总的来说,flex是大势所趋,flex+java的J2EE开发在此后一段时间将是主角
oracle是主流数据库, ajax,既然有了flex,ajax可以不用学了
flex资料确实少,但flex3的中文书还是有几本的,况且现在flex4快要出了,赶紧学习吧。
另外,于Flex竞争的还有微软的SilverLight.最新消息,Sun公司也推出了javaFX,其实都是RIA。
不过由于Flex率先占领了市场,所以支援Flex的开源的东西也相对多很多,所以此后一段时间当主推Flex。

票数: 0

sdihei 2009-3-13 下午12:52:24 218.93.127.* 举报

取这个标题,难免会惹来幼稚和可笑之闲,打心底压根儿没有拿这两者来比过,因为这两者是否具有可比性本身是个疑问.可是最近一些朋友的公司却一直在UI的技术选型上犯愁.一些朋友这么问我,所以我想把自己的想法罗列在此,仅供参考.
先申明,我不是什么牛人,也不是FLEX和EXT方面的高手,甚至对EXT并没有在项目中亲自实践过,写此文之前我问自己,凭什么在此发言? 最后我找到一个说服自己的欠充分的理由.既然有人发问,就应该个答案,既然我不能更清楚地说出答案,何不以此做为源头,让更多的人来揭开这层面纱,此文难免落入俗套,请大家海涵.
不啰嗦了,进入主题吧.
FLEX和EXT该选谁?
一:从项目的风险上来考虑:
项目能否成功, 一定程度上并不关技术鸟事儿.因为不同的技术可能达到同样的效果.而我们往往只选取最有把握的一种技术,这就是让项目归避了风险,那么这两者中哪一种技术你更有把握呢? 大家自己更清楚,我可以大概地分析下两者目前使用现状来做个推测或猜测.不排除有例外.
FLEX在国内已明显比国外慢了半拍,从 FLEX的相关学习资料可以得出这一结论,记得从FLEX2开始我重新开始了对FLEX(以前玩过FLASH)的关注,那时我还在神龙汽车公司里做实施项目用的是SSH架构.项目中有很多有关经销商的数据需要统计和比较,客户非常希望能够直接在系统中看到各经销商特定数据项比较的结果,当时苦于没能说服经理采用FLEX的CHART来实现这一功能,而采用了BO报表这个庞大的系统来”高射炮打蚊子”,当时提出FLEX技术项目组的同志们闻所未闻,我简单地说是FLASH的一种技术,大家听罢都敬而远之.说FLASH依赖于FP.后来学习FLEX变成了我的业余爱好.当然现在在国内大概不会再出现类似的情况了,FLEX以及微软的SL大有山雨欲来风满楼的架势.CSDN的孟岩说”RIA是趋势”,我想倒底是不是趋势大家能感受得到.在这里我想表明一点,是 EXT已经被众多企业以及用户所接受,但FLEX还在考验和推广的路途之中.回到风险的考虑上来讲,你的团队中真正懂FLEX的人不多,或者根本就没有. 因为大家都没有FLEX方面的经验积累,你贸然采用FLEX,一旦在你的项目中遇到FLEX本身或者你的团队不能解决的难题,而又很难在寻求外界的技术支援时,你的项目势必为摇摇欲坠.这种惊心动魄我想大家都不想感受.而EXT听起来虽然是个新名词,但如XX人所说,AJAX只是一个新瓶装老酒的DD,我们几乎不需要额外的学习就可以完美的实现”拿来主义”,我们还可以根据自己的需求来修改内部的代码.所以就目前来讲,EXT的技术风险远低于FLEX.
二:学习和培训的成本:
要上一个项目,你的团队没有FLEX的经验也没有EXT的经验积累.别无它法,一:让团队成员自学交流,二:给团队成员培训一下.而不管是学习还是培训,EXT会更快地让大家所接受,所以EXT的学习培训成本或者说成是使用成本更低.(当然这只是就当前的项目而言,从公司长远的考虑,学习FLEX这个投入可能会得到更高的回报, 这是后话).
三:企业运营的成本:
BS结构的 DD,SERVER是运营时的重头戏,带宽的大小,服务器的负载能力….因素,在项目开发之前技术选型时都要经过深入考究,本着”问题都要在项目开始之先暴露的原则”,我们不能视FLEX项目的更高的带宽要求而不见;FLEX在第一次将整个项目SWF下载到客户端缓存,所以项目大载入会非常慢,鉴于国内的带宽状况似乎还很不乐观,这一点是几乎是FLEX技术选择时最为头疼的问题,这里或许有人会反驳说,有办法解决:我想很严肃地回驳,没办法解决,运用 RSL以及MODULE只能缓解这种尴尬;
针对这一尴尬.ADOBE官方有这么一说,FLEX是为企业级用户准备的,当然你做的是企业级方案,那这一尴尬确实可以不再让其蒙羞;所以FLEX在外网的解决方案的使用上似乎要更加慎重考虑
再回到话题上来,FLEX与EXT相比所需的带宽要求要高得多,最终用户的网络带宽不理想,你用FLEX做再华丽的,再炫,再牛的用户体验,他也体会不到. 当系统的并发数急剧增加时,企业的服务器要相应地提高对用户的响应速度,也需要加大带宽,运用木桶原理,可能需要增加的带宽不小(本人没有数据来说明,因为没有做过测试),支付的费用也会成若干倍增加.
四:开发效率和维护成本
看到上面,好像我更倾向于采用EXT,似乎我把FLEX说得一无是处,不然,FLEX也是时代的英雄,只是他的演出才刚拉开帷幕,从开发效率上来讲EXT根本没法跟FLEX相提并论,

Continue reading flex缺点与比较

Blaze Data Services还是LiveCycle Data Services?

http://www.infoq.com/cn/articles/Blaze-LiveCycle

Blaze Data Services还是LiveCycle Data Services?

作者 Ryan Knight 译者 张龙 发布于 2009年5月5日 下午10时11分

社区 Java 主题 开放源代码, Web 2.0, 数据访问, RIA, 富客户端/桌面 标签 Adobe, Flex, Adobe集成运行时/AIR, Flash

摘要

现在已经有不少文章在谈论各种版本的Data Services,然而却没人能说清楚该如何从这些Data Services中进行选择,同时也没人对端点(end point)和管道(channel)是如何影响着应用性能这个议题进行过详细论述。

尽管Adobe提到4个不同版本的Data Services,但实际上只有2个主要版本。一个是开源的Blaze Data Services,另一个是名为LiveCycle Data Services(LCDS)的私有版本。这两个产品都提供了一个重要特性:借助于Message Broker Servlet连接Flex和Java。他们都可以通过二进制协议,ActionScript消息格式(AMF)使用远程过程调用和消息与服务器端进行通信。此外,Adobe还在这两个核心产品之上提供了支持版本和更具扩展性的产品套件。

从我在Gorilla Logic的工作经历来看,我认为这两个产品之间的主要差异在于支持项与数据管理,而性能和可伸缩性之间的差异则颇具争议。LCDS为客户端通信提供了额外的端点和管道。Adobe宣称这么做的主要好处在于提升了可伸缩性,然而基本的通信方式依然是通过HTTP上的AMF,这样不管服务器端或客户端的配置如何,其性能没什么差别。

LCDS提供的另一个特性是数据管理,它通过实时的冲突调节(conflict resolution)实现Flex客户端和Java服务器端之间的数据同步,同时还提供了数据装配器和适配器以通过JDBC、Hibernate或其他客户化的适配器连接Data Services和持久化存储。

那么这4个不同版本都是什么呢?

  1. Blaze Data Services——免费、开源的版本
  2. LiveCycle Data Services社区版——Blaze DS的一个支持版
  3. LiveCycle Data Service单CPU协议版——商业版的一个免费版本,具有一些额外特性,但只能用在单个CPU上
  4. LiveCycle Data Services——带有支持的商业版的付费版本

同时还有一个Adobe称之为LiveCycle Data Services Enterprise Suite的产品套件。它向核心的Data Services中添加了额外的产品以通过PDF Generation、Forms及Digital Signatures等工具提供内容服务和文档输出功能。

理论上来说,我们可以根据以下三点来选择产品。

  1. 需要支持么?这取决于应用是否需要支持,比如任务关键的应用。
  2. 需要数据管理服务么?这取决于应用对数据同步和管理服务的需求。
  3. 需要额外的LCDS端点和管道么?根据Adobe所述,如果同时有几百个并发连接,那么就需要了,然而这么说还是颇具争议的。服务器能处理的并发连接数取决于多种因素,比如线程和I/O吞吐量等,同时我们还可以通过多个服务器的负载平衡来处理大量的并发连接,就像Java应用服务器那样。

在本文的最后有一张比较图表概览了这4个不同版本。

概览不同的端点——Servlet和NIO

在Data Services中,端点就是服务器监听来自客户端连接的方式。对于Blaze DS和LCDS来说,标准的端点基于Servlet,运行在应用服务器上。端点通过Message Broker Servlet(配置在web.xml中)加入到标准的Servlet过滤器链中,这样我们就可以将Blaze DS和LCDS部署到现有的Java应用中。与Java Servlet端点类似,每个客户端连接都需要服务器上一个单独的线程。

与上面相比,NIO端点的工作方式就完全不同了。NIO表示Java新的输入/输出。NIO端点会创建一个独立的基于NIO的Socket Server。其优势在于单个线程能管理多个I/O流,这样就无需很多线程,同时能处理更多的客户端。

要想使用NIO,我们面临着如下几个挑战:

  • 客户端无法通过客户端代理访问NIO端点。
  • 连接无法通过标准的Servlet链,因为这会打断依赖于Servlet的任何一部分系统。比如我们在项目中使用Servlet处理文件上传。
  • 要想使用NIO,我们必须利用客户化的认证,因为Socket Server运行在与Servlet容器不同的进程中。

尽管我们可以通过配置的方式让NIO服务器监听80端口,但通常它却驻留在其它端口上。这样就给网络配置带来了挑战,我们必须将网络配置成可以接收新端口上的连接。根据服务器内部网络和各种客户端的不同,这可能会产生问题。不过这个问题还是有可能解决的:使用带有sticky sessions的负载平衡器。

然而Java NIO的这些优势却是颇具争议的。开发Java NIO的目的在于让单个线程处理多个连接,这是通过令单个线程遍历连接池以检查是否需要读取或写入新的数据来实现的。但由于NIO是在2002年引入的,那时的JDK版本还是1.4,而现在JVM和Linux中的线程已经得到了极大的改进,因此Java和Linux处理大量线程的能力也已经得到了巨大的提升。

举个例子吧,Linux Kernel 2.6引入了一个新的线程库,也就是本地的POSIX Linux线程库(NPTL)。测试表明,借助于NPTL在IA-32上只需2秒钟1就能启动100,000个线程,而如果不使用NPTL的话,Linux Kernel需要花费15分钟才能启动这么多线程。

更有意思的是Paul Tyma等人发布了一篇博文说到Java NIO实际上是个劣势2, 3, 4。凭借一系列基准,Paul得出了如下一些结论:

  • Java NIO丧失了大约20%到30%的吞吐量
  • 线程上下文切换的代价并不大
  • 同步的代价并不大

Paul 根据这些测试得出如下结论:一个连接对应一个线程完全没问题。在使用JDK 1.6的情况下,JVM可以处理15K到30K的线程数。这意味着Servlet端点已经不再局限于几百个线程了,相反,线程数可以更多,甚至15K个连接以上。当然了,实际的限制取决于硬件配置,比如内存、CPU等等。

概览不同类型的管道

我们可以在基本的网络连接之上使用多种不同类型的管道进行客户端与服务器端的通信。基本远程过程调用使用的是标准AMF管道。

另一种通信形式就是消息,这样应用就可以推送来自于服务器端的消息并进行近乎实时的通信。代表性应用就是聊天服务器、拍卖客户端及协作服务。

Data services处理消息的主要方式就是轮询(polling)。由于HTTP上的标准通信并不会一直打开通信管道,这样一个轮询管道就会让客户端请求一直等待服务器端,直到数据可用为止,其等待时间从几毫秒到几分钟不等。这么做就模拟了从服务器端推送数据的过程。

有两种基本的轮询管道:短轮询与长轮询。其主要区别在于服务器端等到客户端数据变得可用时所需时间的多少。

一种更高级的管道是流式AMF(streaming AMF)。它会打开到服务器端的HTTP连接并让服务器以流的方式在该管道上传输消息(消息的数量没有限制)。这么做就无需客户端轮询了,同时还能使用标准的网络配置。该方式最接近于实时流。流式AMF的挑战在于它使用了HTTP 1.1的持续连接,而不同的浏览器对其的实现方式却不同。

最后一种管道就是RTMP(实时消息协议)管道了,目前只有LiveCycle DS对其提供了支持。Adobe最近宣布将要发布RTMP规范,由此我猜想它最终将会得到其他产品的支持。

设计RTMP的目的是在双向管道上以流的方式处理大量多媒体和数据。RTMP的一个主要好处是可以一直打开与客户端的连接,这样就可以推送服务器端的数据了。凭借这一点,RTMP可用于Comet风格的通信和实时的数据推送。

RTMP有三种形式。一种是基于TCP并使用1935端口,其底层实现要求在客户端浏览器上初始化连接。由于使用了非标准的端口,这样客户端防火墙经常会阻止其运行。

RTMP的另两种形式在HTTP请求内封装了RTMP消息,这样协议就可以穿越防火墙并使用标准的端口。这两种形式分别是RTMPT(用在标准的HTTP上)及RTMPTS(用在安全的HTTPS上)。

在 Flex中,所有对服务器的调用都是异步执行的,因此这些管道都不会对客户端性能造成任何影响。然而他们却对服务器端性能有一定的影响,尤其是在同时打开多个客户端连接的情况下更是如此。例如,流式AMF会导致服务器端打开大量并发的客户端连接,这也就意味着会产生多个线程。但如前所述,多个线程的影响微乎其微。

所有的客户端连接都可以配置默认管道和备选管道,如果默认管道失败则可以切换到备选管道上。根据服务器端处理的不同通信类型,我们可以指定不同的管道链。例如,可以指定RTMP管道,但如果该连接失败,就回到长轮询管道。

结论

相对于Blaze DS来说,LiveCycle DS的真正优势在于其支持与数据管理,而额外的端点和管道所带来的优势却是颇具争议的。根据我们在Gorilla Logic所完成的项目来看,根本无需使用NIO端点或是RTMP。但从技术角度来看,没什么是确定的。我倒是想多点项目经历。

特性比较表

table

关于作者

Ryan Knight是Gorilla Logic的高级软件架构师,他主要从事Flex和Java咨询方面的工作,同时还是Anvil Flex的主要贡献者,这是一个开源的项目,用来帮助企业上手Flex开发。他有着12年的Java开发经验,期间经历了从开发到咨询的各种角色变迁。

资源

1 http://www.linuxjournal.com/article/6530

2 http://paultyma.blogspot.com/2008/03/writing-java-multithreaded-servers.html

3 http://www.theserverside.com/discussions/thread.tss?thread_id=26700

4 http://cometdaily.com/2008/11/21/are-raining-comets-and-threads/

来自Adobe的链接

LiveCycle Homepage

LiveCycle Data Services ES FAQ

Comparison of the different LiveCycle Data Services solutions

其他资源

LiveCycle ES vs LiveCycle DS vs BlazeDS - clearing up the confusion

Why are you NOT using LiveCycle DS?

查看英文原文: Blaze Data Services or LiveCycle Data Services?

Continue reading Blaze Data Services还是LiveCycle Data Services?

Delphi 7编译的程序任何系统都正常显示

Delphi 7编译的程序任何系统都正常显示[这个没提供解决方案,可当基础知识]

字符编码的问题。
字符编码在Delphi7中已经得到了很大提高。
Delphi7自己的IDE虽然不能读取Unicode编码的源代码文件,但编译器已经支持
AnsiString和WideString的转换。也就是说,只要定义的时候定义

Technorati 标签:

WideString,
那么在后面直接给他赋值时,AnsiString自动转换为WideString,反之亦然。
这样有好处也有坏处,好处是在快速开发中,不需要考虑更多的字符转换问题,
能够比较平顺地从Win98向NT字符集转换,坏处是混淆了字符界限,深入看下
去,有时候搞不清我的内存里究竟是Ansi还是Wide,特别是希望仅仅使用宽字
符的情况下,更要留意字符格式的定义。
WideString保存为文本文件时,常用的有UTF-8、Unicode、Ansi、Unicode Big Endian,
其中 UTF-8 的格式,从文件读取的时候,需要利用 Delphi7 提供的 Utf8ToUnicode
转换一下全部编码,其他几种编码本身都不需要转换(BigEndian编码是摩托罗拉规范,是
intel 规范的 Unicode (即我们现在说的 WideString)编码的字符按字节反转,这符合摩
托罗拉生产的计算机芯片的构造特点,所以读取后要按 WORD 反转),但保存为相应格式的
文本文件时,必须按要求在文件头部写入一个编码识别记号,他们分别为:
Ansi:不需要
Unicode:$FEFF (十六进制编辑器看到的是高位在前显示$FFFE,以下同)
BigEndian:$FFFE (正好是上面 Unicode 的反转)
UTF-8:$BBEF $BF (三字节,十六进制编辑器里显示 $EFBB BF)
这样,其他编辑器读取时就可以识别出保存者把文本翻译成了什么编码。
Unicode(即WideString)只要写好文件头,后面的就按照保存Ansi文本一样把
文本写入文件,保存为Big Endian,则按WORD逐字节反转写入,保存为UTF-8
要利用UnicodeToUtf8转换后写入。
在XML解析中,如果带有非ASCII编码的文字,MS默认使用UTF-16编码,如果
原始文本是Ansi编码,这时将获得乱码的字符。这个编码不是Delphi造成的,是
MS的XML库所致,所以在使用非ASCII字符前,建议转换成UTF-8编码,上面例
子中我没有使用WideString,所以没有实现编码转换。
编码转换有很多现成的开源代码可以利用,其中影响最深远的就是JEDI的Unicoee.pas,
但这个文件很庞大,大约有250K大小,它还带有一个转换表的资源文件,如果
处理一些小型的字符转换就显得杀鸡用牛刀了。当然我们可以直接利用Delphi7
提供给我们的函数,比如:
function PUCS4Chars(const S: UCS4String): PUCS4Char;
function WideStringToUCS4String(const S: WideString): UCS4String;
function UCS4StringToWidestring(const S: UCS4String): WideString;
function UnicodeToUtf8(Dest: PChar; Source: PWideChar; MaxBytes: Integer): Integer;
function UnicodeToUtf8(Dest: PChar; MaxDestBytes: Cardinal; Source: PWideChar; SourceChars: Cardinal): Cardinal;
function Utf8ToUnicode(Dest: PWideChar; Source: PChar; MaxChars: Integer): Integer;
function Utf8ToUnicode(Dest: PWideChar; MaxDestChars: Cardinal; Source: PChar; SourceBytes: Cardinal): Cardinal;
function Utf8Encode(const WS: WideString): UTF8String;
function Utf8Decode(const S: UTF8String): WideString;
function AnsiToUtf8(const S: string): UTF8string;
function Utf8ToAnsi(const S: UTF8string): string;
等等。这些已经足够使用了。轻量级的代码是OmniXML中的TGpTextStream,
不过这个代码有不少BUG,并且不支持BigEndian的写入(读取部分也因忘了使
用临时变量而错误)。这些都可以利用。
在Delphi7中,Edit等控件不支持WideString,但有一组TnTWare的开源控件可
以直接支持WideString。
所以,了解了这些内容后,就可以明确这么多编码在读入内存后变成了什么。
读入内存中的字符其实已经只剩下二种格式了:
要么是 AnsiString,
要么是WideString。
因此,对于认识字符编码的关键就是理解读取和理解保存,只有这二个地方需
要对编码有了解才能正确地完成工作。
哦,对了,还要补充一下Delphi中比较特殊的一个事情:本来我们全程使用了
WideString后,在NT系统下应该可以不考虑处于哪种语言环境的,但是Delphi
的全部控件都是基于Ansi的,因此,除非使用了象Tnt控件一样的显示控件,
否则都要注意字符集的定义。象Edit,如果要显示WideString,Edit的Line.Text
会自动转换为AnsiString,这个转换的依据是活动文档的键盘定义或者活动文档
的字符集定义(字符集定义优先),因此一定不要忘记把Edit字符集设置为与
文本相适应的标志,比如中文,就设置为GB2313_CHARSET,这样,转换时会
使用936的中文字符集。这个设置与具体使用的字体无关,只要强制把这个属
性设置好了,字体是否支持这个集合由系统自动转换。
因为 WideString 中最需要转换的编码就是 UTF-8,所以演示了 UTF-8 就可以应用到所有
WideString 编码。
下面的演示代码是把 UTF-8 格式的文本装入只能显示 AnsiString 的 Delphi 自带的 Memo
中,并且可以再将这个 Memo 中的 AnsiString 取出来保存为 UTF-8 格式文本,并且支持
在任何语种的 Windows NT 操作系统上显示中文。
unit frmUnit;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics,
Controls, Forms, Dialogs, ComCtrls, Menus, StdCtrls;
type
TEncodeFlags = (efUnknown, efAnsi, efUnicode, efUncodeBigEn, efUTF8);
TUniEditFrm = class(TForm)
   MainMenu1: TMainMenu;
   mnuFileItem: TMenuItem;
   mnuOpen: TMenuItem;
   mnuSpace1: TMenuItem;
   mnuSaveAs: TMenuItem;
   mnuSpace2: TMenuItem;
   mnuExit: TMenuItem;
   StatusBar: TStatusBar;
   procedure FormCreate(Sender: TObject);
   procedure FormDestroy(Sender: TObject);
private
   { Private declarations }
   FStream: TStream;
   OpenDlg: TOpenDialog;
   SaveDlg: TSaveDialog;
   UnicoMemo: TMemo;
   procedure SetMemoCharset;
   procedure LoadFromFile(fName: string);
   procedure SaveToFile(fName: string);
   procedure SetStatusMessage(Msg: string);
   procedure MenuItemOnClick(Sender: TObject);
   function ChWideToAnsi(const StrW: WideString): AnsiString;
   function ChAnsiToWide(const StrA: AnsiString): WideString;
   function UTF8ToWideString(const Stream: TStream): WideString;
   procedure TextToUTF8Stream(const Text: string; var Stream: TStream);
   function GetEncodeFromStream(const Stream: TStream): TEncodeFlags;
public
   { Public declarations }
end;
var
UniEditFrm: TUniEditFrm;
implementation
{$R *.dfm}
type
TUTF8Falg = packed record
   EF, BB, BF: Byte;
end;
const
Encode: TUTF8Falg = (EF: $EF; BB: $BB; BF: $BF);
MenuActSpace = 0;
MenuActOpen = 1;
MenuActSaveAs = 2;
MenuActExit = 3;
{ TUniEditFrm }
procedure TUniEditFrm.FormCreate(Sender: TObject);
var
n: integer;
begin
mnuOpen.Tag := MenuActOpen;
mnuSaveAs.Tag := MenuActSaveAs;
mnuExit.Tag := MenuActExit;
for n := 0 to mnuFileItem.Count - 1 do
   if mnuFileItem.Items[n].Caption <> '-' then
     mnuFileItem.Items[n].OnClick := MenuItemOnClick;
OpenDlg := TOpenDialog.Create(Self);
OpenDlg.Filter := 'UTF8 Text File|*.txt';
SaveDlg := TSaveDialog.Create(Self);
SaveDlg.Filter := 'UTF8 Text File|*.txt';
SaveDlg.DefaultExt := '.txt';
UnicoMemo := TMemo.Create(Self);
UnicoMemo.Parent := Self;
UnicoMemo.Align := alClient;
UnicoMemo.ScrollBars := ssVertical;
SetMemoCharset;
end;
procedure TUniEditFrm.FormDestroy(Sender: TObject);
begin
OpenDlg.Free; SaveDlg.Free; UnicoMemo.Free;
if Assigned(FStream) then FStream.Free;
end;
procedure TUniEditFrm.MenuItemOnClick(Sender: TObject);
begin
case TComponent(Sender).tag of
   MenuActOpen: if OpenDlg.Execute then LoadFromFile(OpenDlg.FileName);
   MenuActSaveAs: if SaveDlg.Execute then SaveToFile(SaveDlg.FileName);
   MenuActExit: Close;
end;
end;
procedure TUniEditFrm.SetMemoCharset;
begin
UnicoMemo.Font.Charset := GB2312_CHARSET;
UnicoMemo.Font.Size := 12;
end;
procedure TUniEditFrm.SetStatusMessage(Msg: string);
begin
SendMessage(StatusBar.Handle, WM_USER + 1, 0, DWord(PChar(Msg)));
end;
procedure TUniEditFrm.LoadFromFile(fName: string);
begin
if not Assigned(FStream) then FStream := TMemoryStream.Create;
TMemoryStream(FStream).LoadFromFile(fName);
if GetEncodeFromStream(FStream) = efUTF8 then
begin
   SetStatusMessage(Format('File: %s ,Size:%d Byte', [fName, FStream.Size]));
   UnicoMemo.Lines.BeginUpdate;
   UnicoMemo.Clear;
   try
     UnicoMemo.Lines.Add(ChWideToAnsi(UTF8ToWideString(FStream)));
   finally
     UnicoMemo.Lines.EndUpdate;
   end;
end
else SetStatusMessage(Format('File: %s ,Unknown Encode', [fName]));
FStream.Size := 0;
end;
procedure TUniEditFrm.SaveToFile(fName: string);
begin
try
   if not Assigned(FStream) then FStream := TMemoryStream.Create;
   TextToUTF8Stream(UnicoMemo.Lines.Text, FStream);
   TMemoryStream(FStream).SaveToFile(fName);
   SetStatusMessage(Format('Save File: %s ,Size:%d Byte', [fName, FStream.Size]));
finally
   FStream.Size := 0;
end;
end;
function TUniEditFrm.ChWideToAnsi(const StrW: WideString): AnsiString;
var
nLen: integer;
begin
Result := StrW;
if Result <> '' then
begin
   nLen := WideCharToMultiByte(936, 624, @StrW[1], -1, nil, 0, nil, nil);
   SetLength(Result, nLen - 1);
   if nLen > 1 then
     WideCharToMultiByte(936, 624, @StrW[1], -1, @Result[1], nLen - 1, nil, nil);
end;
end;
function TUniEditFrm.ChAnsiToWide(const StrA: AnsiString): WideString;
var
nLen: integer;
begin
Result := StrA;
if Result <> '' then
begin
   nLen := MultiByteToWideChar(936, 1, PChar(@StrA[1]), -1, nil, 0);
   SetLength(Result, nLen - 1);
   if nLen > 1 then
     MultiByteToWideChar(936, 1, PChar(@StrA[1]), -1, PWideChar(@Result[1]), nLen - 1);
end;
end;
function TUniEditFrm.UTF8ToWideString(const Stream: TStream): WideString;
var
nLen: Cardinal;
begin
try
   SetLength(Result, Stream.Size div SizeOf(WideChar) * 3);
   nLen := Utf8ToUnicode(@Result[1], Length(Result),
     Pointer(DWord(TMemoryStream(Stream).Memory) + Stream.Position),
     Stream.Size - Stream.Position);
   SetLength(Result, nLen);
except
   SetLength(Result, 0);
end;
end;
procedure TUniEditFrm.TextToUTF8Stream(const Text: string; var Stream: TStream);
var
StringW, StrW: WideString;
nLen: Cardinal;
begin
try
   if Text <> '' then
   begin
     StrW := ChAnsiToWide(Text);
     nLen := Length(StrW) * 3;
     SetLength(StringW, nLen);
     nLen := UnicodeToUtf8(@StringW[1], nLen, @StrW[1], Length(StrW));
     SetLength(StringW, nLen);
     Stream.Write(Encode, SizeOf(Encode));
     Stream.Write(StringW[1], Length(StringW));
   end
   else
     Stream.Write(Encode, SizeOf(Encode));
except
   SetLength(StrW, 0);
   SetLength(StringW, 0);
end;
end;
function TUniEditFrm.GetEncodeFromStream(const Stream: TStream): TEncodeFlags;
var
FEncode: TUTF8Falg;
begin
Result := efUnknown;
Stream.Read(FEncode, SizeOf(FEncode));
if (FEncode.EF = Encode.EF) and (FEncode.BB = Encode.BB)
   and (FEncode.BF = Encode.BF) then Result := efUTF8;
end;
end.
代码中有几个控件是动态创建的,其中有对话框和 Memo。这是为了随时改变使用不同控件
库进行测试观察的需要,如果你使用支持宽字符的控件,不用改界面,直接把创建实例改一
改就可以测试观察了。这种写法不是最好的,如果使用接口来描述就会更随意些。
-------------------------------------------------------------------------------------
干净搞定delphi多语言-兼论设计模式    
       随着全球化程度加深,软件越来越像蒲公英,到处飘散、扎根。这其中要解决的是不同语言的显示问题。我们当然希望一套程序,可以不修改代码就可以支持不同的语言,不要去维护很多的版本。
       首先要谈到的一个问题是乱码问题,因为delphi win32到11.x版还是不支持unicode,所以一般使用Ansi码,有这样几种情况会显示乱码:

  1. 使用的语言文字与系统当前设定的语言不一样;比如简体版QQ在繁体操作系统(或简体操作系统的区域设置中“非Unicode程序的语言”设定为 繁体)就是乱码。即使改变Font.Charset,某些元件仍然会出现乱码,如StatusBar。因此,在越南文版的windows显示越南文,在伊 朗文版的windows显示伊朗文,不要在越南文版windows显示伊朗文,在伊朗文版windows显示越南文,这样就能确保没有乱码问题。好在一般 这样的错位用法也不多见。
  2. 系统没有安装你要显示的语言的语言包;

       如果你要保证完全无乱码,必须考虑使用unicode码,使用成套的支持unicode的元件,如tnt,但它在UI变现上比较单一,你不可能不使用别的元件。
       言归正传,首先,看看哪些地方的字串需要实现多语言,并来看看各种实现方法的优劣。
           1、界面上的元件,如TButton的Caption;
           2、主动弹出的消息,如ShowMessage('Are you sure?'),Raise Exception.Create('Error!');
           3、例外错误举发的报告信息,如f/0引起的exception;
           4、第3方元件包内部的上述字串;
       实现多语言的方法很多,列举一二:
           1、delphi自带的Resource生成工具
               此工具把专案的dfm文件里的所有字串以及pas中定义为ResourceString的字串列举出来,按不同的语言编译成不同的Resource,专案编译前先选语言,每种语言编译成一个exe。
                这个工具使用很不方便,不是一个完整的解决方案,跟Borland的Midas的demo一样(TClientDataSet通过 ProviderName连接到RemoteDatamodule的TDataSetProvider,实际开发Erp系统时,谁会放100个 TDataSetProvider连接到100个TDataSet?),只是一个原理尚通的示范。
               首先,由于dfm本身也是资源文件的一部分,因此每次修改都要“Update Resource DLLs...”,如修改Button1为Button2,如果你忘了,运行时就会报“找不到资源Button1的错误”;提供的字典编辑画面中,出了字 串,还有Left/Top等资料;字典不能重用,在一个模组翻译了,在第2个模组还要再翻译相同的词。
              其次,每种语言一个exe/bpl,如果你的系统是Package切割,bpl也是每种语言一个,还要小心别把不同语言的bpl组合在了一起,到时候一个画面显示中文,一个显示德文(有一个可能是乱码)就惨了。
              再次,在作bpl组装的系统时,第3方元件如果没有提供多语言的方案,你就需要修改第3方元件,但一般我们不这样干,因为第3方元件会随时更新,难道每次人家更新你也再更新人家。
              因此,一般都没有人使用Delphi本身提供的这个方案(除了作demo)。
           2、Resource dll方式
              用单独的ResourceDll,用LoadResString等函数获得翻译字串,但你要到处写这个函数来一一替换,特别是Form上的字串,噢,会累死人。字典可以重用。
           3、网上讨论很多的ini文件方式
               此方法是写个替换的引擎,在运行时从ini文件读取语言字串来替换画面元件的显示文字。这个方法比第一种进步很多,不需要每种语言编译一个exe了,只要 提供不同的ini文件就好;画面修改时如果ini没有同步更新也不会出现致命错误,最多就是某个文字没有转换;引擎也提供了字串转换函数,因此也可以处理 主动弹出的消息。这个方法在文件格式上有三种不同的实现:
               (1)、[编号]=[字串]
                     每个字串从1开始编号,1,2,3,4......,很麻烦,代码要修改,当然运行时切换语言没问题。
               (2)、[元件.属性]=[字串]
                     这种实现把元件instance一一对应,用RTTI来判断属性,替换很精确,也可以运行时切换语言。不足之处是,略显呆板,多个元件相同的字串会多次列 出;没有扩展性,表现TListView的Columns等复杂元件时比较吃力。
               (3)、[旧字串]=[新字串]
                     不管元件的instance,ini是纯粹的语言对照表,或者叫字典,扩展性、运行时切换语言可能在引擎里。不足之处是不能处理一词多义。
               总的说来,这种方式有很大进步,但为了用ini文件,大家还要费力的破解64k的限制,更专业的方式是使用自定义的文件格式。
               在简单性方面,无疑是这种自定义的转换引擎,[旧字串]=[新字串]的文件格式来得方便,借助字典管理工具,字典文件可以重复使用,也可以提供给专业翻译 公司翻译。那么剩下的问题在引擎上,如何方便,最好用户不写一行代码;如何扩展性强,支持任意的第三方元件;如何有弹性,同一个画面有多种语言的文字,同 一个词可以转换成不同的意思......
            4、给每个元件类继承一个子类,在子类的Loaded方法里转换文字。由于要处理的都是叶级元件(虽然TLabel、TPanel都是从 TCustomControl来,但不能只处理TCustomControl),工作量比较大;对旧有程序除了换元件无能为力。
           5、为每个元件类注册一个转换函数,引擎遍历Container,为每个元件找到血源最近的转换函数,调用这个函数转换这个元件的文字。这样可以不必处理 叶结点,只需在恰当的元件层上注册函数;不必改动旧有程序。設計時Form上只需要放一個轉換元件,這個元件在Loaded后開始掃描Form上的元件, 從for I:=0 to ComponentCount-1或從for i:=0 to ControlCount-1遞歸,找到一個元件就去查找其血緣最近的註冊函數,然後調用這個函數替換其文字。因爲註冊函數是額外加上去的,所以不會動到 舊的代碼,對任意第方元件都可以擴展支持,且也不用去修改人家第3方元件的代碼。
我认为第5种方法很优雅,看起来比较干净。用GOF的设计模式来套,这属 于Mediator pattern(中介者模式)。多年前,我们使用一个叫TXPMenu的元件来获得XP风格的界面,也是感觉到它很干净,一个元件就搞定一切,不用 TLabel换成TFlatLabel,TButton换成TFlatButton......我记得《程序员》上还有文章专门称赞这个元件。但那个元件 没有使用中介者模式,不能很好的扩展对第3方元件的支持。
        最后,我们畅想一下,如果我是Borland,如何在Delphi里完整支援多语言。Delphi提供了一个区块定义的关键字 “ResourceString”,在这个区块定义的字串常量,编译器会把它编译在exe文件的资源区,运行时用LoadStringA这个 Windows API来读取,因此有些外部转换工具可以直接从exe文件读取这些资源字串,再写入转换后的字串;内嵌的转换引擎也可以拦截这个API函数来转换文字。但 是如果exe里的字串资源化不彻底,就无能为力,这个不彻底恰恰来自Delphi的DFM文件,Delphi把DFM文件整个作为一项资源放在exe里, 其上的字串就没法决定是否要don`t resource(Delphi源码里很多常量字串都有这个提示)了。
        如果除了string,widestring,ansistring等等这些数据类型,delphi增加一种数据类型multistring,然后修改 vcl元件定义(拜托Borland连同Unicode一起解决了吧),像TLabel.Caption定义成MultiString,对 MultiString类型,有一种专门的处理方法,如类似ResourceString用LoadString API来处理,每次读取就转换一次,但应该比这个内容更多,比如要传出instance,然后提供一个全局的ApplicationMulti元件,类似 ApplicationEvent,让外面能捕捉到。至于字典,只能外部用户提供(当然可以制定一个标准格式让delphi人都可以共享交换)。
      此法看起来可行,但还有个效率问题要考虑,(1)每次读取都转换,对频繁 draw的东西效率低;(2)比如一个ToolBar有好多的ToolButton,批次更新时一般都会用 BeginUpdate/EndUpdate,vcl如何告知后代来提高这种效率。(补记:效率看起来不是问题,对多次字串更改导致频繁draw,其实元 件自己已经会用beginupdate/endupdate处理,外部不会涉及)

Continue reading Delphi 7编译的程序任何系统都正常显示

delphi入门笔记

源码资源 : http://subsimple.com/delphi.asp
《从入门到精通》 文档中有备份
《从入门到精通》
一般应该重写free方法而不是destroy方法, D不提供垃圾回收机制,需要自己回收内存。
接口方法用静态方法绑定实现生成代码比虚方法实现要小。
except和finally不能同时使用。
子类隐藏父类虚方法不会起到滞后联编的效果,但是覆盖接口的方法却是可以并且是提倡的。
program pt;
{$APPTYPE CONSOLE}
uses
SysUtils;
type
IT = interface
['{A2E180BC-7372-4894-9FB9-9AD286B700CF}']
procedure M1;
end;
type
TA = class(TInterfacedObject,IT)
public
procedure M1();
end;
procedure TA.M1();
begin
Writeln('Am');
end;
type
TB = class(TA,IT) //重新声明继承IT接口,如果不这样,不会起到滞后联编的效果
public
procedure M1();
end ;
procedure TB.M1() ;
begin
Writeln('Bm');
end;
var t:IT;
begin
t := TB.Create;
t.M1;
Readln;
end.

使用delphi7 lite集成了cnpack,但是代码提示快捷键无法修改(ctrl+space),其他快捷键可在

快捷键:
格式化:ctrl+shift+F
----------------------------------------------------------------------------------------构造函数
每个应用程序将分配给其运行的内存分为四个区域: 

代码区(Code area)
全局数据区(data area)
堆区(heap area)
栈区(stack area)

图2 程序内存空间
代码区:存储程序中程序代码,包括所有的函数代码
全局数据区:存储全局数据。
堆区:又叫“自由存储区”,存储动态数据(在Delphi中包括对象和字符串)。作用域为整个应用程序的整个生命周期直到调用了析构方法。
栈区:又叫“自动存储区”存储程序中的局部数据,在C 中,局部变量实际上是auto类型的变量。作用域为函数内部,函数调用完系统就立即回收栈空间。
在C 中,对象既可创建在堆(heap)上,也可以创建在栈(stack)中,还可以在全局数据中创建对象,故C 有全局对象、局部对象、静态对象和堆对象四种对象之说。而在Delphi中,所有的对象都是建立堆(heap)存储区上,所以Delphi构造函数不能自 动被调用,而必须由程序员自己调用(在设计器拖动组件,此时对象由Delphi创建)。下面的程序说明Delphi和C 中创建对象的区别:
在Delphi中:
Procedure CreateObject(var FooObjRef:TFooObject);
begin
FooObjRef:=TfooObject.create;
//由程序员调用,过程调用完之后,对象依然存在.不需要进行拷贝
FooObject.caption=’I am created in stack of CreateObject()’;
End;
在做popupmanager时
EnumWindows(@popwatcher.ProcessFun, LPARAM(self));//一定要这样写@popwatcher.ProcessFun,网上的都没搞清楚,而且这个函数不能是类的函数(静态和对象都不行),否则虽然会调用但是会抛内存访问错误。
另外LPARAM(self)和LPARAM(@self)都能work,偶不懂。尝试自己转指针,没有成功,直接在参数定义上与传入参数一致就可以了:function ProcessFun(hwnd: HWND; pw:TPopWatcher): boolean; stdcall; 

 

Vista提供的UAC机制,是Vista的新增功能之一。它的主要目的是防止对于操作系统本身的恶意修改。如果想对于Vista的系统设置进行改动,必须通过UAC的验证才能够进行。通过这样的手段,大大提供了系统的安全性。 

关于UAC的利弊,网络上的说法褒贬不一,在这里就不具体讨论了。

对于Delphi程序的影响,UAC主要在于以下几点:

1、由于UAC机制,Delphi对于系统的操作可能无声的失败,而同样的程序,在2000/X下面可能运行正常。譬如注册表的改动。。。

2、为了避免这样的情况,Delphi程序必须支持Vista UAC标注,也就是说,在UAC程序的图标下面显示盾牌标志。这样可以在需要系统更高权限的时候,提醒用户。

为了让程序显示UAC标志,现在看来Vista是通过读取程序的资源(Resource)里面的MANIFEST资源,来决定是否显示“UAC盾牌”。

为了支持Vista,Delphi程序必须在资源里面嵌入MANIFEST信息。

1、首先编辑一个文件,内容如下:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
<security>
<requestedPrivileges>
<requestedExecutionLevel level="requireAdministrator"/>
</requestedPrivileges>
</security>
</trustInfo>
</assembly>

保持为UAC.manifest,这里文件是随意的。特别注意红色的“requireAdministrator”,这个表示程序需要管理员(Administrator)才能正常运行。

2、然后编辑一个RC文件,名为uac.rc

1 24 UAC.manifest

其中:

1-代表资源编号

24-资源类型为RTMAINIFEST

UAC.manifest-前面的文件名称

3、用brcc32编译这个rc文件为res文件

brcc32 uac.rc -fouac.res

4、在程序里面加入

{$R uac.res}  //注意这一步,必须添加。不要以为{$R *.res}中的*是包含所有,它是指对应的意思,对你程序中包含的文件会包含其对应的.res,例如abc.pas会包含abc.res(如果有)

让Delphi编译的时候,把uac.res编译进exe文件

5、把文件放到vista里面运行,就会看程序图标下面显示UAC盾牌标志了。

 

Continue reading delphi入门笔记

Pagination


Total views.

© 2013 - 2024. All rights reserved.

Powered by Hydejack v6.6.1