[转载]Android内存机制分析2——分析APP内存使用情况 - 泡泡糖 - 博客园

mikel阅读(867)

[转载]Android内存机制分析2——分析APP内存使用情况 – 泡泡糖 – 博客园.

  上面一篇文章说了Android应用运行在dalvik里面分配的堆和栈内存区别,以及程序中什么代码会在哪里运行。今天主要是讲解一下Android里面如何分析我们程序内存使用情况。以便后续可以分析我们程序瓶颈,优化方案。

(PS:新建的QQ群,有兴趣可以加入一起讨论:Android群:322599434)

 

 

1、APP默认分配内存大小

  在Android里,程序内存被分为2部分:native和 dalvik,dalvik就是我们普通的java使用内存,也就是我们上一篇文章分析堆栈的时候使用的内存。我们创建的对象是在这里面分配的,对于内存 的限制是 native+dalvik 不能超过最大限制。android程序内存一般限制在16M,也有的是24M(早期的Android系统G1,就是只有16M)。具体看定制系统的设置, 在Linux初始化代码里面Init.c,可以查到到默认的内存大小。有兴趣的朋友,可以分析一下虚拟机启动相关代码。这块比较深入,目前我也没时间去分析,后面有空会去钻研一下。

 

//Edited by mythou
//http://www.cnblogs.com/mythou/
gDvm.heapSizeStart = 2 * 1024 * 1024;   // heap初始化大小为2M
gDvm.heapSizeMax = 16 * 1024 * 1024;    // 最大的heap为16M

 

 

2、Android的GC如何回收内存

   Android的一个应用程序的内存泄露对别的应用程序影响不大。为了能够使得Android应用程序安全且快速的运行,Android的每个应用程序都 会使用一个专有的Dalvik虚拟机实例来运行,它是由Zygote服务进程孵化出来的,也就是说每个应用程序都是在属于自己的进程中运行的。 Android为不同类型的进程分配了不同的内存使用上限,如果程序在运行过程中出现了内存泄漏的而造成应用进程使用的内存超过了这个上限,则会被系统视 为内存泄漏,从而被kill掉,这使得仅仅自己的进程被kill掉,而不会影响其他进程(如果是system_process等系统进程出问题的话,则会 引起系统重启)。

 

  做应用开发的时候,你需要了解系统的 GC(垃圾回收)机制是如何运行的,Android里面使用有向图作为遍历回收内存的机制。Java将引用关系考虑为图的有向边,有向边从引用者指向引用 对象。线程对象可以作为有向图的起始顶点,该图就是从起始顶点开始的一棵树,根顶点可以到达的对象都是有效对象,GC不会回收这些对象。如果某个对 象 (连通子图)与这个根顶点不可达(注意,该图为有向图),那么我们认为这个(这些)对象不再被引用,可以被GC回收。

  因此对于我们已经不需要使用的对象,我们可以把它设置为null,这样当GC运行的时候,就好遍历到你这个对象已经没有引用,会自动把该对象占用的内存回收。我们没法像C++那样马上释放不需要的内存,但是我们可以主动告诉系统,哪些内存可以回收了。

 

3、查看应用内存使用情况

  下面我们看看如何在开发过程中查看我们程序运行时内存使用情况。我们可以通过ADB的一个命令查看:

//Edited by mythou
//http://www.cnblogs.com/mythou/
//$package_name:应用包名
//$pid:应用进程ID,可以用PS命令查看
adb shell dumpsys meminfo $package_name or $pid

上面是我使用包名查看Gallery例子的内存使用情况图,里面信息很多,不过我们主要关注的是native和Davilk的使用情况。(Android2.X和Android4.X查看的信息排序是不一样的,内容差不多,不过排布有差异,我上面是4.0的截图)

   Android底层内核是基于Linux的,而Linux里面相对 Window来说,有一点很特别的是,会尽量使用系统内存加载一些缓存数据或者进程间共享数据。Linux本着不用白不用的原则,会尽量使用系统内存,加 快我们应用的运行速度。当然,如果我们期待某个需要大内存的应用,系统也能马上释放出一定的内存使用,这是系统内部调度实现。因此严格来说,我们要准备计 算Linux下某个进程内存大小比较困难。 因为有paging out to disk(换页),所以如果你把所有映射到进程的内存相加,它可能大于你的内存的实际物理大小。

  • dalvik:是指dalvik所使用的内存。
  • native:是被native堆使用的内存。应该指使用C\C++在堆上分配的内存。
  • other:是指除dalvik和native使用的内存。但是具体是指什么呢?至少包括在C\C++分配的非堆内存,比如分配在栈上的内存。puzlle!
  • Pss:它是把共享内存根据一定比例分摊到共享它的各个进程来计算所得到进程使用内存。网上又说是比例分配共享库占用的内存,也就是上面所说的进程共享问题。
  • PrivateDirty:它是指非共享的,又不能换页出去(can not be paged to disk )的内存的大小。比如Linux为了提高分配内存速度而缓冲的小对象,即使你的进程结束,该内存也不会释放掉,它只是又重新回到缓冲中而已。
  • SharedDirty:参照 PrivateDirty我认为它应该是指共享的,又不能换页出去(can not be paged to disk )的内存的大小。比如Linux为了提高分配内存速度而缓冲的小对象,即使所有共享它的进程结束,该内存也不会释放掉,它只是又重新回到缓冲中而已。

  上面针对meminfo里面的信息给出解析,这些很多我是参考了网上一些文章,所以如果有理解不到位的,欢迎各位指出。

 

4、程序中获取内存信息

通过ActivityManager获取相关信息,下面是一个例子代码:

复制代码
//Edited by mythou
//http://www.cnblogs.com/mythou/
private void displayBriefMemory() 
{    
    final ActivityManager activityManager = (ActivityManager) getSystemService(ACTIVITY_SERVICE);    
    ActivityManager.MemoryInfo info = new ActivityManager.MemoryInfo();   
    activityManager.getMemoryInfo(info);    

    Log.i(tag,"系统剩余内存:"+(info.availMem >> 10)+"k");   
    Log.i(tag,"系统是否处于低内存运行:"+info.lowMemory);
    Log.i(tag,"当系统剩余内存低于"+info.threshold+"时就看成低内存运行");
}
复制代码

另外通过Debug的getMemoryInfo(Debug.MemoryInfo memoryInfo)可以得到更加详细的信息。跟我们在ADB Shell看到的信息一样比较详细。

 

5、总结

  今天主要是分析了如何获取我们应用的内存使 用情况信息,关于这方面的信息,其实还有其他一些方法。另外还介绍APP应用的默认内存已经Android的GC回收,不过上面只是很浅薄地分析了一下, 让大家有个印象。这些东西真要深入分析得花不少精力。因为我们的目的只是解决OOM问题,所以目前没打算深入分析,后面有时间进行Android系统分析 的时候,我们再深入分析。下一次我们用以前写的Gallery例子讲解如何避免OOM问题,以及内存优化方法。

PS:如发现哪里写错了,烦请各位留言指出,谢谢!

 

相关文章:

Android内存机制分析1——了解Android堆和栈

 

Edited by mythou

原创博文,转载请标明出处:http://www.cnblogs.com/mythou/p/3203536.html

[转载]OpenGL10-骨骼动画原理篇(2) - zlt - 博客园

mikel阅读(866)

[转载]OpenGL10-骨骼动画原理篇(2) – zlt – 博客园.

接上一篇的内容,上一篇,简单的介绍了,骨骼动画的原理,给出来一个

简单的例程,这一例程将给展示一个最初级的人物动画,具备多细节内容

以人走路为例子,当人走路的从一个站立开始,到迈出一步,这个过程是

一个连续的过程,在这个一个过程中,人身体的骨头在位置在发生变化,

骨头发生变化以后,人的皮肤,肌肉就随着变化,上一个例程中我们计算

(OpenGL10-骨骼动画原理篇(1))计算了根据骨头的位置计算皮肤的位置

只是计算量一刻的动作,走路的过程是连续的,就意味着我们要记录下来

骨头在运动过程中所以位置变化数据,这样才可以根据不同时刻的骨骼的

位置计算出来皮肤的位置。

现在问题出来了,如果美术做了一个动画有5秒钟,每一秒播放60帧来

计算,我们要记录非常多的骨头的信息,小下面这样:

假设人有100个骨头

Bone person[100]

一秒钟60帧 × 5秒  × 100,这个就是我们要记录的数据量,由此可见

数据量是非常大的,实际上大可不必这样做,想一,是否可以记录一个

关键帧的,其他的数据又关键帧来计算呢 ?假设我们记录了10个关键点

其他的数据根据时间按照一定的插值算法进行插值,那么数据量就骤然

降低非常多呢。出于这样的想法,我们增加了一个新的概念,关键帧。

骨骼动画系统的流程如下:

下面我们使用程序的角度来描述下该问题:

1.  获取到所有的骨骼数据(可使用矩阵存储)

Bone   bones[n];

2.  获取到关键帧数据

Bone  arKeyFrame[n][KeyNumber];

3  获取到皮肤(顶点数据)

Vertex  verts[vNumber];

4  通过插值计算出来新的骨骼位置

Bone   timeBone[n];

5  根据计算出来骨骼来计算顶点数据

Vert  temp[vNumber];

 

一个定点的声明如下:

struct Vertex
{
//! 颜色
float r, g, b, a;
//! 位置
float x, y, z;
//! 影响度
float weights[2];
//! 矩阵的索引
short matrixIndices[2];
//! 影响整个定点的骨头个数
short numBones;
};

