Учебный центр "ANIT Texno Inform"
Опубликован: 25.06.2014 | Доступ: свободный | Студентов: 2596 / 851 | Длительность: 24:39:00
Специальности: Программист
Лекция 13:

Коллекции (массивы) строк и компоненты для них

< Лекция 12 || Лекция 13: 12 || Лекция 14 >

Списки выбора TListBox и TComboBox

В Lazarus есть еще два компонента, которые используют тип TStrings, или говоря иначе, массивы строк. Это списки выбора TListBox и TComboBox. Такие списки обычно используют в программах не для редактирования текста, а для выбора пользователем одного или нескольких строк. TListBox - это прямоугольный список, похожий на TMemo, а TComboBox - выпадающий список.

Чтобы познакомиться с ними, закроем текущий проект и создадим новый. Сразу же форму переименуем в fMain, сохраним проект в папку 14-02 под именем MyStrings, а модулю главной формы, как обычно, дадим имя Main. В свойстве Caption формы напишите Выбор города, свойство BorderStyle сделайте bsDialog, чтобы пользователь не мог изменять размеры окна. Свойство Position сделайте poDesktopCenter, чтобы окно появлялось по центру - такие мелочи нужно сразу же устанавливать, чтобы программа хорошо смотрелась. Размеры формы сделаем такими: Height = 210, Width = 340.

Свойства Name у компонентов в этом проекте мы менять не будем. Так будет наглядней при обращении к этим компонентам внутри кода. Хотя в реальных проектах рекомендуется давать компонентам соответствующие их назначению названия. Ну, вот вам пример. Если на форме всего одна кнопка, то её имя можно оставить по умолчанию - Button1. Итак понятно, о какой кнопке идет речь. А вот если их три, как в прошлом проекте, тогда разумней дать им понятные имена. bSave, bRead, и bClear - ясно, что речь идет о кнопках, раз первая b (button), и что это кнопки "Сохранить", "Читать" и "Очистить". Если у вас слабые знания английского - не беда, можете давать русские названия, но писать латиницей: kSohranit, kChitat и kOchistit (k - knopka). Поскольку в нашем примере все компоненты будут в единственном экземпляре, менять их свойства Name мы не будем.

Вернемся к проекту. На вкладке Standard Палитры компонентов найдите компоненты TListBox и TComboBox:

Компоненты TListBox и TComboBox

Рис. 14.3. Компоненты TListBox и TComboBox

В левом верхнем углу формы установите TListBox. Обратите внимание на его свойства в Инспекторе объектов. Мы затронем только некоторые, специфичные для TListBox.

Columns - количество столбцов в списке. Если свойство равно нулю или единице, то список выходит в один столбец. Если больше - то столько же будет и столбцов. Если все строки не умещаются в списке, полоса прокрутки будет появляться автоматически. Обычно это свойство не меняют - строки в один столбец используются чаще всего.
ExtendedSelect - расширенное выделение. Подразумевает, что пользователь может выделить сразу целый диапазон строк, если, удерживая <Shift>, щелкнет сперва по первой, а потом по последней строке диапазона. По умолчанию, равно True - такое выделение разрешено. В любом случае, пользователь может выделять выборочные строки, щелкая по ним поочередно и удерживая при этом <Ctrl>. Всё вышесказанное работает, только если свойство MultiSelect = True. Не будем изменять это свойство.
Items - главное свойство компонента, массив (коллекция) строк. То же самое, что и свойство Lines у компонента TMemo, имеет тот же тип TStrings, и соответственно, обладает теми же свойствами и методами. Редактор строк также открывается при нажатии на кнопку "" справа от свойства.
MultiSelect - возможность выделения нескольких строк. По умолчанию, равно False - многострочное выделение запрещено. Если вы пожелаете разрешить пользователю многострочное выделение, то вам в коде придется обходить весь список, чтобы выявить выделенные строки.
Sorted - сортировка списка. Если равно True, список сортируется по алфавиту.

