Строки и списочные компоненты

advertisement
Тема 3. Строки и Списочные компоненты
I. Основные типы данных .NET для C++ и работа со строками
Целочисленные
Byte
8-битовое
SByte
8-битовое целое со
беззнаковое целое
знаком
Int16
16-битовое целое со
Int32
32-битовое целое со
знаком
знаком
Int64
64-битовое целое со
UInt16
16-битовое целое без
знаком
знака
UInt32
32-битовое целое без UInt64
64-битовое целое без
знака
знака
Вещественные
Single
число с плавающей
Double
число с плавающей
точкой с обычной (32
точкой с двойной (64
бита) точностью
бита) точностью
Логические
Boolean
Логическое (булево) значение (true или false)
Другие типы
Char
Unicode (16-битовый) Decimal
Десятичное (128 бит)
символ
значение
IntPtr
Целое со знаком,
UIntPtr
Целое без знака,
значение которого
значение которого
зависит от
зависит от
соответствующей
соответствующей
платформы: 32платформы: 32битовое значение на
битовое значение на
32-битовой платформе
32-битовой платформе
и 64-битовое на 64и 64-битовое на 64битовой платформе
битовой платформе
Классы
Object
Корневой класс для
String
Строка Unicode
иерархии классов
символов постоянной
длины
Пример 1, строки
String ^s = "123456";
String ^s1 = s->Empty; //s1=""; тоже можно
s1="1234";
MessageBox::Show(s->Compare(s,s1).ToString(),"Compare",MessageBoxButtons::OK);
s1=s->Concat(s,s1);
MessageBox::Show(s1,"Concat",MessageBoxButtons::OK);
s1=s->Copy(s); //копирование s в s1
s=s1->Insert (2,"***");
//Вставка "***" со 2-й позиции в s1 (позиции с 0), потом копирование в s
Стандартные функции класса String
s->IndexOf("подстрока") //индекс первого вхождения подстроки с 0 или -1 (не найдено)
s->LastIndexOf("подстрока") //последнего вхождения
s->Length
//длина строки
s->PadLeft(N)
//помещение числа в N позиций с дополнением пробелами слева
s->PadRight(N) //(справа)
s->Remove(N,K)
//удалить K символов, начиная с позиции N
s->Replace(S1,S2) //в s заменить все комбинации символов S1 на S2
s->Substring(N,K) //с позиции N выделить подстроку в K символов (если K не указано - до
конца строки)
Пример 2, строки
int i=i.Parse("123"); //извлечь число из строки
float i=i.Parse("123.45"); //неверный входной формат - FormatException
String ^r=i.ToString();
//см. TryParse из лекции 1
Пример 3, преобразование типов
double d=123.45;
int i=Convert::ToInt32(d);
boolean b=Convert::ToBoolean(d);
String ^s=Convert::ToString(d);
char c=Convert::ToChar(d); //InvalidCastException - Double в Char, можно 65
DateTime dt=Convert::ToDateTime(d); //тоже нельзя, можно "31/12/2013"
String ^r=i.ToString()+" "+b.ToString()+" "+s+" "+c+" "+dt.ToString();
MessageBox::Show(r,"",MessageBoxButtons::OK);
II. Списочные компоненты и работа с ними
1. ListView список с группировкой данных, некоторые свойства
View - вид списка (из 5 стандартных)
/* Columns - заголовки колонок для View = Details или Tile
Groups - позволяет группировать элементы при View != List
HeaderStyle - стиль заголовка колонки при View = Details */
Items - контейнер для элементов
LargeImageList, SmallImageList - иконки (контейнеры ImageList) для основных режимов
просмотра списка
SelectedItems - контейнер для выбранных элементов
MultiSelect - разрешает множественный выбор
CheckBoxes - разрешает выводить окна контроля рядом с элементами при View!=Tile
/* ItemCheck - обработчик события пометки элемента
CheckedItems - свойство для помеченных элементов
CheckedListViewItemCollection - коллекция помеченных элементов
CheckedIndices - индексы помеченных элементов
StateImageList - позволяет задать ImageList вместо чекбоксов контроля
*/
Activation - выбираем элемент через OneClick или TwoClick
HotTracking - позволяет показывать элемент как гиперссылку
GridLines - показывает разделительные линии между элементами
LabelEdit - можно ли редактировать текст элемента
Пример 1. Добавьте список listView1, выровненный на всю клиентскую часть формы.
Свойства ImageList = 3 картинки, создано 2 столбца "Элемент" и "Дата", создано 3
группы.
Реализуйте с помощью верхнего меню "Список" добавление 3 различных видов элементов:
int cnt = listView1->Items->Count+1;
AddItem("HedgeHog"+Convert::ToString(cnt),0); //1,2 с другими названиями элементов
Метод AddItem напишем так, чтобы он добавлял в дополнительный столбец информацию о дате и времени
создания объекта (в первый столбец всегда пишется свойство Text элемента):
private: System::Void AddItem (String ^text, int icon) {
ListViewItem ^ lvi = gcnew ListViewItem();
lvi->Text = text;
lvi->ImageIndex = icon;
ListViewItem::ListViewSubItem ^ lvsi = gcnew ListViewItem::ListViewSubItem();
lvsi->Text = DateTime::Now.ToString();
lvi->SubItems->Add(lvsi);
lvi->Group=listView1->Groups[icon]; //если надо сразу добавлять в группу
listView1->Items->Add(lvi);
}
Реализуйте с помощью верхнего меню "Вид" просмотр списка в различных режимах:
listView1->View = View::LargeIcon; // SmallIcon, Details, List, Tile
Реализуйте удаление элемента, если он выбран в списке (множественный выбор разрешен):
while (listView1->SelectedItems->Count>0) {
ListViewItem ^ item = listView1->SelectedItems[0];
listView1->Items->Remove (item);
}
Возможности, которые можно добавить в решение:
 Сортировка кликом по заголовку столбца в режиме просмотра «Детали» - см. пример в