声明一个类,保存骨头的信息.类如下所示,该类保存动画的所有骨格信息:

复制代码
struct Vertex
{
    //! 颜色
    float r, g, b, a;
    //! 位置
    float x, y, z;
    //! 影响度
    float weights[2];
    //! 矩阵的索引
    short matrixIndices[2];
    //! 影响整个定点的骨头个数
    short numBones;
};
复制代码

 

接下来,声明一动画类,动画类中维护关键帧数据

class   Frame
{
public:
    tmat4x4<float> _bone[2];
};

一个动画类,用来保存所有的关键帧数据,提供计算骨头插值算法,

并输出一帧的骨骼数据,类如下所示。

复制代码
class   SkinAnimation
{
public:
    //! 根据给定的时间,输出一帧骨骼的数据
    void    calcFrame(float t,Frame& frame)
    {

        frame._bone[0]  =   interpolate(_keyFrame[0]._bone[0],_keyFrame[1]._bone[0],t);
        frame._bone[1]  =   interpolate(_keyFrame[0]._bone[1],_keyFrame[1]._bone[1],t);

    }
    //!  该动画有两个关键帧
    Frame   _keyFrame[2];
};
复制代码

 

调用方式如下:

复制代码
                /**
                *   产生时间
                */
                static  float   xxx =   0;

                /**
                *   根据关键帧计算出来新的骨骼位置
                */
                _skinAnima.calcFrame(xxx,frame);
                xxx +=  0.01f;
复制代码

 

然后我们将定点数据与计算出来的骨骼数据计算,得出最后的皮肤数据:

复制代码
/**
*   绘制表皮,保存临时点数据
*   这里根据新的骨头的(就是插值计算出来以后的骨头来计算皮肤的位置了)
*/
Vertex  calQuadVertices[12];
memcpy(calQuadVertices,g_quadVertices,sizeof(g_quadVertices));
for (int i = 0 ;i < 12 ; ++ i )
{
    tvec3<float>    vec(0,0,0);
    tvec3<float>    vecSrc(g_quadVertices[i].x,g_quadVertices[i].y,g_quadVertices[i].z);
    for (int x = 0 ; x < calQuadVertices[i].numBones ; ++ x)
    {
        //! 计算位置
        tvec3<float>    temp    =   vecSrc* frame._bone[g_quadVertices[i].matrixIndices[x]];
        //! 计算权重位置
        vec  += temp * g_quadVertices[i].weights[x];
    }
    calQuadVertices[i].x    =   vec.x;
    calQuadVertices[i].y    =   vec.y;
    calQuadVertices[i].z    =   vec.z;
}
复制代码

 

最后将计算出来的数据给OpenGL,进行绘制了:

复制代码
glColorPointer(4,GL_FLOAT,sizeof(Vertex),calQuadVertices);
glVertexPointer(3,GL_FLOAT,sizeof(Vertex),((float*)calQuadVertices) + 4);
for (int i = 0 ;i < 3 ; ++ i )
{
    glDrawArrays(GL_LINE_LOOP,i * 4,4);
}
复制代码

 

可执行文件以及代码

[转载]OpenGL10-骨骼动画原理篇(1) - zlt - 博客园

mikel阅读(771)

[转载]OpenGL10-骨骼动画原理篇(1) – zlt – 博客园.

本例程展示如何建立骨骼动画,有些人叫蒙皮动画

定义如下:

当前有两种模型动画的方式:顶点动画和骨骼动画。顶点动画中,每帧动画其实

就是模型特定姿态的一个“快照”。通过在帧之间插值的方法,引擎可以得到平滑

的动画效果。在骨骼动画中,模型具有互相连接的“骨骼”组成的骨架结构,通过

改变骨骼的朝向和位置来为模型生成动画。

骨骼动画比顶点动画要求更高的处理器性能,但同时它也具有更多的优点,

骨骼动画可以更容易、更快捷地创建。不同的骨骼动画可以被结合到一起——

比如,模型可以转动头部、射击并且同时也在走路。一些引擎可以实时操纵单

个骨骼,这样就可以和环境更加准确地进行交互——模型可以俯身并向某个方

向观察或射击,或者从地上的某个地方捡起一个东西。多数引擎支持顶点动画,

但不是所有的引擎都支持骨骼动画。

1. 关键帧动画,早期的cs就是用关键帧动画
优点:

计算量小,速度快,在早期计算机性能满足不了要求的时候采用的,

最具代表性的就是Quake(雷神之锤),采用的md2文件格式。

缺点:
画面表现不过好,会有穿刺的情况出现

2.骨骼动画(蒙皮动画)

优点:
画面表现细腻,真实感很强,目前大多数游戏都采用该中类型的动画,典型的代表,

Quake推出的md3文件格式,就是采用骨骼动画

缺点:
所有的定点都是根据骨骼的变化实时计算,计算量非常大。

骨骼动画的原理:

正如其名,这种动画中包含骨骼(Bone)和蒙皮(Skinned Mesh)两个部分

一部分是Bone(骨头),一部分是Skin(皮).就像人体的组成一样。人要想做动作,

骨头要动起来,然后皮就被骨头带动起来,按照这样的理论,就产生了蒙皮动画。

在三维模型当中。bone就是骨头,皮就是skin mesh,mesh的其实就是模型来,

加上skin,说明这个模型的意义,做表皮的。


我们在看待问题,学习东西的时候,要站在设计者的角度去考虑问题,很多

问题就不是问题了,很多问题就更加容易的理解,顺利成章。

现在我们就站在设计者的角度上来看待骨骼动画,首相设计意图我们已经知
道,就是骨头带动肉动起来,那怎么个带动法呢 ?

来看下一,当我们的弯曲手臂的时候,就是肘关节动,其他的关节不动,而随着

肘关节的弯曲,我们肱二头肌会动,但幅度最大的是手臂,那我们想一下,是不

是这样来描述,当我们动一个关节的时候,会带动一部分肌肉动起来,而不是只

要动一个关节全身都在动。那么我们就可以这样来说,一个骨头动,会影响到一

部分的肉和皮动。逆向思路来思考下,肱二头肌要受到几个骨头的影响,会使得

肱二头肌的形状发生变化,影响最大的肘关节,其次是肩关节。肱二头肌是什么?

在程序中,他就是一些列的点数据。

我们定义个如下结构体(伪代码)

class Point

{

float x,y,z;     //! 肌肉的位置

int  arBone[n];  //! 影响肌肉的骨头

float arWeight[n]  //! 每一个骨头对肌肉的影响度,例如 肘关节的影响度对肱二头肌很多,而肩关节要少一点。

};

 

如何来描述肌肉的位置呢?

for( int i = 0 ;i < n ; ++ i)

{

(x,y,z) += 骨头[i] * 骨头的影响度[i];

}

那有如何来描述骨头呢 ?在游戏中,骨头有位置,可以旋转,显示生活中骨头不能缩放,但游戏中可以。

所以描述一个骨头需要三个要素,位置,旋转,和缩放,最容易想到的就是使用一个矩阵来描述他了。

class  Bone :public Matrix

{
};

从上面的描述,我们知道要想绘制出来一模型,我们要存储的信息,所有的定点,所有的骨头,还有

那么每一个点被那么骨头影响,影响度是都少。具体计算如下。

一个人的模型有2000个顶点组成,有20快骨头组成。我们要做的计算如下:

for( int i = 0 ;i < 2000 ; ++ i )

{

for( int x = 0 ; x < 4(假设一个定点被四个骨头影响) ; ++ x )

{

(x1,y1,z1) += (x,y,z) * bone * weight;

}

}

我们可以看出这个计算量是非常大的,几乎都在做矩阵的计算。

 

图中有两个骨头,一个是蓝色的,一个是黄色的,有三个长方形,一个是蓝色的,一个是绿色的,一个是换色的,

蓝色的长方形表示被蓝色的骨头影响,黄色的长方形表示被换色的骨头影响,绿色的表示受两个骨头的影响。

右键按住进行旋转,操作骨头。

可执行文件及源代码 :下载

#include “CELLWinApp.hpp”
#include <assert.h>
#include <math.h>
#include “matrix4x4f.h”
#pragma comment(lib,”opengl32.lib”)
float g_fSpinX_R = 0.0f;
float g_fSpinY_R = 0.0f;

struct Vertex
{
//! 颜色
float r, g, b, a;
//! 位置
float x, y, z;
//! 影响度
float weights[2];
//! 矩阵的索引
short matrixIndices[2];
//! 影响整个定点的骨头个数
short numBones;
};

