624|0

88

帖子

3

TA的资源

一粒金砂(高级)

楼主
 

[STM32MP135F-DK]测评 ⑧使用GTK制作一个时钟+天气预报demo [复制链接]

本帖最后由 不爱胡萝卜的仓鼠 于 2023-12-26 13:32 编辑

        之前跑helloWorld的时候绘制UI使用的就是GTK,那么我这次也使用GTK,本来想在帖子中记录一下我一步一步探索的情况,但是实际情况太复杂,这个GTK我是第一次上手,兜兜转转搞了搞几天,全写下来的话内容是在太多了,我这边只能简单讲一下demo的基本情况,最终我会把demo的源码贴出来,具体的请大家自行阅读代码。

       

        在之前的几个帖子中,我已经掌握了基础的编译文件、下载运行代码、自动更新时间、获取天气信息等,基本上已经满足了制作demo的条件,现在就差学习一下如何使用GTK绘制界面了。(当然还有使用curl库从HTTPS下载天气的json数据、解析天气json数据。但是这两个我觉得还是很快可以上手的)

        首先要去找一下GTK的官方文档,官网网址我放一下:https://www.gtk.org,我需要其中的API文档,连接如下:https://docs.gtk.org/gtk3/,这里面有快速上手的说明文档还有API说明。

        说一下目标:全屏显示,显示当前年月日时分秒、显示天气预报(因为之前申请的是免费无限制接口,只能获取当前天气,反正大致意思到了就行)、一个刷新按钮,(用于手动刷新天气信息,当然代码也会定时自动刷新)、一个退出按钮(用于退出整个程序,因为全屏了,没有右上角的X了)

        整个C代码如下

#include <gtk/gtk.h>
#include <glib.h>  
#include <stdio.h>
#include <time.h>
#include <curl/curl.h>  
#include <json-c/json.h> 

#define FULLSCREEN (1)

/* 界面布局是否从UI文件加载 */
#define LOAD_UI_FROM_FILE (1)

#if !LOAD_UI_FROM_FILE
#define BOX (1)
#define GRID (2)
#define BOX_OR_GRID (BOX)
#endif

char weather_URL[] = "请求天气预报的https连接";
char outfilename[] = "weatherData.json";

void get_weather_data_and_refresh(GtkWidget *widget);

/* 更新时间的回调函数 */
gboolean update_time(GtkWidget *widget) 
{
  time_t current_time;
  struct tm *time_info;
  char time_string[30];

  current_time = time(NULL);
  time_info = localtime(¤t_time);
  strftime(time_string, sizeof(time_string), "%Y-%m-%d %H:%M:%S", time_info);

  gtk_label_set_text(GTK_LABEL(widget), time_string);

  /* 返回true,继续执行定时器 */
  return TRUE; 
}

/* 首次运行更新天气 */
gboolean first_run_update_weather(GtkWidget *widget) 
{
  get_weather_data_and_refresh(widget);
  /* 返回FALSE,不再继续执行定时器 */
  return FALSE; 
}

/* 更新天气 */
gboolean update_weather(GtkWidget *widget) 
{
  get_weather_data_and_refresh(widget);
  /* 返回true,继续执行定时器 */
  return TRUE; 
}