Всё остальное примерно соответствует компоненту TMemo. Итак, установим у нашего ListBox1 следующие свойства:

  • Left, Top = 10
  • Height, Width = 150
  • MultiSelect = True

Теперь в свойстве Items кнопкой "" откройте редактор строк и внесите следующие строки (каждый город на отдельной строке):

Москва
Санкт-Петербург
Киев
Вильнюс
Будапешт
Вена
Париж
Берлин
Лондон
Мехико
Оттава
Лос-Анжелес
Сан-Франциско
Нью-Йорк

Если есть желание отсортировать список городов по алфавиту, установите Sorted = True.

Теперь займемся компонентом TComboBox. Установите его правее ListBox1. У него тоже не так много специфичных свойств:

ArrowKeysTraverseList - при True (по умолчанию) разрешает перемещаться по списку кнопками со стрелками. Хотя у меня и при False так можно было перемещаться, может быть, в следующих версиях Lazarus будет иначе.
AutoComplete - при значении True позволяет выбирать элементы из списка, фильтруя их. То есть, если в строке списка вы ввели "А", то выйдут только те элементы, которые начинаются на "А". Если вы ввели следующую "б", останутся только те, что начинаются на "Аб". И т.д. К сожалению, в моей версии Lazarus (v1.0.10) эта возможность недоработана. Если в свойстве установить True и попытаться сделать такую фильтрацию, то выходит run-time ошибка. Я не знаю, будет ли эта ошибка проявляться у вас, ведь новые версии Lazarus выходят достаточно часто, а у вас, скорее всего, уже более новая версия.
AutoCompleteText - переключатели автозавершения ввода, раскрывающее свойство с пятью переключателями. При True переключатель включен, при False - выключен. Имеются следующие переключатели:
  • cbactEnabled - включение опции AutoComplete.
  • cbactEndOfLineComplete - выполняет AutoComplete, только если курсор находится в конце строки.
  • cbactRetainPrefixCase - сохраняет те же символы, которые ввел пользователь.
  • cbactSearchAscending - при True поиск подходящих строк ведется в восходящем порядке, при False - в нисходящем.
  • cbactSearchCaseSensitive - при True поиск чувствителен к регистру символов (различает прописные и строчные), при False - нет.
Однако, увы, и это свойство пока не работает. Я включал все переключатели, но попытка фильтрации строк все равно приводит к ошибкам.
ItemIndex - это очень важное свойство - индекс выделенной строки. По умолчанию равен -1, то есть, никакая строка не выделена. Индексация строк начинается с нуля, но поскольку, мы не будем вручную заполнять ComboBox1, то оставим -1.
Items - такой же массив строк, как и у TListBox. Его также можно заполнить в процессе проектирования, нажав на "" и вызвав Редактор строк. Однако мы не будем сейчас это делать. Заполнять список и изменять ItemIndex мы будем программно, в коде, ведь нужно освоить и эту возможность!
Sorted - как и у TListBox, это свойство сортирует список по алфавиту.
Text - текст, который выводится в строке компонента. При изменении ItemIndex меняется и свойство Text. По умолчанию, там написано имя компонента. Но нам это не нужно, поэтому просто очистите это свойство.

Итак, установим следующие параметры компонента ComboBox1:

Left = 177
Top = 10
Width = 150
    

Строки в свойстве Items мы не заполняли, другие свойства не изменяли.

Далее, нам понадобится простая кнопка TButton. Её параметры:

Left = 10
Top = 170
Width = 150
Caption = Копировать в ComboBox
    

И напоследок, нам понадобится TEdit, чтобы выводить в него выбранную в ComboBox1 строку. Его параметры:

Left = 177
Top = 172
Width = 150
ReadOnly = True (пользователь не может редактировать эту строку)
Text = '' (то есть, очистить это свойство)
    

В результате у вас должна получиться такая форма:

Форма проекта

Рис. 14.4. Форма проекта