Vertex g_quadVertices[12] =
{
{ 1.0f,1.0f,0.0f,1.0f, -1.0f,0.0f,0.0f, 1.0f,0.0f, 0,0, 1 }, // 蓝色
{ 1.0f,1.0f,0.0f,1.0f, 1.0f,0.0f,0.0f, 1.0f,0.0f, 0,0, 1 },
{ 1.0f,1.0f,0.0f,1.0f, 1.0f,2.0f,0.0f, 0.5f,0.5f, 0,1, 2 },
{ 1.0f,1.0f,0.0f,1.0f, -1.0f,2.0f,0.0f, 0.5f,0.5f, 0,1, 2 },

{ 0.0f,1.0f,0.0f,1.0f, -1.0f,2.0f,0.0f, 0.5f,0.5f, 0,1, 2 }, // 绿色
{ 0.0f,1.0f,0.0f,1.0f, 1.0f,2.0f,0.0f, 0.5f,0.5f, 0,1, 2},
{ 0.0f,1.0f,0.0f,1.0f, 1.0f,4.0f,0.0f, 0.5f,0.5f, 0,1, 2 },
{ 0.0f,1.0f,0.0f,1.0f, -1.0f,4.0f,0.0f, 0.5f,0.5f, 0,1, 2 },

{ 0.0f,0.0f,1.0f,1.0f, -1.0f,4.0f,0.0f, 0.5f,0.5f, 0,1, 2 }, // 黄色
{ 0.0f,0.0f,1.0f,1.0f, 1.0f,4.0f,0.0f, 0.5f,0.5f, 0,1, 2 },
{ 0.0f,0.0f,1.0f,1.0f, 1.0f,6.0f,0.0f, 1.0f,0.0f, 1,0, 1 },
{ 0.0f,0.0f,1.0f,1.0f, -1.0f,6.0f,0.0f, 1.0f,0.0f, 1,0, 1 }
};
float arBone[] =
{
0.0f, 0.0f, 0.0f,
-0.2f, 0.2f,-0.2f,
0.2f, 0.2f,-0.2f,
0.0f, 3.0f, 0.0f,
-0.2f, 0.2f,-0.2f,
-0.2f, 0.2f, 0.2f,
0.0f, 0.0f, 0.0f,
0.2f, 0.2f,-0.2f,
0.2f, 0.2f, 0.2f,
0.0f, 0.0f, 0.0f,
-0.2f, 0.2f, 0.2f,
0.0f, 3.0f, 0.0f,
0.2f, 0.2f, 0.2f,
-0.2f, 0.2f, 0.2f,
};
matrix4x4f g_boneMatrix[2];
matrix4x4f g_matrixToRenderBone[2];
inline vector3f operator * (const vector3f& v, const matrix4x4f& mat)
{
return vector3f
(
v.x*mat.v[0][0] + v.y*mat.v[1][0] + v.z*mat.v[2][0] + 1*mat.v[3][0],
v.x*mat.v[0][1] + v.y*mat.v[1][1] + v.z*mat.v[2][1] + 1*mat.v[3][1],
v.x*mat.v[0][2] + v.y*mat.v[1][2] + v.z*mat.v[2][2] + 1*mat.v[3][2]
);
}

class Tutorial10 :public CELL::Graphy::CELLWinApp
{
public:
Tutorial10(HINSTANCE hInstance)
:CELL::Graphy::CELLWinApp(hInstance)
{
_lbtnDownFlag = false;
_fSpinY = 0;
_fSpinX = 0;
_bMousing_R = 0;
}
virtual void render()
{
do
{
glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
glMatrixMode( GL_MODELVIEW );
glLoadIdentity();
glTranslatef( 0.0f, 0.0f, -15 );

glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_COLOR_ARRAY);

{
g_boneMatrix[0].identity();
g_matrixToRenderBone[0].identity();

matrix4x4f rotationMatrixY;
matrix4x4f rotationMatrixZ;
matrix4x4f boneRotationMatrix;
g_boneMatrix[1].identity();
g_matrixToRenderBone[1].identity();

matrix4x4f offsetMatrix_toBoneEnd;
matrix4x4f offsetMatrix_backFromBoneEnd;

offsetMatrix_toBoneEnd.translate_y( 3.0f );
offsetMatrix_backFromBoneEnd.translate_y( -3.0f );

rotationMatrixY.rotate_y( g_fSpinY_R);
rotationMatrixZ.rotate_z(-g_fSpinX_R);
boneRotationMatrix = rotationMatrixY * rotationMatrixZ;

g_boneMatrix[1] = g_boneMatrix[0] * offsetMatrix_toBoneEnd * boneRotationMatrix;
g_matrixToRenderBone[1] = g_boneMatrix[1];
g_boneMatrix[1] = g_boneMatrix[1] * offsetMatrix_backFromBoneEnd;
}
/**
* 绘制表皮,保存临时点数据
*/
Vertex calQuadVertices[12];
memcpy(calQuadVertices,g_quadVertices,sizeof(g_quadVertices));
for (int i = 0 ;i < 12 ; ++ i )
{
vector3f vec(0,0,0);
vector3f vecSrc(g_quadVertices[i].x,g_quadVertices[i].y,g_quadVertices[i].z);
for (int x = 0 ; x < calQuadVertices[i].numBones ; ++ x)
{
//! 计算位置
vector3f temp = vecSrc* g_boneMatrix[g_quadVertices[i].matrixIndices[x]];
//! 计算权重位置
vec += temp * g_quadVertices[i].weights[x];
}
calQuadVertices[i].x = vec.x;
calQuadVertices[i].y = vec.y;
calQuadVertices[i].z = vec.z;
}
glColorPointer(4,GL_FLOAT,sizeof(Vertex),calQuadVertices);
glVertexPointer(3,GL_FLOAT,sizeof(Vertex),((float*)calQuadVertices) + 4);
for (int i = 0 ;i < 3 ; ++ i )
{
glDrawArrays(GL_LINE_LOOP,i * 4,4);
}
glDisableClientState(GL_COLOR_ARRAY);

/**
* 绘制骨头
*/
glVertexPointer(3,GL_FLOAT,0,arBone);
glPushMatrix();
{
//! 绿色骨头
glMultMatrixf( g_matrixToRenderBone[0].m );
glColor3f( 1.0f, 1.0f, 0.0 );
glDrawArrays(GL_LINE_STRIP,0,sizeof(arBone)/12);
}
glPopMatrix();

glPushMatrix();
{
//! 蓝色骨头
glMultMatrixf( g_matrixToRenderBone[1].m );
glColor3f( 0.0f, 0.0f, 1.0 );
glDrawArrays(GL_LINE_STRIP,0,sizeof(arBone)/12);
}
glPopMatrix();

SwapBuffers( _hDC );
} while (false);
}

/**
* 生成投影矩阵
* 后面为了重用性,我们会写一个专门的matrix类,完成矩阵的一系列擦做
* 这个是很有必须要的,当你对Opengl了解的不断深入,你会发现,很多都是和数学有关的
*/
void perspective(float fovy,float aspect,float zNear,float zFar,float matrix[4][4])
{
assert(aspect != float(0));
assert(zFar != zNear);
#define PI 3.14159265358979323f

float rad = fovy * (PI / 180);

float halfFovy = tan(rad / float(2));
matrix[0][0] = float(1) / (aspect * halfFovy);
matrix[1][1] = float(1) / (halfFovy);
matrix[2][2] = -(zFar + zNear) / (zFar – zNear);
matrix[2][3] = -float(1);
matrix[3][2] = -(float(2) * zFar * zNear) / (zFar – zNear);
#undef PI
}
virtual void onInit()
{
/**
* 调用父类的函数。
*/
CELL::Graphy::CELLWinApp::onInit();
glMatrixMode( GL_PROJECTION );
GLfloat matrix[4][4] =
{
0,0,0,0,
0,0,0,0,
0,0,0,0,
0,0,0,0
};
perspective(45.0f, (GLfloat)_winWidth / (GLfloat)_winHeight, 0.1f, 100.0f,matrix);
glLoadMatrixf((float*)matrix);
glClearColor(0.35f, 0.53f, 0.7f, 1.0f);

}

virtual int events(unsigned msg, unsigned wParam, unsigned lParam)
{
switch(msg)
{
case WM_LBUTTONDOWN:
{
_mousePos.x = LOWORD (lParam);
_mousePos.y = HIWORD (lParam);
_lbtnDownFlag = true;
SetCapture(_hWnd);
}
break;
case WM_LBUTTONUP:
{
_lbtnDownFlag = false;
ReleaseCapture();
}
break;
case WM_RBUTTONDOWN:
{
_ptLastMousePosit_R.x = _ptCurrentMousePosit_R.x = LOWORD (lParam);
_ptLastMousePosit_R.y = _ptCurrentMousePosit_R.y = HIWORD (lParam);
_bMousing_R = true;
}
break;
case WM_RBUTTONUP:
{
_bMousing_R = false;
}
break;
case WM_MOUSEMOVE:
{
int curX = LOWORD (lParam);
int curY = HIWORD (lParam);

if( _lbtnDownFlag )
{
_fSpinX -= (curX – _mousePos.x);
_fSpinY -= (curY – _mousePos.y);
}

_mousePos.x = curX;
_mousePos.y = curY;

_ptCurrentMousePosit_R.x = LOWORD (lParam);
_ptCurrentMousePosit_R.y = HIWORD (lParam);
if( _bMousing_R )
{
g_fSpinX_R -= (_ptCurrentMousePosit_R.x – _ptLastMousePosit_R.x);
g_fSpinY_R -= (_ptCurrentMousePosit_R.y – _ptLastMousePosit_R.y);
}
_ptLastMousePosit_R.x = _ptCurrentMousePosit_R.x;
_ptLastMousePosit_R.y = _ptCurrentMousePosit_R.y;

}
break;
}
return __super::events(msg,wParam,lParam);
}
protected:
unsigned _primitiveType;
/**
* 保存纹理Id
*/
unsigned _textureId;

float _fSpinX ;
float _fSpinY;
POINT _mousePos;
bool _lbtnDownFlag;

POINT _ptLastMousePosit_R;
POINT _ptCurrentMousePosit_R;
bool _bMousing_R;
};

