IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 数据结构与算法 -> 【GTK4】又快年底了,100行代码教你做一款简单的年会抽奖软件 -> 正文阅读

[数据结构与算法]【GTK4】又快年底了,100行代码教你做一款简单的年会抽奖软件

博主未授权任何人或组织机构转载博主任何原创文章,感谢各位对原创的支持!
博主链接

抽奖软件视频

自制一款简单的抽奖软件

一、参与抽奖同事信息准备

在这里插入图片描述
工号和姓名之间使用水平制表符分隔,每行只显示一个同事的信息。

二、准备GTK4环境

安装传送门

三、开始Coding

3.1 创建抽奖资源池

我们首先创建一个单向循环链表,实现同事信息的记录。这里没有使用顺序表存储,主要是考虑到参与抽奖的同事人数可能增加,如果顺序表预分配的太小,还需要修改code。

基本数据结构:

struct employee_s{
    char name[50];   //同事姓名
    char number[20]; //同事工号
    struct employee_s *next; 
};

typedef struct{
    int employee_quantity;  //参与抽奖的人说
    struct employee_s *tail; //单向循环链表尾指针
    struct employee_s * luck_employee; //某一轮中获奖的同事
}employee_queue_t;

static employee_queue_t * g_employee_queue; //全局变量,记录了参与抽奖的所有同事信息
#define get_employee_queue_head(q) (q)->tail->next; //获取链表头节点

创建单向循环链表:

/*
 * input:
 *	employee_queue:一个用于存放参与抽奖同事信息的结构体指针,程序内部分配内存
 *  filename:参与抽奖同事信息文件的路径
 */
static void
create_employee_queue(employee_queue_t ** employee_queue,char *filename)
{
    g_assert(filename!=NULL);
    FILE *fp=NULL;
    
	//创建一个抽奖池
    *employee_queue = g_malloc0(sizeof(employee_queue_t));
    g_assert(employee_queue!=NULL);
    
    //初始化抽奖池状态信息
    (*employee_queue)->employee_quantity=0;
    
    //创建一个空链表头
    struct employee_s *head = g_malloc0(sizeof(struct employee_s));
    g_assert(head!=NULL);
    head->next = head;
    
    //构成一个单向循环链表
    (*employee_queue)->tail = head;
    
    //打开记录有同事信息的文件
    fp = fopen(filename,"r");
    g_assert(fp!=NULL);
    
    struct employee_s *employee;
    while(!feof(fp))
    {
    	//为一个同事创建信息块
        employee = g_malloc0(sizeof(struct employee_s));
        g_assert(employee!=NULL);
        
        //从尾部插入到单向循环链表中
        employee->next=(*employee_queue)->tail->next;
        (*employee_queue)->tail->next=employee;
        (*employee_queue)->tail=employee;
        ++(*employee_queue)->employee_quantity;
        
        //读取同事工号
        fscanf(fp,"%s\t",employee->number);
        
        //读取同事姓名
        fgets(employee->name,sizeof(employee->name),fp);

		//去掉结尾的换行符
        if(employee->name[strlen(employee->name)-1]=='\n')
            employee->name[strlen(employee->name)-1]='\0';
    }
    
    //关闭文件
    fclose(fp);
}

当某个同事中奖后,我们需要把他从抽奖池中移除,避免二次中奖,所以还需要一个从链表中删除节点的功能:

/*
 * input:
 *	employee_queue:资源池指针
 *	employee:需要删除的同事信息节点
 */
static void
remove_employee_from_queue(employee_queue_t *employee_queue,
                           struct employee_s *employee)
{
    g_assert(employee_queue && employee);
    
    //定义一个临时的指针,用于遍历寻找需要删除的同事节点
    struct employee_s * prev = get_employee_queue_head(employee_queue);
    while(prev->next != employee)
        prev = prev->next;
        
    //将该同事从链表中删除
    prev->next = employee->next;
    employee->next=NULL;
    
    //如果删除的是尾节点,需要重新修改尾指针的值
    if(employee_queue->tail == employee)
        employee_queue->tail=prev;
        
    //抽奖池数量减一
    --employee_queue->employee_quantity;
    
    //安全释放资源
    g_clear_pointer(&employee,g_free);
}

当程序退出的时候,我们需要释放所有动态申请的资源:

/*
 * input:
 *	employee_queue:资源池指针
 */
