Glade+GTK+ 实现通讯录管理系统图形界面软件开发
开发环境
OS:Ubuntu20.04.3 LTS
CPU:Intel® Core™ i5-10210U CPU @ 1.60GHz × 8
编译器:gcc 7.5.0 GTK+ 3.24.20
开发语言:C语言
IDE:VS
前期准备
开发环境配置
由于采用了C语言开发,故选择了传统GTK用来界面设计,首先安装好Glade(apt-get install glade),GTK+一般linux系统都会自带,选择好不同版本即可(重要!!!GTK+不同版本差异很大,很多函数在新版本都废弃了……),其他相关配置参见 Ubuntu下安装GTK+及Glade开发C应用界面
软件功能要求
本通讯录信息管理系统要求满足以下功能:
- 系统功能:添加联系人信息,通常包括编号、姓名、性别、电话以及地址等数据项;此外可实现通讯者信息的插入、查询、删除、更新等功能。也支持从系统批量上传联系人信息,或将联系人信息导出。
- 程序可对输入数据的容错性进行检查,保证数据的合法性,如性别取值只能为男或女,电话号码利用正则表达式判断合法性等
- 用户界面的友好性:程序可提供与用户之间合理的交互以及相应的菜单供用户选择。
软件开发过程
数据结构设计
由于该软件是小编的一次数据结构课程实践作业,故数据存储结构采用了数组链表,链表数组一般用于在不借用数据库的情况下,对于大量数据的临时存储,来实现快速查找的功能。
typedef struct Node
{
struct Node *next;
char num[MAXSIZE];//身份证号
char name[MAXSIZE];//姓名,只能为英文,如果是中文在排序时会出错,该问题尚未解决
char gender[MAXSIZE];//性别
char phone_number[MAXSIZE];//手机号
char address[MAXSIZE];//地址
}Linklist;
typedef struct
{
Linklist *root;//头结点
Linklist *rear;//尾指针,始终指向链表的最后一个元素
}Index_linklist;//A-Z 26个字母为索引,分别对应一个链表
Index_linklist system_data[26];
链表函数编写
int linklist_length(Linklist* list)//计算链表元素个数
{
Linklist *p=list;
int length=0;
for (;list->next!=NULL;)
{
length+=1;
}
return length;
}
void insert_linklist(Linklist *list,int index,Linklist *new_node)//在链表第index(从0计数)个元素后插入新节点
{
Linklist *p=list;
for (int i=0;i<index;i++)
{
p=p->next;
}
new_node->next=p->next;
p->next=new_node;
}
void delete_linklist(Linklist *list,int index)//删除链表中第index个元素(1-length)
{
Linklist *p=list;//指向链表头结点
for (int i=1;i<index;i++)
{
p=p->next;//指向第index-1个元素
}
Linklist *q=p->next;
p->next=p->next->next;
free(q);
}
数据处理函数
#include "system_functions.h"
/*
**从文件读取数据初始化链表
*/
int data_insert_from_file(Index_linklist *system,FILE *ip)//从文件读取数据
{
const char whitespace[] = " ";//分隔符
const char linebreak[]="\n";//换行符
char buffer[200];//缓冲区
int count=0;//记录读取了多少条数据
while (fgets(buffer,N,ip))//按行读取
{
Linklist *new_node=(Linklist *)malloc(sizeof(Linklist));
int length=strlen(buffer);
/*进行字符串分割*/
strcpy(new_node->num,strtok(buffer,whitespace));
strcpy(new_node->name,strtok(NULL,whitespace));
strcpy(new_node->gender,strtok(NULL,whitespace));
strcpy(new_node->phone_number,strtok(NULL,whitespace));
strcpy(new_node->address,strtok(NULL,linebreak));
if (insert_data(system,new_node)==1)
{
count+=1;
}
}
return count;
}
int data_export_to_file(Index_linklist *system,char *filename)//数据导出文件
{
FILE *op;
if ((op=fopen(filename,"w"))!=NULL)
{
print_data(system,op);
fclose(op);
return 1;
}
else return 0;
}
int insert_data(Index_linklist *system,Linklist *node)//从终端读取数据进行插入
{
if (strcmp(node->gender,"woman")==0)
{
strcpy(node->gender,"女");
}
if (strcmp(node->gender,"man")==0)
{
strcpy(node->gender,"男");
}
if (strcmp(node->gender,"男")==0 || strcmp(node->gender,"女")==0)
{
//判断性别输入是否合理
int index=to_upper(node->name[0])-'A';
Linklist *p=system[index].root;
//按照姓名顺序进行插入
if (p->next==NULL)//若链表为空则直接插入
{
system[index].rear->next=node;
node->next=NULL;
system[index].rear=node;
}
else
{
for (;strcmp(p->next->name,node->name) <= 0;)
{
p=p->next;
if (p->next==NULL)
{
node->next=NULL;
p->next=node;
system[index].rear=node;
return 1;
}
}
//找到第一个name比新节点大的节点的前一个节点,新节点插在其之前
node->next=p->next;
p->next=node;
}
return 1;
}
else
{
//printf("%s \'s gender input ERROR!\n",node->name);
return 0;
}
}
void print_data(Index_linklist system[],FILE *op)
{
for (int i=0;i<26;i++)
{
Linklist *p=system[i].root->next;
while (p!=NULL)
{
fprintf(op,"%s %s %s %s %s\n",p->num,p->name,p->gender,p->phone_number,p->address);
p=p->next;
}
}
}
int search_data(Index_linklist const system[],char *num,char *name)//根据ID进行完全匹配搜索
{
int index=to_upper(name[0])-'A';
Linklist *p=system[index].root->next;
int flag=0; //标志是否存在
int location=0;
for (;p!=NULL;location+=1)
{
if (strcmp(p->num,num)==0)
{
//printf("%s %s %s %s %s\n",p->num,p->name,p->gender,p->phone_number,p->address);
flag=1;
location+=1;
break;
}
p=p->next;
}
if (flag==0) return -1;//printf("%s is not in the address book!",name);
return location;//当前链表中第location个位置(从头结点下一个节点开始从1计数)
}
void delete_data(Index_linklist *system,char *num,char *name)//删除数据
{
if (name==NULL)
{
return;
}
int index=to_upper(name[0])-'A';
Linklist *p=system[index].root;
int location=search_data(system,num,name);
delete_linklist(p,location);
}
void system_empty(Index_linklist *system)//将系统数据清空
{
for (int i=0;i<26;i++)
{
Linklist *p=system[i].root->next;
system[i].rear=system[i].root;
for (;p!=NULL;)
{
Linklist *q=p;
p=p->next;
free(q);
}
system[i].root->next=NULL;
}
}
int count_linklist_elem_num(Index_linklist *system,char *name)
{
if (name==NULL)
{
return -1;
}
int index=to_upper(name[0])-'A';
Linklist *p=system[index].root;
if (p->next==NULL)
{
return 0;//无元素
}
else if (p->next->next==NULL)
{
return 1;//一个元素
}
else
{
return 2;
}
}
在将数据从文件读取时,采用了标准库中的strtok()
函数进行字符串分割;插入数据时首先进行输入格式判断,再根据首字母进行排序插入。
软件开发设计
系统初始化界面窗口
该窗口为软件的初始化进入窗口,首先借助Glade进行窗口设计,在主窗口GtkWindow下选用GtkFixed固定容器进行布局,添加一些标签和按钮如下图,背景图使用GtkImage进行添加即可。
创建主窗口函数如下:
GtkWidget *create_main_window()//创建主窗口
{
//void on_Enter_button_clicked(GtkWidget *widget,gpointer window);
//void set_widget_font_size(GtkWidget *widget, int size, gboolean is_button);//改变控件字体大小
GtkBuilder *builder;//新建一个GtkBuilder对象用于读取GtkBuilder界面文件
GtkWidget *main_window;
GtkWidget *Enter_button;
GtkWidget *Exit_button;
GtkWidget *about_button;
builder=gtk_builder_new();
gtk_builder_add_from_file(builder,"main_window.glade",NULL);//从glade获取控件
main_window=GTK_WIDGET(gtk_builder_get_object(builder,"main_window"));//获取主窗体
gtk_window_set_icon(GTK_WINDOW(main_window), create_pixbuf("addressbook.png"));//设置软件图标
gtk_window_set_title(GTK_WINDOW(main_window),"通讯录信息管理系统");//设置主窗口标题
Enter_button=GTK_WIDGET(gtk_builder_get_object(builder,"Enter_button"));//获取主窗口中的进入按钮
Exit_button=GTK_WIDGET(gtk_builder_get_object(builder,"Exit_button"));//获取主窗口中的退出按钮
about_button=GTK_WIDGET(gtk_builder_get_object(builder,"About_button"));//获取主窗口中的退出按钮
gtk_widget_set_opacity(main_window,0.2);
//set_widget_font_size(Enter_button,50,TRUE);//改变控件字体大小
// PangoFontDescri