Справочник функций

Ваш аккаунт

Войти через: 
Забыли пароль?
Регистрация
Информацию о новых материалах можно получать и без регистрации:

Почтовая рассылка

Подписчиков: -1
Последний выпуск: 19.06.2015

Загрузка в listview данных из двумерного массива. Обновление контрола.

12K
07 февраля 2010 года
CrazyTSTer
20 / / 09.01.2006
Всем доброго времени суток.
Пожалуйста помогите решить такую проблему:
1) Есть контрол ListView с отображением данных в режиме Details
2) Данные в объект подтягиваются из некоторого двумерного массива str[*,*]
3) Данные подтягиваются предположим каждые 2 секунды, за это время массив str[*,*] меняется

Вот код:
Код:
private void Form1_Load(object sender, EventArgs e)
        {
            str = new string[40, 2];
            for (int i = 0; i < 40; i++)
            {
                for (int j = 0; j < 2; j++)
                {
                    str[i, j] = "a" + Convert.ToString(i) + " b" + Convert.ToString(j);
                }
            }
        }

        private void timer1_Tick(object sender, EventArgs e)
        {
            ListViewItem[] itmx = new ListViewItem[str.GetLength(0)];
            for (int i = 0; i < str.GetLength(0); i++)
            {
                itmx = new ListViewItem(i.ToString());
                for (int j = 0; j < str.Length / str.GetLength(0); j++)
                {
                    itmx.SubItems.Add(str[i, j]);
                }
            }
            listView1.Items.Clear();
            listView1.Items.AddRange(itmx);
        }


Что не устраивает:
1) Все это дело мерцает
2) Полосы прокрутки возвращаются в умолчальное состояния, т.е. горизонтальная - до упора влево, а вертикальная - до упора вверх

Необходимо избавиться от этих двух эффектов.

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

Всем заранее спасибо,
С уважением.
14
08 февраля 2010 года
Phodopus
3.3K / / 19.06.2008
Очевидно - не надо чистить listview. А менять только те итемы которые изменились.
12K
09 февраля 2010 года
CrazyTSTer
20 / / 09.01.2006
Цитата: Phodopus
Очевидно - не надо чистить listview. А менять только те итемы которые изменились.


Дык, в этом то и заключается вопрос: каким образом?
Про VirtualMode я почитал... но не совсем понял.... примеров которые удовлетворяли бы моим условиям я тоже не нашел :( поэтому задаю вопрос здесь

12K
11 февраля 2010 года
CrazyTSTer
20 / / 09.01.2006
Разобрался с режимом VirtualMode в ListView.
Сделал по примеру в MSDN: http://msdn.microsoft.com/ru-ru/library/system.windows.forms.listview.virtualmode.aspx

Отрисовка ListView вызывается по событию timer_tick командой ListView.Refresh()

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

Думаю что это из-за достаточно большого набора данных, если например массив str[*,*] будет размером 42х10.

Подскажите как быть?
5
11 февраля 2010 года
hardcase
4.5K / / 09.08.2005
Цитата: CrazyTSTer

Думаю что это из-за достаточно большого набора данных, если например массив str[*,*] будет размером 42х10.

Подскажите как быть?


А вы обновляете все ячейки? Или только те, что изменились?

12K
11 февраля 2010 года
CrazyTSTer
20 / / 09.01.2006
Цитата: hardcase
А вы обновляете все ячейки? Или только те, что изменились?



Получается что все. Т.к. вызывается listview.Refresh()
Подскажите как перерисовать только измененные поля?

5
11 февраля 2010 года
hardcase
4.5K / / 09.08.2005
Цитата: CrazyTSTer
Получается что все. Т.к. вызывается listview.Refresh()
Подскажите как перерисовать только измененные поля?


Изменить соответсвующий SubItem в списке.
Естественно, все манипуляции производить между вызовами BeginUpdate и EndUpdate, также стоит установить DoubleBuffered в истину (у формы или контрола).

12K
11 февраля 2010 года
CrazyTSTer
20 / / 09.01.2006
Цитата: hardcase
Изменить соответсвующий SubItem в списке.
Естественно, все манипуляции производить между вызовами BeginUpdate и EndUpdate, также стоит установить DoubleBuffered в истину (у формы или контрола).


Т.е. Вы предлагаете:
1) Сначала скопировать полученный массив str[*,*] в str_swap[*,*]
2) Затем отрисовать str_swap[*,*] на listview
3) На следующем шаге сравнить str[*,*] c str_swap[*,*], понять что изменилось
4) Отрисовать изменения
5) Опять скопировать str[*,*] в str_swap[*,*]
Я Вас правильно понимаю?
Ну или сравнивать массив str[*,*] с тем что уже есть в ListView и при обнаружении изменений менять соответствующим Item?

