使用gtk进行多线程进度条控制

时间:2009-12-10 05:23:39

标签: c curl gtk glade

在我的C程序中,从互联网下载一些文件,我使用GTK进度条显示下载进度。

我想如果我下载一个文件,我的应用程序会显示一个进度条

如果我下载三个文件,我的应用程序可以显示三个进度条。其余的可以用同样的方式完成。

  

我使用glade3创建UI。 GtkTreeView   有3列

     
      
  1. 名称
  2.   
  3. 进度
  4.   
  5. 状态
  6.   

我写了一些代码,它可以工作但有一些问题

如果我下载了一个文件,应用程序也不错。

但如果我下载两个文件。 应用程序无法显示twoprogress栏。

app只在一个进度中显示两个线程

我该如何解决?

progress.glade

和源代码:

/*
gcc -Wall -g `pkg-config --cflags --libs gtk+-2.0 gmodule-export-2.0` -lcurl -lgthread-2.0  liststore.c -o liststore
 */
#include <stdio.h>
#include <gtk/gtk.h>
#include <glib.h>
#include <unistd.h>
#include <pthread.h>

#include <curl/curl.h>
#include <curl/types.h> /* new for v7 */
#include <curl/easy.h> /* new for v7 */

gchar *URL = "http://soundclash-records.co.uk/mp3s/upfull_rockers_never_gonna_let_you_down.mp3";

size_t my_write_func(void *ptr, size_t size, size_t nmemb, FILE *stream)
{
    return fwrite(ptr, size, nmemb, stream);
}

size_t my_read_func(void *ptr, size_t size, size_t nmemb, FILE *stream)
{
    return fread(ptr, size, nmemb, stream);
}

typedef struct _Data Data;
struct _Data
{
    GtkWidget *down; /* Down button */
    GtkWidget *tree; /* Tree view */
    gdouble progress;
};

enum
{
    STRING_COLUMN,
    INT_COLUMN,
    N_COLUMNS
};

gboolean set_download_progress(gpointer data)
{
    Data *treeview = (Data *)data;
    GtkListStore* store = GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(treeview->tree)));
    GtkTreeIter iter;

    gtk_tree_model_get_iter_from_string (GTK_TREE_MODEL (store) ,
                                       &iter,
                                       g_strdup_printf ("%d",0));   
    gtk_list_store_set(store, &iter,
                       INT_COLUMN,treeview->progress, -1);

    return FALSE;
}

int my_progress_func(Data *data,
                     double t, /* dltotal */
                     double d, /* dlnow */
                     double ultotal,
                     double ulnow)
{
//  printf("%d / %d (%g %%)\n", d, t, d*100.0/t);
    gdk_threads_enter();
    gdouble progress;
    progress = d*100.0/t;
    data->progress = progress;
    g_idle_add(set_download_progress, data);

    gdk_threads_leave();
    return 0;
}

void *create_thread(void *data)
{
    Data *viewtree = (Data *)data;
    GtkTreeIter iter;

    GtkListStore* store = GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(viewtree->tree)));
    gtk_list_store_append( store, &iter );


    g_print("url\n");
    CURL *curl;
    CURLcode res;
    FILE *outfile;
    gchar *url = URL;

    curl = curl_easy_init();
    if(curl)
    {
        outfile = fopen("test.curl", "w");
        if(outfile)
            g_print("curl\n");

        curl_easy_setopt(curl, CURLOPT_URL, url);
        curl_easy_setopt(curl, CURLOPT_WRITEDATA, outfile);
        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, my_write_func);
        curl_easy_setopt(curl, CURLOPT_READFUNCTION, my_read_func);

        curl_easy_setopt(curl, CURLOPT_NOPROGRESS, FALSE);
        curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION, my_progress_func);
        curl_easy_setopt(curl, CURLOPT_PROGRESSDATA, data);

        res = curl_easy_perform(curl);

        fclose(outfile);
        /* always cleanup */
        curl_easy_cleanup(curl);
    }
    g_object_unref( G_OBJECT( store ) );
    return NULL;
}


