[转载]记一复杂页面的前端优化(1) - 不一样的延迟加载

mikel阅读(1069)

[转载]记一复杂页面的前端优化(1) – 不一样的延迟加载 – BearRui(AK-47) – 博客园.

刚刚做完1个复杂页面的前端性能优化,这里的优化是针对这个页面具体的需求单独做的优化,所以这里不会谈那些减少http请求,合并压缩 js,css,图片合并等等。因为这些是所有页面都需要做的,如果需要了解这些,可以参考我这篇文章:web 高性能开发系列随笔

页面介绍:

该页面是1个记账类的页面,页面如下:

页面主要有4部分组成:

1. 上部的输入部分(有5大不同的类型,每个类型都是1个单独的tab,对应内容也不一样)

2. 左边的分类列表(默认显示一级分类,点击展开子类)

3. 右边时间选择区(按月,年,季,自定义时间过滤等等)

4. 右边下半部分的数据列表(默认只显示每条数据基本信息,点击展开详细信息)

可能看到这里大家不觉的这个页面会很大,那就再细说下,该页面包含记账的所有的功能(添加、删除,修改,分拆,上传图片,显示数据,数据排 序),而且每种下拉列表前面都有个”加号”(见输入部分的下列列表),点击”加号”都会弹出类似如下的窗口进行添加(总共有8个左右的弹出窗口),所有的 这些都是通过js来实现(js代码总共写了大概2000行,不含注释):

问题

该页面有一些用户反映比较慢,经过测试也发现,因为页面比较复杂,js也比较多,所以在IE下速度会比较慢(特别是IE6),而chrome和 firefox速度还是可以的,所以这次的优化主要针对IE,当然优化后的其他浏览器肯定也会受益。

优化1:弹出窗口的延迟加载

本来第1个优化不应该写这个,因为这个优化效果并不是最明显的。把它放在第一位,是因为个人觉得这种延迟加载的想法还不错,比较有新意(目前还 没见过网上有人介绍过这种延迟加载)。

入正题,上面说到,该页面总共有8个弹出窗口,而且每个弹出窗口的都使用了不同的图片(不少是png),监控发现这些弹出窗口用的png图片虽 然设置了缓存头(也使用了document.execCommand(“BackgroundImageCache”, false, true);),但是在IE6下每次都不直接使用缓存,而是发生1个请求,并得到304状态回应(原因我估计跟使用DD_belatedPNG来处理 png图片有关,因为时间关系还没深入研究),监控图如下:

从图中可以看出这几个图片很影响加载速度,其实一开始我们根本用不上这些弹出窗口的图片,因为默认都是隐藏的,而且这些弹出窗口,一般用户都用 的比较少。理所当然,我们想到了延迟加载。

想到延迟加载,第一想到就是先不加载弹出窗口的html代码,这样就不会加载对应的图片了,当用户点击弹出按钮的时候,再去后台加载对应的 html代码。但这样就有个问题,当用户点击”加号”按钮,用ajax去加载html代码,用户明显就会感觉到半天窗口还没弹出来,就会连续的点击,这种 用户体验肯定是失败的。

我们想要的延迟加载是先加载html代码,但不加载html代码使用的图片。但用户点击”加号”的时候,直接弹出窗口并加载图片,这样用户一点 击就可以看到窗口。那如何实现这种功能了,于是我想到了html的注释。我们先把所有弹出窗口的html代码放进注释中(这样就不会加载图片),当用户点 击”加号”时,用js读取注释中的html插入到body中(不需要ajax),然后弹出窗口。

比如有2个弹出窗口,代码大概如下(并不是完整代码,不能直接运行):

01 <script>
02 var loaded = new Object();  // 记录哪些html已经append到body中
03 /**
04 * 加载html,该html已经以注释的方式嵌入的html中,eg:
05 * <div id="fast_model_lazy">
06 * <!--[lazy]>
07 *  sass
08 *  <![endlazy]-->
09 * </div>
10 */
11 function loadHtml(id){
12 // 已经加载过,不再加载
13 if(loaded[id])
14 return false;
15
16 var html = $.trim(document.getElementById(id).innerHTML);
17 // 去掉注释开头(11位)和结尾(14位)
18 html = html.substring(11,html.length-14);
19 $(document.body).append($(html));
20
21 loaded[id]=1;
22 return true;
23 }
24 function click1(){
25 loadHtml("fast_model_lazy");
26 // 弹出窗口
27 $("#fast_model_lazy").showDialog();
28 }
29 function click2(){
30 loadHtml("fast_model2_lazy");
31 // 弹出窗口
32 $("#fast_model2_lazy").showDialog();
33 }
34 </script>
35 <button click="click1()">弹出第一窗口</button>
36 <button click="click2()">弹出第二窗口</button>
37
38 <div id="fast_model_lazy">
39 <!--[lazy]>
40 <div id="fast_model" class="model">
41 ....这里省略html
42 </div>
43 <![endlazy]-->
44 </div>
45
46 <div id="fast_model2_lazy">
47 <!--[lazy]>
48 <div id="fast_model2" class="model">
49 ....这里省略html
50 </div>
51 <![endlazy]-->
52 </div>

总结:

这种延迟加载的方式,主要用于延迟图片的加载,css的应用,js的解析和执行等等,并不是为了延迟加载html。如果是大量的html代码,比如分页的 数据,

使用这种方式就不太合适。个人觉的这种加载方式还可以在很多地方用的到的。

后语:

今天就写这些,下次再写写对该页面的其他的一些优化。

[转载]Asp.net Mvc2中重构View的三种方式

mikel阅读(1053)

[转载]Asp.net Mvc2中重构View的三种方式 – 海纳百川 – 博客园.

我们在ASP.NET mvc的view开发过程中,如果不注意可能会写大量的重复的代码。这篇文章介绍3种方式重构View的代码,来减少View中的重复代码。

1、母板页

ASP.NET mvc中保留了母板页的使用,我们可以使用母板页对我们的站点进行布局。看下面母板页的代码:

<%@ Master Language="C#" Inherits="System.Web.Mvc.ViewMasterPage" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title><asp:ContentPlaceHolder ID="TitleContent" runat="server" /></title>
    <link href="http://www.cnblogs.com/Content/Site.css" rel="stylesheet" type="text/css" />
</head>
<body>
    <div class="page">
        <div id="header">
            <div id="title">
                <h1>My MVC Application</h1>
            </div>
            <div id="logindisplay">
                <%= Html.Action("LogOnWidget", "Account") %>
            </div> 
            <div id="menucontainer">
                <ul id="menu">              
                    <li><%= Html.ActionLink("Home", "Index", "Home")%></li>
                    <li><%= Html.ActionLink("Profiles", "Index", "Profile")%></li>
                    <li><%= Html.ActionLink("About", "About", "Home")%></li>
                </ul>
            </div>
        </div>
        <div id="main">
            <asp:ContentPlaceHolder ID="MainContent" runat="server" />
            <div id="footer"></div>
        </div>
    </div>
</body>
</html>

ASP.NET MVC中使用母板页和Web Form中类似,需要定义ContentPlaceHolder,加上使用一些常用的HTML标签进行布局。 当多个页面都有同样的内容的时候,使用母板页是非常有用的。

2、Partial

Partial类似于Web Form中的用户控件。用它来渲染成内容页,使用Partial最大的好处是这些代码段定义在View页面,而不是代码中。下面举例说明:

渲染partial非常简单,我们可以在父View中使用RenderPartial和Partial方法,Profiles的代码如下,在 Profiles中使用RenderPartial渲染Profile。

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<IEnumerable<Profile>>" %>
<h2>Profiles</h2>
<table>
    <tr>
        <th>Username</th>
        <th>First name</th>
        <th>Last name</th>
        <th>Email</th>
    </tr>
    <% foreach (var profile in Model) { %>
        <% Html.RenderPartial("Profile", profile); %>
    <% } %>
</table>

上面的代码,我将一个profile的List渲染成一个table。每一行定义了一个Partial,用来渲染成一行。即使内容页不能与其他View分 享,在一个View中使用partial可以简化和减少HTML的标签数量。RenderPartial方法需要一个partial名字和一个Model 参数。根据的partial名字用来搜索本地的partial文件,需遵循下面的规则:

1 <Area>\<Controller>\<PartialName>.aspx and .ascx
2 <Area>\Shared\<PartialName>.aspx and .ascx
3 \<Controller>\<PartialName>.aspx and .ascx
4 \Shared\<PartialName>.aspx and .ascx

这些搜索类似于根据view的name搜索view,也可以使用<%= Html.Partial(“Profile”, profile) %> 渲染。Profile文件即可以是一个ASCX文件,如果必要也可以是一个aspx的文件。Profile的代码如下:

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<Profile>" %>
<tr>
    <td>
        <%= Html.ActionLink(Model.Username, "Show", new{ username = Model.Username }) %>
    </td>
    <td><%= Model.FirstName%></td>
    <td><%= Model.LastName%></td>
    <td><%= Model.Email %></td>
</tr>

我们在View中如下渲染Profiles:

    <% Html.RenderPartial("Profiles", Model); %>

渲染效果如下:

ttt

3、Child Action

Partial用来显示已经存在Model的信息时非常方便的。但是有时候View上显示的数据源自其他Model。例如,登录控件可能会显示当前用户的 名称和电子邮件,但该View的主体部分与用户关系不大。可以使用ViewDataDictionary来传递没联系的Model,但是我们可以使用 Child Action。对于在View中显示与主体没有多大关系的信息,这里介绍一下Child Action的使用。下面举例子说明。

在模板页中显示当前的用户信息,当用户登录之后,显示用户名,邮箱之类的信息,当用户没有登录,给出登录的连接。在模板页中加入下面代码:

            <div id="logindisplay">
                <%= Html.Action("LogOnWidget", "Account") %>
            </div> 

LogOnWidget的代码如下,ChildActionOnly确 保只能通过RenderAction调用此方法。

[ChildActionOnly]
public ViewResult LogOnWidget()
{
    bool isAuthenticated = Request.IsAuthenticated;
    Profile profile = null;

    if (isAuthenticated)
    {
        var username = HttpContext.User.Identity.Name;
        profile = _profileRepository.Find(username);
        if (profile == null)
        {
            profile = new Profile(username);
            _profileRepository.Add(profile);
        }
    }

    return View(new LogOnWidgetModel(isAuthenticated, profile));
}

用一个用户控件来显示这个Action的Model的信息,用户控件代码如下。

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl" %>
<%@ Import Namespace="AccountProfile.Controllers"%>
<%
    if (Request.IsAuthenticated) {
%>
        Welcome <b><%= Html.Encode(Page.User.Identity.Name) %></b>!
        [ <%= Html.ActionLink("Log Off", "LogOff", "Account") %> | <%= Html.ActionLink("Profile", "Show", "Profile", new RouteValueDictionary(new { username = Html.Encode(Page.User.Identity.Name) }), null)%> ]
<%
    }
    else {
%> 
        [ <%= Html.ActionLink("Log On", "LogOn", "Account") %> ]
<%
    }
%>

总结:本文阐述了三种方式来重构你View的代码,使用这三种方 式可以大量减少View层重复出现的代码。由于也是最近开始学习ASP.NET MVC,如果叙述和理解有问题欢迎批评指正。

参考:Asp.net  mvc2 in action

作者:朱祁林
出处:http://zhuqil.cnblogs.com
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

[转载]对象设计的艺术

mikel阅读(1182)

[转载]对象设计的艺术 – 张逸:晴窗笔记 – 博客园.

上周末,麦斯博在上海召开了亚太软件研发团队管理年会,我作为讲师参与了架构分会场的演讲。我的演讲题目正是《对象设计的艺术》。“艺术”这个词语 有些大,有点玄,不过我确乎希望能将设计作为一种艺术,与工程结合,既注重实效,又能保证软件的质量,代码的优雅。在这次演讲中,我希望能够深层次地挖掘 所谓设计的本质。这是我的有感而发。因为在设计领域中,前人已经为我们总结了太多的思想、原则与模式。这些内容汗牛充栋,很多程序员根本无法穷尽其内容。 学得越多,感觉懂得越少。而如果就这样无知下去,自然也不利于技能的提升。因此,我尝试着去抓住设计的某些核心价值,这就是我总结出来的七种“武器”:重 用、扩展、分离、变化、简约、一致、间接。

