百度空间 | 百度首页 
 
文章列表
 
您正在查看 "Notes" 分类下的文章

2008-10-24 19:54

In the previous posts I've explained how to set up the development environment and how to create a blog editting page as well as save the post. Now it's time to view the blog we have just created. So we need a page where we can see all the blogs.

Let's make this page the default page of our site. To do this, we create a function called 'index':

@expose(template="blog.templates.index")
    def index(self,cid=None,**kw):
        if cid: #category id is provided, return all posts belonging to this category
            category = Category.get(int(cid))
            blogs = list(Blog.selectBy(category=category).orderBy(DESC('time')))
        else:   #all posts
            category = None
            blogs = list(Blog.select().orderBy(DESC('time')))
        return dict(blogs=blogs,category=category,categories=list(Category.select()))

I don't think I need to explain anything here because it's very similar to the functions we have already created in previous tutorials. And also, the index.kid template file is similar too:

<body>
<h1 py:if="category">Posts under ${category.name}</h1>
<h1 py:if="not category">All posts</h1>
<div id="blogs">
    <div py:for="b in blogs" class="blog" id="b${b.id}">
        <div class="subject"><a href="/viewpost?bid=${b.id}">${b.subject}</a></div>
        <div class="author">${b.author}</div>
        <div class="time">${b.time}</div>
        <div class="content">${b.content}</div>
        <div class="etc">
        Category: <a href="/?cid=${b.category.id}">${b.category.name}</a> |
        <a href="/viewpost?bid=${b.id}#comments">Comments(${len(b.comments)})</a> |
        Views(${b.views}) |
        <a href="editblog?bid=${b.id}">Edit</a> |
        <a href="#" onclick="deleteblog(${b.id});return false;">Delete</a>
        </div>
    </div>
</div>
</body>

As you can see, to get the category a post belongs to, all we need to write is blog.category. This is much easier than writing sql expressions.

After we've got these modifications done, we should be able to see a page similar to the following one:

I tuned the css a little bit so that it will not look too ugly.

The tricky part is the Delete link. This time I'll use AJAX to do the deleting job.

<a href="#" onclick="deleteblog(${b.id});return false;">Delete</a>

On clicking this link, a javascript function deleteblog is called:

<script type="text/javascript">
//<![CDATA[
function deleteblog(bid){
    if (confirm('All the comments will be deleted too. This operation cannot be undone. Are you sure you want to delete this blog? ')){
        new Json.Remote('deleteblog',{onComplete: function(result){
                if (result['deleted']==0){
                    blogid = 'b'+bid
                    $('blogs').removeChild($(blogid));   //remove the blog from this page
                    return;
                }
                else{
                    alert('Failed to delete this blog, try again later.');
                    return;
                }
            }}).send({'bid':bid});
    }
}
//]]>
</script>

this script tag should be added into <head></head>. I'm using mootools instead of Turbogears' official javascript lib MochiKit to do the AJAX call here(to me, mootools is a way better js lib than MochiKit). To include mootools, first download mootools.js from their website and copy it to /static/javascript, and then add <script type="text/javascript" src="/static/javascript/mootools.js"></script> into the <head> tag.

This function makes an AJAX call to http://localhost:8080/deleteblog, and send the id of the post('bid') as the parameter. The response is returned in JSON format.

server side function:

@expose(format="json")
    def deleteblog(self,bid=None,**kw):
        if not bid: return dict(deleted=1) #bid not provided, delete failed
        else:
            blog = Blog.get(int(bid))   #get the blog
            for c in blog.comments: c.destroySelf()     #delete all the comments
            blog.destroySelf() #delete the post itself
            return dict(deleted=0) #deleted, 0 indicates success

As you can see, to get all the comments of a blog, you just have to write blog.comments; similarly, category.blogs will get you all the blogs belonging to this category.

the response is passed to the parameter 'result' of function which will be called when the AJAX call is completed. If result['deleted'] is 0, which means we have successfully deleted this post, we remove the post from this page. Pretty simple, isn't it?

Now you should be able to delete a post without refreshing the page.

Are we forgetting anything? Yes, the viewpost page and the comment part. I think by far you have learned enough to create a page all by yourself. I have put all we've got here so far in an archive, which can be downloaded here.

If you have any questions, please drop me a comment. My name is Stone and I'm the web developer of PapayaMobile.com.

类别:Notes | 评论(2) | 浏览()
 
2008-10-24 17:11

In the previous post I've showed you how to create the development environment, and this time we'll create an editting page for our blog.

This page can either create a new post or edit an existing post. Let's name it "compose".

To make life easier, I will not focus on the styling of our blog, so I'll just use the default master template that quickstart has generated for us. First, go to templates folder, and open master.kid. Turbogrears uses a templating system called kid, which is very well designed and easy to use. The master.kid acts like a framework of all the pages(of course you can choose not to inherit from it too), which includes a common header and a common footer, etc. We'll keep the header, content and footer, and remove all the unnecessary parts in the body tag like this(you can skip this part if you are happy with them):

<body py:match="item.tag=='{http://www.w3.org/1999/xhtml}body'" py:attrs="item.items()">
    <div id="header">&#160;</div>
    <div id="main_content">
        <div py:replace="[item.text]+item[:]">page content</div>
    </div>
    <div id="footer">
        <img src="${tg.url('/static/images/under_the_hood_blue.png')}"
            alt="TurboGears under the hood" />
        <p>TurboGears is a open source front-to-back web development framework
            written in Python</p>
        <p>Copyright &#169; 2007 Kevin Dangoor</p>
    </div>
</body>

Much better. The next step is to create a compose page of our own. In the templates directory, rename welcome.kid to compose.kid, then open it. First, delete the unnecessary parts like this:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="
http://www.w3.org/1999/xhtml" xmlns:py="http://purl.org/kid/ns#"
    py:extends="'master.kid'">