int CALLBACK _tWinMain(
HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPTSTR lpCmdLine,
int nShowCmd
)
{
(void*)hInstance;
(void*)hPrevInstance;
(void*)lpCmdLine;
(void*)nShowCmd;

Tutorial10 winApp(hInstance);
winApp.start(640,480);
return 0;
}

[转载]Android Gallery实现3D相册(附效果图+Demo源码) - 泡泡糖 - 博客园

mikel阅读(841)

[转载]Android Gallery实现3D相册(附效果图+Demo源码) – 泡泡糖 – 博客园.

  今天因为要做一个设置开机画面的功能,主要是让用户可以设置自己的开机画面, 应用层需要做让用户选择开机画面图片的功能。所以需要做一个简单的图片浏览选择程序。最后选用Gallery作为基本控件。加入了一些炫一点的元素,做成 3D滑动效果。下面是Demo例子截图:

  这个效果网上已经很多人做出来了,只是这次需要用到,所以自己也实践了一下 (这里例子我也是根据网上一些资料编写)。特意找了几张美女图片给大家养养眼,O(∩_∩)O哈!下面针对一些关键代码进行简要说明,需要做这方面东西的 朋友可以看看。这篇文章是实用性文章,理论分析不多。

(PS:新建的QQ群,有兴趣可以加入一起讨论:Android群:322599434)

 

1、重载Gallery类

  因为需要加入倒影和3D切换的效果,因此我们需要重载Gallery类,其中 有两个方法我们需要重写,一个是onSizeChanged(),另外一个是getChildStaticTransformation()。下面我们看 看onSizeChanged()需要做的事情。

复制代码
//Edited by mythou
//http://www.cnblogs.com/mythou/
  @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) 
    {
    //重写计算旋转的中心
        mCoveflowCenter = getCenterOfCoverflow();
        super.onSizeChanged(w, h, oldw, oldh);
    } 
复制代码

上面主要做的事情就是在改变大小的时候,重新计算滑动切换时需要旋转变化的中心。下面计算图片位置时,会重新计算。

复制代码
//Edited by mythou
//http://www.cnblogs.com/mythou/
  protected boolean getChildStaticTransformation(View child, Transformation trans) 
    {
     //图像的中心点和宽度
        final int childCenter = getCenterOfView(child);
        final int childWidth = child.getWidth();
        int rotationAngle = 0;

        trans.clear();
        trans.setTransformationType(Transformation.TYPE_BOTH);        // alpha 和 matrix 都变换

        if (childCenter == mCoveflowCenter) 
        {    
        // 正中间的childView
            transformImageBitmap((ImageView) child, trans, 0);    
        } 
        else 
        {        
        // 两侧的childView
            rotationAngle = (int) ( ( (float) (mCoveflowCenter - childCenter) / childWidth ) * mMaxRotationAngle );
            if (Math.abs(rotationAngle) > mMaxRotationAngle) 
            {
                rotationAngle = (rotationAngle < 0) ? -mMaxRotationAngle : mMaxRotationAngle;
            }
       //根据偏移角度对图片进行处理,看上去有3D的效果。
            transformImageBitmap((ImageView) child, trans, rotationAngle);
        }

        return true;
    }
复制代码

上面就是重载Gallery的时候,需要注意处理的事情,其实主要就是做图形变化,效果图里面的图片斜着显示就是这里处理的结果,目的就是让人看上去有立体感。

 

2、编写Adapter适配器

  我们使用很多控件都涉及适配器,就是用来绑定数据源和目标控件的一个中间件。这里我们需要重载BaseAdapter作为我们Gallery的适配器。主要是处理源图像,加入倒影,生成新的数据源图片。

 

复制代码
//Edited by mythou
//http://www.cnblogs.com/mythou/
   public boolean createReflectedForAdapter() 
    {
        final int reflectionGap = 4;
        final int Height = 200;
        int index = 0;
        for (Map<String, Object> map : list) 
        {
            Integer id = (Integer) map.get("image");
            // 获取原始图片
            Bitmap originalImage = BitmapFactory.decodeResource(mContext.getResources(), id);    
            int width = originalImage.getWidth();
            int height = originalImage.getHeight();
            float scale = Height / (float)height;

            Matrix sMatrix = new Matrix();
            sMatrix.postScale(scale, scale);
            Bitmap miniBitmap = Bitmap.createBitmap(originalImage, 0, 0,
                    originalImage.getWidth(), originalImage.getHeight(), sMatrix, true);

            //是否原图片数据,节省内存
            originalImage.recycle();

            int mwidth = miniBitmap.getWidth();
            int mheight = miniBitmap.getHeight();
            Matrix matrix = new Matrix();
            // 图片矩阵变换(从低部向顶部的倒影)
            matrix.preScale(1, -1);            
            // 截取原图下半部分
            Bitmap reflectionImage = Bitmap.createBitmap(miniBitmap, 0, mheight/2, mwidth, mheight/2, matrix, false);
            // 创建倒影图片(高度为原图3/2)
            Bitmap bitmapWithReflection = Bitmap.createBitmap(mwidth, (mheight + mheight / 2), Config.ARGB_8888);    
            // 绘制倒影图(原图 + 间距 + 倒影)
            Canvas canvas = new Canvas(bitmapWithReflection);    
            // 绘制原图
            canvas.drawBitmap(miniBitmap, 0, 0, null);        
            Paint paint = new Paint();
            // 绘制原图与倒影的间距
            canvas.drawRect(0, mheight, mwidth, mheight + reflectionGap, paint);
            // 绘制倒影图
            canvas.drawBitmap(reflectionImage, 0, mheight + reflectionGap, null);    

            paint = new Paint();
            // 线性渐变效果
            LinearGradient shader = new LinearGradient(0, miniBitmap.getHeight(), 0, bitmapWithReflection.getHeight()
                    + reflectionGap, 0x70ffffff, 0x00ffffff, TileMode.CLAMP);
            paint.setShader(shader);    
            // 倒影遮罩效果
            paint.setXfermode(new PorterDuffXfermode(Mode.DST_IN));        
            // 绘制倒影的阴影效果
            canvas.drawRect(0, mheight, mwidth, bitmapWithReflection.getHeight() + reflectionGap, paint);        
            ImageView imageView = new ImageView(mContext);
        // 设置倒影图片
            imageView.setImageBitmap(bitmapWithReflection);        
            imageView.setLayoutParams(new GalleryView.LayoutParams((int)(width * scale),
                    (int)(mheight * 3 / 2.0 + reflectionGap)));
            imageView.setScaleType(ScaleType.MATRIX);
            mImages[index++] = imageView;
        }
        return true;
    }
复制代码

  上面其实就是一个图片处理过程,主要做的事情就是生成倒影,效果图里面底下是 有倒影的。就是利用上面算法生成。我们在适配器添加图片的时候,会把适配器原生图片进行处理,加入倒影的效果。这个我们在图片初始化的时候就可以调用处 理,具体代码可以查看Demo里面的代码关系。

  具体图片滑动的过程,Gallery会帮我们处理好,我们要做的事情其实就是提供添加了特效的图片数据源,以及处理3D显示的变化效果,最后都会提供View作为显示图像给Gallery用来显示。

  今天主要是说说如何实现Gallery的3D显示切换,Demo的代码很多是基于网上一些现成效果,感谢这些分享成果的开发者。下面是Demo的下载,不清楚的可以把Demo下载下来,运行看看效果然后分析一下代码。代码不多,也不是很复杂。

 

Gallery3D例子代码:Grallery3DTest2013-7-19.rar

 

Edited by mythou

原创博文,转载请标明出处:http://www.cnblogs.com/mythou/p/3201126.html

[转载]WEBUS2.0 In Action - [源代码] - C#代码搜索器 - Ivan Zou - 博客园

mikel阅读(803)

[转载]WEBUS2.0 In Action – [源代码] – C#代码搜索器 – Ivan Zou – 博客园.

最近由于工作的需要, 要分析大量C#代码, 在数万个cs文件中搜索特定关键词. 这是一项非常耗时的工作, 用Notepad++要运行接近半个小时. 于是我利用WEBUS2.0 SDK创建了一个代码搜索器程序, 非常方便的完成了这项工作.

Code Search程序首先会在选定的目录中搜索所有cs文件:

复制代码
    private void btnOpen_Click(object sender, EventArgs e)
        {
            try
            {
                if (folderBrowserDialog1.ShowDialog() == System.Windows.Forms.DialogResult.OK)
                {
                    Task.Factory.StartNew(IndexProc);
                    //...
        }
复制代码

然后创建IIndexer, 并在一个线程中为所有找到的文件编制索引:

复制代码
        void IndexProc()
        {
            var files = Directory.GetFiles(folderBrowserDialog1.SelectedPath, "*.cs", SearchOption.AllDirectories);
            if (files != null && files.Length > 0)
            {
                //...this.ResetIndex();
                foreach (var file in files)
                {
                    Document doc = new Document();
                    doc.Fields.Add(new Field("FileName", file, FieldAttributes.None));
                    doc.Fields.Add(new Field("Code", StringHelper.LoadString(file), FieldAttributes.AnalyseIndex));
                    m_Index.Add(doc);
                    //...
                }
            }
            //...
        }

        void ResetIndex()
        {
            if (m_Index != null)
            {
                m_Index.Close();
            }
            m_Index = new IndexManager(new CodeAnalyzer());
            m_Index.DumpDocs = 3000;
            m_Index.DumpSize = 10;
            m_Index.MinIndexSize = int.MaxValue;
            m_Index.MaxIndexSize = int.MaxValue;
            m_Index.MergeFactor = int.MaxValue;
            m_Index.New(AppDomain.CurrentDomain.BaseDirectory + @"\Index");
            m_Searcher = new IndexSearcher(m_Index);
        }
复制代码

通过调节DumpDocs和DumpSize, 可以优化程序的内存占用;

通过调节Min/MaxIndexSize和MergeFactor, 可以优化程序的IO性能, 目前我设置的MinIndexSize最大意味着自始至终只会生成一个索引片段; MergeFactor最大意味着从不合并索引片段.

在创建索引的时候, 我们使用的是专门为代码分析设计的IAnalyzer:

复制代码
    class CodeAnalyzer : IAnalyzer
    {
        //...public ITokenStream GetTokenStream(Webus.Documents.Field field)
        {
            if (field.Name == "Code")
            {
                return this.GetTokenStream(field.Value.ToString());
            }
            else
            {
                return null;
            }
        }
    }

    class CodeTokenStream : ITokenStream
    {
        HashSet<string> stops = new HashSet<string>(new string[] { 
            "abstract",
            "event",    
            "new",    
            //... 
            "enum",    
            "namespace",    
            "string"
        });
        Queue<Token> m_Buffer = new Queue<Token>();
        public CodeTokenStream(string text)
        {
            MatchCollection mc = Regex.Matches(text, @"\w+");
            foreach (Match m in mc)
            {
                var key = m.Value.ToLower();
                if (stops.Contains(key) == false)
                {
                    m_Buffer.Enqueue(new Token(key, m.Index, m.Length));
                }
            }
        }
        //...

    }
复制代码

这个分析器中包含了所有C#的关键词, 由于他们是绝对高频词并且没有搜索的意义, 因此在分析的时候会跳过这些词汇而不做任何处理.

在编制索引的时候通过事件将状态更新到UI上面:

复制代码
        private void frmCodeSearch_Load(object sender, EventArgs e)
        {
            try
            {
                this.StatusChanged += new StatusChangeEventHandler(frmCodeSearch_StatusChanged);
                //...
        }

        delegate void UpdateUI();
        void frmCodeSearch_StatusChanged(object sender, string status)
        {
            this.Invoke(new UpdateUI(() => { this.txtStatus.Text = status; }));
        }
复制代码

这里是跨线程更新UI, 因此需要使用this.Invoke来封送相应操作.

索引编制过程中就可以开始搜索了:

对应代码如下:

复制代码
        private void txtKeyword_TextChanged(object sender, EventArgs e)
        {
            try
            {
                TermQuery query = new TermQuery(new Term("Code", txtKeyword.Text.ToLower()));
                var hits = m_Searcher.Search(query);
                List<SearchResult> result = new List<SearchResult>();
                foreach (HitDoc hit in hits)
                {
                    StandardHighlighter hl = new StandardHighlighter(hit);
                    result.Add(new SearchResult(hit));
                }
                dgvResult.DataSource = result;
            }
            catch (Exception ex)
            {
                MessageBox.Show(ExceptionHelper.ToString(ex));
            }
        }
复制代码

创建一个TermQuery对象, 对Code字段进行搜索, 构建List<SearchResult>类型的结果集, 并且绑定到DataGridView上面, 大功告成! enjoy~!

下载源代码

阅读更多WEBUS2.0 SDK文章

[转载]20个优秀的前端框架-CSDN.NET

mikel阅读(1076)

[转载]20个优秀的前端框架-CSDN.NET.

前端开发并不难,但是要想做得优雅、健壮并不容易,使用一个好的前端框架能够帮你很多忙。本文列举了20个优秀的前端框架,供选择使用。

1. Twitter BootStrap (Apache v2.0;响应式)

时髦、直观并且强大的前端框架,让Web开发变得更加容易。

2. Foundation (MIT;响应式)

最先进的响应式前端框架。

3. 960gs(GPL&MIT;响应式)

960gs提供了一个简单的网格系统,适合快速开发。

4. Skeleton(MIT;响应式)

非常漂亮的Web模板,适合响应式、移动友好的开发。

5. 99lime HTML KickStart(Free)

适合网站快速开发的极简HTML构建模块。

6. Kube(Free;响应式)

面向专业人员的CSS框架。

7. Less Framework(MIT;响应式)

自适应的CSS网格系统。

8. Flameinwork(Free)

适合懒人开发者的前端微框架。

9. G5 Framework(Free)

(x)HTML5、CSS、PHP前端开发框架。

10. Easy Framework(Free)

Easy Framework是一个一体化前端解决方案,分structural、 presentational、interactive三层。

11. Blueprint(Free)

一个旨在减少开发时间的前端框架。

12. YAML(Creative Commons)

(x)HTML+CSS框架,适合开发现代化浮动布局。

13. BlueTrip(Free)

一个功能全面、并且美丽的CSS框架,适合于Blueprint搭配使用。

14. YUI3:Grids CSS(BSD)

YUI Grids CSS是最著名的CSS框架之一,是由Yahoo开发小组开发而成。 YUI Grids CSS为开发者提供了预先设置的四种不同页面宽度,六种不同的模板。

15. 52framework(Creative Commons)

对HTML5支持非常好,简单易用。


16. elastiCSS(MIT)

一个基于Web接口和印刷布局的简单CSS框架。


17. Emastic(Free)

一个与众不同的CSS框架。

18. Fluid 960 Gride System(GPL/MIT)

 

Fluid 960 Grid System的模版是根据Nathan Smith之前的作品而创建的。即960 Grid System:传承了MooTools和JQuery JavaScript libraries的效果。

19. xCSS(MIT)

一个面向对象的CSS框架,能让你的工作流更加简洁。xCSS基于CSS,可以在开发复杂样式时,提供面向对象的工作流。


20. EM CSS Framework(MIT/GPL)

EM CSS Framework提供了一个960px宽 + 12 列网格系统 + CSS的通用样式。

原文链接: usablica.github.com

[转载]Java生成PDF报表 - 仗剑行江湖 - 博客园

mikel阅读(1018)

[转载]Java生成PDF报表 – 仗剑行江湖 – 博客园.

一、前言

前几天,做ASN条码收货模块,需要实现打印下载收货报表,经一番查找,选定iText–用于生成PDF文档的一个Java类库。废话不多说,进入正题。

二、iText简介

iText是著名的开放源码的站点sourceforge一个项目,是用于生成PDF文档的一个java类库。通过iText不仅可以生成PDF或rtf的文档,而且可以将XML、Html文件转化为PDF文件。

iText的安装非常方便,在http://itextpdf.com/ 网站上下载iText.jar文件后,只需要在系统的CLASSPATH中加入iText.jar的路径,在程序中就可以使用iText类库了。

三、建立第一个PDF文档

用iText生成PDF文档需要5个步骤:

①建立com.lowagie.text.Document对象的实例。
Document document = new Document();

②建立一个书写器(Writer)与document对象关联,通过书写器(Writer)可以将文档写入到磁盘中。
PDFWriter.getInstance(document, new FileOutputStream(“Helloworld.PDF”));

③打开文档。
document.open();

④向文档中添加内容。
document.add(new Paragraph(“Hello World”));

⑤关闭文档。
document.close();

通过上面的5个步骤,就能产生一个Helloworld.PDF的文件,文件内容为”Hello World”。

建立com.lowagie.text.Document对象的实例

com.lowagie.text.Document对象的构建函数有三个,分别是:
public Document();
public Document(Rectangle pageSize);
public Document(Rectangle pageSize,
int marginLeft,
int marginRight,
int marginTop,
int marginBottom);

构建函数的参数pageSize是文档页面的大小,对于第一个构建函数,页面的大小为A4,同Document(PageSize.A4)的效 果一样;对于第三个构建函数,参数marginLeft、marginRight、marginTop、marginBottom分别为左、右、上、下的 页边距。

通过参数pageSize可以设定页面大小、面背景色、以及页面横向/纵向等属性。iText定义了A0-A10、AL、LETTER、 HALFLETTER、_11x17、LEDGER、NOTE、B0-B5、ARCH_A-ARCH_E、FLSA 和FLSE等纸张类型,也可以通过Rectangle pageSize = new Rectangle(144, 720);自定义纸张。通过Rectangle方法rotate()可以将页面设置成横向。

书写器(Writer)对象

一旦文档(document)对象建立好之后,需要建立一个或多个书写器(Writer)对象与之关联。通过书写器(Writer)对象可以将 具体文档存盘成需要的格式,如com.lowagie.text.PDF.PDFWriter可以将文档存成PDF文 件,com.lowagie.text.html.HtmlWriter可以将文档存成html文件。

设定文档属性

在文档打开之前,可以设定文档的标题、主题、作者、关键字、装订方式、创建者、生产者、创建日期等属性,调用的方法分别是:
public boolean addTitle(String title)
public boolean addSubject(String subject)
public boolean addKeywords(String keywords)
public boolean addAuthor(String author)
public boolean addCreator(String creator)
public boolean addProducer()
public boolean addCreationDate()
public boolean addHeader(String name, String content)

其中方法addHeader对于PDF文档无效,addHeader仅对html文档有效,用于添加文档的头信息。
当新的页面产生之前,可以设定页面的大小、书签、脚注(HeaderFooter)等信息,调用的方法是:
public boolean setPageSize(Rectangle pageSize)
public boolean add(Watermark watermark)
public void removeWatermark()
public void setHeader(HeaderFooter header)
public void resetHeader()
public void setFooter(HeaderFooter footer)
public void resetFooter()
public void resetPageCount()
public void setPageCount(int pageN)

如果要设定第一页的页面属性,这些方法必须在文档打开之前调用。

对于PDF文档,iText还提供了文档的显示属性,通过调用书写器的setViewerPreferences方法可以控制文档打开时Acrobat Reader的显示属性,如是否单页显示、是否全屏显示、是否隐藏状态条等属性。

另外,iText也提供了对PDF文件的安全保护,通过书写器(Writer)的setEncryption方法,可以设定文档的用户口令、只读、可打印等属性。

添加文档内容

所有向文档添加的内容都是以对象为单位的,如Phrase、Paragraph、Table、Graphic对象等。比较常用的是段落(Paragraph)对象,用于向文档中添加一段文字。
四、文本处理

iText中用文本块(Chunk)、短语(Phrase)和段落(paragraph)处理文本。
文本块(Chunk)是处理文本的最小单位,有一串带格式(包括字体、颜色、大小)的字符串组成。如以下代码就是产生一个字体为HELVETICA、大小为10、带下划线的字符串:
Chunk chunk1 = new Chunk(“This text is underlined”, FontFactory.getFont(FontFactory.HELVETICA, 12, Font.UNDERLINE));

短语(Phrase)由一个或多个文本块(Chunk)组成,短语(Phrase)也可以设定字体,但对于其中以设定过字体的文本块 (Chunk)无效。通过短语(Phrase)成员函数add可以将一个文本块(Chunk)加到短语(Phrase)中, 如:phrase6.add(chunk);

段落(paragraph)由一个或多个文本块(Chunk)或短语(Phrase)组成,相当于WORD文档中的段落概念,同样可以设定段落 的字体大小、颜色等属性。另外也可以设定段落的首行缩进、对齐方式(左对齐、右对齐、居中对齐)。通过函数setAlignment可以设定段落的对齐方 式,setAlignment的参数1为居中对齐、2为右对齐、3为左对齐,默认为左对齐。

五、表格处理

iText中处理表格的类为:com.lowagie.text.Table和 com.lowagie.text.PDF.PDFPTable,对于比较简单的表格处理可以用com.lowagie.text.Table,但是如果 要处理复杂的表格,这就需要com.lowagie.text.PDF.PDFPTable进行处理。这里就类 com.lowagie.text.Table进行说明。

类com.lowagie.text.Table的构造函数有三个:

①Table (int columns)
②Table(int columns, int rows)
③Table(Properties attributes)

参数columns、rows、attributes分别为表格的列数、行数、表格属性。创建表格时必须指定表格的列数,而对于行数可以不用指定。

建立表格之后,可以设定表格的属性,如:边框宽度、边框颜色、衬距(padding space 即单元格之间的间距)大小等属性。下面通过一个简单的例子说明如何使用表格,代码如下:

Table table = new Table(3);
table.setBorderWidth(1);
table.setBorderColor(new Color(0, 0, 255));
table.setPadding(5);
table.setSpacing(5);
Cell cell = new Cell("header");
cell.setHeader(true);
cell.setColspan(3);
table.addCell(cell);
table.endHeaders();
cell = new Cell("example cell with colspan 1 and rowspan 2");
cell.setRowspan(2);
cell.setBorderColor(new Color(255, 0, 0));
table.addCell(cell);
table.addCell("1.1");
table.addCell("2.1");
table.addCell("1.2");
table.addCell("2.2");
table.addCell("cell test1");
cell = new Cell("big cell");
cell.setRowspan(2);
cell.setColspan(2);
table.addCell(cell);
table.addCell("cell test2");

运行结果如下:
header cell test2

代码1-5行用于新建一个表格,如代码所示,建立了一个列数为3的表格,并将边框宽度设为1,颜色为蓝色,衬距为5。

代码6-10行用于设定表格的表头,第7行cell.setHeader(true);是将该单元格作为表头信息显示;第8行 cell.setColspan(3);指定了该单元格占3列;为表格添加表头信息时,要注意的是一旦表头信息添加完了之后,必须调用 endHeaders()方法,如第10行,否则当表格跨页后,表头信息不会再显示。

代码11-14行是向表格中添加一个宽度占一列,长度占二行的单元格。

往表格中添加单元格(cell)时,按自左向右、从上而下的次序添加。如执行完11行代码后,表格的右下方出现2行2列的空白,这是再往表格添加单元格时,先填满这个空白,然后再另起一行,15-24行代码说明了这种添加顺序。

六、图像处理

iText中处理表格的类为com.lowagie.text.Image,目前iText支持的图像格式有:GIF, Jpeg, PNG, wmf等格式,对于不同的图像格式,iText用同样的构造函数自动识别图像格式。通过下面的代码分别获得gif、jpg、png图像的实例。
Image gif = Image.getInstance(“vonnegut.gif”);
Image jpeg = Image.getInstance(“myKids.jpg”);
Image png = Image.getInstance(“hitchcock.png”);

图像的位置

图像的位置主要是指图像在文档中的对齐方式、图像和文本的位置关系。IText中通过函数public void setAlignment(int alignment)进行处理,参数alignment为Image.RIGHT、Image.MIDDLE、Image.LEFT分别指右对齐、居中、 左对齐;当参数alignment为Image.TEXTWRAP、Image.UNDERLYING分别指文字绕图形显示、图形作为文字的背景显示。这 两种参数可以结合以达到预期的效果,如setAlignment(Image.RIGHT|Image.TEXTWRAP)显示的效果为图像右对齐,文字 围绕图像显示。

图像的尺寸和旋转

如果图像在文档中不按原尺寸显示,可以通过下面的函数进行设定:
public void scaleAbsolute(int newWidth, int newHeight)
public void scalePercent(int percent)
public void scalePercent(int percentX, int percentY)

函数public void scaleAbsolute(int newWidth, int newHeight)直接设定显示尺寸;函数public void scalePercent(int percent)设定显示比例,如scalePercent(50)表示显示的大小为原尺寸的50%;而函数scalePercent(int percentX, int percentY)则图像高宽的显示比例。

如果图像需要旋转一定角度之后在文档中显示,可以通过函数public void setRotation(double r)设定,参数r为弧度,如果旋转角度为30度,则参数r= Math.PI / 6。

七、中文处理

默认的iText字体设置不支持中文字体,需要下载远东字体包iTextAsian.jar,否则不能往PDF文档中输出中文字体。通过下面的代码就可以在文档中使用中文了:
BaseFont bfChinese = BaseFont.createFont(“STSong-Light”, “UniGB-UCS2-H”, BaseFont.NOT_EMBEDDED);
com.lowagie.text.Font FontChinese = new com.lowagie.text.Font(bfChinese, 12, com.lowagie.text.Font.NORMAL);
Paragraph pragraph=new Paragraph(“你好”, FontChinese);

八、分页处理

如果只是简单的显示当前页码,使用以下代码即可(设定了页面的大小后,会自动分页)。

1 HeaderFooter footer = new HeaderFooter(new Phrase("页码:",keyfont), true);  
2 footer.setBorder(Rectangle.NO_BORDER);  
3 document.setHeader(footer);

如果要显示当前页码以及总页码。

则需要计算总页数,设定每页大小,使用pdf.newPage( )手动分页。

详见一下代码:

package com.foster;

import java.io.File;
import java.io.FileOutputStream;
import java.util.ArrayList;
import java.util.List;

import com.lowagie.text.Cell;
import com.lowagie.text.Document;
import com.lowagie.text.DocumentException;
import com.lowagie.text.Font;
import com.lowagie.text.HeaderFooter;
import com.lowagie.text.Image;
import com.lowagie.text.Paragraph;
import com.lowagie.text.Table;
import com.lowagie.text.pdf.BaseFont;
import com.lowagie.text.pdf.PdfPCell;
import com.lowagie.text.pdf.PdfWriter;

public class PDFReport {

public static void main(String[] args) throws Exception, DocumentException {

List ponum=new ArrayList();
add(ponum, 26);
List line=new ArrayList();
add(line, 26);
List part=new ArrayList();
add(part, 26);
List description=new ArrayList();
add(description, 26);
List origin=new ArrayList();
add(origin, 26);

//Create Document Instance
Document document=new Document();

//add Chinese font
BaseFont bfChinese=BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);

//Font headfont=new Font(bfChinese,10,Font.BOLD);
Font keyfont=new Font(bfChinese,8,Font.BOLD);
Font textfont=new Font(bfChinese,8,Font.NORMAL);

//Create Writer associated with document
PdfWriter.getInstance(document, new FileOutputStream(new File("D:\\POReceiveReport.pdf")));

document.open();

//Seperate Page controller
int recordPerPage=10;
int fullPageRequired=ponum.size()/recordPerPage;
int remainPage=ponum.size()%recordPerPage&gt;1?1:0;
int totalPage=fullPageRequired+remainPage;

for(int j=0;j&lt;totalPage;j++){
document.newPage();

//create page number
String pageNo=leftPad("页码: "+(j+1)+" / "+totalPage,615);
Paragraph pageNumber=new Paragraph(pageNo, keyfont) ;
document.add(pageNumber);

//create title image
Image jpeg=Image.getInstance("D:\\title.JPG");
jpeg.setAlignment(Image.ALIGN_CENTER);
jpeg.scaleAbsolute(530, 37);
document.add(jpeg);

//header information
Table tHeader=new Table(2);
float[] widthsHeader={2f,3f};
tHeader.setWidths(widthsHeader);
tHeader.setWidth(100);
tHeader.getDefaultCell().setBorder(PdfPCell.NO_BORDER);

String compAdd="河源市高新技术开发区兴业大道中66号";
String company="丰达音响(河源)有限公司";
String vendor="V006";
String vendorName="中山市卢氏五金有限公司";
String ccn="FHH";
String mas_loc="FHH";
String delivery_note="20130718001";
String receive_date="20130718";
String dept="H11";
String asn="0123456789";

Cell c1Header=new Cell(new Paragraph("地址:"+compAdd,keyfont));
tHeader.addCell(c1Header);
c1Header=new Cell(new Paragraph("供应商:"+vendor,keyfont));
tHeader.addCell(c1Header);
c1Header=new Cell(new Paragraph("公司:"+company,keyfont));
tHeader.addCell(c1Header);
c1Header=new Cell(new Paragraph("供应商工厂:"+vendorName,keyfont));
tHeader.addCell(c1Header);
c1Header = new Cell(new Paragraph("CCN: "+ccn+" Master Loc: "+mas_loc,keyfont));
tHeader.addCell(c1Header);
c1Header = new Cell(new Paragraph("送货编号: "+delivery_note+" 送货日期: "+receive_date,keyfont));
tHeader.addCell(c1Header);
c1Header=new Cell(new Paragraph("Dept:"+dept,keyfont));
tHeader.addCell(c1Header);
c1Header=new Cell(new Paragraph("ASN#:"+asn,keyfont));
tHeader.addCell(c1Header);
document.add(tHeader);

//record header field
Table t=new Table(5);
float[] widths={1.5f,1f,1f,1.5f,1f};
t.setWidths(widths);
t.setWidth(100);
t.getDefaultCell().setBorder(PdfPCell.NO_BORDER);
Cell c1 = new Cell(new Paragraph("PO#",keyfont));
t.addCell(c1);
c1 = new Cell(new Paragraph("Line",keyfont));
t.addCell(c1);
c1 = new Cell(new Paragraph("Part#",keyfont));
t.addCell(c1);
c1 = new Cell(new Paragraph("Description",keyfont));
t.addCell(c1);
c1 = new Cell(new Paragraph("Origin",keyfont));
t.addCell(c1);

//calculate the real records within a page ,to calculate the last record number of every page
int maxRecordInPage= j+1 ==totalPage ? (remainPage==0?recordPerPage:(ponum.size()%recordPerPage)):recordPerPage;

for(int i=j*recordPerPage;i&lt;((j*recordPerPage)+maxRecordInPage);i++){
Cell c2=new Cell(new Paragraph(ponum.get(i), textfont));
t.addCell(c2);
c2=new Cell(new Paragraph(line.get(i), textfont));
t.addCell(c2);
c2=new Cell(new Paragraph(part.get(i), textfont));
t.addCell(c2);
c2=new Cell(new Paragraph(description.get(i), textfont));
t.addCell(c2);
c2=new Cell(new Paragraph(origin.get(i), textfont));
t.addCell(c2);
}
document.add(t);

if(j+1==totalPage){

Paragraph foot11 = new Paragraph("文件只作 Foster 收貨用"+printBlank(150)+"__________________________",keyfont);
document.add(foot11);
Paragraph foot12 = new Paragraph("Printed from Foster supplier portal"+printBlank(134)+company+printBlank(40)+"版本: 1.0",keyfont);
document.add(foot12);
HeaderFooter footer11=new HeaderFooter(foot11, true);
footer11.setAlignment(HeaderFooter.ALIGN_BOTTOM);
HeaderFooter footer12=new HeaderFooter(foot12, true);
footer12.setAlignment(HeaderFooter.ALIGN_BOTTOM);
}
}
document.close();
}

public static String leftPad(String str, int i) {
int addSpaceNo = i-str.length();
String space = "";
for (int k=0; k&lt;addSpaceNo; k++){
space= " "+space;
};
String result =space + str ;
return result;
}

public static void add(List list,int num){
for(int i=0;i&lt;num;i++){
list.add("test"+i);
}
}

public static String printBlank(int tmp){
String space="";
for(int m=0;m&lt;tmp;m++){
space=space+" ";
}
return space;
}

}

为了使副标题严格对齐,使用了表格table进行控制,但是却没能找到去掉表格边框的方法……….郁闷…….

九、总结

总的来说,iText是一套java环境下不错的制作PDF的组件。因为iText支持jsp/javabean下的开发,这使得B/S应用中 的报表问题能得到很好的解决。由于iText毕竟不是专门为制作报表设计,所有报表中的内容、格式都需要通过写代码实现,相对于那些专业的支持可视化设计 的报表软件来说,编程的工作量就有一定程度的增加。

[转载]数据库优化和效率问题 - NetUML - 博客园

mikel阅读(681)

[转载]数据库优化和效率问题 – NetUML – 博客园.

在使用SQL语句查询数据库记录时,如果要查询相同的内容,有着不同的多种方法。
仍然,尽管使用多种方法可以得到相同的结果,但是,如果您使用不同的方法,在执行效益上是截然不同的。因此,我们得仔细考虑,如果要查询相同结果,该使用哪种语句,执行效益比较好。
这就是SQL语句的优化。
以下优化语句,针对MS SQL数据库。
1、对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引。
2、应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描,如:
select id from t where num is null
可以在num上设置默认值0,确保表中num列没有null值,然后这样查询:
select id from t where num=0
3、应尽量避免在 where 子句中使用!=或<>操作符,否则将引擎放弃使用索引而进行全表扫描。
4、应尽量避免在 where 子句中使用 or 来连接条件,否则将导致引擎放弃使用索引而进行全表扫描,如:
select id from t where num=10 or num=20
可以改为如下的查询:
select id from t where num=10
union all
select id from t where num=20
5、in 和 not in 也要慎用,否则会导致全表扫描,如:
select id from t where num in(1,2,3)
对于连续的数值,能用 between 就不要用 in 了:
select id from t where num between 1 and 3
6、下面的查询也将导致全表扫描:
select id from t where name like ‘%abc%’
若要提高效率,可以考虑全文检索。
7、如果在 where
子句中使用参数,也会导致全表扫描。因为SQL只有在运行时才会解析局部变量,但优化程序不能将访问计划的选择推迟到运行时;它必须在编译时进行选择。然而,如果在编译时建立访问计划,变量的值还是未知的,因而无法作为索引选择的输入项。如下面语句将进行全表扫描:
select id from t where num=@num
可以改为强制查询使用索引:
select id from t with(index(索引名)) where num=@num
8、应尽量避免在 where 子句中对字段进行表达式操作,这将导致引擎放弃使用索引而进行全表扫描。如:
select id from t where num/2=100
应改为:
select id from t where num=100*2
9、应尽量避免在where子句中对字段进行函数操作,这将导致引擎放弃使用索引而进行全表扫描。如:
select id from t where substring(name,1,3)=’abc’–name以abc开头的id
select id from t where
datediff(day,createdate,’2010-11-30′)=0–‘2010-11-30’生成的id
应改为:
select id from t where name like ‘abc%’
select id from t where createdate>=’2010-11-30′ and createdate<‘2010-12-1’
10、不要在 where 子句中的“=”左边进行函数、算术运算或其他表达式运算,否则系统将可能无法正确使用索引。
11、在使用索引字段作为条件时,如果该索引是复合索引,那么必须使用到该索引中的第一个字段作为条件时才能保证系统使用该索引,否则该索引将不会被使用,并且应尽可能的让字段顺序与索引顺序相一致。
12、不要写一些没有意义的查询,如需要生成一个空表结构:
select col1,col2 into #t from t where 1=0
这类代码不会返回任何结果集,但是会消耗系统资源的,应改成这样:
create table #t(…)
13、很多时候用 exists 代替 in 是一个好的选择:
select num from a where num in(select num from b)
用下面的语句替换:
select num from a where exists(select 1 from b where num=a.num)
14、并不是所有索引对查询都有效,SQL是根据表中数据来进行查询优化的,当索引列有大量数据重复时,SQL查询可能不会去利用索引,如一表中有字段sex,male、female几乎各一半,那么即使在sex上建了索引也对查询效率起不了作用。
15、索引并不是越多越好,索引固然可以提高相应的 select 的效率,但同时也降低了 insert 及 update 的效率,因为 insert 或
update
时有可能会重建索引,所以怎样建索引需要慎重考虑,视具体情况而定。一个表的索引数最好不要超过6个,若太多则应考虑一些不常使用到的列上建的索引是否有必要。
16、应尽可能的避免更新 clustered 索引数据列,因为 clustered
索引数据列的顺序就是表记录的物理存储顺序,一旦该列值改变将导致整个表记录的顺序的调整,会耗费相当大的资源。若应用系统需要频繁更新 clustered
索引数据列,那么需要考虑是否应将该索引建为 clustered 索引。
17、尽量使用数字型字段,若只含数值信息的字段尽量不要设计为字符型,这会降低查询和连接的性能,并会增加存储开销。这是因为引擎在处理查询和连接时会逐个比较字符串中每一个字符,而对于数字型而言只需要比较一次就够了。
18、尽可能的使用 varchar/nvarchar 代替 char/nchar
,因为首先变长字段存储空间小,可以节省存储空间,其次对于查询来说,在一个相对较小的字段内搜索效率显然要高些。
19、任何地方都不要使用 select * from t ,用具体的字段名称代替“*”,不要返回用不到的任何字段。
20、尽量使用表变量来代替临时表。如果表变量包含大量数据,请注意索引非常有限(只有主键索引)。
21、避免频繁创建和删除临时表,以减少系统表资源的消耗。
22、临时表并不是不可使用,适当地使用它们可以使某些例程更有效,例如,当需要重复引用大型表或常用表中的某个数据集时。但是,对于一次性事件,最好使用导出表。
23、在新建临时表时,如果一次性插入数据量很大,那么可以使用 select into 代替 create table,避免造成大量 log
,以提高速度;如果数据量不大,为了缓和系统表的资源,应先create table,然后insert。
24、如果使用到了临时表,在存储过程的最后务必将所有的临时表显式删除,先 truncate table ,然后 drop table
,这样可以避免系统表的较长时间锁定。
25、尽量避免使用游标,因为游标的效率较差,如果游标操作的数据超过1万行,那么就应该考虑改写。
26、使用基于游标的方法或临时表方法之前,应先寻找基于集的解决方案来解决问题,基于集的方法通常更有效。
27、与临时表一样,游标并不是不可使用。对小型数据集使用 FAST_FORWARD
游标通常要优于其他逐行处理方法,尤其是在必须引用几个表才能获得所需的数据时。在结果集中包括“合计”的例程通常要比使用游标执行的速度快。如果开发时间允许,基于游标的方法和基于集的方法都可以尝试一下,看哪一种方法的效果更好。
28、在所有的存储过程和触发器的开始处设置 SET NOCOUNT ON ,在结束时设置 SET NOCOUNT OFF
。无需在执行存储过程和触发器的每个语句后向客户端发送 DONE_IN_PROC 消息。
29、尽量避免大事务操作,提高系统并发能力。

[转载]打破传统思维——大胆尝试在路由器上搭建SVN服务器 - wapco - 博客园

mikel阅读(991)

[转载]打破传统思维——大胆尝试在路由器上搭建SVN服务器 – wapco – 博客园.

       注册博客园挺久了,一直比较懒,虽然有几次想写点文章,但是一直没有行动,今天给大家带来一篇比较有意思的文章,不涉及技术上的。本人文笔不是太好,希望大家轻拍。本文的文字和图片全部为原创,尊重作者转载请注明出处!

说起路由器能够干嘛,大部分想到的就是用来组建局域网呀,或者偶尔用它做做迅雷脱机下载之类的,如果我告诉你用路由器还能用来做SVN服务器,甚至运行mySQL+php你信么?今天我们就来打破这个传统思维,用路由器来做SVN服务器。

SVN托管服务大家都不陌生了,我最早开始用的是谷歌提供的SVN,因为在上面托管的项目都是开源的,所以当有些项目不方便在网上公开的时候,就需要自己 搭建SVN服务器了。windows、linux的用的是最多的了,但是有时候为了放一个SVN要单独放一台电脑并24小时开机,不符合节能减耗的目的。 所以今天笔者就做一个大胆的尝试,使用路由器架设SVN服务器,路由器的功耗只有电脑的百分之一,而且平常也都要开机,这看起来有点像天方夜谭,因为路由 器不论是CPU、内存还是ROM上,都远远不如一台电脑,甚至不如一台普通的安卓手机的性能,但是毛主席说过“实践是检验真理的唯一标准”,所以我们今天 就来做一个尝试并检验它的可能性。

我们都知道,路由器的操作系统也是基于Linux的,既然Linux上可以搭建SVN,那理论上路由器也是可以的,只是我们要用到第三方的路由器固件,因 为路由器自带的固件是封闭的,功能上也有一定的限制,所以我们这里需要一台满足这样需求的路由器:至少带一个USB接口(用来挂载U盘或者移动硬盘之类 的);可以刷DD-WRT、Openwrt、TOMATO其中的一种;CPU主频在300MHZ,RAM在32M以上。在本文中,我们用的是华为的 HG255D路由器,因为这台路由器全新的也就不到100块钱,淘宝二手的50左右即可,有一个USB接口,硬件配置是:CPU Ralink RT3052@384Mhz,16M FLASH,32M DDR内存(+20可以升级64M性能更好)。可以刷DD-WRT和Openwrt系统。

路由器怎么刷第三方固件网上已经有很多资料了,这里就不在详细说明,我们直接拿刷好DD-WRT系统作为今天的测试平台,其他系统基本上操作也都差不多。

1、首先拿一个1G以上的U盘,在Linux系统中格式化为ext3格式,然后插在路由器的USB接口上,打开浏览器输入192.168.0.1(根据自 己实际IP)进入路由器管理界面,然后点“服务”-“USB”选项,按照下图中打开USB支持,并将U盘挂载为opt,点应用后如果提示Status: Mounted on /opt则挂载成功。

2、接下来再点选项卡“服务”-“服务”,然后启用SSH并应用,如下图所示:

3、第三步下载PuttyMan,打开软件后新建连接,注意用户名那边无论路由器的用户名是什么都要填root,密码则是路由器的密码,如下图

4、连接路由器,如果出现下图中信息,那么恭喜你,已经离成功只有一半了,否则请重复前面2步。

5、在PuttyMan客户端中输入以下命令:(请先确认USB存储设备已挂载到/opt)

wget http://www.3iii.dk/linux/optware/optware-install-ddwrt.sh -O - | tr -d '\r' > /tmp/optware-install.sh
sh /tmp/optware-install.sh

在这里我要讲一下题外话,原先是本来考虑用其他Linux系统安装svn的方法,直接下载svn安装包,但是试了很多次都不成功,因为路由器的 Linux是属于轻量型的,里面很多库都不支持,比如gcc库,而安装gcc库的时候又缺少其他的依赖库,所以走了不少弯路,事实也证明用这种方法几乎是 安装不了的,因为安装了太多的库也会导致路由器硬件性能跟不上。所以后面才想到用optware这个更简单的第三方软件运行环境。这也是为什么第一步中要 把U盘挂载为opt的原因。

6、等安装好optware后会提示successfully,接下来就可以用功能强大的optware了,在命令中继续输入以下代码更新opt软件列表:

cd /opt
ipkg-opt update

7、你可以输入ipkg-opt list显示所有可安装的软件(还可以安装httpd、mySQL、php等等,挂上你的个人主页),这里我们只需要安装SVN,所以输入

ipkg-opt install svn

8、安装好SVN后可以在命令行中输入svnserve –version检查一下SVN是否安装成功,如果可以正常显示svn版本号,那么恭喜你,你已经成功在路由器上运行SVN服务了,接下来让我们启动SVN服务,输入命令:

svnserve -d -r /opt/svndata/

9、为了方便每次路由器关机后会自动启动服务,我们可以把它添加到路由器的启动命令中,点“管理”-“命令”选项卡,然后在指令的最后面添加以上代码并保存为启动指令即可。

10、接下来就是测试了,为了方便测试我们开启路由器的Samba服务,进入“服务”-“NAS”选项卡,启用Samba服务,并在文件分享那边选择opt

11、然后打开我的电脑,点“工具”-“映射网络驱动器”,将路由器U盘映射为A盘,映射只是为了方便访问,也可以直接在运行中输入\\192.168.0.1来访问

12、回到命令行中输入以下命令创建版本库:

cd /opt/svndata
svnadmin create svntest

13、创建成功后打开刚刚的文件夹,修改这3个配置文件(添加用户名读写权限等),具体方法参照Linux中svn配置

14、然后回到windows中检出,在版本库中加入我们的程序代码并提交。

提交测试:

路由器负载显示正常(当负载超过7的时候路由器一般会死机):

哈哈,太令人振奋了,和以前用的SVN服务器用起来没什么两样,简直看不出来这是用一台路由器搭建的SVN,测试结果很成功,太完美了。

15、如果需要外网可以访问,则启用动态DNS即可,这样不管家里的IP怎样变化,也可以用域名来访问了,这样使用更方便。如下图设置,方法很简单就不再具体说明。如果你觉得本文对你有帮助,就帮我点一下右下角的推荐吧。(完)

[转载]C#实现在注册表中保存信息 - ProJKY - 博客园

mikel阅读(923)

[转载]C#实现在注册表中保存信息 – ProJKY – 博客园.

最近做的项目需要在注册表中记录一些用户设置,方便在程序下次启动时读取设置,应用上次用户保存的设置,挺简单的。

写出来,方便记忆,以后要用,可以直接改改就能用。

using System;
 
 namespace Backend
 {
     public class RegistryStorage
     {
         public static PageVisibility OpenAfterStart()
         {
             Microsoft.Win32.RegistryKey registryKey;
             PageVisibility visibility = PageVisibility.Visible;
 
             // HKCU\Software\RegeditStorage
             registryKey = Microsoft.Win32.Registry.CurrentUser.OpenSubKey(@"Software\RegistryStorage");
             if (registryKey != null)
             {
                 visibility = (string)registryKey.GetValue("PageVisibility") == PageVisibility.Hide.ToString() ?
                     PageVisibility.Hide : PageVisibility.Visible;
                 registryKey.Close();
             }
 
             return visibility;
         }
 
         public static void SaveBeforeExit(PageVisibility visibility)
         {
             Microsoft.Win32.RegistryKey registryKey;
 
             // HKCU\Software\RegeditStorage
             registryKey = Microsoft.Win32.Registry.CurrentUser.CreateSubKey(@"Software\RegistryStorage");
             registryKey.SetValue("PageVisibility", visibility.ToString());
             registryKey.Close();
         }
     }
 
     public enum PageVisibility
     { 
         Visible,
         Hide
     }
 }