G_MODULE_EXPORT void
cb_add( GtkWidget *button,
         Data      *data )
{
    if (!g_thread_create(&create_thread, data, FALSE, NULL) != 0)
        g_warning("can't create the thread");
} 
int main(int argc, char **argv)
{
    GtkBuilder *builder;
    GtkWidget  *window;
    Data       *data;


    curl_global_init(CURL_GLOBAL_ALL);
    if( ! g_thread_supported() )
        g_thread_init( NULL );

    gdk_threads_init();
    gtk_init(&argc, &argv);

    data = g_slice_new( Data );

    /* Create builder */
    builder = gtk_builder_new();
    gtk_builder_add_from_file( builder, "progress.glade", NULL );

    window    = GTK_WIDGET( gtk_builder_get_object( builder, "window1" ) );
    data->down   = GTK_WIDGET( gtk_builder_get_object( builder, "down" ) );
    data->tree = GTK_WIDGET( gtk_builder_get_object( builder, "treeview" ) );

    gtk_builder_connect_signals( builder, data );
    g_object_unref( G_OBJECT( builder ) );

    gtk_widget_show( window );

    gtk_main();
    g_slice_free( Data, data );

    return 0;
}

=============================================== ==========

更新:12-11-09

/*
gcc -Wall -g `pkg-config --cflags --libs gtk+-2.0 gmodule-export-2.0 gthread-2.0 libcurl` liststore2.c -o liststore2
 */
#include <stdio.h>
#include <gtk/gtk.h>
#include <glib.h>
#include <unistd.h>
#include <pthread.h>

#include <curl/curl.h>
#include <curl/types.h> /* new for v7 */
#include <curl/easy.h> /* new for v7 */

gchar *URL = "http://soundclash-records.co.uk/mp3s/upfull_rockers_never_gonna_let_you_down.mp3";
static GHashTable* TreeRowReferences;
static GPrivate* current_data_key = NULL;

size_t my_write_func(void *ptr, size_t size, size_t nmemb, FILE *stream)
{
    return fwrite(ptr, size, nmemb, stream);
}

size_t my_read_func(void *ptr, size_t size, size_t nmemb, FILE *stream)
{
    return fread(ptr, size, nmemb, stream);
}

typedef struct _Data Data;
struct _Data
{
    GtkWidget *down; /* Down button */
    GtkWidget *tree; /* Tree view */
    gdouble progress;
};

enum
{
    STRING_COLUMN,
    INT_COLUMN,
    N_COLUMNS
};

gboolean set_download_progress(gpointer data)
{
    Data *treeview = (Data *)data;

    GtkListStore* store = GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(treeview->tree)));
    GtkTreeIter iter;

    GtkTreeRowReference* reference = g_hash_table_lookup(TreeRowReferences,data);

    GtkTreePath* path = gtk_tree_row_reference_get_path(reference);

    gtk_tree_model_get_iter(GTK_TREE_MODEL(store), 
                    &iter, path);

    gtk_list_store_set(store, &iter,
                       INT_COLUMN,treeview->progress, -1);

    gtk_tree_path_free (path);
    return FALSE;
}

int my_progress_func(Data *data,
             double t, /* dltotal */
             double d, /* dlnow */
             double ultotal,
             double ulnow)
{
    if(t == 0)
        return 0;
    data->progress = d*100.0/t;

    gdk_threads_enter();
    g_idle_add(set_download_progress, data);
    gdk_threads_leave();
    return 0;
}

void *create_thread(void *data)
{

    Data *current_treeview = g_private_get (current_data_key);

    if (!current_treeview)
    {
      current_treeview = g_new (Data, 1);
      current_treeview = (Data *)data;
      g_private_set (current_data_key, current_treeview);
      g_print("p %g\n",current_treeview->progress);
    }
    else{
        current_treeview = (Data *)data;
        g_print("c %g\n",current_treeview->progress);
    }

    g_print("url\n");
    CURL *curl;
    CURLcode res;
    FILE *outfile;
    gchar *url = URL;
    gdk_threads_enter();
    curl = curl_easy_init();
    if(curl)
    {
        outfile = fopen("test.curl", "w");
        if(outfile)
            g_print("curl\n");

        curl_easy_setopt(curl, CURLOPT_URL, url);
        curl_easy_setopt(curl, CURLOPT_WRITEDATA, outfile);
        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, my_write_func);
        curl_easy_setopt(curl, CURLOPT_READFUNCTION, my_read_func);

        curl_easy_setopt(curl, CURLOPT_NOPROGRESS, FALSE);
        curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION, my_progress_func);
        curl_easy_setopt(curl, CURLOPT_PROGRESSDATA, current_treeview);
        gdk_threads_leave();
        res = curl_easy_perform(curl);

        fclose(outfile);
        /* always cleanup */
        curl_easy_cleanup(curl);
    }
    return NULL;
}