/* 天气代码转英文字符 */
void weather_code_to_english_text(int code, char *text)
{
  switch(code)
  {
    case 0:
    {
      strcpy(text, "Sunny");
    }
    break;
    case 1:
    {
      strcpy(text, "Clear");
    }
    break;
    case 2:
    {
      strcpy(text, "Fair");
    }
    break;
    case 3:
    {
      strcpy(text, "Fair");
    }
    break;
    case 4:
    {
      strcpy(text, "Cloudy");
    }
    break;
    case 5:
    {
      strcpy(text, "Partly Cloudy");
    }
    break;
    case 6:
    {
      strcpy(text, "Partly Cloudy");
    }
    break;
    case 7:
    {
      strcpy(text, "Mostly Cloudy");
    }
    break;
    case 8:
    {
      strcpy(text, "Mostly Cloudy");
    }
    break;
    case 9:
    {
      strcpy(text, "Overcast");
    }
    break;
    case 10:
    {
      strcpy(text, "Shower");
    }
    break;
    case 11:
    {
      strcpy(text, "Thundershower");
    }
    break;
    case 12:
    {
      strcpy(text, "Thundershower with Hail");
    }
    break;
    case 13:
    {
      strcpy(text, "Light Rain");
    }
    break;
    case 14:
    {
      strcpy(text, "Moderate Rain	");
    }
    break;
    case 15:
    {
      strcpy(text, "Heavy Rain");
    }
    break;
    case 16:
    {
      strcpy(text, "Storm");
    }
    break;
    case 17:
    {
      strcpy(text, "Heavy Storm");
    }
    break;
    case 18:
    {
      strcpy(text, "Severe Storm");
    }
    break;
    case 19:
    {
      strcpy(text, "Ice Rain");
    }
    break;
    case 20:
    {
      strcpy(text, "Sleet");
    }
    break;
    case 21:
    {
      strcpy(text, "Snow Flurry	");
    }
    break;
    case 22:
    {
      strcpy(text, "Light Snow");
    }
    break;
    case 23:
    {
      strcpy(text, "Moderate Snow");
    }
    break;
    case 24:
    {
      strcpy(text, "Heavy Snow");
    }
    break;
    case 25:
    {
      strcpy(text, "Snowstorm");
    }
    break;
    case 26:
    {
      strcpy(text, "Dust");
    }
    break;
    case 27:
    {
      strcpy(text, "Sand");
    }
    break;
    case 28:
    {
      strcpy(text, "Duststorm");
    }
    break;
    case 29:
    {
      strcpy(text, "Sandstorm");
    }
    break;
    case 30:
    {
      strcpy(text, "Foggy");
    }
    break;
    case 31:
    {
      strcpy(text, "Haze");
    }
    break;
    case 32:
    {
      strcpy(text, "Windy");
    }
    break;
    case 33:
    {
      strcpy(text, "Blustery");
    }
    break;
    case 34:
    {
      strcpy(text, "Hurricane");
    }
    break;
    case 35:
    {
      strcpy(text, "Tropical Storm");
    }
    break;
    case 36:
    {
      strcpy(text, "Tornado");
    }
    break;
    case 37:
    {
      strcpy(text, "Cold");
    }
    break;
    case 38:
    {
      strcpy(text, "Hot");
    }
    break;
    default:
    {
      strcpy(text, "Unknown");
    }
    break;
  }
}

/* 解析天气json数据,如果成功填充字符串 */
int parse_weather_json_data(char *weather_data)
{
  char weather_text[30];

  /* 从文件中解析JSON数据 */
  json_object *parsed_json = json_object_from_file(outfilename);


  /* 如果JSON解析失败 */  
  if (parsed_json == NULL) 
  { 
    printf("failed to parse weather json data\n");
    sprintf(weather_data, "failed to parse weather data");
  }
  /* 解析成功 */
  else
  { 
    printf("success to parse JSON data\n");
    // printf("weather json data: %s\n", json_object_to_json_string(parsed_json));

    /* 找第一层results */
    json_object *results = json_object_object_get(parsed_json, "results");  
    // printf("results: %s\n", json_object_get_string(results));  
    if (results != NULL) 
    {  
      for (int i = 0; i < json_object_array_length(results); i++) 
      {  
        /* results的数组的第i个 */
        json_object *results_obj = json_object_array_get_idx(results, i);  

        /* 找到第二层的now */
        json_object *now = json_object_object_get(results_obj, "now");  
        if (now != NULL) 
        {  
          /* 得到天气的code和温度数据 */
          int code = json_object_get_int(json_object_object_get(now, "code"));  
          const char *temperature = json_object_get_string(json_object_object_get(now, "temperature"));  
          // printf("code: %d\n", code);  
          // printf("temperature: %s\n", temperature);  
          
          /* 天气code转成英文字符 */
          weather_code_to_english_text(code, weather_text);
          /* 组装整个天气的字符串 */
          sprintf(weather_data, "now weather: %s    now temperature: %s\n", weather_text, temperature);
        }
        else
        {
          printf("can't find now\n");
        }
      }  
    } 
    else
    {
      printf("can't find results\n");
    }

    /* 释放JSON对象内存 */
    json_object_put(parsed_json);
    printf("finish json parse\n"); 
  }  
  return 0;
}