Суть программы в следующем: пользователь выбирает в ListBox1 один или несколько городов, нажимает кнопку "Копировать в ComboBox". Мы программно обойдем весь список ListBox1, от первой до последней строки, и каждую выбранную пользователем строку добавим в список ComboBox1. Если мы что-то в него добавили, нам придется установить ItemIndex = 0, то есть, выделить первую строку списка (иначе сразу будет невидно, добавилось туда что-то или нет). Далее, если в строке ComboBox появился какой-то город, нам нужно будет продублировать его в строку Edit1.

Пример, в общем, простой, но он демонстрирует почти все возможности списков. Для реализации задуманного нам потребуется всего два события: нажатие OnClick на кнопку Button1, и изменение OnChange в компоненте ComboBox1. Сгенерируйте их, вот код этих событий:

procedure TfMain.Button1Click(Sender: TObject);
var
  i: integer; //счетчик
begin
  //очистим ComboBox:
  ComboBox1.Clear;
  //далее в цикле обходим весь ListBox, и если строка
  //выделена - копируем ее в ComboBox:
  for i:= 0 to ListBox1.Count -1 do
    if ListBox1.Selected[i] then
      ComboBox1.Items.Add(ListBox1.Items.Strings[i]);
  //если ComboBox не пустой, выделим первый элемент, иначе
  //не выделяем ничего:
  if ComboBox1.Items.Count > 0 then begin
    ComboBox1.ItemIndex:= 0;
    //копируем в Edit1 выделенную строку из ComboBox:
    Edit1.Text:= ComboBox1.Items.Strings[ComboBox1.ItemIndex];
  end
  else ComboBox1.ItemIndex:= -1;
end;

procedure TfMain.ComboBox1Change(Sender: TObject);
begin
  //копируем в Edit1 выделенную строку из ComboBox:
  Edit1.Text:= ComboBox1.Items.Strings[ComboBox1.ItemIndex];
end;
    

Комментарии достаточно подробны, но все же проясним некоторые моменты. Свойство Items у TListBox и TComboBox, а также свойство Lines у TMemo имеют тип TStrings, поэтому ведут себя похожим образом - что работает у одного компонента, будет работать и у другого. Помните, как мы очищали Memo1? То же самое можно сделать и с TListBox, и с TComboBox:

  ComboBox1.Clear;
    

А вот, как мы реализовали обход всех строк в ListBox1:

  //выделена - копируем ее в ComboBox:
  for i:= 0 to ListBox1.Count -1 do
    if ListBox1.Selected[i] then
      ComboBox1.Items.Add(ListBox1.Items.Strings[i]);
    

Свойство Count компонента ListBox1 возвращает количество строк в компоненте. В Инспекторе объектов его не видно, однако все типы TStrings имеют это свойство, а значит, мы сможем таким же образом узнать количество строки ив Memo, и в ComboBox. Поскольку индексация строк начинается не с единицы, а с нуля, то у последней строки списка номер индекса будет Count - 1.

Далее, свойство Selected возвращает True, если указанная в индексе строка выделена. В принципе, мы можем посмотреть выделена ли какая-то конкретная строка, например, девятая:

    if ListBox1.Selected[8] then
    

но гораздо удобней, конечно, обойти весь список в цикле for, как это было сделано в проекте.

Далее мы смотрим - если текущая строка выделена, то мы копируем её содержимое в список ComboBox1:

ComboBox1.Items.Add(ListBox1.Items.Strings[i]);
    

Содержимое каждой конкретной строки можно получить через её свойство Items, которое содержит непосредственно массив строк Strings. Указав индекс нужной нам строки, мы получим её содержимое. Например, получить содержимое третьей строки у ComboBox и Memo можно было бы так:

s:= ComboBox1.Items.Strings[2];
s:= Memo1.Lines.Strings[2];
    

Метод Add() типа TStrings добавляет указанную строку в конец списка. Точно также мы могли бы добавить строку в конец списка Memo или ListBox:

Memo1.Lines.Add('Новая строка');
ListBox1.Items.Add('Новая строка');
    