static void
destory_employee_queue(employee_queue_t * employee_queue)
{
    g_assert(employee_queue);
    
    struct employee_s *remove_employee;
    struct employee_s *employee = get_employee_queue_head(employee_queue);
    employee = employee->next;

	//从头节点依次删除每个节点
    while(employee_queue->employee_quantity--){
        remove_employee = employee;
        employee = employee->next;
        g_clear_pointer(&remove_employee,g_free);
    }
    
    //释放链表头节点
    g_clear_pointer(&employee,g_free);
	
	//释放抽奖资源池
    g_clear_pointer(&employee_queue,g_free);
}

3.2 创建抽奖界面

三个全局变量:

//用于抽奖时抽奖人信息的滚动
static guint g_timer_id=0;

//用于窗口背景的动态更新
static guint g_bg_timer_id=0;

//窗口背景图缓存区
static GdkPixbuf *g_src_pixbuf=NULL;

Gtk4 运行主程序:

int
main(int argc, char *argv[])
{
    GtkApplication *app;
    int status;

    app = gtk_application_new ("org.gtk.example", G_APPLICATION_FLAGS_NONE);

	//当程序启动后,执行activate函数,做进一步操作
    g_signal_connect (app, "activate", G_CALLBACK (activate), NULL);
    
    //启动程序
    status = g_application_run (G_APPLICATION (app), argc, argv);
    g_object_unref (app);

    return status;
}

activate函数:

static void
activate (GtkApplication* app,
          gpointer user_data)
{
    GtkWidget *window;
    
    //创建抽奖资源池
    create_employee_queue(&g_employee_queue,"H:\\Downloads\\person.txt");
	
	//创建抽奖窗口
    window = gtk_application_window_new (app);
    gtk_window_set_title (GTK_WINDOW (window), "");
    gtk_window_maximize (GTK_WINDOW (window));
    
    //在窗口构建完成后,调用realize_window做一步操作
    g_signal_connect (window,"realize",G_CALLBACK(realize_window),NULL);

	//当窗口关闭后调用析构函数destroy_window
    g_signal_connect (window, "destroy", G_CALLBACK (destroy_window), NULL);
    
    gtk_widget_show (window);
}

窗口析构函数:

static void
destroy_window(GtkWidget* self,gpointer user_data)
{
	//如果定时器开启,则关闭
    if(g_timer_id > 0)
        g_source_remove(g_timer_id);
    
    //如果定时器开启,则关闭
    if(g_bg_timer_id >0)
        g_source_remove(g_bg_timer_id);
    
    //释放资源池
    destory_employee_queue(g_employee_queue);
    
    //释放背景图片的缓存
    g_object_unref(g_src_pixbuf);
}

创建抽奖窗口布局:
在这里插入图片描述

static void
realize_window(GtkWidget* self,
               gpointer user_data)
{
    GtkWidget *box;
    GtkWidget *label;
    GtkWidget *overlay;
    GtkWidget *picture;
    
    overlay=gtk_overlay_new();
    gtk_widget_set_hexpand(overlay,TRUE);
    gtk_widget_set_vexpand(overlay,TRUE);
    gtk_window_set_child(GTK_WINDOW(self),overlay);
    
    //创建抽奖
    g_src_pixbuf = gdk_pixbuf_new_from_file("H:\\Downloads\\lottery.jpeg", NULL);
    picture= gtk_picture_new_for_pixbuf(g_src_pixbuf);

	//设置抽奖窗口背景图
    gtk_overlay_set_child(GTK_OVERLAY(overlay),picture);

	//开启定时器,当窗口改变时,动态调整背景图宽高比例
    g_bg_timer_id=g_idle_add(G_SOURCE_FUNC(update_background),picture);
    
    box=gtk_box_new(GTK_ORIENTATION_HORIZONTAL,0);
    gtk_widget_set_hexpand(box,TRUE);
    gtk_widget_set_vexpand(box,TRUE);
    gtk_overlay_add_overlay(GTK_OVERLAY(overlay),box);
    
    //抽奖人信息显示Label
    label= gtk_label_new("");
    gtk_widget_set_margin_top(label,300);
    gtk_widget_set_hexpand(label,TRUE);
    gtk_widget_set_vexpand(label,TRUE);
    gtk_box_append(GTK_BOX(box),label);

	//定义一个控制器,用于响应鼠标点击事件
    GtkGesture* click_gesture = gtk_gesture_click_new();

	//当鼠标释放时,调用start_stop_lottery函数
    g_signal_connect(click_gesture,"released",G_CALLBACK(start_stop_lottery),label);
    gtk_widget_add_controller(box,GTK_EVENT_CONTROLLER(click_gesture));
}

