我如何通过异步方法更新gtk列表框?

问题描述 投票:2回答:1

因此,在GTK中编写UI时,通常最好在Async方法中处理文件的读取等。通常,诸如列表框之类的东西都绑定到ListModel,ListBox中的项根据items_changed信号进行更新。

因此,如果我有一个实现ListModel的类,并具有一个add函数,还有一些FileReader持有对该ListModel的引用,并从一个异步函数中调用add,那么我该如何从本质上触发[ C0],并相应地更新GTK?

我已经尝试过items_changed,但它从未触发。

我看到了:list.items_changed.connect(message("Items changed!"));但是在此示例中,只是按钮标签被更改,实际上未触发任何信号。

编辑:(应@Michael Gratton的要求添加了代码样本

How can one update GTK+ UI in Vala from a long operation without blocking the UI
gtk signals gtk3 vala gio
1个回答
0
投票

这里是我“解决”的方式。

我并不为此解决方案感到特别自豪,但是有关Gtk ListBox的事情有些糟糕,如果ListBox绑定到ListModel,其中之一就是(这可能实际上是ListModel的问题, ListBox将无法使用sort方法进行排序,至少对我而言,这是一个大问题。我通过制作一个基本上是列表包装器的类解决了此问题,该类具有“添加”信号和“删除”信号。在将元素添加到列表后,将连接添加的信号,因此它将创建一个新的Row对象并将其添加到列表框中。这样,数据以与ListModel绑定Similar的方式进行控制。我不能在不调用ShowAll方法的情况下使其工作。

//Disclaimer: everything here is still very much a work in progress, and will, as soon as I'm confident that what I have is not total crap, be released under some GPL or other open license.

//Note: for the sake of readability, I adopted the C# naming convention for interfaces, namely, putting a capital 'I' in front of them, a decision i do not feel quite as confident in as I did earlier.
//Note: the calls to message(..) was put in here to help debugging    

public class AsyncFileContext : Object{


    private int64 offset;
    private bool start_read;
    private bool read_to_end;

    private Factories.IVCardFactory factory;
    private File file;
    private FileMonitor monitor;

    private Gee.Set<IVCard> vcard_buffer;

    private IObservableSet<IVCard> _vCards;
    public IObservableSet<IVCard> vCards { 
        owned get{
            return this._vCards;
        } 
    }

    construct{
        //We want to start fileops at the beginning of the file
        this.offset = (int64)0;
        this.start_read = true;
        this.read_to_end = false;
        this.vcard_buffer = new Gee.HashSet<IVCard>();
        this.factory = new Factories.GenericVCardFactory();
    }

    public void add_vcard(IVCard card){
        //TODO: implement
    }

    public AsyncFileContext(IObservableSet<IVCard> vcards, string path){
        this._vCards = vcards;
        this._vCards = IObservableSet.wrap_set<IVCard>(new Gee.HashSet<IVCard>());
        this.file = File.new_for_path(path);
        this.monitor = file.monitor_file(FileMonitorFlags.NONE, null);
        message("1");
        //TODO: add connect
        this.monitor.changed.connect((file, otherfile, event) => {
            if(event != FileMonitorEvent.DELETED){
                bool changes_done = event == FileMonitorEvent.CHANGES_DONE_HINT;
                Idle.add(() => {
                    read_file_async.begin(changes_done);
                    return false;
                });
            }
        });
        message("2");
        //We don't know that changes are done yet
        //TODO: Consider carefully how you want this to work when it is NOT called from an event

        Idle.add(() => {
            read_file_async.begin(false);
            return false;
        });
    }


    //Changes done should only be true if the FileMonitorEvent that triggers the call was CHANGES_DONE_HINT
    private async void read_file_async(bool changes_done) throws IOError{
        if(!this.start_read){
            return;
        }
        this.start_read = false;
        var dis = new DataInputStream(yield file.read_async());
        message("3");
        //If we've been reading this file, and there's then a change, we assume we need to continue where we let off
        //TODO: assert that the offset isn't at the very end of the file, if so reset to 0 so we can reread the file
        if(offset > 0){
            dis.seek(offset, SeekType.SET);
        }

        string line;
        int vcards_added = 0;
        while((line = yield dis.read_line_async()) != null){
            message("position: %s".printf(dis.tell().to_string()));
            this.offset = dis.tell();
            message("4");
            message(line);
            //if the line is empty, we want to jump to next line, and ignore the input here entirely
            if(line.chomp().chug() == ""){
                continue;
            }

            this.factory.add_line(line);

            if(factory.vcard_ready){
                message("creating...");
                this.vcard_buffer.add(factory.create());
                vcards_added++;
                //If we've read-in and created an entire vcard, it's time to yield
                message("Yielding...");
                Idle.add(() => {
                    _vCards.add_all(vcard_buffer);
                    vcard_buffer.remove_all(_vCards);
                    return false;
                });
                Idle.add(read_file_async.callback);
                yield;
                message("Resuming");
            }
        }
        //IF we expect there will be no more writing, or if we expect that we read ALL the vcards, and did not add any, it's time to go back and read through the whole thing again.
        if(changes_done){ //|| vcards_added == 0){
            this.offset = 0;
        }
        this.start_read = true;
    }

}

//The main idea in this class is to just bind the IObservableCollection's item_added, item_removed and cleared signals to the items_changed of the ListModel. IObservableCollection is a class I have implemented that merely wraps Gee.Collection, it is unittested, and works as intended
public class VCardListModel : ListModel, Object{

    private Gee.List<IVCard> vcard_list;
    private IObservableCollection<IVCard> vcard_collection;

    public VCardListModel(IObservableCollection<IVCard> vcard_collection){
        this.vcard_collection = vcard_collection;
        this.vcard_list = new Gee.ArrayList<IVCard>.wrap(vcard_collection.to_array());

        this.vcard_collection.item_added.connect((vcard) => {
            vcard_list.add(vcard);
            int pos = vcard_list.index_of(vcard);
            items_changed(pos, 0, 1);
        });

        this.vcard_collection.item_removed.connect((vcard) => {
            int pos = vcard_list.index_of(vcard);
            vcard_list.remove(vcard);
            items_changed(pos, 1, 0);
        });
        this.vcard_collection.cleared.connect(() => {
            items_changed(0, vcard_list.size, 0);
            vcard_list.clear();
        });

    }

    public Object? get_item(uint position){
        if((vcard_list.size - 1) < position){
            return null;
        }
        return this.vcard_list.get((int)position);
    }

    public Type get_item_type(){
        return Type.from_name("VikingvCardIVCard");
    }

    public uint get_n_items(){
        return (uint)this.vcard_list.size;
    }

    public Object? get_object(uint position){
        return this.get_item((int)position);
    }

}

//The IObservableCollection parsed to this classes constructor, is the one from the AsyncFileContext
public class ContactList : Gtk.ListBox{

    private ListModel list_model;

    public ContactList(IObservableCollection<IVCard> ivcards){
        this.list_model = new VCardListModel(ivcards);

        bind_model(this.list_model, create_row_func);
        list_model.items_changed.connect(() => {
            message("Items Changed!");
            base.show_all();
        });
    }

    private Gtk.Widget create_row_func(Object item){
        return new ContactRow((IVCard)item);
    }

}

即使这绝不是我所提出的最佳代码,它也可以很好地工作。

© www.soinside.com 2019 - 2024. All rights reserved.