WPF. ListView. ObservableCollection. Кратко

Доброго времени суток. Несколько раз мне писали на e-mail и скайп с вопросами работы c ListView WPF. В данном посте я предлагаю посмотреть небольшой пример работы с ним при помощи связывания ListView и ObservableCollection
ObservableCollection выбрана потому, что она предлагает уведомления о своем изменении и предоставляет весь необходимый функционал списков. То есть, при добавлении\\удалении элементов из коллекции ObservableCollection уведомит контрол о своем изменении и необходимые данные "перерисуются".

Задача
В виде задачи поставим цель написания приложения, которое будет состоять из двух таблиц.
В первой таблице будут содержаться некие значения (Items), во второй - эти же значения + количество времени которое они будут находиться в этой таблице.
Как только время истекает, данные значения будут переходить обратно в первую таблицу.

Решение
Для решения задачи нам потребуется класс Item, который будет хранить 2 свойства - первый это строковое описание Item и второе - это количество времени, которое он должен находиться в таблице 2 (на самом деле верным решеним по проектированию было - создать интерфейс со свойством Name, создать класс Item и добавить декоратор, добавляющий таймер. Но так как пример надуманный и создан для демонстрации возможностей, опустим эти классы)

Опустив классы, получаем следующий код:
  1. public class Item
  2.    {
  3.      public String Name{get;set;}
  4.      public TimeSpan Tax { get; set; }
  5.    }

This code was highlighted with code.xnim.ru

Класс готов. Его мы и будем использовать внутри ObservableCollection:
  1. public partial class MainWindow : Window
  2.    {
  3.      public ObservableCollection <Item > LeftList { get; set; }
  4.      public ObservableCollection <Item > RightList { get; set; }
  5.      public Timer MyTimer = new Timer();

This code was highlighted with code.xnim.ru


Здесь мы также добавили таймер, который пригодится немного позже.
(p.s. ObsetvableCollection находится в пространстве имен System.Collections.ObjectModel, не забудьте добавить его)

После того, как мы создали контейнеры, создадим представление для пользователя (UI). Для этого:
Создадим 2 ListView, один из который будет содержать только значение Item, а другой - значение и таймер:
1 таблица)
  1.   <ListView Name="Game" DockPanel.Dock="Left" Width="100px" ItemsSource="{Binding LeftList}"  FontSize="10" FontFamily="Segoe UI Symbol" Margin="0,0,0,-0.2" >
  2.          <ListView.View >
  3.            <GridView >
  4.               <GridViewColumn Width="100px"  Header="Item" DisplayMemberBinding="{Binding Name}" / >
  5.            </GridView >
  6.          </ListView.View >
  7.       </ListView >

This code was highlighted with code.xnim.ru

2 таблица)
  1.   <ListView Name="Bench" DockPanel.Dock="Left" Width="300px"  ItemsSource="{Binding RightList}"  FontSize="10" FontFamily="Segoe UI Symbol" Margin="0,0,0,-0.2" IsSynchronizedWithCurrentItem="True" >
  2.          <ListView.View >
  3.            <GridView >
  4.               <GridViewColumn Width="100px"  Header="Item" DisplayMemberBinding="{Binding Name}" / >
  5.               <GridViewColumn Width="200px" Header="Time tax" DisplayMemberBinding="{Binding Tax}" / >
  6.            </GridView >
  7.          </ListView.View >
  8.       </ListView >

This code was highlighted with code.xnim.ru

Заметьте, связывание данных происходит посредством задания в ItemsSource коллекции, а путь для отображения задается свойством класса)

Добавим 2 кнопки для добавления item и их переноса с одной таблицы в другую (с 1 во 2)
  1.   <Button Width="131" DockPanel.Dock="Top" Content="Добавить items" Margin="0,0,0,-0.2" Click="Button_Click" > </Button >
  2.       <Button Width="200" DockPanel.Dock="Top" Content="Вправо на 10 секунд" Margin="0,0,0,-0.2" Click="Button2_Click" > </Button >

This code was highlighted with code.xnim.ru