msdn https://msdn.microsoft.com/ruru/library/system.windows.forms.listview.columnclick(v=vs.110).aspx
 Возможность редактирования выделенного двойным щелчком элемента, например, в
новом окне формы;
 Сохранение/восстановление данных (см. пример 2).
2. ComboBox комбинация редактируемого поля и списка
DropDownStyle - стиль вывода: Simple - редактирование, кнопка раскрытия списка
спрятана, DropDown - выпадающий с редактированием, DropDownList - выпадающий без
редактирования
Text - есть для доступа к редактируемой строке
DropDownWidth, DropDownHeight - ширина и высота выпадающего списка
FlatStyle - стиль окна редактирования: Popup - всплывающее, System - смена цвета
FormatString - форматирование даты, чисел, валют и т.п.
AutoCompleteMode - ???
Примеры:
comboBox1->DataSource = System::Environment::GetLogicalDrives(); //на Load формы
//Заполним список из массива строк:
array <System::String ^> ^s = {"P1","P2","P3"};
for (int i=0; i<s->Length; i++) comboBox1->Items->Add (s[i]);
comboBox1->SelectedIndex = 0; //или comboBox1->SelectedItem = s[0];
//Разберём строку списка по формату:
comboBox1->Items->Add ("123,4567");
label1->Text = String::Format ("S={0,12:N2}",comboBox1->Items[0]);
/*
{ index[,длина][ :формат] }
Квадратные скобки обозначают необязательные элементы.
+длина выравнивание по правому краю, -длина по центру, нету - по левому
формат N2 - 2 знака после запятой
*/
3. ListBox, базовый список, основные свойства
MultiColumn - разрешает вывод содержимого в несколько колонок
ColumnWidth - ширина колонки в пикселах при многоколоночном списке
SelectionMode - сколько элементов можно выбирать за один раз
SelectedIndex - индекс первого выбранного элемента или -1
SelectedItem - выбранный элемент (обычно строка)
Count - количество элементов в списке
Items - изменяемый контейнер с элементами:
String ^s=ListBox1->Items[i]->ToString();
String ^is = this->ListBox1->Items[this->ListBox1->SelectedIndex]->ToString();
//получили строку, по которой выполнен щелчок
Методы: Items->Add(), Items->Insert(), Items->Clear(), Items->Remove("строка"), Items>RemoveAt(номер)
int w=(int)ListBox1->CreateGraphics()->MeasureString(
ListBox1->Items[ListBox1->Count-1]->ToString(), ListBox1->Font).Width;
ListBox1->ColumnWidth = w; //узнали ширину последнего элемента в пикселах
Читаем строки списка из файла, открытого обычной fopen:
char s[128];
fgets (s,128,fp);
String ss = gcnew String (s);
ListBox1->Items->Add (ss->SubString(0,ss->Length-1));
4. CheckedListBox - разновидность с отметкой элементов чекбоксами
5. MaskedTextBox - для ввода данных по шаблону
Свойство Mask: 0 - цифра, 9 - цифра или пробел или пусто, # - цифра или пробел или
пусто или + L - буква ASCII, ? - буква ASCII или пусто, & - символ, C - символ или пусто
A - любая буква или цифра или пусто
. - разделитель дестичный, , - разделитель тысяч, : - разделитель времени / - даты, $ символ валюты
< - в нижний регистр, > - в верхний, | - запрещает влияние "или"
Пример 2. Редактируемый телефонный справочник на
основе списка
Form1: FormsBorder=FixedDialog, MaximizeBox=false,
Size=590,470,
StartPosition=CenterScreen,Text="Справочник"
listBox1: Anchor=Top,Left,Right,
Context=contextMenuStrip1 (с пунктами Добавить и
Удалить), Sorted=true
Остальные элементы размещены на Panel1
(Anchor=Top,Bottom,Left,Right)
textBox1, textBox2 - ввод номера и комментария,
Label1, Label2 - метки с рисунками (свойство Image),
Size=90,90, загружены рисунки того же размера
В пользовательской секции кода модуля Form1.h (после #pragma endregion) начнём писать
нужные для приложения функции.
Логичней и проще всего, если наше приложение будет автоматически читать считывать файл с
данными при загрузке и автоматически сохранять при выходе. Хотелось бы также, чтобы
отсутствие файла в текущей папке не смущало приложение и оно могло бы создать его заново.
Напишем метод LoadFromFile для чтения из файла с фиксированным именем data.txt, а
метод SaveToFile - для записи. Из кода видно, что мы отделяем номер телефона от фамилии или
имени просто символом табуляции \t.
private: void LoadFromFile (String ^File, ListBox^
String ^d, ^b;
listBox1->Items->Clear();
try {
b = System::IO::File::ReadAllText(File);
}
catch (...) {
System::IO::File::WriteAllText("base.txt","");
return;
}
while (b->Length>0) {
int i=b->IndexOf("\n");
if (i<0) continue;
d=b->Substring(0,i);
listBox1->Items->Add(d);
b=b->Substring(i+1,b->Length-d->Length-1);
}
this->listBox1->SelectedIndex = -1;
}
private: void SaveToFile (String ^File, ListBox^
String ^a,^b;
int n=listBox1->Items->Count;
System::IO::File::Delete(File);
for (int i=0; i<n; i++) {
a=listBox1->Items[i]->ToString();
b+=a->Concat(a,"\n");
}
System::IO::File::AppendAllText(File,b);
}
listBox1) { //чтение из файла
listBox1) { //запись в файл
Пункт Добавить контекстного меню, вызываемого нажатием правой кнопки мыши на списке,
обработает и поместит в список данные, которые набраны в полях ввода. При этом он гибко
позволит как редактировать имеющиеся данные (если в списке есть выбранный элемент), так и
создать новый элемент. Повторное добавление одинаковых записей исключим, а действие метода
также будем дублировать нажатие клавиши Enter во втором поле ввода (имя). Заодно сделаем
так, чтобы при нажатии Enter в первом поле ввода курсор автоматически переходил во второе, а
после Enter во втором поле не только добавлялась или изменялась запись, но и очищались поля
ввода с установкой курсора обратно на первое. Это облегчит массовый ввод записей. Лишних
сообщений об ошибках плодить не будем, от пришедших из прошлого века назойливых
модальных окон с кнопкой ОК вообще следует избавляться.
private: System::Void toolStripMenuItem1_Click(System::Object^ sender,
System::EventArgs^ e) { //кнопка Добавить
String ^r="";
String ^s1=this->textBox1->Text->Trim();
String ^s2=this->textBox2->Text->Trim();
if (!(String::IsNullOrEmpty(s1) || String::IsNullOrEmpty(s2))) {
r=r->Concat(s1,"\t",s2);
if (this->listBox1->SelectedIndex!=-1) {
this->listBox1->Items[this->listBox1->SelectedIndex]=r;
this->textBox1->Clear();
this->textBox2->Clear();
this->listBox1->SelectedIndex=-1;
}
else {
if (listBox1->FindString(r)==-1) this->listBox1->Items->Add(r); //не добавлять, если
уже есть в списке
}
}
}
private: System::Void textBox1_KeyDown(System::Object^ sender,
System::Windows::Forms::KeyEventArgs^ e) {
//Обработка Enter в первом поле ввода
if (e->KeyCode == Keys::Enter) this->textBox2->Focus();
}
private: System::Void textBox2_KeyDown(System::Object^
System::Windows::Forms::KeyEventArgs^ e) {
//Обработка Enter во втором поле ввода
if (e->KeyCode == Keys::Enter) {
this->toolStripMenuItem1_Click(this,e);
this->button2_Click(this,e);
}
}
sender,
Первому полю ввода также понадобится обработчик события KeyPress, разрешим там только
цифры, пробел, Enter, Backspace и "минус" (дефис) с "плюсом". Заметим, что это не страхует нас
от появления недопустимых символов, например, из Буфера Обмена. Можно придумать много
способов контролировать это, например, сделать полю своё контекстное меню вместо системного
и контролировать действия Вырезать-Копировать-Вставить, но самый простой путь - обработать
событие Validating от поля и "не выпускать" из него, если что-то введено неверно. Вопросы
корректности формата вводимых данных оставим на совести пользователя, а вообще-то, для этого
есть MaskedTextBox и проверка регулярными выражениями.
private: System::Void textBox1_KeyDown(System::Object^ sender,
System::Windows::Forms::KeyEventArgs^ e) {
//Обработка Enter в первом поле ввода
if (e->KeyCode == Keys::Enter) this->textBox2->Focus();
}
private: System::Void textBox2_KeyDown(System::Object^
System::Windows::Forms::KeyEventArgs^ e) {
//Обработка Enter во втором поле ввода
if (e->KeyCode == Keys::Enter) {
this->toolStripMenuItem1_Click(this,e);
this->button2_Click(this,e);
}
sender,
}
private: System::Void textBox1_KeyPress(System::Object^ sender,
System::Windows::Forms::KeyPressEventArgs^ e) {
//обработка нажатий клавиш в первом поле ввода
char c=(char)e->KeyChar;
if (Char::IsDigit(c) || c== (char)Keys::Back || c==(char)Keys::Enter || c== (char)'-'
|| c== (char)'+') return;
else e->Handled = true;
}
private: System::Void textBox1_Validating(System::Object^ sender,
System::ComponentModel::CancelEventArgs^ e) {
//валидация введённой строки в певом поле ввода
String ^t = "0123456789-+ ";
for (int i=0; i<textBox1->Text->Length; i++) {
String ^s = textBox1->Text->Substring(i,1);
if (t->IndexOf(s)==-1) {
e->Cancel=true; //отменить выход из поля, если недопустимые символы
textBox1->SelectAll();
return;
}
}
}
На закрытие формы будем просто вызывать наш метод сохранения данных в файл, а на загрузку
формы - метод чтения из файла.
private: System::Void Form1_FormClosing(System::Object^ sender,
System::Windows::Forms::FormClosingEventArgs^ e) {
SaveToFile ("base.txt",this->listBox1);
}
private: System::Void Form1_Load(System::Object^
LoadFromFile ("base.txt",this->listBox1);
}
sender, System::EventArgs^
e) {
Пункт меню Удалить сделает свою работу, если он вызван при каком-то выбранном в списке
элементе:
private: System::Void toolStripMenuItem2_Click(System::Object^ sender,
System::EventArgs^ e) { //кнопка Удалить
if (this->listBox1->SelectedIndex!=-1) {
this->listBox1->Items->Remove(this->listBox1->SelectedItem);
if (this->listBox1->Items->Count==0) {
this->listBox1->SelectedIndex = -1;
}
}
}
Обычный клик левой кнопкой по элементу списка разберёт хранящуюся там строку на части
"номер" и "имя" и скопирует их в нижние поля ввода для редактирования.
private: System::Void listBox1_Click(System::Object^ sender, System::EventArgs^ e) {
//клик по списку
if (this->listBox1->SelectedIndex<0) return;
String ^ s = this->listBox1->Items[this->listBox1->SelectedIndex]->ToString();
int i=s->IndexOf("\t");
if (i<0) return;
String ^s1=s->Substring(0,i);
this->textBox1->Text = s1;
this->textBox2->Text = s->Substring(i+1,s->Length-s1->Length-1);
}
Код кнопки Найти выглядит чуть сложнее, но решит, в сущности, простую задачу - пометит
выделенными все элементы списка, хотя бы частично совпадающие с введёнными в первое или
второе текстовое поле строками.
private: System::Void button1_Click(System::Object^ sender, System::EventArgs^ e) {
//кнопка Найти
String ^a;
String ^s1=this->textBox1->Text->Trim();
String ^s2=this->textBox2->Text->Trim();
int n=listBox1->Items->Count;
for (int i=0; i<n; i++) listBox1->SetSelected(i,false); //снять все выделения в списке
for (int i=0; i<n; i++) {
a=listBox1->Items[i]->ToString();
int p=a->IndexOf("\t");
String ^phone, ^name;
if (p>-1) {
phone=a->Substring(0,p);
name=a->Substring(p);
if (!String::IsNullOrEmpty(s1) && phone->IndexOf(s1)>-1 ||
!String::IsNullOrEmpty(s2) && name->IndexOf(s2)>-1) {
listBox1->SetSelected(i,true);
}
}
}
}
Наконец, кнопка Очистить точно не представит проблемы. Она сотрёт содержимое нижних полей
ввода и установит курсор в первое из них.
private: System::Void button2_Click(System::Object^ sender, System::EventArgs^ e) {
this->textBox1->Clear();
this->textBox2->Clear();
this->textBox1->Focus();
}
Наше приложение готово, конечно, оно далеко несовершенно, но основой для вашей дальнейшей
работы служить может.
Download