5
12 февраля 2010 года
hardcase
4.5K / / 09.08.2005
Цитата: CrazyTSTer
Ну или сравнивать массив str[*,*] с тем что уже есть в ListView и при обнаружении изменений менять соответствующим Item?

Я думаю, что стоит держать набор (например, массив) каких-то промежуточных объектов (скажем, DataItem), которые будут осведомлены о предыдущем состоянии ячейки и в то же время будут иметь ссылку на соответствующий им SubItem списка. При изменении своего состояния они будут напрямую менять данные в списке.

12K
12 февраля 2010 года
CrazyTSTer
20 / / 09.01.2006
Цитата: hardcase
Я думаю, что стоит держать набор (например, массив) каких-то промежуточных объектов (скажем, DataItem), которые будут осведомлены о предыдущем состоянии ячейки и в то же время будут иметь ссылку на соответствующий им SubItem списка. При изменении своего состояния они будут напрямую менять данные в списке.



А Вы не могли бы привести пример кода? что-то я уже битых 3 часа ковыряюсь и пока ничего не придумал :(

5
12 февраля 2010 года
hardcase
4.5K / / 09.08.2005
Цитата: CrazyTSTer
А Вы не могли бы привести пример кода? что-то я уже битых 3 часа ковыряюсь и пока ничего не придумал :(


Вот пример кода, должно быть все достаточно прозрачно, но есть один хак, не без этого.

Код:
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Windows.Forms;

namespace WindowsFormsApplication8 {
    public partial class Form1 : Form {

        private const int COLS = 20;
        private const int ROWS = 50;

        public Form1() {
            InitializeComponent();

            listView1.OptimizeForDoubleBuffer();

            this.data = GenerateArray(COLS, ROWS);
            this.data_items = DataItemHelper.FillListView(listView1, data);
        }

        private int[,] data;

        private DataItem<int>[,] data_items;

        private static int[,] GenerateArray(int col_count, int row_count) {
            int[,] result = new int[row_count, col_count];
            int value = 0;
            for (int col = 0; col < col_count; ++col) {
                for (int row = 0; row < row_count; ++row) {
                    result[row, col] = value++;
                }
            }
            return result;
        }

        private void Form1_Load(object sender, EventArgs e) {
            timer1.Enabled = true;
        }

        private Random random = new Random();

        private void timer1_Tick(object sender, EventArgs e) {
            int items_to_change = random.Next(20);
            for (int i = 0; i < items_to_change; ++i) {
                int row = random.Next(ROWS);
                int col = random.Next(COLS);

                ++data[row, col];
            }
            DataItemHelper.RefreshListView(listView1, data, data_items);
        }
    }

    public static class ListViewHelper {

        public static void OptimizeForDoubleBuffer(this ListView list_view) {
            // list_view.SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
[COLOR=Red]            list_view.GetType()
                .GetMethod("SetStyle", BindingFlags.Instance | BindingFlags.NonPublic)
                .Invoke(list_view, new object[] { ControlStyles.OptimizedDoubleBuffer, true });
[/COLOR]        }
    }

    public class DataItem<T> {

        private static readonly EqualityComparer<T> comparer = EqualityComparer<T>.Default;

        public DataItem(ListViewItem.ListViewSubItem sub_item, T value) {
            this._sub_item = sub_item;
            this._value = value;
            ValueChanged();
        }

        private readonly ListViewItem.ListViewSubItem _sub_item;

        private T _value;

        public T Value {
            get {
                return _value;
            }
            set {
                if (!comparer.Equals(_value, value)) {
                    _value = value;
                    ValueChanged();
                }
            }
        }

        private void ValueChanged() {
            _sub_item.Text = _value.ToString();
        }
    }


    public static class DataItemHelper {

        private const int COL_INDEX = 1;
        private const int ROW_INDEX = 0;

        public static DataItem<T>[,] FillListView<T>(ListView list, T[,] data) {
            int row_count = data.GetUpperBound(ROW_INDEX);
            int col_count = data.GetUpperBound(COL_INDEX);

            list.BeginUpdate();
            try {
                list.Clear();

                list.Columns.Add(new ColumnHeader() {
                    Width = 0,
                    DisplayIndex = 0
                });

                for (int col = 0; col <= col_count; ++col) {
                    list.Columns.Add(new ColumnHeader() {
                        Text = col.ToString(),
                        Width = 30
                    });
                }

                DataItem<T>[,] result = new DataItem<T>[row_count + 1, col_count + 1];
                for (int row = 0; row <= row_count; ++row) {
                    ListViewItem item = list.Items.Add("");
                    for (int col = 0; col <= col_count; ++col) {
                        result[row, col] = new DataItem<T>(item.SubItems.Add(""), data[row, col]);
                    }
                }
                return result;
            } finally {
                list.EndUpdate();
            }
        }

        public static void RefreshListView<T>(ListView list, T[,] data, DataItem<T>[,] data_items) {
            int row_count = data.GetUpperBound(ROW_INDEX);
            int col_count = data.GetUpperBound(COL_INDEX);

            if (row_count != data_items.GetUpperBound(ROW_INDEX))
                throw new ArgumentException();

            if (col_count != data_items.GetUpperBound(COL_INDEX))
                throw new ArgumentException();

            for (int row = 0; row <= row_count; ++row) {
                for (int col = 0; col <= col_count; ++col) {
                    data_items[row, col].Value = data[row, col];
                }
            }
        }

    }

}
На форме находится едиенственный ListView, в режиме отображения Details, также есть один таймер Timer1 с интервалом обновления в 200мс для эмуляции изменения данных.

Класс DataItem<T> оборачивает данные типа T и при изменении значения Value производит изменение соответствующего ListViewItem.ListViewSubItem-а.

Класс DataItemHelper содержит два метода: FillListView для начального заполнения списка и RefreshListView для обновления представления данных, последний метод как раз изменяет свойство Value у DataItem-ов.

Теперь по поводу хака. Для включения двойной буферизации в ListView необходимо вызвать защищенный метод SetStyle, безопасно можно это сделать просто пронаследовавашись от ListView и вызвав его в конструкторе (см. комментарий в коде). Я сделал небольшой хак, позволяющий избежать наследования, но вызвать этот метод посредством отражения, реализация находится в методе-расширении OptimizeForDoubleBuffer класса ListViewHelper.
12K
13 февраля 2010 года
CrazyTSTer
20 / / 09.01.2006
Огромное спасибо, будем пробовать, я бы сам точно до этого быстро не дошел бы, т.к. только-только начинаю изучать C#
12K
15 февраля 2010 года
CrazyTSTer
20 / / 09.01.2006
hardcase, подскажите пожалуйста, возник вот какой вопрос, а если мой массив с данными динамический? И например из массива удаляется целая строка.
Тогда мы получаем сообщение об ошибке вот в этом месте:
 
Код:
if (row_count != data_items.GetUpperBound(ROW_INDEX))
                throw new ArgumentException();

А сообщение такое:
 
Код:
ArgumentException не обработано
Значение не попадает в ожидаемый диапазон.

Тоже самое происходит если в массив наоборот добавляется еще одна строка
5
15 февраля 2010 года
hardcase
4.5K / / 09.08.2005
Цитата: CrazyTSTer
hardcase, подскажите пожалуйста, возник вот какой вопрос, а если мой массив с данными динамический? И например из массива удаляется целая строка.

Значит нужно удалять/добавлять лишние/недостающие строки в списке а не швырять исключение, как в примере. ;)

12K
15 февраля 2010 года
CrazyTSTer
20 / / 09.01.2006
Цитата: hardcase
Значит нужно удалять/добавлять лишние/недостающие строки в списке а не швырять исключение, как в примере. ;)


