Delphi programming blog
Источник: http://teran.karelia.pro/articles/item_4469.html
 

Практика #1. Постановка задачи.

Опубликовано 09.05.2011 г. 22:26

Недавно в работе попалась одна задача, о реализации которой, возможно, кому то будет интересно узнать. В основном, конечно, новичкам в Delphi. Такие ключевые моменты, на которые стоит обратить внимание новичкам, я буду стараться выделять жирным шрифтом. Однако конечно основная идея - не показать как пользоваться теми или иными инструментами, а использование их в комплексе, поэтому подразумевается наличие некоторых знаний. В статье будет поставлена задача, и описаны методы для ее решения, с точки зрения применения конструкций и возможностей языка, таких как использование наследования, абстрактных классов/методов, виртуальных методов, их перекрытие; классовые методы; использование обобщений (generics), переопрделение операторов для записей, информация о типах (rtti) и т.п.

Задача состоит в следующем: в некотором приложении есть форма редактирования данных. Задачу редактирования нам и предстоит решить. С точки зрения интерфейса данные представляются следующим образом: первым делом на форме есть выпадающий список, с помощью которого мы выбираем, какие данные изменять. Такие пункты сгруппированы в "категории". Например, пунктом может быть "Показатели рождаемости", а категория "Демография". Т.е элементы комбобокса будут выглядеть так:
1. Демография -- 1.1. Рождаемость и смертность -- 1.2. Численность населения по возрастам 2. Экономика
И так далее. Следующим элементом интерфейса является таблица в которой эти данные представлены. Размерность таблицы мы, конечно, на данном этапе не знаем, так как не знаем какие данные там будут. Но условимся, что значения таблицы - числовые. Следующее что нам требуется - две кнопки: сохранить и отменить. И последний штрих: давайте условимся, что наши данные описываются не только числовым значением, но и еще парой параметров. Т.е когда мы выделяем число в таблице, то это элемента данных, который имеет само числовое значение, также имеет источник записи (предположим, что данные представляют некоторую статистическую информацию, тогда источником может быть например сайт РосСтата, или какой нить статистический сборник, и т.п), ну и допустим третий параметр - владелец, пользователь, который добавил или изменил запись. Вот такая абстрактная задача. И в этой абстрактности есть свои плюсы: решать мы ее будем так же абстрактно. Не привязываясь к тому, откуда берутся и куда сохраняются данные. Это будет последняя проблема - в реализации конечных классов. Решение задачи надо начинать с построения логики работы, "миниархитектуры" так сказать. С чего начинается наша работа - мы выбираем элемент списка. Далее все можно попробовать представить по пунктам.
  1. Необходимо настроить таблицу данных: определить ее размер, заполнить заголовки строк и столбцов
  2. Заполнить таблицу данными
  3. Можем внести раскраску ячеек: неизмененное значение, отредактированное значение, добавленное значение, удаленное значение
  4. Когда мы редактируем каким то образом значение таблицы, мы не применяем все правки моментально. Любые изменения фиксируются по нажатию кнопки Сохранить. Пока же все правки хранятся в некотором кеше. Который можно очистить кнопкой Отмена.
Условимся, что набор категорий в списке у нас будет ограниченный, пусть так и остается Демография и Экономика. Наши элементы представления данных назовем DataItem.

С этого мы и начнем.

Введем перечисление, описывающее категории:
    TDataItemCategory = (dicEconomics, dicDemography);
Теперь мы можем сформировать словарь названий этих категорий. При этом это будет в прямом смысле словарь - унаследованный от обобщенного класса TDictionary из Generics.Collections. Ключом словаря будут элементы вышеприведенного перечисления, а значениями - строки. Заполнятся словарь будет сам собой в конструкторе. (напоминаю опять же, что набор категорий у нас статичен).
    TCategoryCollection  = class(TDictionary<TDataItemCategory, string>)
      public
        constructor Create();
    end;
....
constructor TCategoryCollection.Create();
begin
    inherited Create(integer(high(TDataItemCategory)));

    Add(dicEconomics,    'Макроэкономические показатели');
    Add(dicDemography, 'Демографические показатели');
end;
У нас конечно же будет коллекция конечных классов, реализующих элементы списка. Все они будут происходить от базового класса TCustomDataItem. Коллекция представляет собой список объектов, опять же тут удобно применение обобщенных классов - TObjectList.
    TDataItemsCollection = TObjectList<TCustomDataItem>;