/* 回调函数,用于获取HTTP响应码 */
static int check_response(void *clientp, curl_infotype type, char *buffer, size_t size) 
{
  /* 暂时不需要响应码,屏蔽掉 */
#if 0
  int response_code = 0;
  if (type == CURLINFO_RESPONSE_CODE) 
  {
      response_code = *((int *)clientp);
  }
#endif
  return 0;
}

/* 下载天气的json数据 */
int download_weather_json_data(void)
{
  CURL *curl;
  CURLcode res;
  FILE *fp;

  curl = curl_easy_init();
  if (curl) 
  {
    fp = fopen(outfilename, "wb");
    if (!fp) 
    {
        printf("Error opening file\n");
        return 1;
    }

    curl_easy_setopt(curl, CURLOPT_URL, weather_URL);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, NULL); // 使用默认的写入函数
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, fp); // 将文件指针传递给写入函数
    curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, NULL); // 不需要处理头部信息
    curl_easy_setopt(curl, CURLOPT_HEADERDATA, NULL); // 不需要传递头部数据
    curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); // 不验证SSL证书(不安全,仅用于演示)
    curl_easy_setopt(curl, CURLOPT_DEBUGFUNCTION, check_response); // 设置调试回调函数,用于获取HTTP响应码
    res = curl_easy_perform(curl); // 发送请求并下载文件
    if (res != CURLE_OK) 
    {
      printf("curl_easy_perform() failed: %s\n", curl_easy_strerror(res));
      fclose(fp); // 关闭文件指针
      return 1;
    }
    else 
    { 
      printf("success download weather json data\n");
      fclose(fp); // 关闭文件指针。  
    }

    curl_easy_cleanup(curl); // 清理资源
  }
  else 
  {
    printf("curl_easy_init() failed\n");
    return 1;
  }
  return 0;
}

/* 获取天气数据并刷新 */
void get_weather_data_and_refresh(GtkWidget *widget)
{
  char weather_string[50];
  int result;

  /* 获取天气JSON数据 */
  result = download_weather_json_data();

  /* 解析天气JSON数据 */
  if (result == 1)
  {
    /* 下载天气json数据失败,显示对应的文本 */
    sprintf(weather_string, "fail to download weather data");
    gtk_label_set_text(GTK_LABEL(widget), weather_string);
  }
  else
  {
    sprintf(weather_string, "success to download weather data");
    parse_weather_json_data(weather_string);
    gtk_label_set_text(GTK_LABEL(widget), weather_string);
  }
}

/* 刷新按钮回调函数,按钮被按下时调用  */
void button_refresh_clicked_cb(GtkButton *button, GtkWidget *widget) 
{  
  gtk_label_set_text(GTK_LABEL(widget), "get weather data ......");
  get_weather_data_and_refresh(widget);
} 

