Ленивый просмотр списка в GTK - PullRequest
17 голосов
/ 02 июля 2010

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

Ответы [ 4 ]

9 голосов
/ 18 июля 2011

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

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

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

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

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

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

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

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

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

Вы можете заставить 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 для лучшего объяснения того, что делают каждый из методов и их параметры. Документы Mono оставляют желать лучшего.

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 голосов
/ 28 июня 2011

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

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

0 голосов
/ 19 июля 2011

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

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

0 голосов
/ 31 мая 2011

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

...