Я видимо тупой но подскажите как, особенно если взять общий случай если добавилась новая строка не в конец массива, или если удалилась не последняя строка

5
15 февраля 2010 года
hardcase
4.5K / / 09.08.2005
Цитата: CrazyTSTer
Я видимо тупой но подскажите как, особенно если взять общий случай если добавилась новая строка не в конец массива, или если удалилась не последняя строка


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

12K
15 февраля 2010 года
CrazyTSTer
20 / / 09.01.2006
Цитата: hardcase
А, собственно, не важно: из списка удалять последние элементы, если строк стало меньше, или добавлять нужное количество в конец в случае, когда строки наоборот добавились. Представление данных в конце концов все равно будет скорректировано.


Т.е. по сути можно удалит последнюю строку, или добавить новую и пустую, а потом после вызова очередного Refresh все перерисуется как нужно, верно я Вас понял? Т.е. главное что бы количество строк/столбцов совпадало?

5
16 февраля 2010 года
hardcase
4.5K / / 09.08.2005
Цитата: CrazyTSTer
Т.е. по сути можно удалит последнюю строку, или добавить новую и пустую, а потом после вызова очередного Refresh все перерисуется как нужно, верно я Вас понял? Т.е. главное что бы количество строк/столбцов совпадало?


Количество можно в методе Refresh контролировать вместо выброса исключений.