<head>
<meta content="text/html; charset=utf-8" http-equiv="Content-Type" py:replace="''"/>
<title>New post - Stone's Blog</title>
</head>
<body>
</body>
</html>

Notice 'py:extends="'master.kid'"' indicates this page inherits from the master.kid, which means the header and footer and other stuff will be carried on to our compose page.

Then open controller.py, delete the unnecessary parts like this:

import turbogears as tg
from turbogears import controllers, expose, redirect, paginate
from blog.model import *

class Root(controllers.RootController):

then we are ready to write the server side code for our compose page, which is defined in a function called compose. This function will be called when we visit http://localhost:8080/compose.

@expose(template="blog.templates.compose")
    def compose(self, bid=None, **kw):
        if bid:
            blog = Blog.get(int(bid))
        else:
            blog = None
        return dict(blog=blog,categories=list(Category.select()))

@expose is a special kind of function in python called the decorator. In this decorator, we specified a parameter called templates, indicates the template we are going to use. compose takes 1 or no parameter. bid is the id of the post we'll edit, and if it is not provided, we are creating a new one. We have to assign a category to our post when editing, so we need all the categories too. Finally, we return this blog object and all the categories back to our template file - compose.kid.

<body>
<h1 py:if="blog">Edit Post</h1>
<h1 py:if="not blog">Compose</h1>
<form action="/saveblog" method="post">
    <input py:if="blog" type="hidden" name="bid" value="${blog.id}"/>
    <div>Subject: <input name="subject" value="${blog.subject if blog else None}"/></div>
    <div>Author: <input name="author" value="${blog.author if blog else None}"/></div>
    <div>Category:
        <select name="category">
            <option py:for="c in categories" value="${c.id}" selected="${'selected' if blog and blog.category==c else None}">${c.name}</option>
        </select>
    </div>
    <div>Content:</div>
    <div><textarea name="content" cols="50" rows="10">${blog.content if blog else None}</textarea></div>
    <div><input type="submit" value="Submit"/></div>
</form>
</body>

Let's walk through this line by line.

<h1 py:if="blog">Edit Post</h1>
<h1 py:if="not blog">Compose</h1>

this indicates, if the blog is None, then the page title will be Compose, meaning that we are creating a new post; else page name will be edit post, meaning that we are editing an existing post. The whole tag will not be created if the expression in py:if is not true.

<input py:if="blog" type="hidden" name="bid" value="${blog.id}"/>

if we are editing a post, we need to carry the id of our post on or there's no way we'll should update the changes to an existing post instead of creating a new one when we save it.

Subject: <input name="subject" value="${blog.subject if blog else None}"/>

this indicates the subject textbox will show the blog's subject if we are editing it, or will show nothing if we are creating a new post. Notice that if None is the value in ${}, then the whole value attribute will not be created. This is a very neat design because it won't leave a 'value=""' or 'value=' in the html generated, which could cause some trouble if we are dealing with attributes like 'selected' and 'checked', because if something like 'selected=""' is generated, the browser will regard it as the selected item just like any other items with 'selected="selected"' in their attributes, and this is not what is desired.

<select name="category">
     <option py:for="c in categories" value="${c.id}" selected="${'selected' if blog and blog.category==c else None}">${c.name}</option>
</select>

notice the py:for in the option. This means the kid template will generate len(categories) option tags, each with its category's id assigned to the value attribute, and a display text of the category's name. The generated code may be something like this:

<select name="category">
      <option value="0"> Default </option>
      <option value="1" selected="selected"> Web development </option>
      <option value="2"> Funny </option>
</select>

Look how simple it is to generate a dropdown list with the kid template. Be ware that the kid template is a xml document so we cannot offer to generate code like: <option selected>Funny</option>.

And now we are ready to see what we've got here. Save the files and start the web server. Navigate to http://localhost:8080/compose, we should be able to see the compose page now:

Cool! Now we'll have to define a function that can save the post. Remember the action attribute of our form in the compose page? Yes, we are going to create a function called 'saveblog'.

@expose()
    def saveblog(self,bid=None,subject=None,author=None,category=None,content=None,**kw):
        if category:    #category is selected
            cate = Category.get(int(category))
        else:   #category is not provided
            if Category.selectBy(name="Default").count()==0:    #if Default category does not exist
                cate = Category(name="Default")     #then create it
            else:   #Default category exists
                cate = Category.selectBy(name="Default").getOne()   #get the Default category
        if bid: #editing an existing post
            blog = Blog.get(int(bid))   #get the post
            blog.set(subject=tostr(subject),author=tostr(author),category=cate,content=tostr(content)) #update
        else:
            blog = Blog(subject=tostr(subject),author=tostr(author),category=cate,content=tostr(content)) #create a new post
        raise redirect('/viewpost',bid=blog.id)

notice that we have a "tostr" function here(defined outside the Root class):

def tostr(s):
    if type(s)==unicode:
        return s.encode('utf8','ignore')
    else:
        return s

This function is designed to handle unicode characters.

I've left a comment behind every line of saveblog, which I think is already very self-explanatory. The only thing I want to point out is that we did not pass any parameters to the expose decorator, because there's no page to show. What this function does is to save the blog and then guide you the page where you can read it. After adding these 2 functions to your controllers.py, and hit the submit button on the compose page, you'll probably get a 404 error. That is because we haven't got the viewpost funcion ready yet. We'll create this page later.

类别:Notes | 评论(1) | 浏览()
 
2008-10-24 15:12

Turbogears is a rapid web development frame work, which I've been using for a while now. Unlike Django, which is pretty buggy, Turbogears offers a more stable environment. And it truly makes web development easier.

In this tutorial I'll show you how to build your own blog with turbogears in 2 hours. First thing first, you have to make sure python and turbogears are all properly installed and configured on your computer. There is a very detailed installation guide here so I'm not gonna explain this in details this time.

The next step is to create the project using turbogears' administration tool - quickstart. It will create the project template for us. Let's just name our project 'blog'. To create the project folder, open your cmd window or terminal window, and type:

         "tg-admin quickstart blog"

