Подтвердить что ты не робот

Ленивый загруженный список в GTK #

Я ищу для отображения большого набора данных через представление списка в GTK #, и производительность здесь является проблемой. В настоящее время я использую TreeView, поддерживаемый ListStore, но добавление всех моих данных в ListStore выполняется навсегда. Есть ли вид виджетов вида в GTK, который поддерживает ленивую загрузку данных? В Winforms вы можете использовать свойство VirtualMode DataGridView для обработки этого, но я не вижу ничего подобного для GTK.

4b9b3361

Ответ 1

Там, насколько мне известно, никакой виджет для того, чтобы делать то, что вы хотите в Gtk, однако, вы можете сделать что-то подобное в конечном результате для свойства VirtualMode в TreeView.

Проблема с элементом управления TreeView заключается в том, что он будет извлекать все данные из этой модели заранее. Если бы это было не так, я бы предложил модельный подход только к этой проблеме, но, к сожалению, TreeView жадна, когда дело доходит до получения данных, поэтому необходимо контролировать, когда данные загружаются из представления, иначе как это происходит чтобы определить, когда строка видна и, таким образом, сообщить модели, или прокси, чтобы получить данные для строки, когда она станет видимой.

Вам нужно 3 вещи, чтобы заставить это работать

1) модель для использования в древовидной структуре, в которой изначально есть все строки, но нет данных ни в одном из полей
2) способ получения данных из любой базы данных, которую вы используете
3) средство определения, какие строки для извлечения данных для

Первые два элемента могут быть выполнены на уровне модели. Определение того, какие строки для извлечения требуется виджет Treeview и способ определения того, какие строки отображаются. Метод, который я использую ниже, не является оптимальным, но он действительно работает и может быть убран и/или адаптирован для любого использования, которое вы имеете в виду.

Я использую класс прокси для хранения в модели и используется для получения данных, относящихся к этой строке. В моем примере это называется ProxyClass. Он извлекает и удерживает данные для строки, которая изначально равна нулю. В этом случае метод Fetch просто создает и возвращает строку "некоторые данные" + id

Это будет храниться внутри экземпляра MyNode, который наследует TreeNode, представляя строку данных. первый столбец возвращает данные, хранящиеся в прокси, а второй столбец, который никогда не отображается, содержит экземпляр класса прокси.

Затем вы создаете свой NodeStore, вашу модель, заполняя его экземплярами MyNode (id), как вы можете видеть ниже в этом примере.

Контроль над загрузкой данных осуществляется из CellDataFunc. Этот метод является ключом к тому, чтобы это работало. CellDataFunc отвечает за установку текста в CellRendererText для определенного столбца в строке, идентифицированной переданным им итератором. Он вызывается каждый раз, когда treeview показывает строку и только для вновь обнаруженной строки. Таким образом, будут получены только данные для ячеек, которые отображаются на дисплее. Это дает вам возможность контролировать, когда данные извлекаются, тем самым получая их только тогда, когда вам это нужно.

Вы можете заставить TreeView использовать CellDataFunc для загрузки ваших данных по мере необходимости, применив его к одному из столбцов с помощью TreeViewColumn.SetCellDataFunc. Вам нужно сделать это только в одном столбце, так как он может извлекать данные для всей строки.

Чтобы остановить все, кроме видимых строк, из-за получения данных, можно выполнить проверку, чтобы проверить, находится ли текущая ячейка в видимом диапазоне или нет. Для этого вы вызываете TreeView.GetVisibleRange(out start, out end), а затем смотрите, находится ли текущий итератор, переданный этой функции, в пределах диапазона начала и конца, которые являются объектами TreePath, поэтому их необходимо сначала изменить на TreeIters. Model.GetIter(out iter_start, start). Затем проверьте, не является ли iter.UserData.ToInt32() >= iter_start.UserData.ToInt32() и меньше, чем iter_end. Если текущий iter попадает в диапазон от iter_start до iter_end, тогда выберите данные, иначе оставьте это.

Вот мой пример.

ProxyClass

namespace LazyTree
{

    public class ProxyClass 
    {
      int id;
      string data;

      public ProxyClass (int id)
      {
        this.id = id;
        data = null;
      }


      public void Fetch()
      {
        data = "some data " + id;
      }


      public int Id
      {
        get { return id; }
      }

      public string Data
      {
        get {return data;}
      }
  }
}

Пользовательский экземпляр TreeNode

namespace LazyTree
{
    [Gtk.TreeNode (ListOnly=true)]
    public class MyNode : Gtk.TreeNode
    {
        protected ProxyClass proxy;

        public MyNode (int id)
        {
            proxy = new ProxyClass(id);
        }

        [Gtk.TreeNodeValue (Column=1)]
        public ProxyClass Proxy
        {
            get {return proxy;}
        }

