Visual C++: делаем приложение на основе ListBox
Здесь я приведу пример на работу со списком общего назначения ListBox, а по ListView сделаю попозже отдельную заметку. Для QT5 чем-то похожие коды есть вот тут, а для Builder - здесь.
Как и у других классов библиотеки .NET, у списка ListBox
есть масса свойств и методов, подробно они описаны, разве что, в справке msdn по ссылке выше, а в первую очередь кажутся полезными следующие:
MultiColumn
- разрешает вывод содержимого в несколько колонок;ColumnWidth
- ширина колонки в пикселах при многоколоночном списке;SelectionMode
- сколько элементов можно выбирать за один раз;SelectedIndex
- индекс первого выбранного элемента или-1
;SelectedItem
- выбранный элемент (обычно строка);Count
- количество элементов в списке;Items
- изменяемый контейнер с элементами:
String ^s=ListBox1->Items[i]->ToString(); //получили i-ый элемент как строку 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; //узнали ширину последнего элемента в пикселах
Зато прочитать строки списка из файла с дескриптором fp
, открытого обычной функцией fopen
, будет совсем просто:
char s[128]; //и далее в цикле: fgets (s,128,fp); String ss = gcnew String (s); ListBox1->Items->Add (ss->SubString(0,ss->Length-1));
Но это всё присказка. В качестве работающего примера сделаем на основе списка редактируемый телефонный справочник. Возможно, код его получится неоптимальным, так как будет писаться прямо сейчас, что ж, останется место и для вашего творчества :)
Создадим приложение Windows Forms с одной формой Form1
фиксированного размера, например, у неё могут быть следующие свойства: FormsBorder=FixedDialog
, MaximizeBox=false
, Size=590,470
, StartPosition=CenterScreen
, Text="Справочник"
.
На форме размещён listBox1
, привязанный "якорями" ко всем её сторонам, кроме нижней (Anchor=Top,Left,Right
), для списка также создано контекстное меню 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^ listBox1) { //чтение из файла 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^ listBox1) { //запись в файл 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); }
Пункт Добавить контекстного меню, вызываемого нажатием правой кнопки мыши на списке, обработает и поместит в список данные, которые набраны в полях ввода. При этом он гибко позволит как редактировать имеющиеся данные (если в списке есть выбранный элемент), так и создать новый элемент. Повторное добавление одинаковых записей исключим, а действие метода будем дублировать нажатием клавиши 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^ sender, System::Windows::Forms::KeyEventArgs^ e) { //Обработка Enter во втором поле ввода if (e->KeyCode == Keys::Enter) { this->toolStripMenuItem1_Click(this,e); this->button2_Click(this,e); } }
Первому полю ввода также понадобится обработчик события 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^ sender, System::Windows::Forms::KeyEventArgs^ e) { //Обработка Enter во втором поле ввода if (e->KeyCode == Keys::Enter) { this->toolStripMenuItem1_Click(this,e); this->button2_Click(this,e); } } 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^ sender, System::EventArgs^ e) { LoadFromFile ("base.txt",this->listBox1); }
Пункт меню Удалить сделает свою работу, если он вызван при каком-то выбранном в списке элементе:
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(); }
Наше приложение готово, конечно, оно далеко не совершенно, но основой для вашей дальнейшей работы служить может.
Скачать этот проект Visual C++ в архиве .zip (29 Кб)
26.09.2015, 12:40 [20453 просмотра]