You'll be asked to enter the package name, which is 'blog' of course, and whether you need Identity control in this project. Turbogears offers a well-written identity control package, which have saved you a lot of time. But we'll not use it at this time.

The quickstart command generates a bunch of folders and files like this:

To start your website, enter the blog folder, and type "python start-blog.py". By default, the server listens to the 8080 port of the localhost. Open a browser and navigate to http://localhost:8080/ and you should be able to see the default welcome page of turbogears telling you that "your application is now running":

Next, we'll probably have to determine the data structure of our blog. By default, Turbogears uses SQLObject to handle the database objects, which saves you from writing complicated sql commands. And the database models are defined in model.py.

First, every blog is belonged to a category, for example, 'Funny','IT', etc. So we'll add a table called Category first:

class Category(SQLObject):
    name = StringCol(length=50)
    blogs = MultipleJoin('Blog')

MutipleJoin indicates that there could be a number of blogs belonging to one category.

then Blog:

class Blog(SQLObject):
    author = StringCol(length=50)
    subject = StringCol(length=200)
    content = BLOBCol(sqlType='blob', default=None)
    category = ForeignKey('Category')
    views = IntCol(default=0)
    comments = MultipleJoin('Comment')
    time = DateTimeCol(default=DateTimeCol.now)

ForeignKey('Category') indicates that this blog belongs to a category.

Comments:

class Comment(SQLObject):
    blog = ForeignKey('Blog')
    author = StringCol(length=50)
    email = StringCol(length=100)
    content = StringCol(length=1000)
    time = DateTimeCol(default=DateTimeCol.now)

Please refer to the SQLObject doc for grammar details. I think the grammar is pretty self-explonatory.

After we've created these 3 database object, the next step is to actually create it in our local database. Take mysql for example. First, find dev.cfg in our project directory and open it. Comment 'sqlobject.dburi="sqlite://%(current_dir_uri)s/devdata.sqlite"' by adding a '# '(a blank space is needed after the #), and then uncomment line 11 (or the line with 'mysql:' in), modify it to your mysql setting, e.g.:

sqlobject.dburi="mysql://root:@127.0.0.1:3306/blog"

this means that we are using the mysql server at localhost, port 3306, with the user root and no password, and the database named 'blog'.

Then add the following lines to the very beginning of model.py:

from sqlobject import *
from turbogears import config

con = "mysql://root:@127.0.0.1:3306/blog"
sqlhub.processConnection = connectionForURI(con)

Now we are ready to create the tables in our database. Stop the server(by hitting Ctrl+C in the previous cmd window where we started the web server) and type:

tg-admin sql create

and the tables are created.

Notice that MultipleJoin does not actually create a column in the table, while ForeignKey creates the blog_id and category_id columns.

By far we have successfully set up the evironment and are ready to rock. If the evironment is already set up for use, creating the project folder / configure the config file / creating tables in the database wouldn't take us more than 5 minutes. In the next post I'll show you how to create pages for our blog.

(I'm not a native English speaker so forgive me if there's any grammar mistakes.)

类别:Notes | 评论(1) | 浏览()
 
2008-03-23 05:12

每天写十几个小时的网站,我真是一具勤奋啊。在我的网站里有4个有背景图片的<a>标签,用来当4个按钮,显示成块状,css比较容易控制,不像button需要override很多样式。比如:

<a class="download" href="#" onclick="toggle(0)"><span>Download now</span></a>

点击的效果就是执行toggle(0)。这个函数将让一个元素A显示一段动画效果,时间是1.5秒。现在的问题是,我如果在这1.5秒之内又点击了它,或者触发了任何也让A显示动画的函数,两个动画就会冲突,这个让它这么动,另一个让它那么动,就乱掉了。所以在这1.5秒里我要把这4个按钮禁用,等动画播放完毕了才可以继续点击,否则不响应。就这么个玩意儿折腾死我了。

首先想到的是移除onclick属性。但是再往上加的时候就有问题。比如我用一个array来存这4个按钮,分别为menus[0]-menu[3]。然后循环一遍,让每个的onclick=toggle(i),这样出来的结果就是大家都变成toggle(3)。所以这样不可以。然后想到的是设置其为disabled,比如menus[0].disabled = true。这样有另外一个问题,就是浏览器不兼容。IE中这样写是可以的,但是firefox/safari不支持把<a><div><span>等东西设成disabled,你设了也不报错,就是没用,该能点的还能点。后来我苦苦的搜啊搜啊,发现有人说firefox里这个东西的写法应该是menus[0].disabled = 'disabled'。但是我试验了一下,发现还是不行。我不知道他说的这样写是不是可以禁用掉navigate到href所指向的链接内容,我也没精力去试验……

搜不到的话,就只能再想别的方法。这时候想到可以不用循环来添加onclick属性,而是把这个属性存放在另外一个地方,再往上添的时候直接拿过来,不涉及什么参数。这样问题就解决了,虽然很龌龊,但是我确实没想出什么其他的办法,希望有经验的人有更好的办法。代码大致如下:

function setDisabled(el, disabled){
if(disabled){
     el.setAttribute('_onclick', el.getAttribute("onclick"));
    el.removeAttribute('onclick');
}
else{
    el.setAttribute('onclick', el.getAttribute("_onclick"));
}
}

意思就是把onclick存到_onclick里面,然后删掉,就禁用了onclick;要恢复的时候再从_onclick里面拿出来扔到onclick里面去就可以了。当然这个函数有一些可以改进的地方,我没有判断是否有这个attribute,因为我这里不适用,都有,为了减小代码,就不写了。还有默认逻辑是先disable后enable的,判断也省了,倒过来大概是会错的。不过这个方法在IE里行不通。所以要禁用menus[i],就要写:

menus[i].disabled = true;        setDisabled(menus[i], true);

这么两句话。