/* mian函数 */
int main(int argc, char *argv[]) 
{
  /* 初始化GTK */
  gtk_init(&argc, &argv);

#if LOAD_UI_FROM_FILE
  /* 从ui文件加载界面布局 */
  GtkBuilder *builder = gtk_builder_new();
  GError *error = NULL;  
  gtk_builder_add_from_file (builder, "/usr/local/ui.ui", &error);
  if (error != NULL) 
  {  
    printf("error: fail to load UI file: %s\n", error->message);  
    g_error_free(error);  
    return 1;  
  }
#endif 

  /* 创建窗口 */
#if LOAD_UI_FROM_FILE
  GtkWidget *window = GTK_WIDGET(gtk_builder_get_object(builder, "window"));  
  if (window == NULL)
  {
    printf("error: get object window fail\n");
  }
#else
  GtkWidget *window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
#endif
  /* 设置窗口标签 */
  gtk_window_set_title (GTK_WINDOW (window), "Clock and Weather Forecast");
  /* 设置窗口的最小大小 */
  gtk_widget_set_size_request(window, 400, 250);
  /* 绑定窗口的X,可以从触摸屏关闭整个程序 */
  g_signal_connect(G_OBJECT(window), "destroy", G_CALLBACK(gtk_main_quit), NULL); 

#if !LOAD_UI_FROM_FILE
#if (BOX_OR_GRID == BOX)
  /* 创建一个box,后续的控件都放box中 */
  GtkWidget *box1 = gtk_box_new(GTK_ORIENTATION_VERTICAL, 20); 
  /* 把box放到window中 */ 
  gtk_container_add(GTK_CONTAINER(window), box1);  
#elif (BOX_OR_GRID == GRID)
  /* 创建一个grid,后续的控件都放grid中 */
  GtkWidget *grid = gtk_grid_new ();
  /* 把grid放到window中 */ 
  gtk_container_add (GTK_CONTAINER (window), grid);
#endif
#endif

  /* 创建一个标签,用于显示时间 */
#if LOAD_UI_FROM_FILE
  GtkWidget *label_timer = GTK_WIDGET(gtk_builder_get_object(builder, "label_timer"));  
#else
  GtkWidget *label_timer = gtk_label_new("waiting for get sys time");
#if (BOX_OR_GRID == BOX)
  /* 把标签放进box中 */
  gtk_box_pack_start(GTK_BOX(box1), label_timer, FALSE, FALSE, 0);  
#elif (BOX_OR_GRID == GRID)
  /* 把标签放进grid中 */
  gtk_grid_attach (GTK_GRID (grid), label_timer, 0, 0, 3, 1);
#endif
#endif

  /* 创建一个标签,用于天气信息 */
#if LOAD_UI_FROM_FILE
  GtkWidget *label_weather = GTK_WIDGET(gtk_builder_get_object(builder, "label_weather"));  
#else
  GtkWidget *label_weather = gtk_label_new("waiting for get weather forecast information");
#if (BOX_OR_GRID == BOX)
  /* 把标签放进box中 */
  gtk_box_pack_start(GTK_BOX(box1), label_weather, FALSE, FALSE, 0); 
#elif (BOX_OR_GRID == GRID)
  /* 把标签放进grid中 */
  gtk_grid_attach (GTK_GRID (grid), label_weather, 0, 1, 1, 1);
#endif
#endif

  /* 创建一个button,用于手动刷新天气信息 */
#if LOAD_UI_FROM_FILE
  GtkWidget *button_refresh = GTK_WIDGET(gtk_builder_get_object(builder, "button_refresh"));  
#else
  GtkWidget *button_refresh = gtk_button_new_with_label(refresh);
  // gtk_widget_set_size_request(button_refresh, 50, 20);
#if (BOX_OR_GRID == BOX)
  /* 把button放进box中 */
  gtk_box_pack_start(GTK_BOX(box1), button_refresh, FALSE, FALSE, 0);  
#elif (BOX_OR_GRID == GRID)
  /* 把标签放进grid中 */
  gtk_grid_attach (GTK_GRID (grid), button_refresh, 3, 1, 1, 1);
#endif
#endif
  /* 设置回调函数 */
  g_signal_connect(button_refresh, "clicked", G_CALLBACK(button_refresh_clicked_cb), label_weather); 

  /* 创建一个button,用于退出程序 */
#if LOAD_UI_FROM_FILE
  GtkWidget *button_quit = GTK_WIDGET(gtk_builder_get_object(builder, "button_quit"));  
#else
  GtkWidget *button_quit = gtk_button_new_with_label("quit");
  // gtk_widget_set_size_request(button_quit, 50, 20);
#if (BOX_OR_GRID == BOX)
  /* 把button放进box中 */
  gtk_box_pack_start(GTK_BOX(box1), button_quit, FALSE, FALSE, 0); 
#elif (BOX_OR_GRID == GRID)
  /* 把标签放进grid中 */
  gtk_grid_attach (GTK_GRID (grid), button_quit, 3, 3, 1, 1);
#endif
#endif
  /* 设置回调函数 */
  g_signal_connect(button_quit, "clicked", G_CALLBACK (gtk_main_quit), NULL);


  /* 把刚才创建的窗口显示出来 */
  gtk_widget_show_all(window);
#if FULLSCREEN
  /* 全屏显示 */
  gtk_window_fullscreen(GTK_WINDOW(window));
#endif

  /* 创建定时器,每隔1s刷新时间 */
  g_timeout_add(1000, (GSourceFunc)update_time, label_timer);

  /* 创建定时器,等时间到了,不再运行定时器。用于首次上电后更新天气数据。如果在这里直接更新,一旦下载或解析有问题,GTK界面会出来的很慢 */
  g_timeout_add(1000, (GSourceFunc)first_run_update_weather, label_weather);

  /* 创建定时器,30min定时更新一次天气数据 */
  g_timeout_add(1800000, (GSourceFunc)update_weather, label_weather);


  gtk_main();
#if LOAD_UI_FROM_FILE
  g_object_unref(builder);  
#endif

  return 0;
}