鼠标事件响应回调函数:

static void
start_stop_lottery(
        GtkGestureClick* self,
        gint n_press,
        gdouble x,
        gdouble y,
        gpointer user_data)
{
	//双击
    if(n_press==2){
    	//如果抽奖程序未开启,则开启抽奖,每隔10ms调用一次luck_circulation函数,动态
    	//滚动参与抽奖人信息
        if(g_timer_id == 0)
            g_timer_id=g_timeout_add(10,G_SOURCE_FUNC(luck_circulation),user_data);
        //如果已经在抽奖中,则停止定时器,也就是公布中奖结果
        else
        {
            g_source_remove(g_timer_id);
            g_timer_id=0;
            
            //将中奖人从抽奖池中删除
            remove_employee_from_queue(g_employee_queue,g_employee_queue->luck_employee);
        }
    }
}

抽奖程序:

static gboolean
luck_circulation(gpointer user_data)
{
    GtkWidget *label=user_data;
    
    //如果抽奖池中没有同事了,则直接退出
    if(g_employee_queue->employee_quantity == 0)
    {
        g_timer_id=0;
        return G_SOURCE_REMOVE;
    }
    
    //初始化随机种子
    srand(g_get_real_time());
    
    //随机生成一个抽奖人信息索引
    guint begin_pos = rand()%g_employee_queue->employee_quantity+1;
    
    //从链表头遍历找到这个抽奖人
    struct employee_s *employee = get_employee_queue_head(g_employee_queue);
    while(begin_pos--)
        employee=employee->next;

	//在抽奖窗口显示抽奖人信息
    char *markup = g_markup_printf_escaped("<span font=\"100\">%s %s</span>",
            employee->name,employee->number);
    gtk_label_set_markup (GTK_LABEL(label),markup);
    g_free(markup);
    
    //更新此时刻选中的同事信息
    g_employee_queue->luck_employee = employee;
    
    //进行下一轮抽奖人随机选择流程
    return G_SOURCE_CONTINUE;
}

抽奖窗口背景图自适应程序:

static gboolean
update_background(gpointer user_data)
{
	//记录上一次窗口长宽比
    static double prev_ratio =0.0;
    int width = gtk_widget_get_width(user_data);
    int height = gtk_widget_get_height(user_data);

	//计算当前窗口长宽比
    double curr_ratio = ((double)width)/((double)height);

	//如果初次运行或者窗口窗宽比发生变化,则更新窗口背景图长宽比
    if(prev_ratio==0.0 || prev_ratio!=curr_ratio)
    {
        prev_ratio = curr_ratio;
        GtkPicture *picture=user_data;

		//将原始窗口背景图进行比例缩放
        GdkPixbuf *update_pixbuf = gdk_pixbuf_scale_simple(g_src_pixbuf,
                width,height,
                GDK_INTERP_NEAREST);
        gtk_picture_set_pixbuf(picture,update_pixbuf);
        g_object_unref(update_pixbuf);
    }

    return G_SOURCE_CONTINUE;
}

四、程序使用建议

程序设计原因,建议抽奖池人数小于500人。

背景图片随便换,不限于我提供的这个,只需要将使用的背景图命名为 “lottery.jpeg” 即可!



源代码可执行程序下载链接


这里是从善若水的博客,感谢您的阅读💰💰💰


在这里插入图片描述

  数据结构与算法 最新文章
【力扣106】 从中序与后续遍历序列构造二叉
leetcode 322 零钱兑换
哈希的应用:海量数据处理
动态规划|最短Hamilton路径
华为机试_HJ41 称砝码【中等】【menset】【
【C与数据结构】——寒假提高每日练习Day1
基础算法——堆排序
2023王道数据结构线性表--单链表课后习题部
LeetCode 之 反转链表的一部分
【题解】lintcode必刷50题<有效的括号序列
上一篇文章      下一篇文章      查看所有文章
加:2021-11-14 21:58:23  更:2021-11-14 21:59:00 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2025年1日历 -2025/1/9 0:59:37-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码