G_MODULE_EXPORT void
cb_add( GtkWidget *button,
         Data      *data )
{    
    Data *current_download = (Data *)data;
    GtkTreeIter iter;
    GtkListStore* store = GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(current_download->tree)));
    gtk_list_store_append( store, &iter );

    GtkTreeRowReference* reference = NULL;
    GtkTreePath* path = gtk_tree_model_get_path(GTK_TREE_MODEL(store), &iter);
    reference = gtk_tree_row_reference_new(GTK_TREE_MODEL(store), path);
    g_hash_table_insert(TreeRowReferences, current_download, reference);
    gtk_tree_path_free(path);

    if (!g_thread_create(&create_thread, current_download, FALSE, NULL) != 0)
        g_warning("can't create the thread");
} 

int main(int argc, char **argv)
{
    GtkBuilder *builder;
    GtkWidget  *window;
    Data       *data;


    curl_global_init(CURL_GLOBAL_ALL);
    if( ! g_thread_supported() )
        g_thread_init( NULL );

    gdk_threads_init();
    gtk_init(&argc, &argv);

    data = g_slice_new( Data );

    /* Create builder */
    builder = gtk_builder_new();
    gtk_builder_add_from_file( builder, "progress.glade", NULL );

    window    = GTK_WIDGET( gtk_builder_get_object( builder, "window1" ) );
    data->down   = GTK_WIDGET( gtk_builder_get_object( builder, "down" ) );
    data->tree = GTK_WIDGET( gtk_builder_get_object( builder, "treeview" ) );
    TreeRowReferences = g_hash_table_new(NULL, NULL);

    gtk_builder_connect_signals( builder, data );
    g_object_unref( G_OBJECT( builder ) );

    gdk_threads_enter();
    gtk_widget_show( window );
    gdk_threads_leave();

    gtk_main();
    g_slice_free( Data, data );

    return 0;
}

1 个答案:

答案 0 :(得分:2)

以下是让您的代码正常运行的提示:

  1. 如果有一个库的pkg-config文件,比如gthread或libcurl,那么使用它而不是混合pkg-config调用和-l开关。这不是问题,但它可能会在以后给你带来麻烦。所以编译你的文件:

    gcc -Wall -g `pkg-config --cflags --libs gtk+-2.0 gmodule-export-2.0 gthread-2.0 libcurl` liststore.c -o liststore
    
  2. 如果始终使用线程,总是,请在gtk_main()gdk_threads_enter()之间将您的来电包裹到gdk_threads_leave()。这也不是问题,但它肯定以后给你带来问题。

  3. 下一步,消除所有在控制台上喷出的警告。当它产生的警告在其他50个警告中没有丢失时,更容易发现错误。那些警告意味着你做错了什么,所以不要忽视它们。

    一个。我得到的第一个警告是抱怨NaNs。 NaN代表“不是数字”,它是你除以零时得到的。代码中的唯一除法是my_progress_func(),因此CURL有时可能会将dltotal参数作为零传递。如果你检查一下,它会消除这些警告:

    int my_progress_func(Data *data,
                 double t, /* dltotal */
                 double d, /* dlnow */
                 double ultotal,
                 double ulnow)
    {
        if(t == 0)
            return 0;
        data->progress = d*100.0/t;
        gdk_threads_enter();
        g_idle_add(set_download_progress, data);
        gdk_threads_leave();
        return 0;
    }
    

    湾警告的下一个原因是thread_create()末尾的unref语句。你正在做的是使用gtk_tree_view_get_model()从树视图中获取模型,但这并没有提供对模型的引用。因此,当你取消它时,你实际上是在树视图仍在使用它的同时销毁模型。删除该语句,所有警告神奇地消失。您根本不需要取消模型。树视图拥有对它的唯一引用,当树视图被破坏时,它将自动取消模型。但这仍然不是问题。

  4. 现在所有这一切都已经解决了,你可以确定问题不是由一些无效的指针或线程问题引起的。毕竟事实证明这很简单:

    gtk_tree_model_get_iter_from_string (GTK_TREE_MODEL (store) ,
                                         &iter,
                                         g_strdup_printf ("%d",0));
    

    在这里,你总是得到一个指向第0行的iter,即第一行。这就是为什么所有下载进度都出现在第一行的原因。我建议使用GPrivate使每线程数据结构具有自己的进度分数,并在启动线程时创建一行GtkTreeRowReference。获取CURL以将该数据结构发送到回调。 不要使用GtkTreeIterGtkTreePath来存储该行。相反,让你的GtkTreeIter像这样:

    gtk_tree_model_get_iter(gtk_tree_row_reference_get_model(row_reference), &iter, gtk_tree_row_reference_get_path(row_reference));
    
  5. 祝你好运。