存档: 2010 April
今天开始看unp volume1(Unix Network Programming)了,发现W.Richard Stevens真的很有风格,不管是apue还是unp,书里的程序第一行必定是书名,比如说#include “apue.h”和#include “unp.h”,用头文件将打印函数,包裹函数封装起来无可厚非,但对我这种初学者来说,底层的东西变成了透明,写起程序来虽然会很方便,对程序的把握却又低了一个层次..
nup volume1第一章很简单,介绍了2个很容易的程序,一个服务端做时间伺服器,另一个客户端查询时间,数据的传输是从服务端到客户端的,为了练手,我照着程序写了个客户端传输数据给服务端的hello world,当然把#include “unp.h”去掉了..源码和编译好的程序在这..
记下各函数原型做个备忘.
socket
#include <sys/socket.h> int socket(int family, int type, int protocol);
bind
#include <sys/socket.h> int bind(int socket_fd, const struct sockaddr *myaddress, socklen_t address_length);
listen
#include <sys/socket.h> int listen(int socket_fd, int backlog);
connect
#include <sys/socket.h> int connect(int socket_fd, const struct sockaddr *server_address, socklen_t address_length);
accept
#include <sys/socket.h> int accept(int socket_fd, struct sockaddr *client_address, socklen_t address_length);
recv
#include <sys/socket.h> ssize_t recv(int socket_fd, void *buffer, size_t buffer_size, int flags);
send
#include <sys/socket.h> ssize_t send(int socket_fd, const void *buffer, size_t buffer_size, int flags); 回头看看动态签名有没有效..
What
这几天用Gtk写Mysql的GUI前台,今天终于写得差不多了,在博客里记记,源码在这http://godorz.info/wp-content/uploads/2010/04/mysqlgui.zip, 1000多行几乎都是手打,可以自由复制,但其中的致谢信息不可去掉,因为贪图方便,连基本的头文件和实现都没分开,大汗..编译命令为: gcc -o main main.c -I/usr/include/mysql -L/usr/lib/mysql -lmysqlclient `pkg-config –cflags –libs gtk+-2.0` ,截图如下:

How
程序分为几个部分,各函数功能如下:
- cb_openmenuitem()用来显示子对话框,输入服务器,端口,用户名和密码,点击Connect按钮后调用cb_connectbutton()函数,得到用户输入,初始化myconnection,连接服务器.
- cb_aboutmenuitem()是一些版权信息.其实是用来练手对话框的.
- cb_executebutton()用于得到用户在主面板的输入(mysql script命令),调用make_tree_view( )将执行结果显示在showresultvbox中.
- cb_savebutton()将用户修改过的数据表保存下来.想法可以参考这里http://godorz.info/2010/04/display-mysql-data-with-gtkclist/
- GType getdatatype(MYSQL_FIELD *field)将数据表每一列的数据类型MYSQL_TYPE转化为对应的Gtk中的数据类型G_TYPE.函数在这里没有用上,其实是为了以后修改代码时做准备的.比如说要提供一个时间类型的加减功能,就必须事先知道该列中存储的数据类型为吗MYSQL_TYPE_TIME而不是MYSQL_TYPE_STRING.
- SETLIST()这是一个宏命令,为了使treeview每一列的类型为editable,需要对每一列绑定不同的回调函数,在回调函数中指名列号.在程序中,我认定数据表列数不超过30列,30个基本一样的回调函数写起来很麻烦,因此用宏写了.
- setup_tree_view()根据传入的MYSQL conn新建显示数据表需要用到的treeview,指定列名和每列的数据类型(这里全部指定为G_TYPE_STRING),然后设置每列均为可编辑.
- make_tree_view()有了调用setup_tree_view()得到的treeview后,将数据写在treeview上,然后放在滚动窗口scrolled_win,之后将scrolled_win放在showresultvbox上,显示数据表的内容.
- make_clist()和setup_tree_view() + make_tree_view()的功能类似,显示数据表的内容,只不过这里改成了gtk_clist实现,而不是gtk_tree_view,在程序中这个函数也没有被使用,仅仅是做个比较罢了.
- create_and_fill_model()设置数据库名称和数据表名称的显示类型treestore–树形显示,而不是数据表内容的treeview–列表显示.
- create_view_and_model()将数据库名称和数据表名称以树形形式dbsandtablestreeview放至滚动窗口scrolled_win_dbs_tables,再将滚动窗口显示在showdbsandtablesvbox中.
- on_changed()接受用户选择数据库(表)的事件,将选择的数据库和数据表分别存为selecteddatabase和selectedtable
Problems
遇到的比较恶心的问题有好几个,好在大概都解决了:
- 回调函数,SETLIST()就是很恶心的一个,很认真的查了资料,却没有发现好的解决方法,貌似每列一个回调函数指定该列为可编辑是必须的.还有一个是树形显示中接受用户选择的GtkTreeSelection *selection的回调函数,g_signal_connect可以,但g_signal_connect_swapped就不行了,printf()的方式调式了好久才发现这个问题,悲剧的是到现在还是搞不清两者的区别..
- vbox的刷新,其实这么说是不正确的,因为new出来的一个vbox,是无法再在面板上显示出来的,唯一的方法是将vbox内的widget全部remove掉,然后再gtk_container_add进去,remove时需要注意的是事先需要得到需要remove的widget的引用(gtk_widget_ref)..假如该widget之后不再需要使用了,还需要将引用去掉(gtk_widget_unref).
- 修改后的数据表的保存,当一个单元cell为空时,将gtk_tree_model_get得到的字符串s以printf打印出来是(null),但理所当然地以为s == NULL是个大错,没有值与值为空在界面上看来都是一个空的单元,但它们还是存在区别的,这也是为什么我将对空的单元内容的判断从if( s == NULL)改成了if( s == NULL || strlen(s) < 1 )..不过,虽然知道这一点,我写程序时还是偷懒了,一律将空的单元以”"的形式写回数据表..其实,负责任点的话,是需要在insert时仅仅写明需要插入的列的,至于空的单元,不理就是了.顺便提一下数据表的问题,我没有对字符编码做判断,因此数据出现中文时程序显示乱码,并非判断很难,而是我对用数据库存储中文或多或少存在一些偏见.
To be continued..
前面解决了一些问题,但是,程序没有解决的问题还有很多:
- 为了数据的安全,每次对数据表的操作,不管是选择,显示,修改还是保存,其实在操作之前都是需要重新连接数据库的,而在操作完之后将连接关闭.而且保存是其实还是有一些数据要判断的,因为affected_row很不靠谱.这个真的很简单,但是我没改,因为有太多的bug了.:-)
- 程序主面板中还有一个命令输入框scriptentry和执行按钮executebutton,其实都是测试程序时留下的,本该去掉的,如果不去掉的话,至少应该在cb_executionbutton里加入一些判断语句,禁止用户执行create table/database和drop table.database等语句,因为真的有太多bug了.一旦程序出现问题把数据库搞坏就对不起自己了.也正因为如此,凡是涉及到删除的功能程序中全部没有提供,在数据表中新建一列都被和谐了..
- 然后,虽然clist比起treeview真的太简单了,连单元可编辑都步支持,更别提在单元里放入widget了..但想了几天后,我觉得是可以曲线救国的,用gtk_clist_get_text()得到鼠标点击的行列,然后新建一个输入框,大小等同于clist中一个单元,让它悬浮在鼠标点击单元之上,将其完全覆盖,当用户停止输入后,将得到的结果覆盖单元原值,这么依赖,用户看起来的效果就和单元可编辑没什么两样啦.
- …
Reference
最主要的参考资料有:
- Gtk+ 2.0 tutorial
- Dev help
然后是gtk+的邮件列表(gtk-devel-list@gnome.org),老外真的很热心,有什么问题一问基本上都有人回,但是,为了节省大家的时间,不妨先看完How To: Ask Questions The Smart Way..这里是邮件列表的archive,有什么不懂的在这先搜搜..http://www.mail-archive.com/gtk-app-devel-list@gnome.org/info.html
EOF
想起一个笑话,从前有个太监,下面呢..下面没了..
其实下面还有的,测试一下动态签名图:
之前困惑于GtkClist中数据类型与MySQL数据类型之间转换的问题,在gtk-app-dvel-list邮件列表上发了一个主题提问:
Arthur1989 says:
Hello, I used clist to display data iI fetch from mysql server,but when writing data back into the table of database, I got this question: the data type of clist is always gchar *, while there are quite a lot of other data types in mysql. What can I do to get this question solved? Any tips will be appreciated.
今天发现居然有人把实现的源码都贴到邮件里了..转到博客记下.
Shawn Bakhtlar(***@hotmail.com)replys:
//Ran into the same problem.
//I use a structure like
_IsiField {
int type,
int pos,
int ....
}
//Then create my own list with
_IsiList {
GList Fields;
GList rows;
}
//Every time retrieve a set of values, I have a routing which sets type to a G_TYPE, which corresponds to the MYSQL_TYPE
//Here is what I do:
GList *
isi_database_fetch_fields(IsiDatabase *self)
{
MYSQL_FIELD *field;
IsiFields *l;
GList *gl = NULL;
guint column = 0;
/* Sanity Check */
g_return_val_if_fail(self != NULL, NULL);
g_return_val_if_fail(self->priv != NULL, NULL);
g_return_val_if_fail(self->priv->dispose_has_run != TRUE, NULL);
g_return_val_if_fail(self->priv->res != NULL, NULL);
/* Rewind the feild set */
mysql_field_seek(self->priv->res,0L);
while((field = mysql_fetch_field(self->priv->res)))
{
/* Initialize a new IsiFields structure */
//l = (IsiFields*) g_new0(IsiFields, 1);
l = g_new0(IsiFields, 1);
/* Set the values */
l->alias = g_strdup(field->name);
l->name = g_strdup(field->org_name);
l->length = field->length;
/* always make fields visable */
l->hidden = FALSE;
l->sortable = FALSE;
l->pos = column++;
switch (field->type){
/* Integer types */
case MYSQL_TYPE_TINY:
case MYSQL_TYPE_SHORT:
case MYSQL_TYPE_INT24:
/* Check for signage */
if (field->flags &amp; UNSIGNED_FLAG)
l->type = G_TYPE_UINT;
else
l->type = G_TYPE_INT;
break;
/* Long types */
case MYSQL_TYPE_LONG:
case MYSQL_TYPE_LONGLONG:
/* Check for signage */
if (field->flags &amp; UNSIGNED_FLAG)
l->type = G_TYPE_ULONG;
else
l->type = G_TYPE_LONG;
break;
/* Decimal types */
case MYSQL_TYPE_DECIMAL:
case MYSQL_TYPE_NEWDECIMAL:
case MYSQL_TYPE_FLOAT:
case MYSQL_TYPE_DOUBLE:
l->type = G_TYPE_DOUBLE;
break;
/* Bit types */
case MYSQL_TYPE_BIT:
l->type = G_TYPE_BOOLEAN;
break;
/* ENUM types */
case MYSQL_TYPE_ENUM:
l->type = G_TYPE_ENUM;
break;
/* All other types */
default:
case MYSQL_TYPE_STRING:
case MYSQL_TYPE_VAR_STRING:
case MYSQL_TYPE_BLOB:
case MYSQL_TYPE_SET:
case MYSQL_TYPE_TIMESTAMP:
case MYSQL_TYPE_DATE:
case MYSQL_TYPE_TIME:
case MYSQL_TYPE_DATETIME:
case MYSQL_TYPE_YEAR:
case MYSQL_TYPE_GEOMETRY:
case MYSQL_TYPE_NULL:
if(l->length <= 1){
l->type = G_TYPE_CHAR;
}else{
l->type = G_TYPE_STRING;
}
break;
}
/*DEBUG*/
//g_print("%s %d %d \n", l->alias,l->type,l->length);
/* Save pointer to list */
gl = g_list_append(gl,(gpointer)l);
}
return gl;
}
//now convert the row data to a GList and you have two GLists in your one lists, one with the field header info, the other with the data.
//and create the liststore like this:
GtkTreeModel *
isi_display_liststore_create(IsiDisplay *self, GList *fields)
{
guint num_fields, i;
IsiFields *l;
GtkTreeModel *model;
GType *types;
guint search_col_adj = 0;
/* Sanity Check */
g_return_val_if_fail(self != NULL, NULL);
g_return_val_if_fail(self->priv != NULL, NULL);
g_return_val_if_fail(self->priv->dispose_has_run != TRUE, NULL);
/* Get the number of fields */
if(fields != NULL){
num_fields = g_list_length(fields);
/* Initialize values based on number of columns */
types = (GType*) g_new0( GType, num_fields);
for(i=0;i<num_fields;i++){
l = (IsiFields*)g_list_nth_data(fields,i);
types[i] = l->type;
}
/* create the model store for data input */
model = (GtkTreeModel*) gtk_list_store_newv(num_fields,types);
g_free(types);
for(i=0;i<num_fields;i++){
l = (IsiFields*)g_list_nth_data(fields,i);
/* Setup sorting functions for the modle */
switch(l->type){
case G_TYPE_INT:
l->sortable=TRUE;
gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(model), l->pos, sort_by_int,(gpointer) l->pos, NULL);
break;
case G_TYPE_UINT:
l->sortable=TRUE;
gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(model), l->pos, sort_by_uint,(gpointer) l->pos, NULL);
break;
case G_TYPE_LONG:
l->sortable=TRUE;
gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(model), l->pos, sort_by_long,(gpointer) l->pos, NULL);
break;
case G_TYPE_ULONG:
l->sortable=TRUE;
gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(model), l->pos, sort_by_ulong,(gpointer) l->pos, NULL);
break;
case G_TYPE_DOUBLE:
l->sortable=TRUE;
gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(model), l->pos, sort_by_double,(gpointer) l->pos, NULL);
break;
case G_TYPE_STRING:
l->sortable=TRUE;
gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(model), l->pos, sort_by_string,(gpointer) l->pos, NULL);
break;
}
}
return model;
}
//Hope this helps,
//Shawn
认真的看了一下,发现原来和自己想的差不多,都是事先记录好MySQL数据表中每列的数据类型,往回写时做个判断进行转换..只不过我是想用类似于map
PS: 本文在vim下通过vimpress插件编辑发布,不知道浏览器看起来怎样,呵呵..