Представим что мы открываем нашу форму редактирования. Первое, что надо сделать - получить эту коллекцию элементов. Каждый из элементов будет конечно же содержать информацию о категории. Для создания коллекции добавим одноименную функцию:
function CreateDataItemsCollection(): TDataItemsCollection;
begin
    result := TDataItemsCollection.Create();

    result.Add( TBirthrateDataItem.Create() );
end;
Теперь при создании формы, мы готовы заполнить наш выпадающий список. Первым делом создаем словарь категорий, и список объектов:
procedure TDataEditForm.FormCreate(Sender: TObject);
begin
    FItemIndex := -1;
    FCategoryCollection := TCategoryCollection.Create();
    FItemsCollection := CreateDataItemsCollection();

    InitParametersComboBox();
end;
как видите, наша форма имеет несколько дополнительных private членов. И непосредственно само заполнение:
procedure TDataEditForm.InitParametersComboBox;
var catId : TDataItemCategory;
    c : integer;
    i, k : integer;
    di : TCustomDataItem;
begin
    c := 0;
    for catId in FCategoryCollection.Keys do begin
        inc(c);
        ParametersComboBox.Items.AddObject(Format('%d. %s',[c, FCategoryCollection[catId]]), TObject(-1));

        k := 0;
        for i := 0 to FItemsCollection.Count - 1 do begin
            di :=   FItemsCollection[i];
            if di.Category <> catId then continue;

            inc(k);
            ParametersComboBox.Items.AddObject(Format('-- %d.%d. %s',[c, k, di.Title]), TObject(i));
        end;
    end;
end;
Последовательно перебираем категории по словарю, После чего из списка объектов выбираем те, которые принадлежат данной категории. Здесь мы пользуемся тем, что в список TStrings (элементы TComboBox), можно записывать не только строки, но и связанные объекты. Однако заметим, что пользуясь явным приведением типов, мы записываем туда не объекты, а числа. Если в выпадающий список добавляется элемент "категория", то соответствующее значение -1, если же конечный объект данных, то его порядковый номер в списке объектов. Как мы можем заметить, элемент данных TCustomDataItem содержит два свойства - title & category. Теперь мы можем выбирать элемент в списке. Немного расширим private секцию формы:
  strict private
    { Private declarations }
    FCategoryCollection : TCategoryCollection;
    FItemsCollection : TDataItemsCollection;
    FCurrentItem : TCustomDataItem;
    FItemIndex : integer;
Добавив туда используемые коллекции категорий и элементов. Также указатель на текущий элемент и его индекс. Не стоит использовать без крайней необходимости для таких целей глобальные переменные. Вдобавок к этому, мы введем public свойство ItemIndex обозначающее индекс выбранного элемента (индекс в списке). Для изменения индекса будет использоваться метод SetItemIndex. Его задача - обновить набор данных для выбранного элемента (refresh), обновить состояние формы и грида данных.
    property ItemIndex : integer read FItemIndex write SetItemIndex;
....
procedure TDataEditForm.setItemIndex(newIndex: integer);
begin
    if newIndex = FItemIndex then exit;
    FItemIndex := newIndex;

    if FItemIndex = -1  then   FCurrentItem := nil
    else FCurrentItem := FItemsCollection[FItemIndex];

    UpdateUIState();

    if not assigned(FCurrentItem) then exit;

    FCurrentItem.Refresh();
    UpdateGridState();

    with dataGrid do begin
        row := fixedRows;
        col := fixedCols;
        Select();
    end;
end;
Теперь все, что нам необходимо сделать при смене пункта в выпадающем списке - изменить ItemIndex. При этом мы помним, что в выпадающем списке хранятся индексы, или в случае категорий -1.
procedure TDataEditForm.ParametersComboBoxChange(Sender: TObject);
var cIndex : integer;
begin
    cIndex := ParametersComboBox.ItemIndex;
    ItemIndex := integer(ParametersComboBox.Items.Objects[cIndex]);
end;
Следующий шаг, который нам предстоит - настройка таблицы, и вывод в нее текущих значений. Это будет куда более интересным занятием, но уже в следующей статье. Содержание:
  1. Практика #1. Постановка задачи.
  2. Практика #2. Настройка таблицы.
  3. Практика #3. Абстракция
  4. Практика #4. Обобщения и кэширование.
  5. Практика #5. Конечная реализация
Метки:  generics  |  ООП 

Комментарии

Нет комментариев
- Имя
- e-mail*
- Сайт
вы можете использовать теги [i],[b],[code],[quote]
Дополнительно