Вот таким способом мы заполняем список ComboBox1 теми строками, которые были выделены в ListBox1. Однако, поскольку пользователь мог и не выделять никаких строк, и все же нажать кнопку, мы делаем проверку:

  //если ComboBox не пустой, выделим первый элемент, иначе
  //не выделяем ничего:
  if ComboBox1.Items.Count > 0 then begin
    ComboBox1.ItemIndex:= 0;
    //копируем в Edit1 выделенную строку из ComboBox:
    Edit1.Text:= ComboBox1.Items.Strings[ComboBox1.ItemIndex];
  end
  else ComboBox1.ItemIndex:= -1;
end;
    

Тут все достаточно прозрачно: если список ComboBox1 пуст, то его свойство Count будет равно нулю. В этом случае, нам нужно присвоить свойству ItemIndex значение -1, то есть, никакая строка не выделена. Если попытаться установить значение 0, то есть, выделить первую строку, произойдет ошибка, ведь строк-то нет! Если все же строки есть, то мы, во-первых, выделяем в списке первую строку:

    ComboBox1.ItemIndex:= 0;
    

и, во-вторых, мы копируем её в Edit1:

    Edit1.Text:= ComboBox1.Items.Strings[ComboBox1.ItemIndex];
    

Тут происходит вот что: ComboBox1.ItemIndex вернет текущий индекс, то есть, индекс выделенной строки. А ComboBox1.Items.Strings[x] вернет содержимое строки под индексом x, как это происходило у ListBox1 в цикле.

И напоследок, в событии OnChange компонента ComboBox1 мы дублируем код копирования выделенного текста в Edit1 - это событие будет срабатывать, когда пользователь выберет в списке какой-то другой город.

Свойства и методы типа TSrings

В завершение рассмотрим основные свойства и методы типа TStrings - базового класса массива строк, широко используемого в некоторых компонентах. Это может быть как свойство Lines компонента TMemo, так и свойства Items компонентов TListBox и TComboBox. Контейнер TRadioGroup, который мы с вами изучали в "Логические типы, конструкции и компоненты" , также имеет свойство Items типа TStrings. Имеются различные свойства этого типа и у других компонентов, как стандартных, так и созданных сторонними разработчиками. К сожалению, создавать переменные такого типа нельзя, для этого служит другой класс - TStringList, который является наследником TStrings, и обладает более расширенными возможностями. Но о нем мы будем говорить уже в другой лекции. А сейчас рассмотрим основные возможности типа TStrings.

Таблица 14.1. Свойства и методы типа (класса) TStrings
Наименования Описание
Свойства
Count Количество строк в списке
Strings Индексированный массив строк. К отдельной строке обращаются по её индексу, например Strings[0] - первая строка массива.
Text Содержит текст всех строк, включая символы перехода на новую строку, в виде единой строки.
Методы
Add Добавляет строку в конец списка и возвращает индекс добавленной строки. Впрочем, возвращаемое значение можно игнорировать, как это делали мы в примере.
Append Добавляет строку в конец списка, но в отличие от Add, не возвращает никакого значения.
AddStrings Добавляет в конец списка другой список строк, также имеющий тип TStrings.
Clear Очищает список.
Delete Удаляет из списка строку по её индексу. Например, удалить вторую строку из Memo можно так:
Memo1.Lines.Delete(1);
        
Insert Вставляет в список строку по указанному индексу. Весь остальной текст сдвигается вниз. Если текста нет, строка будет вставлена по индексу 0.
LoadFromFile Считывает строки из указанного файла и заполняет ими компонент.
SaveToFile Наоборот, считывает строки из компонента, и сохраняет их в указанный файл. Если файла нет, он будет создан, если есть - перезаписан.
< Лекция 12 || Лекция 13: 12 || Лекция 14 >
Инга Готфрид
Инга Готфрид
Александр Скрябнев
Александр Скрябнев

Через WMI, или используя утилиту wmic? А может есть еще какие более простые пути...