Тема 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(); } Наше приложение готово, конечно, оно далеко несовершенно, но основой для вашей дальнейшей работы служить может.