框架差不多搭完了,剩下就是再搞花里胡哨一点儿,颜色字体讲究讲究,就差不多了。还扩展了一下mootools的accordion,加了一个displayAll的函数,因为它这个accordion只能同时展开一个,有时候比较不爽。有时间的话再给它多加几个选项,让它更灵活一些。

以下供英文用户检索……

-------

You may find it useless to set the 'disabled' attribute of an <a> <div> or <span> tagged element to "true" in firefox/safari, which means you can still click on it and it'll do whatever it's gonna do before that. And here's a solution to fix this problem. The main idea is to remove the functioning attribute, like onclick or href, store it somewhere else; and later when you wanna enable the element again, just get it back from where you stored it before. The code is shown below, which is pretty self explanatory so there's no comment. However some if statement may be needed to avoid null exceptions according to your actual need.

function setDisabled(el, disabled){
if(disabled){
     el.setAttribute('_onclick', el.getAttribute("onclick"));
    el.removeAttribute('onclick');
}
else{
    el.setAttribute('onclick', el.getAttribute("_onclick"));
}
}

I've tested it in firefox/safari and it works fine. It doesn't work in IE so you'll have to write another line for IE, fortunately it's much simpler, just set element.disabled = true and there you go.

类别:Notes | 评论(14) | 浏览()
 
2008-03-15 15:31

我记得以前看过一个zoom导航,是一堆正方形,假设说导航结构是一颗树,每个节点都是一个正方形,父节点的正方形边长是子节点的2倍,也就是说一个父节点最多有4个子节点。然后可以通过鼠标点击来聚焦到任意一个子节点,从而实现一个树状的导航。不过我最初玩儿那个导航的时候,觉得有些迷惑,需要适应那个作者的思维方式。后来我想到他这个有些像是一个门。门开了,走进去,里面还可以有其他的门。但是我只能做推拉门,因为不知道怎么画斜线。似乎也不能画(如果能画那个falling blade effect就可以实现啦),只能拿小直线慢慢拼,那样划不来,所以做成推拉门。“滑动门”,就是所谓Sliding Door,特指一种CSS的小伎俩,所以我这里叫推拉门,免得和它撞车引起混淆。不过英文就不知道应该改成什么了。这里有一个演示。目前很简陋,代码很沙茶(直接拿之前的那个改的……),hard code很多,对css的依赖很大,而且只有一层,没有门的嵌套,所以可以说是没法儿用的,不过可以看看样子,我觉得这个导航还是可以实现的,而且将会比较有趣。下一步则想把这个统统推翻了,写一个可以多层嵌套的,参数化的框架,这样如果有人赏脸拿来用的话可以自己定制门框、门、把手等等的大小和样式之类的,而不会影响动画的工作。所以暂时不提供源码的下载了(虽然你看一眼源码就能把所有东西都找齐了),等我做好再说吧。

最近来了+要来不少朋友,有朋自远方来,还是很高兴的。不过想到下周一估计又要去趟星光大道,不禁不寒而栗(鄙视google拼音,竟然连不寒而栗都没有)……再去我就真能把顺序背下来了。

类别:Notes | 评论(5) | 浏览()
 
2008-03-12 19:12

mootools实现的,很弱智,代码也很难看,初学javascript,拿来练手的。效果请参见这里。源代码在这里。随便改,随便用,唯一一条term of use就是不许笑话我。另外我这里带的那个mootools的包是全的,里面有很多函数什么的根本没用到,而且没有压缩,你可以去官方网站上下载你需要的部分,这样会小很多。我好像只用了类似Fx.Styles, Fx.Transitions这两个包,你去下载的时候点这两个,会自动把其他依赖的包都下载下来。

The multi-tab page with animations

Implemented in mootools. Just an exercise in learning javascript. It's free to use, however you may want to take a look at mootools license and term of use if you wanna modify and use this work. A demo is available here, and source code here, which is very ugly because I'm just a beginner. I included a full version of mootools lib, including all the comments, and is uncompressed, so you may wanna download another copy for yourself here, click on the checkboxes belonging to "Fx.Styles" and "Fx.Transitions" and choose your desired compression type and press "download“.

类别:Notes | 评论(10) | 浏览()
 
2008-02-17 14:04

好,再写一篇……我竟然也能写技术类文章,真是少见啊哈哈哈。

前两天在思考这个功能的实现,我觉得网上应该有现成的控件,就搜什么asp.net repeater extender / paging control之类的,确实找到几个,codeproject上有一个貌似还挺好用,但是看了看EULA好像不能用作商业用途。我还真是很认真的思考了一下这个问题,然后决定放弃了,真不像盗版王国子民的风格。后来想自己做一个extender,就去看extender怎么做。我想做一个可以规定每页多少个Item,然后下面自动给出链接导航,可以自己规定css的,就行了。或者不给导航链接,只提供一个pageIndex和相应的事件。看了半天发现有做这个extender的工夫我手工分页都分好了……虽然说写个extender可能更有意义一些,不过我还是懒了。后来搜到一个东西叫PagedDataSource,才发现原来分页其实很简单,就是让repeater把DataSource Bind到这个PagedDataSource就可以了。

我采用的是access 数据库,就拿这个举例,其他都类似。

首先要知道当前是第几页:

protected int CurrentPage
        {
            get
            {
                // look for current page in ViewState
                object o = this.ViewState["_CurrentPage"];
                if (o == null)
                    return 0; // default to showing the first page
                else
                    return (int)o;
            }

            set
            {
                this.ViewState["_CurrentPage"] = value;
            }
        }

然后根据这个来显示相应的内容:

private void GetUserComments()
        {
            // 从OleDb(access文件)中读取记录到DataSet中

            OleDbConnection aConnection = new OleDbConnection(@"Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" + Server.MapPath("~\\App_Data\\db.mdb"));

            OleDbCommand selectCMD = new OleDbCommand("SELECT contact_name, contact_email, contact_hp, contact_msg, contact_date FROM contact_cmt ORDER BY ID DESC", aConnection);

             OleDbDataAdapter cmtDA = new OleDbDataAdapter();
            cmtDA.SelectCommand = selectCMD;

            DataSet Items = new DataSet();
            cmtDA.Fill(Items, "contact_cmt");

            // 创建PagedDataSource
            PagedDataSource cmtPds = new PagedDataSource();
            cmtPds.DataSource = Items.Tables[0].DefaultView;

            cmtPds.AllowPaging = true;
            cmtPds.PageSize = 15;                                  // 一页多少个Item
            cmtPds.CurrentPageIndex = CurrentPage;
            

            // 首页或者尾页的时候Disable相应的导航按钮
            btm_prev.Enabled = !cmtPds.IsFirstPage;
            btm_next.Enabled = !cmtPds.IsLastPage;

            repeaterItems.DataSource = cmtPds;
            repeaterItems.DataBind();
        }

        protected void btmPrev_Click(object sender, System.EventArgs e)
        {
            // 前一页
            CurrentPage -= 1;

            // Reload
            GetUserComments();
           
        }

        protected void btmNext_Click(object sender, System.EventArgs e)
        {
            // 后一页
            CurrentPage += 1;

            // Reload
            GetUserComments();
        }

Page_Load的时候也要调用GetUserComments();

aspx中该怎么写怎么写,跟不分页没有任何区别。如果把Repeater放在Ajax的UpdatePanel中,就可以不用刷新页面了。不过昨天我写完了以后,发现点前一页和后一页都没任何反应……但是如果单独拿出来,就没问题。所以肯定是跟什么东西犯冲了。我只好把它单独拎出来,然后一点一点的把其他东西加上去看看有什么反应……加到最后发现是和我的一个WebUserControl犯冲,是一个留言框,几个textbox组成,加上几个Validater,用来检查用户留言的时候是否输入了Name,Email,留言,Email是不是正确的格式,如果输入Homepage是不是一个有效的url。原因就是那两个导航button会导致页面验证,也就是CauseValidation默认为true。上面验证通不过,下面就不能翻页;上面填好了,下面就可以了。把两个button的CauseValidation改成false,问题解决。

不过以后如果有空,我还是想写个extender出来,用的时候往里一拖就行了,省得自己写后台了。topcoder似乎有一个,不过不让我下载,似乎是收费的。这玩意有没有现成免费的?

类别:Notes | 评论(9) | 浏览()
 
2007-05-07 21:16

这本来并不是什么很复杂的事情,但是我比较笨,而且上述4个关键字(linux、C、fortran、动态链接库)我没有一个熟的,所以吭吭哧哧搞了很久才搞定,回头一看却发现弱智无比。不过鉴于我搜了很久很久,我还是发出来,以后再有人找就方便了。

我对fortran一无所知,只知道其优势在于科学计算还比较方便,再有就是某领域的早期程序大多由fortran编写。出于重用方便的考虑,我们现在要把fortran写的代码编译成动态链接库,然后通过C来调用。Windows下动态链接库是很常见的东西,linux下也有,换了一个名字,叫standard object,大多形如lib*.so。SO文件可以通过编译器的-shared选项得到。

比如,我们有一个fortran程序名为subf1.f,如下:

subroutine sUbF1()
print*,'hello world.'
return
end

如果有一个C程序希望调用sUbF1(),就可以将 subf1.f 编译成为动态链接库。

gcc -shared -o libf1.so subf1.f

这个命令将产生libf1.so这个文件,此文件即是一个linux下的动态链接库。gcc会根据文件的扩展名来调用相应的编译器,不用你操心。此例中事实上实际的编译器是f77,我机器上没有f90。

在main.c里面调用sUbF1(),如下:


int main(){
       subf1_();
       return 0;
}

注意到我们调用的时候,名字变成了“ subf1_ ”。这是编译器(f77)的一个命名规则,没有为什么,它就是把你在fortran中的函数名字全转换成小写,然后在最后加一个下划线。我昨天搜了很多版本,头昏脑胀,怎么调都说找不到,也没有想到要自己看看。今天一早突然想到用hex编辑器看一下就是了,于是一看,里面果然有真正的函数名。后来看program版kb也给了正确的解答,很钦佩;伟大的康神还教导我抛弃hex编辑器,用nm,热泪盈眶……

找到正确的函数名,直接调用就可以,好像你已经在你的C文件里实现了这个函数一样,不需要include任何东西,只需要在编译时告诉编译器你用了哪个动态链接库就可以了,如下:

gcc -o out main.c libf1.so

这时候编译器有可能会报告如下错误:

libf1.so: undefined reference to 'do_lio'
libf1.so: undefined reference to 'e_wsle'
libf1.so: undefined reference to 's_wsle'

这时在后面加上-lg2c -B108的选项应该就可以了,也就是

gcc -o out main.c libf1.so -lg2c -B108

这时会得到out的可执行文件。运行out,屏幕会输出hello world。关于这两个选项,我也着实搜了一阵,不是很好搜。当时看了眼原因,可能是有关编译器版本和字符方面的,没有仔细看,成功了以后大喜,就懒得看了。刚才再搜,搜了半天也没搜到-_-

明天又要上班了,真不开心……假期过得太充实的一个不好就是假期会显得很短,没几天就过去了。要是可以天天吃海鲜看大海看迷笛多好哇……

类别:Notes | 评论(17) | 浏览()
 
2007-04-17 17:01

我上班的时候一般都戴着耳机,可能边听音乐边工作惯了,另外大家都在大厅,有时候相当吵。所以我最经常做的事情就是调音量。而键盘不是多媒体的,总要点右下角的小喇叭,然后再调,很烦人。常常伸手去找音量键,因为在家用笔记本用惯了。由此想到让键盘做这个事情。