xaml готов. Кроме последнего штриха. По умолчанию в wpf DataContext не установлен на просмотр "самого себя" и поэтому связав данные таким образом мы не усможем увидеть их добавление\\изменение\\т.п. чтобы появилась возможность видеть данные в таблицах, необходимо установить верный путь в DataContext
Это можно сделать в xaml коде. Для этого добавим строку в описание window (добавляем как параметр):
  1.    DataContext="{Binding RelativeSource={RelativeSource Self}}"

This code was highlighted with code.xnim.ru


На этом работа с xaml закончена. Приступаем к кодированию:
Инициализация класса формы:
Здесь необходимо проинициализировать коллекции, задать работу таймера:
  1. public MainWindow()
  2.      {
  3.         LeftList = new ObservableCollection <Item >();
  4.       
  5.         RightList = new ObservableCollection <Item >();
  6.       
  7.         InitializeComponent();
  8.         //OnTimedEvent - будет описана ниже
  9.         MyTimer.Elapsed += new ElapsedEventHandler(OnTimedEvent);
  10.         
  11.         MyTimer.Interval = 1000 ;
  12.         MyTimer.Enabled = true;
  13.       
  14.      }

This code was highlighted with code.xnim.ru


Нажатие на кнопку добавления items:
Зададим создание и добавление в коллекцию нескольких значений: (здесь простое добавление)
  1. private void Button_Click(object sender, RoutedEventArgs e)
  2.      {
  3.          LeftList.Add(new Item { Name = "Item1" });
  4.          LeftList.Add(new Item { Name = "Item2" });
  5.          LeftList.Add(new Item { Name = "Item3" });
  6.          LeftList.Add(new Item { Name = "Item4" });      
  7.      }

This code was highlighted with code.xnim.ru


Перевод из одной таблицы в другую.
Физически, перевод осуществляется посредством "перекладывания" элемента из одной коллекции в другую. За счет того, что этот тип данных (ObservableCollection уведомляет о своем изменении - таблицы сами производят свой Refresh())
  1. private void Button2_Click(object sender, RoutedEventArgs e)
  2.      {
  3.         if (Game.SelectedIndex > -1)
  4.         {
  5.            var temp = LeftList[Game.SelectedIndex];
  6.            LeftList.RemoveAt(Game.SelectedIndex);
  7.            temp.Tax = new TimeSpan(0,0,10);
  8.            RightList.Add(temp);
  9.         }
  10.      }

This code was highlighted with code.xnim.ru


Работа таймера:
По каждому тику таймер должен:
1) из всех элементов, которые хранятся в правой таблице вычислить значение времени 1 (1 секунда)
Это делается с помощью простого цикла foreach

2) Обновить значения. ObservableCollection не может отслеживать изменения объекта, поэтому необходимо сделать вручную. Так как мы работаем в цикле таймера (а он работает асинхронно, то необходимо производить действия с колекцией только посреством вызова Dispatcher объекта Application. С его помощью можно получить из других потоков доступ к основному (потоку формы) потоку.

3) Все элементы, чье время истекло, перевести в левую таблицу. Для этого оставшееся время каждого объекта сраниваем с TimeSpan.Zero и производим перестановку их одного массива в другой с помощью Dispatcher и анонимного метода.
  1.    private void OnTimedEvent(object source, ElapsedEventArgs e)
  2.      {
  3.         foreach (var x in RightList)
  4.         {
  5.            x.Tax -= new TimeSpan(0,0,1);
  6.         }
  7.         Application.Current.Dispatcher.Invoke((Action)(() = > Bench.Items.Refresh()));
  8.       
  9.        for (var i = RightList.Count - 1 ; i >= 0 ; i--)
  10.         {
  11.            if (RightList[i].Tax <= TimeSpan.Zero)
  12.            {
  13.              Application.Current.Dispatcher.Invoke((Action)(() = >
  14.              {
  15.                 LeftList.Add(RightList[i]);
  16.                 RightList.RemoveAt(i);
  17.              }));
  18.            }
  19.         }
  20.       
  21.      }

This code was highlighted with code.xnim.ru


Таким образом мы реализовали приложение, которое позволяет продемонстрировать работу ListView. по ссылке можно скачать непосредственно пример http://xnim.ru/Load/TestAdd.zip
2013-06-27