重用

软 件开发的最大敌人就是重复。它会导致重复开发、无法有效复用以及解决方案蔓延。避免重复的方法包括:保持对象的细粒度、高内聚以及对对象的合理封装。我们 可以从方法级、类级以及模块级提高软件的复用性。例如,我们提取方法或类,定义辅助类,按照依赖关系划分模块。如下的类图就是在JUnit Framework中利用模板方法模式实现部分逻辑的复用:image 扩展

优良的软件结 构可以很好地支持扩展,而不用修改源代码。对于扩展性而言,代表两重含义。其一是内部的扩展,它不会在外部接口上增加新的功能,而仅仅是对对象职责的装 饰,或通过代理对象对其进行控制。其二则是外部扩展,我们可以利用继承和组合在重用的基础上,完成对象功能的扩展。当然,最重要的方法是利用抽象。例 如,Java提供的Runnable接口,可以有效地支持多线程编程中对业务的扩展:

class MyThreadStart implements Runnable {
public void run()  {
//do something
}
}

Thread controller = new Thread(new ThreadStart());
controller.start();
分离

在构建架构时,最重要的一个设计原则就是关注点分离。经典的架构模式例如分层模 式与MVC模式正是关注点分离的体现。分离的关键元素是分离变与不变,其中的核心价值即SRP(单一职责原则)。同时,我们在分离对象之后,还要考虑它们 之间的协作。下图展示了我对分离的观点:image image image变化

在软件开发中, 变化是不可避免的。在分析需求时,我们必须寻找变化点。根据我的经验,这些功能点经常会发生变化:
1、业务规则(解决方案:规则模式)
2、算法策略(解决方案:策略模式)
3、命令请求(解决方案:命令模式)
4、硬件支持(解决方案: 入口模式)
5、协议标准(解决方案:元数据)
6、数据格式(解决方案:数据封装)
7、业务流程 (解决方案:工作流定制)
8、系统配置(解决方案:元数据、数据库)
9、界面表现(解决方案:分层模式、MVC模 式)
10、外界服务(解决方案:服务外观)

简约

保持软件的简 约,需要谨记两个原则:KISS(保持软件的简单与易用)和YAGNI(只实现实际需要的功能,而不要想当然地添加功能)。如何才能简化复杂的实现呢?利 用封装可以隐藏复杂的实现,利用抽象可以统一模型,从而消除功能的不同。作为一名架构师,总是希望追求完美的解决方案,这是错误的。许多反模式真是来源于 此,例如分析瘫痪,意外的复杂度,以及货运崇拜(在不理解的情况下使用模式)。

一致

所 谓“一致”包括接口、形式、调用与解决方案的一致。接口一致,则实现就可以替换;形式一致,则可以窥一斑而知全豹;调用一致,则客户端可以透明访问;而一 致的解决方案,则是团队合作的基石。例如,我们可以通过使用合成模式,实现调用的一致:

image 间接

David Wheeler说过:“计算机科学中的大多数问题都可以通过增加一层间接性来解决。”诚哉斯言。在软件开发中,间接可以通过委托、抽象、协作来体现。间接 可以降低依赖,隐藏细节,简化客户端调用。许多模式都体现了间接的思想,例如门面模式、调停者模式、适配器模式、策略模式以及服务定位器模式。

[转载]Template merging with NVelocity and ASP.NET

mikel阅读(1122)

[转载]Template merging with NVelocity and ASP.NET – CodeProject.

Template merging with NVelocity and ASP.NET

By Simone Busoli | 24 Aug 2007
A tutorial about merging templates with NVelocity and ASP.NET.

Introduction

If you have ever tried to implement a newsletter system or a service for communicating with website users, you have probably faced the requisite to send multiple email messages with a common template and some differences, like a personalized greeting in the message header, or an unsubscribe link with a personalized querystring.

Maybe the first way to accomplish this is by writing the static parts of the message in plain text files – for example: emailheader.txt, emailfooter.txt – and reassemble them in the code, merging the varying parts, like recipient’s name, surname, personalized querystring link, and so on.

But what about having a template engine which, given a template – be it a file on the file system, an assembly embedded resource, or an in-memory object – and the information we want to merge in the template, does this all for us?