我记得我当时专业时间是跟“强”一起做一个键盘和鼠标的project,我当时负责的大致就是驱动和程序演示部分,跟硬件基本不相关……强好像是3班的一个很实诚的同学,这是我背地里叫他的名字;因为两人分工,各写各的,而我无论实现了多么简单的一个功能,他看完都会说,我考,qiiaaaannng。搞得我啼笑皆非……所以我想,既然多媒体键盘可以调整音量,那么普通键盘做一个什么转制应该也是可以的。

在WindowsXP里,键盘每敲击一下,会传来一个扫描码(Scan Code),事实上是一串扫描码,抬起来的时候也会有对应的扫描码,为了简化思维,我就把它当成一个固定的码。这个码是一个16进制的数字,不同的按键对应不同的号码。去微软的网站搜scan code,就会发现,Windows提供了一个叫Scan Code Mapper的东西,从名字来看,这就是我要找的东西了……

概况的说,如果在注册表的HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Keyboard Layout路径下新增加一个名为"Scancode Map"的REG_BINARY类型的新变量,安装一定规则赋值,就可以实现把键来回map的功能。Scancode Map的格式如下:

Start offset (in bytes) Size (in bytes) Data

0

4

Header: Version Information

4

4

Header: Flags

8

4

Header: Number of Mappings

12

4

Individual Mappings

Last 4 bytes

4

Null Terminator (0x00000000)

前两个DWORDS存储的是版本信息和标志等,都为0。第三个DWORD是后门mapping的个数,包括最后的终止符也算作一个mapping,所以最小值应该为1。后面每个DWORD都由两个scancode组成,比如XY,表示XY互换。如果X0,则表示取消X。下面是个例子。

00000000 00000000 03000000 3A001D00 1D003A00 00000000

It is important to remember that entries stored in the registry are always in little Endian format. The following table contains these entries broken into DWORD fields and the bytes swapped.

Value Interpretation

0x00000000

Header: Version. Set to all zeroes.

0x00000000

Header: Flags. Set to all zeroes.

0x00000003

Three entries in the map (including null entry).

0x001D003A

Left CTRL key --> CAPS LOCK (0x1D --> 0x3A).

0x003A001D

CAPS LOCK --> Left CTRL key (0x3A --> 0x1D).

0x00000000

Null terminator.

我起初没有看到中间的那句话,于是怎么也不明白这个001D003A是怎么断句断出来的……后来才看到,搜了一下,这个"little Endian format"直观的来说,就是把一个形为 00 04 的数写成40 00。而这里不但要这样写,而且要把两个bytes互相调个个儿,也就是变成04 00。所以3A001D00其实就是001D003A —— 表示将左CTRL与CAPSLOCK对换。

所以我们找到音量控制的Scan Code:

volume up -> 0xE030
volume down -> 0xE02E
mute -> 0xE020

我把它们map到F10, F11, F12上,三个我貌似从来都不用的键(后面会证明我错了),编辑键值如下:

重启机器就可以了。
今天正爽时,有人来我机器旁边跟我商量事情。按照习惯,我按Ctrl + F11,这是我隐藏live messenger的快捷键,结果……失灵了……
类别:Notes | 评论(17) | 浏览()
 
2007-01-15 15:28

公司的服务器常常会跑很巨大的作业,有的能跑十几个小时,而周末是跑作业的高峰,因为服务器相对空闲,比较适合进行一些比较激烈的操作。有时候两个作业有前驱后继的关系,维护人员就需要周末跑到公司来,看看运行的结果,然后根据结果再决定是否调用后一个作业。我觉得如果希望单纯看一看其结果就大老远跑一趟,这个实在太发指,需要有个解决的办法。而且有时候等了半天,作业早出错了都不知道,估摸好了时间过去一看,得,错了,又得查错,重来。这样很多效率就被浪费掉了。

所以就想到编一个email通知的程序,挂在系统上,作为一个optional的模块,如果希望被通知,打个钩,填好email地址就可以了。这样一出结果就可以及时知道;如果是周末,就发到自己的外网邮箱就好。另一个想实现的东西是利用email的远程控制。因为公司内的所有机器统一属于一个内网,外面连不进来。不过这个我没有做过,不知道如何做。周末在井里问,有人说,你申请个vpn不就行了嘛。我想,确实没错,只不过我不知道哪里有一个印象,好像出于安全考虑,这些远程控制的手段都是不可能被批准的。今天过来一问,果然是不行。那偷偷摸摸的穿透NAT最容易的方法恐怕就是email了。

不过后者的可行性和风险,我需要好好的调研一下,一方面是说这种技术的风险,比如会不会被大坏蛋利用起来做坏事;另一个则是有关政治——不能因为省了点儿事情把饭碗丢了……所以上午编了个email通知的小程序。这本来是很弱智的程序,但是我今天又一次验证了我总会更弱智一些这个真理。以后可能有别人或者别的地方还会用到,把核心代码摘抄在下面:

Using System.Net;
Using System.Net.Mail;

public void send_email(){

            MailMessage mymail = new MailMessage();   // create a mail.
            mymail.From = new MailAddress("from@abc.com");
            mymail.To.Add(new MailAddress("to@asdf.cn"));  // re-call to add more
            mymail.Subject = "Email Test";
            mymail.Body = "Test OK";
            SmtpClient smtpServer = new SmtpClient();  // specify a smth server
            smtpServer.Host = "10.1.1.1";
            smtpServer.Port = 25;
            mymail.Attachments.Add(new Attachment("e:\\test.txt")));  // can add multiple atts
            CredentialCache myCache = new CredentialCache();
            NetworkCredential netCre = new NetworkCredential();
            netCre.UserName = "wst";      // username for smtp server login
            netCre.Password = "asdf";     // password
            smtpServer.Credentials = netCre;
            smtpServer.Send(mymail);   //send

}

我调试的时候总是会出问题。因为我把thunderbird里设置的pop3服务器的地址看成smtp服务器的地址了……pop3的端口是110,smtp的端口是25。我还琢磨呢,贵单位整得还挺时髦儿,还不用默认端口……结果调来调去就是过不去,最后又看了一眼,哦,就是25。