配套的Makefile

PROG = gtk_time
SRCS = gtk_time.c

CLEANFILES = $(PROG)

# Add / change option in CFLAGS and LDFLAGS
CFLAGS += -Wall $(shell pkg-config --cflags gtk+-3.0)
LDFLAGS += $(shell pkg-config --libs gtk+-3.0)

CFLAGS += -I/path/to/curl/include  
LDFLAGS += -L/path/to/curl/lib -lcurl 

LDFLAGS += -L/path/to/json-c/lib -ljson-c

all: $(PROG)

$(PROG): $(SRCS)
	$(CC) -o $@ $^ $(CFLAGS) $(LDFLAGS)

clean:
	rm -f $(CLEANFILES) $(patsubst %.c,%.o, $(SRCS))

对应的ui.ui文件代码

<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.22.2 -->
<interface>
  <requires lib="gtk+" version="3.20"/>
  <object class="GtkWindow" id="window">
    <property name="can_focus">False</property>
    <child type="titlebar">
      <placeholder/>
    </child>
    <child>
      <object class="GtkBox">
        <property name="visible">True</property>
        <property name="can_focus">False</property>
        <property name="orientation">vertical</property>
        <child>
          <object class="GtkBox">
            <property name="visible">True</property>
            <property name="can_focus">False</property>
            <property name="orientation">vertical</property>
            <child>
              <object class="GtkLabel" id="label_timer">
                <property name="visible">True</property>
                <property name="can_focus">False</property>
                <property name="label" translatable="yes">waiting for get sys time</property>
              </object>
              <packing>
                <property name="expand">False</property>
                <property name="fill">True</property>
                <property name="position">0</property>
              </packing>
            </child>
          </object>
          <packing>
            <property name="expand">False</property>
            <property name="fill">True</property>
            <property name="padding">32</property>
            <property name="position">0</property>
          </packing>
        </child>
        <child>
          <object class="GtkBox">
            <property name="visible">True</property>
            <property name="can_focus">False</property>
            <child>
              <object class="GtkLabel" id="label_weather">
                <property name="visible">True</property>
                <property name="can_focus">False</property>
                <property name="halign">center</property>
                <property name="valign">center</property>
                <property name="margin_left">38</property>
                <property name="label" translatable="yes">waiting for get weather forecast information</property>
                <property name="justify">center</property>
              </object>
              <packing>
                <property name="expand">False</property>
                <property name="fill">True</property>
                <property name="position">0</property>
              </packing>
            </child>
            <child>
              <object class="GtkButton" id="button_refresh">
                <property name="label" translatable="yes">refresh</property>
                <property name="visible">True</property>
                <property name="can_focus">True</property>
                <property name="receives_default">True</property>
              </object>
              <packing>
                <property name="expand">False</property>
                <property name="fill">True</property>
                <property name="pack_type">end</property>
                <property name="position">1</property>
              </packing>
            </child>
          </object>
          <packing>
            <property name="expand">False</property>
            <property name="fill">True</property>
            <property name="padding">41</property>
            <property name="position">1</property>
          </packing>
        </child>
        <child>
          <object class="GtkBox">
            <property name="visible">True</property>
            <property name="can_focus">False</property>
            <property name="orientation">vertical</property>
            <child>
              <object class="GtkButton" id="button_quit">
                <property name="label" translatable="yes">quit</property>
                <property name="visible">True</property>
                <property name="can_focus">True</property>
                <property name="receives_default">True</property>
                <property name="margin_left">170</property>
                <property name="margin_right">170</property>
              </object>
              <packing>
                <property name="expand">False</property>
                <property name="fill">True</property>
                <property name="position">0</property>
              </packing>
            </child>
          </object>
          <packing>
            <property name="expand">False</property>
            <property name="fill">True</property>
            <property name="pack_type">end</property>
            <property name="position">2</property>
          </packing>
        </child>
      </object>
    </child>
  </object>