12K
16 февраля 2010 года
CrazyTSTer
20 / / 09.01.2006
Цитата: hardcase
Количество можно в методе Refresh контролировать вместо выброса исключений.


А каким образом можно переопределить размер массива data_items?
Я так понимаю что если у нас в массиве data строк больше чем в data_items то добавляем в data_items еще одну, т.е. меняем верхнюю границу массива
Если в data строк меньше, то удаляем одну строку из data_items.
Как это сделать что бы не создавать новый массив?

5
16 февраля 2010 года
hardcase
4.5K / / 09.08.2005
Цитата: CrazyTSTer
Как это сделать что бы не создавать новый массив?

Никак, массивы в .NET не могут изменять размер. Array.Resize<T>, которая работает только для одномерных массивов занимается элементарным копированием данных из старого в новый.

12K
16 февраля 2010 года
CrazyTSTer
20 / / 09.01.2006
Цитата: hardcase
Никак, массивы в .NET не могут изменять размер. Array.Resize<T>, которая работает только для одномерных массивов занимается элементарным копированием данных из старого в новый.



Блин, ну как же тогда быть??? я никак не могу придумать нормального решения... помогите пожалуйста
Сделал так, но работает только при добавлении нового элемента... на удаление никак не реагирует:
Код формы

Код:
private void RefreshDataTimer_Tick(object sender, EventArgs e)
        {
            int SelectItem=0;
            DataItemHelper.RefreshListView(ListView1, data, data_items);
            if (Torrents_list.SelectedIndices.Count > 0)
            {
                SelectItem = ListView1.SelectedIndices[0];
            }
        }

        private void button1_Click(object sender, EventArgs e)
        {
            if (Working == false)
            {
                backgroundWorker1.RunWorkerAsync();
                Working = true;
                GetDataTimer.Enabled = true;
            }
            else
            {
                backgroundWorker1.CancelAsync();
                GetDataTimer.Enabled = false;
                RefreshDataTimer.Enabled = false; FirstTime = true;
                Working = false;
            }
        }

        private void GetDataTimer_Tick(object sender, EventArgs e)
        {
            if (swap != null) { data = swap; }
            if (data != null && FirstTime == true)
            {
                FirstTime = false;
                data_items=DataItemHelper.FillListView(ListView1, data);
                RefreshDataTimer.Enabled = true;
            }
        }


А в Вашем классе подправил следующий кусок таким образом:
Код:
public static void RefreshListView<T>(ListView list, T[,] data, DataItem<T>[,] data_items)
        {
            int row_count = data.GetUpperBound(ROW_INDEX);
            int col_count = data.GetUpperBound(COL_INDEX);

            if (row_count != data_items.GetUpperBound(ROW_INDEX))
----------------                FirstTime = true;// throw new ArgumentException(); ------------------------------
           
            if (col_count != data_items.GetUpperBound(COL_INDEX))
                throw new ArgumentException();

            for (int row = 0; row <= row_count; ++row)
            {
                for (int col = 0; col <= col_count; ++col)
                {
                    data_items[row, col].Value = data[row, col];
                }
            }


Переменная FirstTime объявлена как Public
Реклама на сайте | Обмен ссылками | Ссылки | Экспорт (RSS) | Контакты
Добавить статью | Добавить исходник | Добавить хостинг-провайдера | Добавить сайт в каталог