贵系人请勿留言,谢谢。。

类别:Notes | 评论(18) | 浏览()
 
2006-12-05 16:25

1.  www.pcauto.com.cn 太平洋汽车网:车库里大部分车都有;信息量很大,不过有很多无用信息,比如常常有的一堆一堆车模照片,这个特别烦人,我们买车呢,又不是买女人,她长得再好看,随车附送吗?评测栏目还不错,基本主流车型都有涉及;车型有自己的频道;可以分城市查看信息;总之信息比较全面。另外一个就是傻逼太多,基本进每辆日本车的频道,下面都有沙比愤青在嗷嗷叫骂。我倒不是说喜欢日本人或者怎么样,但是也不看看这车是哪里生产哪里纳税的就瞎叫唤一气,有智商没有?觉得特别无厘头。

2.  www.autohome.com.cn :提供了很不错的对比功能,可以清楚的看到车参数、配置方面的差异。也有测评,新闻等,比pcauto要简洁、速度快。最棒的是有汽车社区,按车型分,里面有真正的车主在说话,看看很有好处。我现在已经基本抛弃pcauto转战这里了。

3.  www.cheshi.com.cn :最权威、最全的网上报价。

4.  www.che168.com:可以看车内全景,不错的功能。

5.  auto.163.com:有时候会有一些不错的文章,不过大部分时候没有什么信息量;信息分类简单、混乱,只是偶尔来看看。

6. cn.autoblog.com:流口水用的。

类别:Notes | 评论(5) | 浏览()
 
2006-11-23 10:10

我很懒,我总是希望有个特别方便的方法,比如一个银行账户,或者支付宝之类的东西,让我上网的时候随便点两下按钮,就可以给需要上学的贫困孩子捐点儿钱。但是我从很早以前就开始搜,搜了很久,也没有搜到,或者是搜到了觉得无法信任。我心理还很阴暗,总是“不惮以最坏的恶意”去揣测事情:我捐出去的钱,是不是被人买五粮液啦?这样我总觉得捐了的钱就好像大片儿里FBI提供的黑枪,是“totally untraceable”的,可以随便去用。于是我总是说捐钱捐钱,却总也捐不了钱。当然,上学的时候,我也没什么钱可捐。

所以最近我开始思考这个问题,觉得不能两全——因为我虽然愿意捐钱,但是我不希望有个孩子总是写信过来说他/她很感谢我的捐助;我虽然希望去trace一下我的钱到底干啥了,但是也只希望单方面的trace,不希望有什么来自受助人的feedback,而只希望看到说您某日捐助的¥xxx已经送交某某县某某村某某手中。这样其实骗子依然可以愚弄我,他随便杜撰一个赵县赶驴村王翠花,我总没有办法去找到这个人,甚至我也没有去找这个人确认一下的愿望。所以我的解决方案一下变得比较明朗:找一个捐款方式异常简单、看起来又比较容易信任的机构,以最好的善意去揣测他,然后把钱捐出去,放弃trace一下的权力。这样就方便多了,既满足了我做善事的愿望,又方便,也不烦心。所以说操心太多,基本不好,自己活得不痛快,还丧失了做很多事情的热情。我妈就是操心太多,导致我从小到大,脑子里闪过很多疯狂的念头,都没有做成,于是现在就老气横秋了。

我选择的是发短信捐款,一条5块,做得并不方便,需要发若干次才能到一个稍微像样点儿的金额,比较麻烦。描述如下:

“通过手机短信捐款:
中国移动通信134-139手机用户,写信息“ZX”,发送到\"8858\",即捐款5元人民币.每月捐款上限为60元人民币。 发送“CX”到“8858”,即可查询通过您的手机向“8858”手机公益短信捐款的累积金额。

每次捐款,中国移动通信另收全球通用户0.1元,神州行用户0.15元的短信通讯费。

您爱的捐助,将让这些贫困学生,拥有更多的希望,在人生路上更加坚强!

感谢您并祝福善良的您!”

还有另外一个更详细的描述是这样的:

“中国移动通信先后开通了8858(中国儿童基金会手机公益短信捐款)、8899(中国残疾人联合会手机短信捐款)、5838(全国妇联捐助贫困母亲手机短信捐款)、8595(中华健康快车基金会扶贫治盲手机短信捐款)等特服号,并在公交、春节晚会等公共场所推广,起到了很好的社会反响。
————————————————————————————————————
以下资料来源于:中国儿童少年基金会
[url]http://www.cctf.org.cn/HelpNote/denote_1.htm[/url]

    中国第一条手机公益短信:8858
  中国移动通讯135-139手机用户,写信息"1"至"30"中任意数字,发送到"8858",即捐款1-30元人民币;也可"包月捐款",写信息"01"或"030"中任意数字,发送到"8858",既每月捐款1-30元人民币。每月捐款上限为60元人民币。
  每次捐款,中国移动通行另收全球通用户0.1元,神州行用户0.15元的短信通讯费。

    12361固定电话捐款热线
  12361 全网募捐 IVR 呼叫专线在全国范围使用同一个 12361 号码,通过国内基础电信运营商的有线、无线公众电信网络接入各运营商的信息化语音信息平台,不受地区限制,不分移动和固定电话,用户可通过 12361 的 IVR 语音提示,实现电话捐款,是真正的全国、全网慈善募捐专线。
  “12361”目前主要面对对象是:全国所有固话、手机用户
  “12361”专线捐款方法:拨打 12361,通过 IVR 系统语音提示,选择电话捐款、在线查询、慈善了解、人工服务等。用户可根据语音提示,按 1 表示捐一元、按 100 表示捐 100 元。费用直接从用户话费中扣除。用户也可按“ 01 ”至“ 0XXX ”实现“包月捐款”,每月捐款 1-XXX 元人民币。用户每次捐款的额度最少为 1 元,不设上限。”