</interface>

界面显示如下

 

这个代码写的界面有以下几种配置方式

①是否全屏显示,修改FULLSCREEN宏定义

②界面布局是否从UI文件加载,如果是,那么就需要把上面的ui.ui文件也放到135中,我代码中写的路径是/usr/local/。修改LOAD_UI_FROM_FILE宏定义

③不使用UI文件的情况下,使用BOX布局还是GRID布局,修改BOX_OR_GRID。这是我前期探索时,手动制作的界面布局

 

最终版的配置参数是全屏+使用UI加载

 

详细的代码我就不解读了,我写了非常详细的注释。

 

运行demo步骤如下,在ubuntu中编译好代码,将执行文件和ui.ui放到135的/usr/local下,修改两个文件的权限,然后运行

chmod 777 ui.ui

chmod 777 gtk_time

su -l weston -c "/usr/local/gtk_time"

 

我这里要说一下ui.ui文件是怎么来的,手动绘制界面真的非常麻烦,效率也很低,我在网上搜索到一个好东西“Glade”,他只管绘制界面,有点类似前端后端分离的感觉,他最终会生产一个文件,然后GTK加载这文件,就可以直接显示出绘制的界面,这个软件需要在ubuntu中运行(win貌似也可以,但是我没有找到对应的安装包),在ubuntu中安装只需要使用以下命令

sudo apt-get install glade

安装好后就有一个Glade应用程序的图标,点击即可运行(下图是我绘制的界面)

这个软件我就不细讲了,大家可以自行摸索,总的来说还是很容易上手的,我大约捣鼓了十来分钟就基本上手了

绘制好后,点击save,默认的文件扩展名是“.glade”,其实就是xml,可以手动修改后缀,然后就可以使用了(给到135后还要修改一下文件的权限,保证他可以被读取)

 

最后放一段演示视频

WeChat_20231224212942

 

此帖出自stm32/stm8论坛
点赞 关注
 

回复
举报
您需要登录后才可以回帖 登录 | 注册

随便看看
查找数据手册?

EEWorld Datasheet 技术支持

相关文章 更多>>
关闭
站长推荐上一条 1/8 下一条

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

About Us 关于我们 客户服务 联系方式 器件索引 网站地图 最新更新 手机版

站点相关: 国产芯 安防电子 汽车电子 手机便携 工业控制 家用电子 医疗电子 测试测量 网络通信 物联网

北京市海淀区中关村大街18号B座15层1530室 电话:(010)82350740 邮编:100190

电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2025 EEWORLD.com.cn, Inc. All rights reserved
快速回复 返回顶部 返回列表