NVelocity[^] is a .NET-based template engine. It permits anyone to use the simple yet powerful template language to reference objects defined in .NET code. The purpose of this project is to port the Jakarta Velocity[^] project to Microsoft .NET (written in C#).”

Going back to the newsletter example, using NVelocity, the developer just needs to:

  • create a template for email messages specifying the parts that will have to be merged using a simple language called VTL (Velocity Template Language).
  • supply the information to be merged.

NVelocity will do the rest.

Background

In order to write NVelocity templates, a basic knowledge of the language it uses would be helpful. Anyway, in email templates, it’s rarely necessary to write loops, methods, or branch conditions; instead, more often, the need is to replace single variables with values provided programmatically.

In this article, I’m going to show a simple way of writing templates, but if you think that you need something more complex, my advice is to read the VTL Reference Guide[^].

The notation for variables, as taken from the VTL Reference guide, is the following:

Notation (variable name):

$ [ ! ][ { ][ a..z, A..Z ][ a..z, A..Z, 0..9, , _ ][ } ]

Examples:

  • Normal notation: $mud-Slinger_9
  • Silent notation: $!mud-Slinger_9
  • Formal notation: ${mud-Slinger_9}

About the article

In this article, I am going to show how I implemented a wrapper for NVelocity functionalities, which simplifies merging templates coming from a file system, assembly resources, or in-memory objects, and will build a simple web project for demonstration. The source code zip file contains the wrapper project, while the sample project zip file contains a web project which shows how to use it to merge templates in ASP.NET.

As a note, keep in mind that the wrapper can be used in any other project type, from console applications to Windows Forms and WebServices as well. I have chosen to build the demo in ASP.NET because it is a good compromise between a nice user interface and ease of creation.

Using the code

The main access point is the class NVelocityEngineFactory, which exposes three static methods. Each of these methods return an object that implements the INVelocityEngine interface, which can respectively be used to retrieve templates from embedded resources, file system, or memory. The boolean parameter required by each of the methods is used to specify whether the result of the merge has to be cached or not.

The three correspondent engine types are NVelocityAssemblyEngine, NVelocityFileEngine, and NVelocityMemoryEngine.

All of them inherit from the NVelocityEngineBase base class, which defines a common constructor and a static protected method.

Since implementing the INVelocityEngine interface, the three engine classes expose a public method with an overload called Process, which take two or three parameters and return a string or void.

In the first case, the two input parameters are the IDictionary object containing the values to be merged in the template and the template itself, while the return value is the merged template put in a string object.

In the second case, the additional parameter is a TextWriter object which, after the processing, will contain the merged template.

The template

To use the three types of engines, we need to create a template to be merged. Using VTL syntax, we write a simple template which can be used to render a message containing a simple personalized greeting, along with the date when the merging was done.

To test all of the three engines, the demo project contains a template file placed on the file system, a template file embedded in the assembly as a resource, and an in-memory template (a string) created at runtime.

To write a template file, we just need to open a text editor and save the file with the .vm extension.

Hi $name $surname, this output has been generated from a $templateType template on $date.

This is a very simple template, but it’s not difficult to imagine in its place an entire HTML email with nice formatting and some images.

Using the wrapper along with the templates

Now, all we need to do is:

  • Create new instances of the template engines, supplying the information about the template. In order to process a file template, the directory of the template is needed, while processing an embedded resource template will require the assembly name. Finally, to process an in-memory object, no specific parameter is needed, the template will be supplied when the Process method of the engine is called.
  • Add some contents to an object implementing the IDictionary interface and pass it to the engine Process method.

File template

Supposing we have placed the template file in a subdirectory called Templates in the root folder of our web project and named it SimpleTemplate.vm:

string templateDir = HttpContext.Current.Server.MapPath("Templates");
string templateName = "SimpleTemplate.vm";

INVelocityEngine fileEngine = 
    NVelocityEngineFactory.CreateNVelocityFileEngine(templateDir, true);

IDictionary context = new Hashtable();

context.Add("name", TextBox1.Text);
context.Add("surname", TextBox2.Text);
context.Add("templateType", "file");
context.Add("date", DateTime.Now.ToString("D"));

LabelMergedFile.Text = fileEngine.Process(context, templateName);

The LabelMergedFile Text attribute will contain the text of the template and, in place of the variables, the values supplied in the code.

Embedded resource template

Supposing we have placed the template file in a subdirectory called EmbeddedResources in the root folder of our web project, named it SimpleTemplate.vm, and marked it as an embedded resource (under Visual Studio, this can be accomplished by right clicking the file, choosing Properties, and then setting the “Build Action” property to “Embedded Resource”):

string assemblyName = Assembly.GetExecutingAssembly().GetName().Name;

INVelocityEngine embeddedEngine = 
    NVelocityEngineFactory.CreateNVelocityAssemblyEngine(assemblyName, true);

IDictionary context = new Hashtable();

context.Add("name", TextBox1.Text);
context.Add("surname", TextBox2.Text);
context.Add("templateType", "embedded");
context.Add("date", DateTime.Now.ToString("D"));

LabelMergedEmbedded.Text = 
    embeddedEngine.Process(context, "EmbeddedResources.SimpleTemplate.vm");

Note that when calling the Process method, you need to specify the path of the resource, like in file system paths, but with dots in place of slashes.

In-memory template

Differently from the previous cases, now the template must reside in memory, so we create a string object that contains the template:

string template = "Hi $name $surname, this output has been " + 
                  "generated from a $templateType template on $date.";

INVelocityEngine memoryEngine =
    NVelocityEngineFactory.CreateNVelocityMemoryEngine(true);

IDictionary context = new Hashtable();

context.Add("name", TextBox1.Text);
context.Add("surname", TextBox2.Text);
context.Add("templateType", "memory");
context.Add("date", DateTime.Now.ToString("D"));

LabelMergedMemory.Text = memoryEngine.Process(context, template);

Note that the keys of the objects you place in the IDictionary object must correspond to the variable’s names specified in the template if you want NVelocity to merge them. In case you forget to supply a key whose corresponding variable is contained in the template, NVelocity just forgets about it and will give in output $variablename.

Points of interest

I wrote this brief tutorial because I myself have been looking for something like this while I was implementing a newsletter service and after getting crazy at trying to merge the templates by hand.

NVelocity can do much more than this anyway, and if you want to find a place where NVelocity is truly “milked”, you should take a look at CastleProject[^], where they use it as a view engine in their MonoRail[^] project.

The sad thing about NVelocity is that the project is almost dead; the latest release, 0.4.2, is dated October 27, 2003.

The positive thing is that CastleProject developers have made a fork of the project, fixed some bugs, and killed a tedious assembly dependence on an old Log4Net distribution, which NVelocity used as the internal logger engine.

Actually, this NVelocity fork has reached version 0.5, which is the one I have included in the sample project, and whose source can be found in CastleProject’s SVN repository.

Revision history

  • 09 Feb 2006: Added support for embedded and in-memory templates.
  • 17 Jan 2006: Initial release.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Simone Busoli

Software Developer

Italy Italy

Member

[转载]Swing第三刀:做套ERP,要配得上我的登录界面!

mikel阅读(1096)

[转载]Swing第三刀:做套ERP,要配得上我的登录界面! – Java综合 – Java – JavaEye论坛.

在《Swing第一刀》和《Swing第二刀》中提到了一个我正在折腾的一个 ERP界面小框架,不少童鞋表示灰常感兴趣,这里继续和大家分享一个新的小进展:一个全新的登录界面。也许可以改变一些你对Swing和UI设计的看法。

为啥又是登录界面

登录界面是一个软件系统中最简单的一个界面,也是最重要的一个界面。为什么?因为它是用户看到的第一个界面;因为它是用户每天都要看的界面。要想让 用户爱上你的软件,一个美观、耐看、友好的登录界面是一个重要的前提。下功夫做一个好的登录界面,你的系统就成功了一半。这就是为什么很多美工初学者总是 喜欢从模仿、创作登录界面开始。

不信咱仔细瞅瞅。瞧,这是你做的登录界面:

抓图来自网络,版权归作者所有

瞧,这是我的登录界面(来自本文案例):

美工设计效果图

程序实际运行图

哪个更讨人喜欢?你是凤姐,我是志玲姐;你是芙蓉姐姐,我是Lady Gaga姐,不大好比哦。嗯,正如你所说,是“各有所长”吧!不过,除非你有严重的自虐倾向,否则你的选择应当跟我一样。

估计有人已经在叫了:“你这是Swing做的?”“用JNI了?”“这透明圆角,这透明文字,这阴影…咋弄的?”“快扔源码!”“Flex做这个太 容易了”“花里胡哨有P用?”…你看,程序员的“老毛病”又来了。

不错,这个界面是Swing做的;也没有JNI(从来也没整过JNI);代码也极其简单,就是窗户纸一捅就破;也谈不上多么高深技术;也无意和 Flex、.NET之类的技术相比较。我只是想让大家更多的了解Java和Swing的能力,以及我们对待技术和UI的态度。如果急吼吼的看一下代码 run一下demo “哦!”一下,继续扭头堆代码,那我就算是白费这劲了。

Swing变冷了,还是变热了

JavaEye上最近关于Swing的帖子络绎不绝,不少人惊呼:Swing似乎难道又要热了?面对这样的疑问,国内著名Java专家、《程序员》 杂志特约撰稿人、美国密苏里州立大学计算机科学与技术学院客座教授、JavaEye创始人Robbin却表现的很坦然,他告诉我说“对于我们Swing程 序员来说,这是很正常的事情。”在Robbin看来,这是再正常不过的现象了。“Swing本来就很强大, 而最近频频出现的热帖也不是因为Swing发展加快了,只是媒体关注变多了。我使用Swing已经30多年了,在我上大学读书开始就接触 Swing,”Robbin说,“毫无疑问,我相信Swing会取得成功”。“美洲大陆已经存在上亿年了,只是哥伦布发现了它而已。所以,不必惊呼新大陆 很美很新鲜,只能怪我们很傻很无知。”

(注:以上访谈纯属自编自导个人杜撰,如有雷同,纯属巧合。)

Swing做界面,是快刀还是钝刀

用Swing做一个上面的登录界面,需要多少时间?答绝不可能是几分钟。而要做到“技惊四座、一鸣惊人”,用“鬼斧神工”搬的精致来彻底讨好和吸引 用户,就必须下足功夫。这往往不是程序员一个人在战斗!美工师、程序员、熟悉业务的系统工程师…需要一起雕章琢句、废寝忘食。然而,好的设计带来的回报也 会远远超过你的想象。所以,讨论所谓**技术更“快”和更“慢”毫无意义,当初快如闪电的PB、Delphi如今也难觅踪迹。所以,程序的价值和回报不在 于速度,更多的在于我们的态度:精雕细琢成大器,还是粗制滥造堆垃圾呢?

程序员:我们并不在意程序是否好看

真的吗?你真的这样看吗?真是很可怕的一件事情。当然,在Java领域里,只要一提到J2EE、企业应用,大家就会立刻想到业务、重构、集群、云计 算、松耦合、SOA、线程池、EJB、JPA、Hibernate、Spring、JBPM、SSH、JBoss、Lucene、Seam数之不尽的 buzzword和framework。很多程序员浸淫在各种框架之中乐此不疲、无法自拔,甚至从来没有写下过例如“public class MyClass extends *** implements ***”亲手create过一个自己的class,实在是挺可怕。软件说到底是拿来“用”的,不是用来“学”的。再多的框架和技术,最终还是要为用户服 务,和用户交互就要有好的UI。UI无非就是User Interface,是一切between在用户和机器之间的东西。忽视UI技术和UI设计,确实对一个程序员的成长是不利的。

程序员:我们需要左脑还是右脑

美国的斯佩里和日本角田等人的研究表明,左脑支配右半身的神经和感觉,是理解语言的中枢,主要完成语言、逻辑、分析、代数的思考认识和行为,它是进 行有条不紊的条理化思维,即逻辑思维,是程序员的典型活跃区域,也是我们的“国家级重点保护区”。喜欢踢足球的程序员们注意了:尽量不要头球;用也要尽量 用右脑;用左脑最好戴个摩托头盔先,别把吃饭的家什整坏了。右脑支配左半身的神经和感觉,是没有语言中枢的哑脑,但有接受音乐的中枢,主要负责可视的、综 合的、几何的、绘画的思考认识和行为,也就是负责鉴赏绘画、观赏自然风光、欣赏音乐,凭直觉观察事物,纵观全局,把握整体。右脑具有类别认识能力、图形认 识、空间认识、绘画认识、形象认识能力,是形象思维。看上去右脑似乎也恰恰是咱们程序员的软肋。但这能很好的解释为什么程序员弹吉他蓬蓬作响却像弹棉花, 拉二胡一身汗还是像驴叫,跟大妈吵架被骂的插不上嘴,这不能怨我们自己,原来是有生理原因的。

但是从事逻辑思维的程序员恰恰还需要有很强的创造力。而右脑在创造性工作中具有不可替代的作用。美国科学家在《思维的艺术》一书中,将创造过程分为 四个阶段,即准备阶段、酝酿阶段、闪光阶段和验证阶段。这其中,直觉和顿悟是创造的泉源,但是它必须经过语言的描述和逻辑的检验才具有价值。左右脑的这种 协同关系是创造力的真正基础和源泉。如果我们一味的抱怨右脑发育不良,甚至自认右脑天生脑残,那就只能一生一世做一个不折不扣的coder编码机器了。

勤奋和聪明,哪个更重要

这个话题有点大。作为程序员,不管缺了哪样,都是致命的,还不如早点放弃这条路的好。这论调似乎很没新意,如果非要选择一个的话,我选择勤奋。不聪 明,哪怕做不了程序员还可以干其他的嘛,比如当个官员、干个城管、踢个足球啥的(仅限国足,男队)。但要是不勤奋,恐怕就一事无成了,想做贪官都难(不勤 奋你以为就能贪的到啊)。勤奋又聪明,再加上那么一点点激情,你就厉害了!

细节是魔鬼:登录还是登陆?

这真是一个问题。Google一下看,很多人都在争论和分析是“登陆”还是“登录”,这似乎不仅仅是一个软件问题,而变成了一个语言学术研究问题。 《北京晚报》还在2007年5月专门发表解青的署名文章《“登陆网站”还是“登录网站”》进行深入的分析。经过一系列复杂的公式推导逻辑运算,作者最终得 出的结论是应当使用“登陆网站”。我们程序员语言能力一向很差,虽然“登陆网站”还说得过去,但是软件的login界面如果用“登陆”那我们就会感觉有点 点怪了,总让人想起彪炳史册的伟大的“诺曼底”这个地方。毫无疑问,“登录”是更正确的选择。别说程序员不用这样较真,别说这个跟我程序员无关。如果哪个 大型软件的login界面用“登陆”两个字,请你立刻鄙视它、嘲笑它、讽刺它、挖苦它、打击它、永远别买它、永远离开它,离啊离开它,离~开~它!不服气 现在就重启系统看看Windows界面用的什么,去Google一下主流软件抓图用的是什么。如果你做的软件真的由此而不能卖出去,此事也与你无关吗?细 节决定成败,细节就是魔鬼,这不是口号。你不提防它,它就吃掉你。

Swing与不规则窗体的“哥德巴赫猜想”

不管你信不信,“如何用Swing做不规则窗体”是近10年来Swing被最常问到的几个问题之一。Google上搜索“Swing 不规则 窗体”有将近10万个中文结果,而且从1999年就有人在致力于这一问题的解决方法,至今甚至已经出现多种不同“流派”的解决方案。

第一招:抓屏法

最早出现的比较靠谱的一个解决方法出现在《Swing Hacks》一书中。《Swing Hacks》一书是著名的图书出版商O’Reilly在2007年出版的Swing技巧图书,作者是Swing开发组成员Joshua Marinacci,他也是目前JavaFX开发组成员。在这本书的41章中介绍了一种制作透明窗体和不规则窗体的方法。由于当时的JDK功能所 限,Java本身并未提供任何直接的API来制作半透明或不规则窗体,所以这一技巧是利用Robot来截取屏幕原有内容生成内存图片,然后将图片显示在 Swing的窗体中作为背景,“欺骗”大家的眼睛误以为是窗口“透”过去了。这一招确实体现了程序员的聪明才智。

不过这一方法的缺陷是,当窗口被移动一下后就会露馅,所谓“透明”区域的抓图不知道位置变化,也不会随之改变。所以在该书的例子程序中,作者又将 JFrame的setUndecorated设置为true去掉了标题栏,让你无法移动窗口;再启动了一个线程,每250毫秒就重新抓屏一次,更新 Swing窗口的图片背景。不过由于Swing窗口显示出来后,它本身又遮挡了屏幕后面的物体,作者只好先frame.hide()把窗口隐藏一下,然后 马上抓图,然后再frame.show恢复窗口显示。透明效果是勉强出来了,但是程序在那里有事没事的一直忽隐忽现,真是够怪异的,效率、实时性也都惨不 忍睹。

以下是相关代码:

Java代码
  1. public void run( ) {
  2. try {
  3. while(true) {
  4. Thread.sleep(250);
  5. long now = new Date( ).getTime( );
  6. if(refreshRequested &&
  7. ((now – lastupdate) > 1000)) {
  8. if(frame.isVisible( )) {
  9. Point location = frame.getLocation( );
  10. frame.hide( );
  11. updateBackground( );
  12. frame.show( );
  13. frame.setLocation(location);
  14. refresh( );
  15. }
  16. lastupdate = now;
  17. refreshRequested = false;
  18. }
  19. }
  20. catch (Exception ex) {
  21. p(ex.toString( ));
  22. ex.printStackTrace( );
  23. }
  24. }

《Swing Hacks》中介绍的方法代码片段

第二招:AWTUtilities.setWindowShape法

随着Sun公司对JavaFX技术的猛醒和大力持续投入,JDK从6就开始从底层为JavaFX的未来做好准备,提供更多底层功能支撑。作为 “酷”、“炫”的UI技术的先锋,窗口透明、不规则窗口自然是将来JavaFX不可缺少的元素和特性。所以,Sun在JDK6中提供了几个新的函数,用来 支持窗口透明度、窗口任意形状:

Java代码
  1. void setWindowOpacity(Window window, float opacity) //设置窗口透明度
  2. void setWindowShape(Window window, Shape shape) //设置窗口形状

这里有官方的具体介绍:

http://java.sun.com/developer/technicalArticles/GUI/translucent_shaped_windows/

setWindowOpacity方法提供了官方的、彻底的方法来生成不规则形状窗体。不过依旧有以下几个问题:

  • AWTUtilities并非JDK公开类,将来可能会发生变化。当然除了编译时的一个不爽的警告外,也不用过度担心,即使将来API发生变化, 相信Sun和Oracle也会妥善处理好。
  • 用Shape形状定义的窗口边缘粗糙,显示效果差。使用setWindowShape函数对窗口设定形状后,其窗口切割的边缘并未做抗锯齿 (anti-alias)处理,也没有相应的函数或参数进行控制,导致显示效果粗糙。看看Sun自己做出来的例子:http://java.sun.com/developer/technicalArticles/GUI/translucent_shaped_windows/9.jpg, 一个简单的椭圆Shape,其边缘就已经粗糙不堪。更不用说更复杂的透明图片边缘了。本人经过好几天的反复尝试,发现其效果始终不甚理想(如图),无论对 图片的透明边缘如何精细处理,甚至直接new Shape,都完全达不到美工设计出来的效果图。
  • 透明PNG图片的边缘Shape不好获取。如果我们的窗体不是一个规则的、可定义的几何形状Shape,而是一个任意透明PNG图片,该如何获取 图片的透明边缘Shape,进而设置window的不规则形状呢?这确实是一个难题。在网上有人专门讨论这一算法,基本上是读取PNG图片的每一个像素, 获得像素透明边界点,对边界点进行不断的合并与逼近,最后形成一个最终Shape。TWaver的TWaverUtil工具类中就有一个 getImageShape方法用来获得任意图片的边缘shape。经反复测试验证,就是采用了这种算法。不过这种算法的缺点很明显:边缘必须是连续的, 甚至必须是“外凸”的;如果png图片中间有一个透明的“洞”,甚至边缘有一个凹陷透明区域,生成的Shape都无法准确反映出来。

第三招:终极解决之道

经过反复的研究探索,终于获得了一个完美的解决方法:不用shape、不用抓图、不用workaround,真正的、彻底的、完全的、随意的在桌面 上任意绘图、涂鸦、撒野,真正的属于程序员的Freedom!下面就来一起揭开这层窗户纸吧!

在程序中依次设置以下几个参数:

  • 设置窗口完全透明:AWTUtilities.setWindowOpaque(frame, false);
  • 设置窗口无边缘:frame.setUndecorated(true);
  • 设置窗口的ContentPane为要显示的Pane:frame.setContentPane(myPane);
  • 在myPane中放置具体要显示的内容,也可以重载paint方法进行Java2D绘制。这些paint会直接发生在桌面背景上。
  • 接下来,就是见证奇迹的时刻!

(不好意思,暴露我的桌面了)

通过上面方法,可以做一个任意大小、任意位置的window,在相应的桌面位置上随意显示Swing组件,或做任意Java2D画图。比如下面小例 子可以在屏幕上直接画一个红色的立体矩形,而没有显示窗口:

Java代码
  1. import com.sun.awt.AWTUtilities;
  2. import java.awt.Color;
  3. import java.awt.Graphics;
  4. import javax.swing.JFrame;
  5. import javax.swing.JPanel;
  6. public class Test {
  7. public static void main(String[] args) {
  8. JFrame frame = new JFrame();
  9. frame.setDefaultCloSEOperation(JFrame.EXIT_ON_CLOSE);
  10. frame.setUndecorated(true);
  11. frame.setBounds(500, 500, 300, 300);
  12. AWTUtilities.setWindowOpaque(frame, false);
  13. JPanel pane = new JPanel() {
  14. @Override
  15. public void paint(Graphics g) {
  16. super.paint(g);
  17. g.setColor(Color.red);
  18. g.fill3DRect(10, 10, 100, 100, true);
  19. }
  20. };
  21. frame.setContentPane(pane);
  22. frame.setVisible(true);
  23. }
  24. }

运行效果如下图:

窗口的拖拽移动

窗口不再规则,窗口标题栏不再出现,如何移动窗口?按照其他类似软件的习惯做法,应当允许用鼠标直接拖拽窗体任意位置进行窗口移动。做一个鼠标监听 器对窗口中的元素进行拖动监听,对窗口进行相应拖动距离的移动:

Java代码
  1. private MouseAdapter moveWindowListener = new MouseAdapter() {
  2. private Point lastPoint = null;
  3. @Override
  4. public void mousePressed(MouseEvent e) {
  5. lastPoint = e.getLocationOnScreen();
  6. }
  7. @Override
  8. public void mouseDragged(MouseEvent e) {
  9. Point point = e.getLocationOnScreen();
  10. int offsetX = point.x – lastPoint.x;
  11. int offsetY = point.y – lastPoint.y;
  12. Rectangle bounds = FreeLoginUI.this.getBounds();
  13. bounds.x += offsetX;
  14. bounds.y += offsetY;
  15. FreeLoginUI.this.setBounds(bounds);
  16. lastPoint = point;
  17. }
  18. };

对窗体上的组件安装这一listener,就可以对窗口中任意元素进行拖拽,直接拖动窗体四处晃悠了。

图片的切割

要做好的界面,需要一个耐心、有创意的美工大力协助,例如图片的切割就很重要。下图展示了如何从效果图进行具体切割素材:

制作渐变背景Panel

仔细观察中间的输入区域部分,其背景是有渐变设计的。其制作方法也很简单:首先让美工帮助制作一个一个像素宽、整个panel高度的小图片作为素 材;然后用这个图片创建纹理Paint;最后用这个纹理对真个panel进行fill。

Java代码
  1. private JPanel inputPane = new JPanel() {
  2. private String backgroundImageURL = FreeUtil.getImageURL(“login_background.png”);
  3. private TexturePaint paint = FreeUtil.createTexturePaint(backgroundImageURL);
  4. @Override
  5. protected void paintComponent(Graphics g) {
  6. super.paintComponent(g);
  7. Graphics2D g2d = (Graphics2D) g;
  8. g2d.setPaint(paint);
  9. g2d.fillRect(0, 0, this.getWidth(), this.getHeight());
  10. }
  11. };

肆虐你的桌面:六月飘雪!

既然窗户纸捅破了,在桌面上就随意折腾吧。这几天窗外酷热难耐,咱们就来个桌面飘雪,也许可以望梅止渴,带来丝丝清凉吧!

先准备一个雪花的png透明图片,然后在桌面上随机生成50个雪花坐标,每次paint让每个雪花的左右略微抖一下(snowX[i] += TWaverUtil.getRandomInt(5) – 3),垂直距离下坠5像素(snowY[i] += 5),再旋转个小角度。然后,用一个线程不停的repaint窗口。

雪花png图片:

程序代码如下:

Java代码
  1. public class TestSnow {
  2. public static void main(String[] args) {
  3. final JFrame frame = new JFrame();
  4. frame.setAlwaysOnTop(true);
  5. frame.setDefaultCloSEOperation(JFrame.EXIT_ON_CLOSE);
  6. frame.setUndecorated(true);
  7. frame.setExtendedState(JFrame.MAXIMIZED_BOTH);
  8. AWTUtilities.setWindowOpaque(frame, false);
  9. final JPanel pane = new JPanel() {
  10. private int[] snowX = null;
  11. private int[] snowY = null;
  12. private int[] angles = null;
  13. private int count = 50;
  14. @Override
  15. public void paint(Graphics g) {
  16. super.paint(g);
  17. Rectangle bounds = frame.getBounds();
  18. if (snowX == null) {
  19. snowX = new int[count];
  20. for (int i = 0; i < snowX.length; i++) {
  21. snowX[i] = TWaverUtil.getRandomInt(bounds.width);
  22. }
  23. snowY = new int[count];
  24. for (int i = 0; i < snowY.length; i++) {
  25. snowY[i] = TWaverUtil.getRandomInt(bounds.height);
  26. }
  27. angles = new int[count];
  28. for (int i = 0; i < snowY.length; i++) {
  29. angles[i] = TWaverUtil.getRandomInt(360);
  30. }
  31. }
  32. Graphics2D g2d = (Graphics2D) g;
  33. Image image = TWaverUtil.getImage(“/free/test/snow.png”);
  34. for (int i = 0; i < count; i++) {
  35. snowX[i] += TWaverUtil.getRandomInt(5) – 3;
  36. snowY[i] += 5;
  37. angles[i] += i / 5;
  38. snowY[i] = snowY[i] > bounds.height ? 0 : snowY[i];
  39. angles[i] = angles[i] > 360 ? 0 : angles[i];
  40. int x = snowX[i];
  41. int y = snowY[i];
  42. int angle = angles[i];
  43. g2d.translate(x, y);
  44. double angleValue = Math.toRadians(angle);
  45. g2d.rotate(angleValue);
  46. g2d.drawImage(image, 0, 0, null);
  47. g2d.rotate(-angleValue);
  48. g2d.translate(-x, -y);
  49. }
  50. }
  51. };
  52. frame.setContentPane(pane);
  53. frame.setVisible(true);
  54. Thread thread = new Thread() {
  55. @Override
  56. public void run() {
  57. while (true) {
  58. try {
  59. Thread.sleep(10);
  60. catch (Exception ex) {
  61. ex.printStackTrace();
  62. }
  63. pane.repaint();
  64. }
  65. }
  66. };
  67. thread.start();
  68. }
  69. }

快快运行代码,让雪花飘起来吧!

如果愿意折腾,还可以修改代码中的:

  • private int count = 50,调整雪花的数量;
  • 修改angles[i] += i / 5,调整雪花翻滚的速度;
  • 修改snowY[i] += 5,调整雪花下坠的速度;
  • 修改snowX[i] += TWaverUtil.getRandomInt(5) – 3,调整雪花左右摆动的速度;

别说你不知道怎么结束程序啊,不会Alt+F4的话,你这个程序员肯定不合格了。

秘密背后的秘密

当把透明窗口Frame设置特别大以后(例如10000*10000),你会发现不但界面变得极其缓慢,而且还会内存溢出。Sun的秘密不言自明 了:还是使用了BufferedImage。否则,鼠标点击你画的椭圆或桌面的图标,它如何知道是点击了窗体,还是操作了桌面?只能生成内存图片,在里面 进行像素判断了。要挖掘再深入的秘密,我也不清楚了,自己继续探索吧!

代码和可执行文件

作为一个完整的小Swing UI框架的一部分,可以在本 文下方下载整个zip包,执行其中的login.bat来运行登录窗体、ERP程序框架、屏幕下雪例子。源代码还在整理中,请大家耐心等待。关于 整个框架的更多介绍,可以查看《Swing第一刀》 和《Swing第二刀》。希望大家喜欢,并给我 留言,也希望各位高手批评指正!

谢谢!

  • 大小: 2.7 KB
  • 大小: 32.7 KB
  • 大小: 20.9 KB
  • 大小: 252.2 KB
  • 大小: 9.8 KB
  • 大小: 24.7 KB
  • 大小: 853.8 KB
  • 大小: 19.2 KB

[转载]Swing第二刀:枝间新绿一重重

mikel阅读(1063)

[转载]Swing第二刀:枝间新绿一重重 – Java综合 – Java – JavaEye论坛.

关于绿色

喜欢绿色,喜欢雅黑,无可救药。在这个吵吵闹闹的软件行业,绿色也忽然从“春风一拂千山绿”唯美变成俗 不可耐的buzzword。比如:

  • 绿色软件:大大的buzzword。忽然一夜之间,所有的软件都绿色了,好像不“绿”就跟不上形势。比如绿色杀毒,绿色OFFICE,绿色 ERP,绿色windows。反正全绿。
  • 绿色征途:看,精神鸦~片也可以很“绿”的;
  • 绿~坝-花季护航:呃,老好的软件,不多说了;
  • 绿色世博:嗯,喊的老响了。至今一头雾水。
绿色还有一些不好的词,比如人人避之不及的“绿帽子”之类。台湾的绿营也代表了大坏蛋那帮人(至少陈水扁带了个头)。还有“我把老板气的脸都绿 了”、”老板整天灯红酒绿“,也都不是什么好词。
不过绿色更多的还是代表了“春天、自然、环保、低碳”等没好的东西。皮尤慈善信托基金(Pew)在一份名为《绿色改变世界》的研究报告中指出, 计算机屏幕如果使用绿色可以节省大量电能并降低辐射,保护使用者双眼和皮肤。例如一个全屏显示的绿色软件界面可以让一台液晶显示器消耗功率降低13.7 瓦;假设美国电脑拥有量为5299万台来计算,每年仅关机状态功耗一项可以节约 6.2亿度电。如果我们把所有的软件都设计成“绿色”这个环保色,每台计算机都会减少5%的电能和辐射,人的情绪也会更加平缓舒畅,心脏和双肺由此减少 12%的血液循环负担,从而降低人类对氧气的需求和二氧化碳的呼出达到14%。加上节省下来的对皮肤护理保养、心肺疾病治疗、近视以及眼镜相关行业对自然 资源的消耗,每年全球可以节省1400亿美元的资源消耗,相当于减少砍伐8500平方公里的亚马逊热带雨林,对于处于正在复苏之中的世界经济具有说一不二 的作用。看来,“绿色软件”,先把自己的界面搞“绿”了,就是地球的一大幸事!
(注意:上述报告和数据纯属胡诌八扯,如有雷同,纯属巧合。)
但是制作一个绿色的软件界面确实一个心愿。尤其是能够体现“枝间新绿一重重,小蕾深藏数点红”的那种感觉!现在终于有了,经过一个多星期的 折腾,终于有了一个雏形,在上一篇博文《Swing是一把 刀》中给大家看到的:

框架,还是框架

这个程序的设计初衷是快速建立一个美观的Swing应用程序外观。但是,具体来说,它又并非完全是下面 几个东西:

  • LookAndFeel:这个程序并非一个LnF。一个LnF会对所有的Swing组件进行重新定义Paint并可以通过 UIManager.setLookAndFeel进行启用。这个程序用到了大量LookAndFeel的机制,甚至也直接定义了不少UI。不过它并不是 一个完整的LookAndFeel。这些定制完全是为这个程序框架服务的。也就是说,这些UI和重绘机制只有在当前的程序框架起作用,而无法指望一句 UIManager.setLookAndFeel就将你的任意Swing程序变成上图风格。
  • 组件库:也不是组件库。其实里面的组件,除了这个OutlookPane(左侧的模块树)是完全新做出来的(而且没有从JTabbedPane继 承,也许理论上还经不起太严谨的推敲),其他的组件都是很简单、现成的。例如列表、按钮、菜单等,都是直接用Swing的,只是重载了一些方法或者定义了 UI而已。而且我并非是想让大家直接new OutlookPane()这样来使用,而是使用XML文件对整个界面进行配置使用;
  • GUI程序框架:似乎有点大。这仅仅是一个很小的程序而已。

做Java的喜欢满嘴Framework。你要是不能气定神闲一口气提到20个Framework并有 意无意的暗示自己很精通,那~都不好意思跟人家说话;最好再能挑一个有点名气的,指手画脚、评头论足、怒其不争一下下,那就像大牛了(例如 Hibernate就是个很不错的candidate)。所以,咱这个小程序也就死乞白赖往“框架”上凑凑,反正已经带上了“绿帽子”,也不怕丢人丢到 底。

好吧,这是一个框架,虽然我也不知道框架该怎么定义。反正我的设计初衷是:如果你用Swing开发一个 类似上图结构的应用程序,那么你可以直接用这个框架。这个程序框,包含了上面菜单、地下状态条、左边模块栏、右边功能快捷列表,中间多tab标签页的各种 内容(也提供了几个常用的内容页风格,例如列表、流程图等)。这个窗口已经被封装好,通过XML配置文件来定义菜单、状态条、模块栏、流程图、右边的快捷 列表。同时,这些都是联动的。例如:点击左边的模块栏中的子模块,一个对应的流程图会显示在中间tab页;选中流程图中的节点,可以把该节点相关的功能列 在右侧。点击右侧列表,可以执行各种定义好的动作(动作通过动作码定义,后面会详细介绍)。使用么,直接new 一个窗口类,set各个部分的XML文件名,然后setVisible(true)就OK了。至于中间的各个组件和大家关心的LookAndFeel,则 都定义好了,基本上不用太关心细节。

不管怎么说,我们就叫它“框架”吧。

XML配置

每个应用程序都千奇百怪,功能各异。如何用一个同样的界面来组织呢?的确,这个界面并非适合所有人。不 过这里的所有菜单、按钮、流程图、图标等,其动作都是可以用一个“动作码”类定义的,所有的动作都会回调一个统一的函数。而我们只要在这个函数处插入监 听,就可以拦截具体动作码,执行我们想做的任何事情,例如格式化C盘、往aobama@whitehouse.com邮箱发个垃圾邮件啥的。

例如,要定义主菜单,通过这个XML:

Xml代码
  1. <?xml version=“1.0” encoding=“UTF-8”?>
  2. <menubar>
  3. <menu text=“System”>
  4. <menu text=“One Sub Module”>
  5. <menuitem text=“Test Report Item” tooltip=“Tooltip”
  6. icon=“/free/email.png” action=“A001”/>
  7. <menuitem text=“Test Report Item” tooltip=“Tooltip”
  8. icon=“/free/email.png” action=“A001”/>
  9. <menuitem text=“Test Report Item” tooltip=“Tooltip”
  10. icon=“/free/email.png” action=“A001”/>
  11. <menuitem text=“Test Report Item” tooltip=“Tooltip”
  12. icon=“/free/email.png” action=“A001”/>
  13. </menu>
  14. </menubar>

以上XML可以定义一个System的主菜单,以及一个One Sub Module的菜单项,以及一系列的二级菜单。每个菜单都可以设置icon图标、文字、tooltip文字,以及动作码(就是那个action)。如下 图:

左侧的模块栏就是典型的Outlook的风格,很多软件干脆都叫它OutlookPane(我这里也是 如此)。这个OutlookPane的配置,通过如下类似XML:

Xml代码
  1. <outlook>
  2. <module text=“Engineering Box”
  3. icon=“/free/test/module_unselected.png”
  4. selected_icon=“/free/test/module_selected.png”
  5. network=“network.xml”>
  6. </module>
  7. </outlook>

同样,主模块(也就是每个大分栏)包含了模块栏的文字、icon图标(选中和未选中两个),以及一个 xml文件。这个xml文件包含了一个流程图,流程图包含了具体的子模块。点击展开大模块栏后,所有的子模块也会显示在栏目中,同时模块的流程关系会通过 对应的xml文件中定义的方式,显示在一个图形化的流程图界面中,最终显示在中间的tab页上。

可以看到,左侧的模块列表和中间的图形节点是一一对应的。当鼠标选中节点后(变成橙色),左侧的列表对 应的项也会被选中。同时,和这个节点(代表了一个具体子模块)相关的功能,都会显示在右侧的快捷列表中(这是通过指向的network.xml文件定义 的)

上面例子中的Network.xml内容举例如下:

Xml代码
  1. <?xml version=“1.0” encoding=“UTF-8”?>
  2. <network>
  3. <node x=“30” y=“50”
  4. text=“AP Prepay”
  5. network_text=“AP Prepay”
  6. tooltip=“This is a tooltip of a node”
  7. icon=“/free/test/module.png”
  8. list_icon=“/free/test/submodule.png”>
  9. <button1 tooltip=“Tooltip”
  10. icon=“/free/test/module_attachment.png” action=“X002”/>
  11. <button2 tooltip=“Tooltip”
  12. icon=“/free/test/module_attachment.png” action=“X002”/>
  13. <button3 tooltip=“Tooltip”
  14. icon=“/free/test/module_attachment.png” action=“X002”/>
  15. <shortcuts>
  16. <separator text=“Most Often Used Reports”/>
  17. <shortcut text=“Add a Part” tooltip=“Tooltip”
  18. icon=“/free/submodule.png” action=“Z010”/>
  19. <shortcut text=“Delete a Part” tooltip=“Tooltip”
  20. icon=“/free/submodule.png” action=“Z010”/>
  21. <shortcut text=“AP Aging Report” tooltip=“Tooltip”
  22. icon=“/free/chart.png” action=“Z010”/>
  23. <shortcut text=“MRP for All Parts Used this Month” tooltip=“Tooltip”
  24. icon=“/free/user.png” action=“Z010”/>
  25. <shortcut text=“Dashboard of this Month” tooltip=“Tooltip”
  26. icon=“/free/email.png” action=“Z010”/>
  27. <shortcut text=“All Open Purchase Orders” tooltip=“Tooltip”
  28. icon=“/free/chart.png” action=“Z010”/>
  29. <shortcut text=“Search in Address Book” tooltip=“Tooltip”
  30. icon=“/free/user.png” action=“Z010”/>
  31. <shortcut text=“All Online Users” tooltip=“Tooltip”
  32. icon=“/free/email.png” action=“Z010”/>
  33. <separator text=“Common Reports”/>
  34. <shortcut text=“All Open Purchase Orders” tooltip=“Tooltip”
  35. icon=“/free/chart.png” action=“Z010”/>
  36. <shortcut text=“Search in Address Book” tooltip=“Tooltip”
  37. icon=“/free/user.png” action=“Z010”/>
  38. <shortcut text=“All Online Users” tooltip=“Tooltip”
  39. icon=“/free/email.png” action=“Z010”/>
  40. <shortcut text=“All Open Purchase Orders” tooltip=“Tooltip”
  41. icon=“/free/chart.png” action=“Z010”/>
  42. <shortcut text=“Search in Address Book” tooltip=“Tooltip”
  43. icon=“/free/user.png” action=“Z010”/>
  44. <shortcut text=“All Online Users” tooltip=“Tooltip”
  45. icon=“/free/email.png” action=“Z010”/>
  46. <shortcut text=“All Open Purchase Orders” tooltip=“Tooltip”
  47. icon=“/free/chart.png” action=“Z010”/>
  48. <shortcut text=“Search in Address Book” tooltip=“Tooltip”
  49. icon=“/test/user.png” action=“Z010”/>
  50. <shortcut text=“All Online Users” tooltip=“Tooltip”
  51. icon=“/free/email.png” action=“Z010”/>
  52. </shortcuts>
  53. </node>
  54. </network>

其中,每个node定义了一个子模块节点。节点上包含x、y坐标信息、文本信息、tooltip、 icon图标(中间大的主图标),以及三个按钮。每个node如图所示,可以携带3个按钮。每个按钮可以挂一个图标、tooltip、icon以及动作 码。我们可以定义其任意动作。

然后,每个node又携带了一个shortcuts列表,包含了这个节点所有相关的功能点,在node 被选中后,以右侧的列表方式列出。如图所示:

此外,流程图中的箭头是通过类似如下XML在network.xml中定义:

Xml代码
  1. <arrow x=“170” y=“80” direction=“left” rotation=“0”/>

其中x、y是坐标,direction是方向,可以是上下左右以及斜向共8个方向。此外 rotation还提供了旋转角度。如果right_up这个45度的右上角度不符合要求,可以在增加rotation进行进一步调节。

右侧快捷列表的分割文字,也是通过如下xml进行定义:

Xml代码
  1. <shortcuts>
  2. <separator text=“Most Often Used Reports”/>
  3. <shortcut text=“Add a Part” tooltip=“Tooltip”
  4. icon=“/free/test/submodule.png” action=“Z010”/>

分隔条可以携带一个文字,用来对很多列表项进行分组:

还有工具条也是可以配置的。工具条在这个框架里被放在了模块栏的顶部。通过如下XML配置其按钮:

Xml代码
  1. <?xml version=“1.0” encoding=“UTF-8”?>
  2. <toolbar>
  3. <button tooltip=“Tooltip” icon=“/free/test/message.png” action=“B001” />
  4. <separator/>
  5. <button tooltip=“Tooltip” icon=“/free/test/user.png” action=“B001” />
  6. <separator/>
  7. <button tooltip=“Tooltip” icon=“/free/test/email.png” action=“B001” />
  8. <separator/>
  9. <button tooltip=“Tooltip” icon=“/free/test/viewer.png” action=“B001” />
  10. <separator/>
  11. <button tooltip=“Tooltip” icon=“/free/test/chart.png” action=“B001” />
  12. <separator/>
  13. <button tooltip=“Tooltip” icon=“/free/test/capture.png” action=“B001” />
  14. <separator/>
  15. <button tooltip=“Tooltip” icon=“/free/test/image_edit.png” action=“B001” />
  16. <separator/>
  17. </toolbar>

其中<separator>定义了一个分割竖线 ,完全美观之用。显示效果如下:

最后一个配置项是软件右上角的LOGO。每个系统都想有一个地方漂亮的显示咱家的LOGO,那才有成就 感!这里通过menubar菜单的XML文件如下配置:

Xml代码
  1. <logo image=“/free/test/logo_company.png” tooltip=“WPT Power Transmission” />

可以对图片、tooltip进行定义。显示效果如下:

这样,一个完整的”纯绿色“应用程序框架就差不多被”配置“出来了。

程序接口

其实设计初衷是无需使用源代码,直接使用下方提供的jar包即可进行二次开发。因为毕竟通过XML就可 以对界面元素进行定义了。如果要把这个框架集成到你的应用程序中,并执行你的具体动作,只需要对free.Shell这个类进行一个函数重载即可。

Java代码
  1. public void command(String action) {
  2. String message = “Perform action “ + action + “.”;
  3. this.lbStatusMessage.setText(message);
  4. }

在Shell这个类中,界面上所有的按钮、菜单、列表等被点击后,都会回调这个Shell的 command方法,并传回action字符串,也就是我们在XML中定义的动作码。接着你就用if else或者case啥的进行处理动作吧!当然也可以调用addTab方法在Shell界面中添加一个tab页面。其使用方法会下次详细阐述。

关于效率、内存、布局和其他

有朋友说很担心执行效率、内存占用。这是对Swing常被攻击的的一个老话题了。简单直接的回答 是:Swing效率没问题。内存占用没问题。满眼哗哗一片字符串,就一定占内存吗?table有1000行,渲染的花里胡哨,就内存问题吗?完全不是这么 一回事。例如,如果你了解Swing的table的renderer机制,其实无论表格有多少行,一个列是用同一个renderer实例来paint的。 注意,是一个实例哈。editor也是一样。很多初学者以为这一列用JComboBox编辑,1000行就会create成1000个ComboBox。 那就完全错了。Renderer就是一个“橡皮章”,一个章,不停的在每个格子里面“盖章”,重绘就重新盖;Editor就是当一个“萝卜”,哪个“坑” 需要编辑时,table动态把它“放”在这个单元格(坑)上面,下次下个单元格需要编辑,再被挪过去。对于一个列来说,就一个萝卜,一个橡皮章。怎么会说 “占用内存”?如果说1000行字符串占用内存,那这些字符串用什么语言和平台不占用内存呢?同样字符串在不同的语言和平台上可以说占用内存几乎没什么差 别。这个以后可以专门讨论。

在这个本程序中,状态栏上有一个封装好的内存监控工具条:

它的作用是监测目前Java的总的申请的堆内存以及使用的内存。相关代码如下:

Java代码
  1. MemoryMXBean memorymbean = ManagementFactory.getMemoryMXBean();
  2. long usedMemory = memorymbean.getHeapMemoryUsage().getUsed();
  3. long totalMemory = memorymbean.getHeapMemoryUsage().getMax();

上面图片中的“18M/63M”标明默认Java堆大小是64M目前使用了18M。尝试多打开一些表格 界面、拓扑图界面,可见内存使用并不是很多。

说Swing的效率低,也不是很有说服力。其实Swing本质还是Java2D在paint东西,看看 那些杂乱的Metal***UI源码就知道了。不过Swing确实设计的够复杂啰嗦,一个LnF就能绕死人。这些机制会导致Swing慢一些。不过说到底 程序的快慢瓶颈还不是在Swing上,还是在如何设计和使用上。例如线程的处理等等,这些都不是初学者很容易搞定的东西。这也导致Swing总是被贴上 “慢”的标签。

自动布局方面,也是比较啰嗦的。如果用IDE的话,推荐NetBeans里面的GUI编辑工具,它使用 的实际是Matisse这个layout。这里有两篇官方文章介绍:
http://wiki.netbeans.org/UsingGUIEditor
http://netbeans.org/features/java/swing.html

如果手写代码,我还是推荐一个超强但比较复杂的TableLayout。这里有其介绍:
http://java.sun.com/products/jfc/tsc/articles/tablelayout/

其他简单的布局,大多可以用Swing内置的几个layout搞定。另外尽量多套用Panel进行嵌 套,化繁为简。不要试图一次一个Panel+Layout把一个复杂的界面搞定,那样会很累。

有朋友很不齿这里使用到了JGoodies和TWaver,这里也说明一下。在JGoodies的基础 上再去定制UI确实美观了不少,不过理论上确实可以彻底抛开JGoodies。这样就要完全彻底重写一整套的LnF了,包括文本框按钮啊所有的东西。目前 确实还没有这样的时间和精力,以后会考虑出一个WithoutJGoodies版本。使用TWaver主要是为了做中间的流程图和拓扑图,并且使用的也比 较顺手了。如果不需要中间的拓扑图流程图,也可以without TWaver,以后会考虑改一版。不过这些东西都是很不错的工具,能用就拿来为我所用,不喜欢用就借鉴一下了事。

程序下载

本 程序可以在这里下载

执行方法:解压zip文件,双击其中的run.bat。

运行环境:Java 6。

其他说明:zip包中的free.jar就是本文提到的框架。其他两个jar包是jgoodies lnf和twaver做流程图的支撑包,可不用理会。

由于牵扯到公司的业务,程序的源代码还在整理之中,预计还要一周左右会整理完毕。到时候一定会跟大家分 享整个程序的源代码。下一次我会详细介绍每一个组件的制作方法和相关代码。

  • 大小: 2.7 KB
  • 大小: 195.7 KB

[转载]自动输出SQL Server存储过程依赖列表到EXCEL文件

mikel阅读(1051)

[转载]自动输出SQL Server存储过程依赖列表到EXCEL文件 – 灵感之源 – 博客园.

前言

类似的软件很多年前写过,不过现在在新国家,新环境,印度佬(我囧)资深系统分析员要求我:给现有的数据库的所有存储过程分别列举所有依赖的对象。

需求

现在数据库很老很大,表不多,200来个,但数据量很大:最大的数据表2亿6千万条,每天增加50多w,925个存储过程。

系统大,耦合度很高,牵一发而动全身。人员变动频繁,接手的人员要在修改之前,就得花相当长的时间来分析关联性。

所以,印度资深系统分析员要求我在一个EXCEL文件中,把925个存储过程的所有依赖的对象(表、函数、视图、存储过程等等)都列举出来。

分析

手工逐个打开存储过程去做,对写软件的人来说是很傻的事情,一般重复性工作,如果预计耗时超过3分钟,我就会卷起袖子写个代码。

工作内容有3部分:

1.获取所有的存储过程。我们可以用sysobjects这个系统表,它存储了所有的 表、存储过程、视图、函数等。其中存储过程的xtype是P。另外说一下,如果是CLR存储过程,类型是PC。

2. 获取某存储过程所依赖的对象,当然是先google了。很久之前我就知道可以用系统存储过程sp_depends来 获取,不过还是应该看看还有什么更好的办法。首先我发现这个:http://www.mssqltips.com/tip.asp?tip=1294 。 作者研究出4种办法:INFORMATION_SCHEMA.ROUTINES/sp_depends/syscomments /sp_MSdependencies。其中就有我一直在用的sp_depends。其它办法有的霸王硬上弓:用charindex来遍历存储过程内容,或者用LIKE来判断。。。。。我服了,写代码的风格千差万别,一些是 [Foo],一些是Foo,而且不同的存储过程名称可能存在完全给另外一个包含,譬如Foo Foo1 AFoo等。

看完之后,我还是觉得使用sp_depends相对靠谱。为什么说“相对靠谱”呢?因为我发现它某 些情况下也会没有返回所有依赖的,这应该是SQL Server的bug吧?如果要把所有依赖都找回来,你可以去修改被遗忘的引用存储过程,随便加个空行,运行(就是保存结果),你会发现之前没有显示的依 赖终于出现了。而且,sp_depends会输出重复的记录。。。所以我们在代码中要剔除掉。

3. 既然是输出到EXCEL文件,我们就需要找相应的代码。在这个网站已经有很多EXCEL文件生成的代码了,譬如NPOI。我最后采用了GemBox的,因 为够轻便。本来想用更轻便的MyXLS, 但发现它不支持单背景色。当然你也可以用别的,譬如XML格式的EXCEL文件,这是你个人的选择了。

解决了上述的2个问题,我们就可以大干一场了。我还是采用VS2005+C#2.0,因为公司还是在用古老的XP搭配VS2005,鬼 佬国家要求什么都正版,自然不会像我们在中国那样随便就升级到2010了。所以只能放弃LINQ,老老实实地写冗余的代码了。

以下代码没有什么特别的,都是循环所有存储过程,然后循环每个存储过程的依赖对象,然后排序输出(先按照类型,然后按照名称)。代码也 写得很quick and dirty,10来分钟的事情,不要跟代码规范较真。

代码

using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Drawing;
using System.Data;
using System.Data.SQLClient;
using GemBox.Spreadsheet;

namespace SQLServerDocumenter
{
class Program
{
static void Main(string[] args)
{
if (args.Length == 0)
{
args
= new string[3];
args[
0] = FooDB;
args[
1] = FooServer;
args[
2] = FooPassword;
}

string db = args[0];
string dataSource = args.Length > 1 ? args[1] : string.Empty;
string password = args.Length > 2 ? args[2] : string.Empty;

ExcelFile xls = new ExcelFile();
ExcelWorksheet sheet
= xls.Worksheets.Add(Dictionary);
CellStyle typeStyle
= new CellStyle(xls);
typeStyle.FillPattern.SetSolid(Color.Yellow);
typeStyle.Font.Color
= Color.Black;
typeStyle.Font.Weight
= ExcelFont.BoldWeight;
CellStyle nameStyle
= new CellStyle(xls);
nameStyle.FillPattern.SetSolid(Color.DarkGray);
nameStyle.Font.Color
= Color.Black;
nameStyle.Font.Weight
= ExcelFont.BoldWeight;
CellStyle itemStyle
= new CellStyle(xls);
itemStyle.FillPattern.SetSolid(Color.LightGray);
itemStyle.Font.Color
= Color.Black;
itemStyle.Font.Weight
= ExcelFont.BoldWeight;

sheet.Cells[0, 0].Value = string.Format({0} database dictionary, db);

sheet.Cells[4, 0].Value = Name;
sheet.Cells[
4, 0].Style = nameStyle;

sheet.Cells[4, 1].Value = Dependencies;
sheet.Cells[
4, 1].Style = nameStyle;

sheet.Cells[4, 2].Value = Type;
sheet.Cells[
4, 2].Style = nameStyle;

string connectionString = string.Format(Password={0};Persist Security Info=True;User ID=sa;Initial Catalog={1};Data Source={2}, password, db, dataSource);
using (SqlConnection connection = new SqlConnection(connectionString))
{
connection.Open();

sheet.Cells[5, 0].Value = Stored Procedures;
sheet.Cells[
5, 0].Style = typeStyle;

DataSet data = new DataSet();
using (SqlCommand command = new SqlCommand(SELECT * FROM sysobjects WHERE XTYPE=’p’ ORDER BY NAME, connection))
{
SqlDataAdapter adapter
= new SqlDataAdapter(command);
adapter.Fill(data);

DataTable objects = data.Tables[0];
int index = 6;
for (int i = 0; i < objects.Rows.Count; i++)
{
string objectName = objects.Rows[i][name].ToString();
sheet.Cells[index,
0].Value = objectName;
sheet.Cells[index,
0].Style = itemStyle;
DataSet data2
= new DataSet();
using (SqlCommand command2 = new SqlCommand(string.Format(exec sp_depends ‘{0}’, objectName), connection))
{
adapter
= new SqlDataAdapter(command2);
adapter.Fill(data2);
}
if (data2.Tables.Count > 0)
{
DataTable dependencies
= data2.Tables[0];
Dictionary
<string, KeyValuePair<string, string>> uniqueDependencies = new Dictionary<string, KeyValuePair<string, string>>();
for (int j = 0; j < dependencies.Rows.Count; j++)
{
string itemName = dependencies.Rows[j][name].ToString();
if (!uniqueDependencies.ContainsKey(itemName))
uniqueDependencies.Add(itemName,
new KeyValuePair<string, string>(itemName, dependencies.Rows[j][type].ToString()));
}
List
<KeyValuePair<string, string>> allItems = new List<KeyValuePair<string, string>>();
foreach (KeyValuePair<string, KeyValuePair<string, string>> item in uniqueDependencies)
{
allItems.Add(
new KeyValuePair<string, string>(item.Value.Key, item.Value.Value));
}
allItems.Sort(
new KVPComparer());
foreach (KeyValuePair<string, string> item in allItems)
{
index
++;
sheet.Cells[index,
1].Value = item.Key;
sheet.Cells[index,
2].Value = item.Value;
}
}
else
{
index
++;
sheet.Cells[index,
1].Value = (N/A);
}
index
+= 3;
Console.WriteLine(
string.Format(({0}/{1}) {2} done, i + 1, objects.Rows.Count, objectName));
}
}

connection.Close();
}

string path = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location) + @”\ + db + .xls;
xls.SaveXls(path);

Console.WriteLine();
Console.WriteLine(all done!);
Console.Read();
}
}

internal class KVPComparer : IComparer<KeyValuePair<string, string>>
{
public int Compare(KeyValuePair<string, string> x, KeyValuePair<string, string> y)
{
int compare = string.Compare(x.Value, y.Value);
if (compare == 0)
return string.Compare(x.Key, y.Key);
else
return compare;
}
}
}

使用

使用很简单,编译(你得找个EXCEL输出代码。。。),在命令行(改成Win应用也可以啊)输入3个参数:数据库名、服务器名和密码。当然,大家 都有自己的品味,喜欢怎么改输出格式就怎么改吧。

结论

印度资深系统分析员只是让我给个EXCEL文件,没有让我写代码,所以把我自己的研究成果发上来也无伤大雅。一般我都喜欢把写的东西弄成可重用的, 不仅仅为了一个固定的目的,所以也便有了3个参数。

最后输出的的EXCEL文件有6000多行,我真怀疑到底有多少人愿意看这个文件。。。

题外话

其实漂洋过海来了澳洲,来到这个都是印度开发人员的公司,经常让我做些工作,最后都不采纳的,或许,印度人跟哪个国家的人都一样,对the new guy表现好的就要让他halt一下。。。

譬如让我用了一个星期研究SSIS,成果都出来了,最后给无视了。所以,也便有了 数据处理利器-SSIS入门与进阶 这篇文章,省得让我的研究给扔到大海。

另外一个题外话:同事给报表执行一个复杂的SQL查询(存储过程),以前都是在几秒内完成的,某天开始,要4分钟,怎么改都是要4分钟,任何机器都 是,但在数据库本身所在的SSMS跑却正常。后来在业务执行插入SET ARITHABORT ON,问题解决。最后发现是SQL Plan出了问题,只需要修改一下存储过程(随便加个空行),保存便可,不需要SET ARITHABORT ON。

[转载]REST风格的应用程序实现

mikel阅读(1107)

[转载]REST风格的应用程序实现 – 小狼的世界 – 博客园.

莫笑我老土,因为我确实是最近才听说REST风格的,以前就是觉得 /category/product/pid

这样的地址非常 的漂亮,但是那只是表象罢了,了解深入以后,发现必须有一个客户端的Ajax Engine和Server端的服务配合,才能实现一个REST风格的应用,下面就是我的实验。

问题?

要对外提供哪些服务。服务器端的服务可能会被众多的浏览器请求,也可能被第三方应用程序所调用,所以需要从总体上来考虑这个对外的“应用程序接 口”(API),尽量保持接口的稳定性。REST是一种风格,并且形成了自己的规则,构建这样的应用,应尽量遵循REST的原则。

以一个足球服务为例,众多的观众会要求观看比赛的记录,上传新比赛记录,更新比赛记录,更正现有的比赛或者删除比赛等等。像这样描述的话,我们 需要提供众多不同的服务,并且最终会倒在维护一致性的工作上。那么应该怎么做呢,考虑一下客户可能的请求方式:

GET方式请求一个新建比赛服务:

http://example.com/newMatch?id=995&bluebaggers=150&redlegs=60

POST或PUT方式请求一个新建比赛服务:

http://example.com/newMatch

附加的XML为:

<match id=”995″>

<score team=”bluebaggers”>150</score>

<score team=”redlegs”>60</score>

</match>

CGI 风格的POST或PUT请求:

http://example.com/newMatch

请求体:

id=995&bluebaggers=150&redlegs=60

或 者一个维护服务的GET请求:

http://example.com/matchMaintenance/command=newMatch&id=995&bluebaggers=150&redlegs=60

或者POST请求

http://example.com/matchMaintenance/command=newMatch

<match id=”995″><score team=”bluebaggers”>150</score><score team=”redlegs”>60</score></match>

以此类推,可以有很多这样的功能。有些人觉得这并不是什么问题,对越来越多的请求,我们只要建立服务,然后给出 相应的说明就可以了。但是,他还是存在缺点的。

也许我们会假设访问 只是来自脚本,那么这种情况可能会简单一点。但实际上,还有很多的因素会涉及到,例如网页浏览器(会存在后撤和刷新按钮的问题)、Web服务器(可能会有 缓存和编译问题)、网络路由和缓存问题、应对爬虫的骚扰、一些个人站点对网站内容的抓取。如果我们考虑这些不同的请求,我们的程序就可以表现的更健壮。

理想的情况下,一个服务应该有自我说明的能力。如果一个服务建立在一种约定俗成的条件下, 那么大家就很容易适应并且进行后续的开发。

REST就是考虑了这些 因素,可以使用RESTful API来实现上面的服务。

RESTful 原则介绍

REST 的主要原则有:

用URL表示资源。资源就像商业实体一样,是我们希望作为API实体呈现的一部分。 通常是一个名词,每个资源都用一个独一无二的URL来表示。

HTTP方法表示操作。REST充分利 用了HTTP的方法,特别是GET、POST、PUT和DELETE。注意XMLHttpRequest对象实现了全部的方法,具体可以参看W3C HTTP 1.1 Specification

也就是说,客户端的任何请求都包含一个URL和一个HTTP方法。回到上面的例子中,比 赛显然是一个实体,那么对于一个特定比赛的请求就表示为:

 http://example.com/matches/995

这种方式是清晰明了的,也许和精确命名的方式有所区别,但是只要遵循这种形式,我们就能很快的进行GET、DELETE、UPDATE和新建操作。

RESTful的原则:

1、URL表示资源

2、HTTP方法表示操作

3、GET只是用来请求操作,GET操作永远都不应该修改服务器的状态。但是这个也要具体情况进行分析,例如一个页面中的计数器,每次访问的时候确 实引起了服务器数据的改变,但是在商业上来说,这并不是一个很重要的改变,所以仍然可以接收使用GET的方式来修改数据。

一个案例,使用GET方式修改数据遭受损失的案例

Witness the the debacle caused by the Google Accelerator interacting with non-RESTful services in mid-2005. The accelerator jumps ahead of the user and prefetches each link in case they should click on it (a non-Ajaxian example of Predictive Fetch). The problem came when users logged into non-RESTful applications likeBackpack. Because Backpack deletes items using GET calls, the accelerator – in its eagerness to activate each GET query – ended up deleting personal data. This could happen with regular search engine crawlers too, though the issue doesn’t tend to come up because they don’t have access to personal accounts.

4、服务应该是无状态的

在有状态的会话中,服务器可以记录之前的信息。而RESTful风格中是不应该让服务器记录状态的,只有这样服务器才具备可扩展性。当然,我们可以 在客户端使用cookie,而且只能用在客户端向服务器发送请求的时候。

5、服务应当是“幂等”的

“幂等”表示可以发送消息给服务,然后可以再次毫不费力的发送同样的消息给服务。例如,发送一个“删除第995场比赛”的消息,可以发送一次,也可 以连续发送十次,最后的结果都会保持一致。当然,RESTful的GET请求通常是幂等的,因为基本上不会改变服务器的状态。注意:POST 请求不能被定义为“幂等”,特别是在创建新资源的时候,一次请求创建一个资源,多次请求会创建多个资源。

6、拥抱超链接

7、服务应当自我说明

例如 http://example.com/match/995 请求了一个具体的比赛,但是 http://example.com/match 并没有对任何实体进行请求,因此,应当返回一些介绍信息。

8、服务约束数据格式。数据必须符合要求的格式

在PHP的程序中,想要实现这种REST风格的URL,仅仅依靠程序是不行的,还需要在服务器端配置rewrite规则,例如,对于一个REST风 格的资源请求:

http://www.api.com/product/113

一般实现的脚本为

http://www.api.com/product.php?id=113

这个是基于QueryString的,也可以做一个统一的 index.php 入口,然后通过处理URI的方式实现,例如:

http://www.api.com/index.php/product/113

这样的URL,都可以通过rewrite来实现rest风格。总之,REST是一种程序设计的风格,为我们整理自己的应用设计提供了一个原则,在利 用这些原则带来的遍历的同时,可以根据实际情况进行灵活的处理。

参考资料:

1、RESTful Service

2、RPC Service

3、HTTP Methods

4、verbs Can also be nouns

[转载]为ASP.NET MVC 2.0添加Razor模板引擎 (on .NET4)

mikel阅读(991)

[转载]为ASP.NET MVC 2.0添加Razor模板引擎 (on .NET4) – 重典的博客 – 博客园.

根据ScottGu的博客记述(http://weblogs.asp.net/scottgu/archive/2010/07/02/introducing-razor.aspx), 在未来不久将会发布一个ASP.NET MVC 3.0的Preview版本,在这个版本中可以使用多个内置的模板引擎,以它发布出来的截图来看,其中包括NHaml,Spark以及微软刚刚发布的 ASP.NET Web Pages(Razor)。 ASP.NET Web Pages包含在Web Matrix中,提供了一种新的模板模式,其扩展名为 .vbhtml/.cshtml,可以使用类似以下语法来做视图显示:

@{
	var i = 11;
}

@(i+1)
<br>

@if (i%2==1){
	<p>true</p>
}else{
	<p>false</p>
}

输出结果为:

12
<br>
 
<p>true</p>

在不久之后Ms还会对此提供Visual Studio 高亮及智能感知支持。

这种模板如此简捷,如果能用在现有的ASP.NET MVC 2.0上做为一个模板引擎是不错的。

首先我们要安装ASP.NET Web Pages,下载地址:http://bbs.eice.com.cn/showtopic-409.aspx ,当然直接安装WebMatrix也是可以。

安装之后在IIS中就会添加对cshtml及vbhtml的支持。

安装后程序集文件会被复制到Program Files\Microsoft ASP.NET\ASP.NET Web Pages\v1.0\Assemblies目录下。

其中包括

Microsoft.Data.dll
Microsoft.Web.Infrastructure.dll
Microsoft.Webpages.Compilation.dll
Microsoft.Webpages.Configuration.dll
Microsoft.Webpages.dll
Microsoft.Webpages.Helpers.dll
Microsoft.Webpages.Helpers.Toolkit.dll

下面我们就动手对ASP.NET MVC程序添加一个Razor的模板引擎:

首先建立一个ASP.NET MVC的项目,然后对其中的Web.Config的system.web/compilation/assemblies节点上添加内容:

<add assembly="Microsoft.WebPages.Compilation, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
<add assembly="Microsoft.Data, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
<add assembly="Microsoft.WebPages.Helpers, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
<add assembly="Microsoft.WebPages, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>

并对system.web/compilation/buildProviders添加内容:

<add extension=".cshtml" type="Microsoft.WebPages.Compilation.InlinePageBuildProvider" />

并引用相应的

Microsoft.Data.dll 
Microsoft.Webpages.dll 
Microsoft.Webpages.Helpers.dll 
Microsoft.Webpages.Compilation.dll

几个文件

准备工作做好了,下面就来实现相应的模板引擎,我们先来实现一个IView对象:

public class WebPageView : IView
    {
        public WebPageView(string viewPath)
            : this(viewPath, null)
        {
        }

        public WebPageView(string viewPath, string masterPath)
        {
            if (string.IsNullOrEmpty(viewPath))
            {
                throw new ArgumentException("viewPath can't null", "viewPath");
            }
            this.ViewPath = viewPath;
            this.MasterPath = masterPath ?? string.Empty;
        }

        public virtual void Render(ViewContext viewContext, TextWriter writer)
        {
            if (viewContext == null)
            {
                throw new ArgumentNullException("viewContext");
            }
            WebPage page = WebPage.CreateInstanceFromVirtualPath(this.ViewPath);//load cshtml file
            if (page == null)
            {
                throw new InvalidOperationException("cshtml file not exists");
            }
            else
            {
                this.RenderViewPage(viewContext, page);
            }
        }

        private void RenderViewPage(ViewContext context, WebPage page)
        {
            if (!string.IsNullOrEmpty(this.MasterPath))
            {
                page.LayoutPage = this.MasterPath;
            }
            page.VirtualPath = this.ViewPath;
            page.ExecutePageHierarchy(CreatePageContext(context)
                , context.HttpContext.Response.Output, null);
            //execute cshtml file
        }

        internal static WebPageContext CreatePageContext(ViewContext content)
        {
            var pc = new WebPageContext();
            var t = pc.GetType();
            t.InvokeMember("HttpContext", BindingFlags.SetProperty | BindingFlags.NonPublic | BindingFlags.Instance
                , null, pc, new[] { content.HttpContext });
            t.InvokeMember("ViewContext", BindingFlags.SetProperty
                | BindingFlags.NonPublic | BindingFlags.Instance
                , null, pc, new[] { content });
            return pc;
        }

        /// <summary>Gets or sets the master path.</summary>
        /// <returns>The master path.</returns>
        public string MasterPath { get; private set; }

        /// <summary>Gets or sets the view path.</summary>
        /// <returns>The view path.</returns>
        public string ViewPath { get; private set; }
    }

然后我们再来实现一个IViewEngine对象:

    /// <summary>
    /// WebPage View Engine
    /// </summary>
    class WebPageEngine : VirtualPathProviderViewEngine
    {
       
        public WebPageEngine()
        {
            // how to find the template path
            base.MasterLocationFormats = new string[] { 
                "~/Views/{1}/{0}.cshtml", 
                "~/Views/Shared/{0}.cshtml" 
            };
            base.AreaMasterLocationFormats = new string[] { 
                "~/Areas/{2}/Views/{1}/{0}.cshtml", 
                "~/Areas/{2}/Views/Shared/{0}.cshtml"
            };
            base.ViewLocationFormats = new string[] { 
                "~/Views/{1}/{0}.cshtml", 
               "~/Views/Shared/{0}.cshtml"
            };
            base.AreaViewLocationFormats = new string[] { 
                "~/Areas/{2}/Views/{1}/{0}.cshtml", 
                "~/Areas/{2}/Views/Shared/{0}.cshtml" 
            };
            base.PartialViewLocationFormats = base.ViewLocationFormats;
            base.AreaPartialViewLocationFormats = base.AreaViewLocationFormats;
        }

        protected override IView CreatePartialView(ControllerContext controllerContext, string partialPath)
        {
            return new WebPageView(partialPath, null);
        }

        protected override IView CreateView(ControllerContext controllerContext, string viewPath, string masterPath)
        {
            return new WebPageView(viewPath, masterPath);
        }

    }

这样我们就实现了Razor的模板引擎了,我们只要在Global.asax中将模板引擎添加进去:

        protected void Application_Start()
        {
            AreaRegistration.RegisterAllAreas();
            RegisterRoutes(RouteTable.Routes);
            ViewEngines.Engines.Clear();
            ViewEngines.Engines.Add(new WebPageEngine());
        }

并且将Global的基类改为WebPageHttpApplication:

public class MvcApplication : WebPageHttpApplication
{
//...
}

这样整个程序就可以工作了

我们在Views/Home下添加一个Index.cshtml:

@Html.ActionLink("Home","index","User")
<br>
@ViewData["Message"]

这样在我们访问/Home/Index的时候就可以得到ASP.NET MVC默认工程的HomeController.Index所生成的页面了:

<a href=”/User”>Home</a>
<br>
欢迎使用 ASP.NET MVC!

可见在这个模板引擎中,先天对ASP.NET MVC有良好的支持,本身已经集成了Helper、ViewData等诸多ASP.NET MVC的特性。

让我们期待ASP.NET MVC 3.0及Razor对VS的支持吧

8189E6B8-FBE4-4F01-8F9F-5687C0EA9F59

[转载]蛙蛙推荐:使用ASP.NET开发WAP2.0(XHTML MP)页面

mikel阅读(1023)

[转载]蛙蛙推荐:使用ASP.NET开发WAP2.0(XHTML MP)页面 – 蛙蛙池塘 – 博客园.

摘要:目前大多数手机已经不仅仅支持WAP1.0(WML),而是支持WAP2.0(XHTML MP)了,甚至有些手机不久就要支持HTML5了。本文演示如何用ASP.NET 开发WAP2.0页面,查阅了一些资料,整理分享给大家,希望大家能以后能少走弯路。手机浏览器作为一个人机交互界面,而且手机又有随时随地能使用的优 势,我们在这方面是可以大有作为的。

XHTML MP简介

WAP2.0有一套规范,XHTML MP是其中用于浏览器显示的规范,底层可以使用HTTP传输,它也是XHTML的一个子集,详细参考如下链接:

XHTML Mobile Profile

DOCTYPE设置

XHTML MP有自己的DOCTYPE,如下

<!DOCTYPE html PUBLIC "-//WAPFORUM//DTD XHTML Mobile 1.1//EN" "http://www.openmobilealliance.org/tech/DTD/xhtml-mobile11.dtd">

可以把它在Master文件里设置,以便在所有内容页里自动使用,而不是每个页面重复的设置。

配置浏览器文件

ASP.NET 项目里右键添加App_Browsers文件夹,再在里面添加一个mobile.browser文件,如下

代码

<browsers> <browser id="NewBrowser" parentID="Default"> <identification> <header name="Accept" match="application/xhtml\+xml; profile|application/vnd\.wap\.xhtml\+xml" /> </identification> <capabilities> <capability name="preferredRenderingMime" value="application/vnd.wap.xhtml+xml" /> <capability name="preferredRenderingType" value="xhtml-mp" /> </capabilities> <controlAdapters markupTextWriterType="System.Web.UI.XhtmlTextWriter" /> </browser> </browsers>

这个浏览器配置文件的意思,如果HTTP请求的Accept头里包含xhtml mp的MIME类型,则在给客户端返回Response的时候使用application/vnd.wap.xhtml+xml的 ContentType,且RenderType使用XHTML-MP类型,Render的时候强制使用XhtmlTextWriter。

浏览器配置文件是ASP.NET 2.0新增的机制,代替以前在WebConfig里配置的BrowserCaps节点。

WebConfig配置

因为我们开发的网页是面向手机的,手机的功能比较弱,有的甚至不支持Cookie等,而且只支持简单的控件,所以我们把Cookie和 ViewState等禁掉,如下

代码

<?xml version="1.0"?> <configuration> <system.web> <compilation debug="false" /> <authentication mode="None" /> <xhtmlConformance mode="Strict" /> <pages enableViewState="false" /> <sessionState cookieless="true"/> </system.web> </configuration>

启用页面压缩来减少流量传输

有些手机浏览器支持Gzip等页面压缩算法,我们应该检测出来这种情况,并让这种手机节省流量。可以在Global.asax里写一段代码支持,该 方法来源于网络,引用如下,原始链接参考文末的参考链接

代码

void Application_PreRequestHandlerExecute(object sender, EventArgs e) { HttpApplication app = sender as HttpApplication; string acceptEncoding = app.Request.Headers["Accept-Encoding"]; Stream prevUncompressedStream = app.Response.Filter; if (!(app.Context.CurrentHandler is Page || app.Context.CurrentHandler.GetType().Name == "SyncSessionlessHandler") || app.Request["HTTP_X_MICROSOFTAJAX"] != null) return; if (acceptEncoding == null || acceptEncoding.Length == 0) return; acceptEncoding = acceptEncoding.ToLower(); if (acceptEncoding.Contains("deflate") || acceptEncoding == "*") { // defalte app.Response.Filter = new DeflateStream(prevUncompressedStream, CompressionMode.Compress); app.Response.AppendHeader("Content-Encoding", "deflate"); } else if (acceptEncoding.Contains("gzip")) { // gzip app.Response.Filter = new GZipStream(prevUncompressedStream, CompressionMode.Compress); app.Response.AppendHeader("Content-Encoding", "gzip"); } }

在没有启用压缩和配置浏览器文件之前,手机访问页面抓包如下

代码

GET /mobileoaweb/?t=25345 HTTP/1.1 Host: 114.249.124.57 Accept: text/html, application/xhtml+xml, application/vnd.wap.xhtml+xml, application/vnd.wap.wmlc, application/vnd.wap.wmlscriptc, text/vnd.wap.wml, text/vnd.sun.j2me.app-descriptor, */*, text/x-vcard, text/x-vcalendar, image/gif, image/vnd.wap.wbmp Accept-Charset: ISO-8859-1,UTF-8,US-ASCII,UTF-16BE,windows-1252,UTF-16LE,GB2312,windows-1250 Accept-Language: zh-CN,zh;q=0.5 Accept-Encoding: gzip,deflate Content-length: 0 Via: WTP/1.1 BJBJ-PS-WAP2-GW07.bj2.monternet.com (Nokia WAP Gateway 4.1 CD1/ECD13_D/4.1.04) X-Forwarded-For: 10.140.248.32 X-Source-ID: BJGGSN06BMT-CSK X-Nokia-CONNECTION_MODE: TCP X-Up-Bear-Type: GPRS/EDGE X-Nokia-gateway-id: NWG/4.1/Build4.1.04 Connection: close HTTP/1.1 200 OK Server: Microsoft-IIS/5.1 Date: Sat, 10 Jul 2010 12:26:43 GMT X-Powered-By: ASP.NET Connection: close X-AspNet-Version: 2.0.50727 Cache-Control: private Content-Type: text/html; charset=utf-8 Content-Length: 2180

可以看到这时候给客户端返回的Content-Type是text/html,而且Content-Length很长,是2180,而配置浏览器文 件以及启用压缩之后,则如下

代码

GET /mobileoaweb/?t=25345&t=36045&t=26421 HTTP/1.1 Host: 114.249.124.57 Accept: text/html, application/xhtml+xml, application/vnd.wap.xhtml+xml, application/vnd.wap.wmlc, application/vnd.wap.wmlscriptc, text/vnd.wap.wml, text/vnd.sun.j2me.app-descriptor, */*, text/x-vcard, text/x-vcalendar, image/gif, image/vnd.wap.wbmp Accept-Charset: ISO-8859-1,UTF-8,US-ASCII,UTF-16BE,windows-1252,UTF-16LE,GB2312,windows-1250 Accept-Language: zh-CN,zh;q=0.5 Accept-Encoding: gzip,deflate Cookie: jid=M4GKS9T0hh!-849429530 Content-length: 0 Via: WTP/1.1 BJBJ-PS-WAP2-GW18.bj2.monternet.com (Nokia WAP Gateway 4.1 CD1/ECD13_D/4.1.04) X-Forwarded-For: 10.140.83.123 X-Source-ID: BJGGSN06BMT-CSK X-Nokia-CONNECTION_MODE: TCP X-Up-Bear-Type: GPRS/EDGE X-Nokia-gateway-id: NWG/4.1/Build4.1.04 Connection: close HTTP/1.1 200 OK Server: Microsoft-IIS/5.1 Date: Sat, 10 Jul 2010 14:33:48 GMT X-Powered-By: ASP.NET Connection: close X-AspNet-Version: 2.0.50727 Content-Encoding: deflate Cache-Control: private Content-Type: application/vnd.wap.xhtml+xml; charset=utf-8 Content-Length: 1295

看到变化了吧。

样式的考虑

WAP2.0里规定了WAP CSS规范,和普通的HTML 的CSS差不多,支持支持的属性少一些,但像font-weight,background-color,color这些都是支持的,而margin这些 经我测试在黑莓8310浏览器上是不支持的。我们开发的页面是给手机用的,所以也就不要用花里胡哨的样式以及很复杂的布局了,样式上一般就用加粗和颜色来 区分不同重要程度就行了,布局上一般就是单纵栏的布局,css文件最好使用独立的文件,这样手机浏览器第一次访问后可以缓存起来,另外样式表定义的话死后 尽量用伪类,不要用ID,这样可以少定义一些样式,减少网络流量,而且伪类的名字尽量短一些,以减少HTML页面的尺寸。

控件的使用

尽量使用简单的控件,如链接,图片,输入框,按钮等基本的控件,如果要显示数据列表,最好自己用Repeter控件自己控制输出,可以防止生成不必 要的代码。表格也尽量少用,尤其是嵌套表格,虽然支持,但会影响客户端解析速度。不建议用<p>来实现段落,<p>默认 margin不为0,而且css样式无法把它设为0,所以在设计页面的时候就尽量用<br />和<hr />吧。

参考链接

Enabling Gzip and Deflate HTTP Compression in ASP.NET pages
XHTML MP MIME 类型与文件扩展
Visual Studio 和 ASP.NET 中的 XHTML 标准
ASP.NET 2.0 / XHTML-MP Examples
XHTML Mobile Profile