也就是说8858这个号码,你可以发金额过去,但是具体做什么就不一定。如果发“ZX”,则会用做助学。我最关心的还是教育问题,所以我需要痛苦不堪的连续发多条ZX过去。当然这一切的一切都是在这个服务可以信任的基础上。发过去以后,你会收到一个回执,捐款就算是捐完了。

5块钱很少的,没事就发一个好了,各位款爷、款姐、白领、大拿们。

类别:Notes | 评论(28) | 浏览()
 
2006-11-21 10:37

虽然时间还早,但是我看车的热情依然非常高涨,并且假模假式的锁定了两款车——雪铁龙凯旋旗舰以及马自达轿跑。前者除了从某些角度比较难看,发动机一般(4档手自一体),没有天窗(不利于在车内吸烟,呵呵)以外,有一个问题就是,上市这么久了,价钱这么合理,好评也不少,为什么没人买?这我实在理解不了。相比之下价钱差不多,有人夸有人骂而且骂得还相当狠的马六就满大街跑(好在轿跑还没有)。

这里是autohome的一个配置的对比。从一个外行的角度来对比,凯旋吸引我的东西有:外形(从某些角度看还不错,黑色、白色比不锈钢色看起来更有气质,而且显得年轻),安全配置齐全,内饰、中控台、方向盘好看(尤其方向盘看起来很厚实,很酷),保有量不大(人真是奇怪的动物……),价钱公道(旗舰版19.38w),非常省油;困扰我的首先是一个A级车硬加了个屁股,导致车看起来十分瘦长,不是很好看(尤其从斜后45度);没有天窗;发动机一般;定价满公道的,降价还相当狠,使我惴惴;内液晶屏是橘黄底色,使我想到很多低档手机。

而马六轿跑吸引我的东西有:外形年轻(虽然由于满大街都是马六,导致我已经非常讨厌马六的外形了,不过轿跑还是比马六好看不少);操控据说是本价位前驱车里最好的,堪称人车合一,过弯并线都很精准;号称非常非常省油;动力没的说,6档手动或者5档手自一体,变速箱先进,充满驾驶乐趣;安全配置齐全;有天窗;最最重要的:7喇叭bose音响。这个配置太猛了,我从来都想不到20万的车可以配这样的一个音响。马六轿跑我不是很满意的地方,一个是价钱,如果自动档有凯旋旗舰的价钱,那就不用比较了,肯定选马六。另一个是我觉得马六的标志实在是丑,傻不啦叽的,让我很为难。这样一数,我还真挑不出什么特别糟糕的毛病……其实说白了还是太贵,手动档20~21万,自动档22~23万,我只能承受手动档的价钱,却又想开自动档。。因为自动档比手动档不光发动机有别,还多了氙气大灯等3项挺实用的配置。我倒是觉得手动档开起来估计更有乐趣,等我老了再换自动档好了,不过氙气大灯还是很有诱惑力的……要是不少这个配置,那就肯定手动了。

所以如果这周末没有事情,我初步准备去看个车……

类别:Notes | 评论(15) | 浏览()
 
2006-10-23 16:03

hourglass(TC)推荐的视频站点,我觉得还不错,各位可以试试。中心思想就是会自动帮你下载视频。下面是简略的使用方法。

TC 说 (15:43):
double click to start
TC 说 (15:43):
and the program will download videos automatically for you
TC 说 (15:43):
you should customize the channels first
Stone 说 (15:44):
i can't see how
TC 说 (15:44):
because if you turn on the filters, some videos that contain explicit images or languages may be filtered
TC 说 (15:44):
......
TC 说 (15:44):
are u sure u r using metacafe?
Stone 说 (15:45):
you mean i should receive all kinds of videos
Stone 说 (15:45):
i found it
TC 说 (15:46):
if you do not want to see erotic or violent videos, you can turn on the filters.
Stone 说 (15:47):
then how can you send me your clips
TC 说 (15:47):
i can give you the clip name so you can download yourself.
TC 说 (15:47):
really fast
Stone 说 (15:48):
oh, through emails right?
TC 说 (15:49):
i give you the name, u download through the metacafe website. aren't i clear?
Stone   @office 说 (15:50):
i saw a button "send to friend"
Stone   @office 说 (15:50):
it means you can also do this through email you stupid fuck
TC 说 (15:51):
why should i use email while i could talk to you on msn you stupid fucker
Stone   @office 说 (15:51):
because there's a button which means it's designed to use emails you stupid fucking fuck
TC 说 (15:53):
that's for someone who doesn't use other more convenient communications you stupid fucking fuckers fucker
Stone   @office 说 (15:54):
how the fuck can you tell for sure that msn is more convenient than emails you dickhead?
Stone   @office 说 (15:54):
what if you cannot log on to msn or when i'm offline
TC 说 (15:55):
then i can still send messages
Stone   @office 说 (15:55):
i can tell you why there's a button. it contains the addr automatically so you don't have to copy and i don't have to search
TC 说 (15:56):
that make a little sense
Stone   @office 说 (15:58):
you're really a dickhead.

类别:Notes | 评论(4) | 浏览()
 
2006-10-22 14:49

http://tvunderground.org.ru/index.php

这里的东西比较新,比较全。这句话的论据不是很充分,因为我只看到《south park》更新得很快,甚至比south park stuff还要快,avi版最新的两集的已经有了,所以估计其他的应该更没问题了。。推荐一下。无需注册,比某些恶心的网站好很多。俄国人牛比,这样的站好多都是.ru的。

类别:Notes | 评论(1) | 浏览()
 
     
 
 
文章分类
 
 
日记(70)
 
随笔(58)
 
音乐(25)
 
Notes(18)
 
扯淡(70)
 
 
 
     
 
文章存档
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
     
 
最新文章评论
   

我就特崇拜考试作文写的好的!
 

太逗了
 

回复metaphox:同感
 

Bull!
 

贴点照片巴。实在人!
 
     


©2009 Baidu