        [Gtk.TreeNodeValue (Column=0)]
        public string Data
        {
            get { return proxy.Data; }
        }
    }
}

Окно, содержащее прокручиваемое окно и дерево. Это также где CellDataFunc определен, хотя это можно было бы где угодно.

namespace LazyTree
{

    public class MyWindow : Gtk.Window
    {
        int NUMBER_COLUMNS = 10000;
        Gtk.NodeStore store;
        Gtk.NodeStore Store {
            get {
                if (store == null) {
                    store = new Gtk.NodeStore (typeof (MyNode));
                    for(int i = 0; i < NUMBER_COLUMNS; i++)
                    {
                        store.AddNode (new MyNode (i));
                    }
                }
                return store;
            }
        }


        protected void CellDataFunc(Gtk.TreeViewColumn column,
                                    Gtk.CellRenderer cell,
                                    Gtk.TreeModel model,
                                    Gtk.TreeIter iter)
        {
            try {
                string data = (string)model.GetValue(iter, 0);
                ProxyClass proxy = (ProxyClass)model.GetValue(iter, 1);
                Gtk.TreeView view = (Gtk.TreeView)column.TreeView;
                Gtk.TreePath start, end;
                bool go = view.GetVisibleRange(out start,out end);
                Gtk.TreeIter iter_start, iter_end;
                if(go)
                {
                    model.GetIter(out iter_start, start);
                    model.GetIter(out iter_end, end);
                }
                if (go &&
                    data == null && 
                    iter.UserData.ToInt32() >= iter_start.UserData.ToInt32() &&
                    iter.UserData.ToInt32() <= iter_end.UserData.ToInt32())
                {
                    Console.WriteLine("Lazy Loading " + proxy.Id + ", Visible: " + cell.Visible);
                    proxy.Fetch();
                }

                ((Gtk.CellRendererText)cell).Text = data;
            } catch(Exception e) {
                Console.WriteLine("error: " + e);
            }
        }


        public MyWindow () : base("Lazy Tree")
        {
            Gtk.NodeView view = new Gtk.NodeView(Store);

            Gtk.ScrolledWindow scroll = new Gtk.ScrolledWindow();
            scroll.Add(view);
            Add(scroll);
            Gtk.CellRendererText cell = new Gtk.CellRendererText ();
            view.AppendColumn ("Lazy Data", cell, "text", 0);

            Gtk.TreeViewColumn column = view.GetColumn(0);

            column.SetCellDataFunc(cell, CellDataFunc);
        }


        protected override bool OnDeleteEvent (Gdk.Event ev)
        {
            Gtk.Application.Quit ();
            return true;
        }

        public static void Main()
        {
            Gtk.Application.Init ();
                MyWindow win = new  MyWindow();
            win.SetDefaultSize(200, 200);
                    win.ShowAll ();
            Gtk.Application.Run ();
        }
    }


}

Надеюсь, это то, что вы после.

См. документацию c для лучшего объяснения того, что делают каждый из методов и их параметров. Моно-документы оставляют желать лучшего.

SetCellDataFunc (C docs) http://developer.gnome.org/gtk/stable/GtkTreeViewColumn.html#gtk-tree-view-column-set-cell-data-func

(CeCellDataFunc) http://developer.gnome.org/gtk/stable/GtkTreeViewColumn.html#GtkTreeCellDataFunc

(DestroyFunc) http://developer.gnome.org/glib/unstable/glib-Datasets.html#GDestroyNotify

Ответ 2

Это имеет огромное значение, если вы вставляете данные в модель, пока она подключена к представлению, или вы вставляете все данные во время "офлайн" и подключаете их только к виду, когда вы закончите. Позже много быстрее, потому что в противном случае древовидная структура должна постоянно обновлять внутренние структуры.

Если вставка не является основной проблемой, но получение данных из вашего источника данных является фактической медленной частью, то, что очень помогает, можно быстро получить количество строк для модели. Если это так, то я бы предложил сначала создать liststore со всеми строками, выделенными пустым содержимым, которые можно подключить к представлению, а затем заполнить фактическое содержимое из простоя-обратного вызова или потока. К сожалению, для обновления модели нет пакетной обработки api, так что сразу несколько строк могут быть обновлены.

Ответ 3

Возможно, вы можете добавить данные с помощью другого потока, чтобы текущее приложение не "зависало", а просто продолжало работать. Вероятно, это займет столько же времени, но по крайней мере пользователь может работать с остальной частью приложения в среднем.

Ответ 4

В качестве альтернативы вы можете посмотреть, как реализовать свой собственный Gtk.TreeModelImplementor, как описано в Реализация GInterfaces на веб-сайте Mono Project. Вы можете увидеть один пример здесь.

Это должно быть довольно тривиально сделать такую ​​реализацию "ленивой".