Сценарий создания приложения
Мы не станем возиться с каким-нибудь простеньким примером, а встроим в наше диалоговое окно почти все возможные элементы управления. Работа эта несложная, потому что нам поможет редактор диалоговых окон MVS-2010. Законченный вид диалогового окна представлен на рис. 1.
Рис. 1. Законченное диалоговое окно в полной красе
Как видите, это диалоговое окно предназначено для учета кадров. Это пример довольно скучного делового приложения — неплохо бы сделать с ним что-нибудь такое, что было невозможно во времена перфокарт (вы хоть знаете, что это такое?). Программу слегка оживляют ползунки вредность и лицемерие — классический пример прямого ввода и наглядного отображения данных. Еще интереснее были бы здесь элементы управления ActiveX, но использовать их мы пока не будем, так как пока не научились. Впрочем, вы можете себе позволить добавить в диалоговое окно все, что угодно.
Шаг 1. Генерация каркаса приложения. Выполните команду File→New Project... и в появившемся окне (рис.2) в списке Project types (тип проекта) выберите поддержку MFC в ветви Visual C++ и MFC Application в окне Templates и задайте имя приложения в поле Name, предположим, DiaWin. Проследите, чтобы в поле Location было выбрано корректное имя каталога, в котором будет размещен каталог проекта. Рекомендуется не устанавливать переключатель Create directory for solution для получения более структуры каталогов файлов проекта. После нажатия кнопки ОК будет показано окно (рис. 3), с помощью которого Вы сможете задать свойства проекта, отличные от заданных по умолчанию. Если в этом окне Вы сделаете щелчок ЛКМ по полю Application Type, то в правой части основного окна Вы сможете выбрать параметры приложения в соответствии с рис. 3: выберите, обязательно, тип приложения Dialog Based, язык ресурса – Руский и включите переключатель Use Unicode Libraries. Не устанавливайте переключатель Use HTML dialog, так как это опасно для жизни! Последовательно нажимая на кнопку Next, просмотрите, но не изменяйте параметры приложения для User Interface Features, Advanced Features и Generated Classes. Альтернативно, можно было бы выбирать соответствующие поля – User Interface Features, Advanced Features и Generated Classes – и устанавливать свойства приложения. Если Вы уверены, что не хотите изменять свойства приложения по умолчанию, можете сразу щелкать по кнопке Finish. Таким образом, не приходя в сознание, ИС создаст вам проект, диалоговое окно которого основано на базовом классе CDialogEX.
Рис. 2. Параметры приложения DiaWin
Рис. 3. Окно выбора свойств проекта
После нажатия на эту самую кнопку Finish спец ИС создаст каркас приложения, основу которого составляют два файла: DiaWin.cpp и DiaWinDlg.cpp. Шаг 2. Изучаем текст программы. В файле DiaWin.cpp находится реализация класса приложения CDiaWinApp с единственной, но важной, функцией BOOL CDiaWinApp::InitInstance() и конструктором класса CDiaWinApp::CDiaWinApp(). Кроме того, в этом же файле глобально объявлен объект приложение: CDiaWinApp theApp;
Ознакомьтесь, пожалуйста, с текстом функции CDiaWinApp::InitInstance(), в частности, с комментариями. Строку SetRegistryKey(_T("Local AppWizard-Generated Applications"));
рекомендую просто закомментировать или удалить вовсе, так как она предназначена для создания ключа в реестре Windows, под которым будут сохраняться параметры приложения. Вот следующие строки функции, с моими комментариями: CDiaWinDlg dlg; /* Определяется экземпляр класса диалога. Само по себе диалоговое окно на экране, естественно, пока не появляется*/ m_pMainWnd = &dlg; /* Адрес объекта диалоговое окно запоминается в член-данном m_pMainWnd класса CDiaWinApp */ INT_PTR nResponse = dlg.DoModal();/* Вызываем на выполнение функцию CDialog::DoModal(), вследствие чего на экране появляется
диалоговое окно. Возврат из этой функции произойдет при нажатии пользователем клавиши ОК или Cancel, что в следующем условном операторе и проверяется*/ if (nResponse == IDOK) { /* Доделайте: поместите сюда операторы, которые должны быть выполнены при завершении диалога пользователем с помощью клавиши OK*/ } else if (nResponse == IDCANCEL) { /* Доделайте: поместите сюда операторы, которые должны быть выполнены при завершении диалога пользователем с помощью клавиши Cancel*/ } /* Так как диалог завершен, то функция CDiaWinApp::InitInstance() возвращает значение false, указывая на то, что приложение должно завершиться, а не входить в цикл обработки сообщений*/
Теперь посмотрим на файл DiaWinDlg.cpp. В нем, очевидно из соображений экономии, мастером размещена реализация сразу двух классов: CAboutDlg и CDiaWinDlg. Оба этих класса являются наследниками MFC-класса CDialogEX. Назначение класса CAboutDlg достаточно ясно из его названия (отображает окно с информацией «О программе») и мы его обсуждать не будем, а вот класс CDiaWinDlg собственно и представляет всю «визуальную» часть нашего приложения (если не считать окна About). В конструкторе класса CDiaWinDlg::CDiaWinDlg(CWnd* pParent /*=NULL*/) : CDialogEx(CDiaWinDlg::IDD, pParent) { m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME); }
используется очень полезная MFC-функция AfxGetApp(), которая возвращает указатель на объект theApp, объявленный в другом файле (DiaWin.cpp) и, следовательно, «невидимый» в файле DiaWinDlg.cpp. Этот самый указатель на объект theApp частенько бывает нужен в самых разных местах (файлах, классах) программы и, поэтому, постарайтесь запомнить функцию AfxGetApp(). В данном конструкторе она используется для тривиальной цели: загрузки изображения иконки приложения. Назначение функции CDiaWinDlg::DoDataExchange(), сгенерированной мастером, станет, возможно, понятным позднее. Пока что она, можно сказать, ничего не делает. Следующий фрагмент файла представляет собой так называемую карту сообщений (message map): BEGIN_MESSAGE_MAP(CDiaWinDlg, CDialogEx) ON_WM_SYSCOMMAND() ON_WM_PAINT() ON_WM_QUERYDRAGICON() //}}AFX_MSG_MAP END_MESSAGE_MAP()
В этой самой карте сообщений указаны макросы, которые генерируют заголовки обработчиков сообщений Windows, присутствующие в данном классе. В данном случае это макросы сообщений ON_WM_SYSCOMMAND(), ON_WM_PAINT() и ON_WM_QUERYDRAGICON(), смысл которых Вам может быть понятен (но скорее всего не может) из их названий. Если Вы посмотрите на содержимое заголовочного файла DiaWinDlg.h, то обнаружите там такие объявления (сопоставьте их с приведенной выше картой сообщений):
// Generated message map functions virtual BOOL OnInitDialog(); afx_msg void OnSysCommand(UINT nID, LPARAM lParam); afx_msg void OnPaint(); afx_msg HCURSOR OnQueryDragIcon(); DECLARE_MESSAGE_MAP()
Важное замечание. В принципе Вы вообще (українською взагалі) можете не морочить себе голову с этими картами сообщений, классами, функциями и т.п. ерундой, если не собираетесь получать представление и навыки по разработке приложений Windows в MVS-2010☻.
Как видно из этих строк, объявления функций-обработчиков сообщений Windows, сгенерированные мастером, начинаются с макроса afx_msg. Когда в дальнейшем Вы будете создавать свои обработчики сообщений Windows, ИС MVS-2010 будет добавлять их в карты сообщений подобно тому, как это уже сделано в данном каркасе приложения. Важно. Пока Вы не наберетесь опыта в разработке программ, не редактируйте вручную приведенные фрагменты программы, касающиеся обработчиков сообщений. Делайте это с помощью «Окна свойств» (команда ViewèProperties Window). Этот инструмент позволяет добавлять обработчики сообщений и, что не менее важно, удалять их
Функция CDiaWinDlg::OnInitDialog(), как следует из ее названия, вызывается при инициализации диалогового окна, но до его появления на экране. В частности, в этой функции к системному меню окна добавляется команда вызова окна About и к приложению «привязываются» иконки. Самое важное, что именно в эту функцию надо будет добавлять программный код, обеспечивающий, в частности, засылку начальных значений в визуальные управляющие элементы диалогового окна. Функция CDiaWinDlg::OnSysCommand() предназначена для выполнения команд системного меню. Функция CDiaWinDlg::OnPaint() вызывается при необходимости прорисовки содержимого диалогового окна, например, при первом отображении окна, а также когда окно разворачивается или перекрывается другим окном. Необходимость добавления своего кода в эту функцию возникает редко, например, при добавлении в диалоговое окно какой-либо «картинки». Собственно, всю прорисовку окна выполняет функция CDialogEx::OnPaint() базового класса.
Совет. Выполнять трассировку таких функций, как OnPaint(), практически невозможно из-за того, что при остановке в этой функции она вызывается повторно для прорисовки затертого отладчиком изображения окна и, таким образом, мы попадаем как бы в «вечный» цикл. Выйти из этой ситуации можно очень просто: добавить в функцию вызов макроса TRACE для вывода сообщений в окно Debug ИС MVS-2010
Шаг 3. Сборка и выполнение приложения. С помощью макросов TRACE удобно отслеживать порядок вызова функций приложения и, при необходимости, выводить значения переменных в окно Debug. Добавьте такие макросы в функции этого приложения, выполните компиляцию и сборку приложения, запустите его на выполнение в режиме отладки (с помощью команды F5), проследите за содержимым окна Debug. Обратите внимание, что в окне уже имеются кнопки ОК и Cancel. При закрытии диалогового окна любым способом завершается и выполнение приложения. На этом этапе выполнения работы составьте отчет, в котором отразите последовательность выполнения функций класса CDiaWinDlg, установленную с помощью макросов TRACE. Размещение в окне управляющих элементов Для размещения в окне своих управляющих элементов необходимо перейти к вкладке Resource View в окне решений, раскрыть в ней ветвь Dialog и сделать двойной щелчок ЛКМ по имени ресурса диалогового окна – в нашем случае это идентификатор IDD_DIAWIN_DIALOG (рис. 4). Любое окно, в том числе и Resource View, можно вызвать с помощью соответствующей команды меню View.
Рис. 4. Вкладка Resource View окна решений
После появления диалогового окна для размещения в нем управляющих элементов надо вызвать окно ToolBox (команда меню ViewèToolBox) и просто перетаскивать в окно необходимые управляющие элементы либо выбирать их в окне ToolBox и затем размещать в диалоговом окне путем щелчка ЛКМ (рис. 5). Окно Properties свойств диалогового окна и размещенных в нем управляющих элементов вызывается с помощью команды меню ViewèProperties Window.
Краткая справка по окну свойств Properties
Это окно используется на этапе разработки приложения (design-time) для просмотра и редактирования свойств и событий выбранных объектов, размещенных в визуальном редакторе. Это же окно можно использовать для просмотра и редактирования свойств решения (solution), проекта, файлов и классов. Свойства, отображаемые серым цветом, являются свойствами только для чтения. В верхней части окна свойств имеется выпадающий список, с помощью которого можно выбрать просматриваемый объект. Если в окне редактора выделено сразу несколько элементов, то окно списка пустое. В этом случае отображаются только общие свойства и события выделенных элементов.
Назначение кнопок, размещенных на инструментальной панели окна свойств, приведено в следующей таблице.
Таблица 1. Назначение кнопок, размещенных на инструментальной панели окна свойств Properties
Управление размерами и положением управляющих элементов в окне При размещении элемента управления в диалоговом окне его размеры и положение можно задавать как с помощью мыши, так с помощью клавиш управления курсором. В последнем случае для точного задания размеров управляющего элемента нужно удерживать нажатой клавишу Shift и использовать клавиши управления курсором. Размеры элемента, как и его положение в окне, отображаются в строке статуса главного окна ИС. В окне свойств размеры и координаты элемента не отображаются. Единица измерения – так называемые «диалоговые единицы» (DLU – dialog unit). Фактическая величина DLU базируется на размере шрифта диалогового окна, обычно это MS Sans Serif 8. Горизонтальная DLU – это среднее значение ширины символов шрифта, деленное на 4. Вертикальная DLU – это среднее значение высоты символов шрифта, деленное на 8.
Рис. 5. Окна ToolBox и Properties ИС MVS-2010 Шаг 4. Размещение элементов управления в диалоговом окне. Перетащите мышью нужные элементы из окна ToolBox в создаваемое диалоговое окно, а затем разместите и отмасштабируйте их в соответствии с рис. 1. Предварительно удалите из окна статический текст, размещенный мастером. Теперь коротко рассмотрим элементы управления нашего диалогового окна. Статический текст (Static Text) для поля Фамилия. Этот элемент просто выводит на экран данные и собственно к диалогу с пользователем прямого отношения не имеет. Расположив ограничивающий прямоугольник, введите текст в поле Caption окна Properties (размеры прямоугольника можно при необходимости изменить). Это единственный элемент управления «статический текст», который здесь упоминается, но в нашем диалоговом окне есть и другие подобные элементы: создавайте их по аналогии. Все они имеют один и тот же идентификатор IDC_STATIC, равный -1, но это не важно, поскольку доступ к ним программе не нужен. Кстати, значением идентификатора в файле ресурсов является просто целое число. Поле ввода (Edit Control) Name, расположенное правее текста Фамилия. Поле ввода — основное средство ввода текста в диалоговых окнах. Смените его идентификатор с IDC_EDIT1 на IDC_NAME. Остальные свойства оставьте такими, какие предлагаются по умолчанию. Заметьте: по умолчанию устанавливается Auto HScroll==true, т.е. текст по мере заполнения текстового поля прокручивается справа налево. Начальное значение для этого поля в окне свойств задать невозможно – мы это будем делать позже. Обратите внимание на свойство Number: если выбрать для него значение true, то в поле можно будет вводить только числа. Поле ввода Биография. Это такое же поле ввода, как и предыдущее, но с установленным свойством Multiline=True (многострочное) и AutoHScroll=False. Присвойте ему идентификатор IDC_BIO, бог с ним. Группирующая рамка (Group Box) Вид оплаты. Этот элемент нужен только для того, чтобы сгруппировать два переключателя. Введите его название (caption) Вид оплаты.
Переключатели (Radio Button) Сдельная оплата и Ставка. Разместите эти переключатели, которые часто называют радио-кнопками, в группирующей рамке Вид оплаты. Кнопке Сдельная оплата присвойте идентификатор IDC_CAT, установите Group=True и TabStop=true. Идентификатор для кнопки Ставка оставьте без изменений (IDC_RADIO2), установите Group=False и TabStop=true. Убедитесь в том, что у обеих кнопок установлено свойство Auto=true (пo умолчанию) и что только у кнопки Сдельная установлено свойство Group. При правильном задании этих свойств Windows гарантирует, что пользователь сможет одновременно выбрать только одну из кнопок. Группирующая рамка Вид оплаты никак не сказывается на их функционировании. Группирующая рамка Страховка. В этом элементе управления размещаются три флажка. Разместите в окне рамку и введите ее название — Страховка.
Примечание. Позже, установив порядок обхода элементов в диалоговом окне, Вы добьетесь того, чтобы группирующая рамка Страховка следовала за последней кнопкой рамки Вид оплаты. Только не забудьте установить у элемента управления Страховка свойство Group=True, чтобы «завершить» предыдущую группу. Если этого не сделать, особой беды не будет, но, запустив программу под отладчиком, Вы получите предупреждающее сообщение.
Флажки (Check Box) Жизни, По инвалидности и Медицинская. Разместите эти элементы управления в группирующей рамке Страховка. Примите свойства, предлагаемые по умолчанию, но замените идентификаторы на IDC_LIFE (жизнь), IDC_DIS (по инвалидности) и IDC_MED (медицинская). В отличие от переключателей (радио-кнопок), флажки ведут себя независимо — пользователь сможет устанавливать любую их комбинацию.
Комбинированный список (Combo Box) Профессиональные навыки. Это первый из трех типов комбинированных списков. Измените его идентификатор по умолчанию на IDC_SKILL, а затем задайте Туре=Simple. В поле значения свойства Data введите наименования трех профессий, отделяя их друг от друга символом «точка с запятой». Не забудьте «растянуть» по вертикали список Профессиональные навыки в окне редактора, иначе на этапе выполнения Вы не увидите введенных строк (названий профессий). Замечание. Чтобы выровнять два или более элемента управления, щелкните сначала первый, а затем, удерживая клавишу Shift, все прочие. Далее вызовите меню FormatèAlign и выберите подходящий способ выравнивания. То же самое можно сделать с помощью кнопок панели инструментов Dialog Editor. Любую панель инструментов можно вызвать, если щелкнуть ПКМ в свободной области панели инструментов ИС и выбрать из контекстного меню нужную панель
Этот тип комбинированного списка называется простым (Simple). В поле ввода, располагающемся вверху, пользователь может вводить любые данные и/или при помощи мыши выделять элемент в окне списка. Заметьте, что у списка есть свойство Sort, управляющее сортировкой элементов списка. Замечание. Можно без запуска программы на выполнение тестировать диалоговое окно, в том числе и его элементы, с помощью команды FormatèTest Dialog
Комбинированный список (Combo Box) Образование. Замените идентификатор IDC_COMBO2 на IDC_EDUC, а прочие параметры оставьте такими, какими они предлагаются по умолчанию. Введите в поле Data три уровня образования: Детясли, Детсад, Верховна Рада. В этом комбинированном списке (Type==Dropdown) пользователь сможет набрать в поле ввода все, что ему заблагорассудится или, щелкнув стрелку, раскрыть прикрепленное окно списка и выбрать любой элемент (мышью или клавишами-стрелками Up/Down). Список (List Box) Отдел. Замените идентификатор IDC_LIST1 на IDC_DEPT, а прочие параметры оставьте такими, какими они предлагаются по умолчанию. В этом списке пользователь сможет выбрать лишь один элемент мышью, клавишами Up/Down или введя первый символ нужной строки. Строки этого списка мы будем вводить уже в программном коде, так как он не имеет соответствующего свойства, доступного из графического редактора. Как это сделать, Вы узнаете чуть попозже. Комбинированный список (Combo Box) Язык. Замените идентификатор IDC_COMBO4 на IDC_LANG и присвойте свойству Туре значение Drop List. Введите в поле Data названия трех языков: Говяжий, Длинный и Суржик. Как и прежде, разделяйте названия языков точкой с запятой, о чем есть подсказка в нижней части окна Properties. В этом комбинированном списке пользователь сможет выбирать элементы только из раскрывающегося списка. Для этого он должен будет, щелкнув стрелку, указать нужную строку или ввести ее первую букву и при необходимости уточнить выбор при помощи стрелок управления. Полосы прокрутки (Horizontal Scroll Bar) Вредность и Лицемерие. Прежде всего отметим, что эти полосы прокрутки не имеют ничего общего с полосами, используемыми для скроллинга окна. В данном случае мы их будем использовать для наглядного ввода дискретного значения соответствующей величины, трактуя ее единицу измерения как процент. Разместите два ползунка, отмасштабируйте их и присвойте им идентификаторы IDC_HARM и IDC_HYPO. Значения остальных свойств оставьте без изменения. Кнопки (Button) OK и Cancel. Эти кнопки уже есть и имеют идентификаторы IDOK и IDCANCEL. Мы не будем изменять значения свойств этих кнопок. Чуть позже Вы узнаете об особом предназначении идентификаторов по умолчанию IDOK и IDCANCEL. Произвольный значок (Picture Control). В диалоговом окне при помощи элемента управления Picture Control можно отобразить любой значок или растровое изображение, если они определены в описании ресурсов. Воспользуемся уже имеющимся в ресурсе MFC-значком программы, имеющем идентификатор IDR_MAINFRAME. Установите свойство Туре=Icon, a Image=IDR_MAINFRAME. Идентификатор IDC_STATIC не изменяйте, так как он Вам не нужен. Можно добавить в диалоговое окно свою «картинку», но для этого надо сначала добавить bmp-файл в ресурс, где ему будет присвоен идентификатор, и указать этот идентификатор в свойстве Image.
Итак, все элементы управления размещены. Можно проверить вид диалогового окна с помощью команды тестирования Ctrl+T. Окно должно выглядеть примерно так, как показано на рис. 6. Обратите внимание, что в списке Отдел присутствуют случайные данные. Рис. 6. Вид диалогового окна
Шаг 5. Корректировка порядка перехода между элементами управления. Проверьте порядок перехода между элементами управления. Выполните команду FormatèTab Order. Укажите мышью порядок переключения между элементами управления, который будет соблюдаться при нажатии клавиши Tab. Для этого щелкните мышью каждый из элементов управления в требуемом порядке, как показано на рис. 7, а затем нажмите Enter. Рис. 7. Порядок перехода между элементами управления диалогового окна
Если в процессе указания последовательности выбора элементов управления с помощью клавиши Tab Вы ошибетесь – начните сначала с помощью двойного щечка ЛКМ по первому элементу управления. Замечание. В текст заголовка элемента управления «статический текст» (например, Фамилия) можно вставить знак &. В период выполнения символ, за которым стоит этот знак, будет подчеркнут, как это часто выглядит в меню. Это позволяет переходить к нужным элементам управления нажатием Alt+«подчеркнутый символ». Ясно, что такие символы должны быть уникальны в пределах диалогового окна.
В принципе на этом этапе Вы можете собрать и запустить приложение на выполнение и диалоговое окно должно выглядеть и вести себя так же, как и при его тестировании с помощью ИС. Однако Вы еще не завершили разработку приложения.
Шаг 6. Добавление член-данных в класс CDiaWinDlg. Итак, на этом этапе мы имеем ресурс «диалоговое окно», т.е. собственно окно с расположенными в нем элементами управления, для которых мы задавали значения (некоторых) свойств. Где зафиксированы выполненные нами действия, т.е. все эти идентификаторы элементов управления, их размеры, расположение и др. свойства? Вот именно – в сущности, называемой ресурсом. В данном случае это файл DiaWin.rc. В текстовом виде этот файл можно посмотреть (и только посмотреть – не редактируйте его, пока не наберетесь опыта), если на вкладке Solution Explorer окна решений выбрать файл DiaWin.rc, с помощью ПКМ вызвать для него контекстное меню и выбрать в нем команду View Code. Рекомендую посмотреть на его текст и закрыть файл, не модифицируя! Кроме этого ресурса у нас имеется еще и класс CDiaWinDlg, который уже имеет некоторые сгенерированные мастером ИС методы и в который мы можем (и будем!) добавлять свои методы и член-данные для того, чтобы «добраться» до данных, которые пользователь будет вводить или выбирать с помощью созданных нами элементов управления, ну и решать какую-либо уже прикладную задачу. (Кстати, пока Вы «возились» с размещением элементов управления в диалоговом окне, никаких изменений в объявлении и реализации класса CDiaWinDlg, т.е. в файлах DiaWinDlg.h и DiaWinDlg.cpp, не появилось.) Рассмотрим конкретную задачу. Например, как сделать так, чтобы при запуске приложения в поле фамилия (Edit Control) уже была отображена какая-нибудь конкретная фамилия, а когда пользователь введет новую фамилию, то как ее узнать, т.е. поместить в какую-нибудь свою переменную? Для решения подобного рода задач MFC предлагает сопоставить с каждым элементом управления специальную переменную, которая будет член-данным класса диалога CDiaWinDlg. Если до показа диалогового окна этой переменной мы присвоим некоторое значение, то оно будет (автоматически) отображено в соответствующем поле, а после закрытия диалогового окна в этой переменной будет храниться значение, введенное пользователем. Естественно, эта «автоматика» обеспечивается некоторым уже встроенным в класс программным кодом. Тип переменной, которую мы будем сопоставлять с конкретным управляющим элементом, не может быть совершенно произвольным. Например, для случая Edit Control этот тип не может быть булевским или указателем, а для Radio Button, как нетрудно догадаться, тип связанной переменной может быть булевским, но может быть и целым. Сделаем что-нибудь конкретное. Для добавления связанной переменной к управляющему элементу Фамилия (Edit Control) нужно выделить в диалоговом окне поле ввода фамилии (идентификатор IDC_NAME), с помощью ПКМ вызвать контекстное меню и выбрать в нем команду Add Variable, в результате чего появится окно определения переменной (рис. 8). Это же окно можно вызвать и с помощью команды ProjectèAdd Variable. Обсудим назначение элементов этого окна. Прежде всего отметим, что значения полей взаимосвязаны. Когда мы заполним все обязательные поля и нажмем кнопку Finish, в заголовочный файл DiaWinDlg.h класса диалога будет добавлено объявление этой переменной, а в реализацию конструктора класса (файл DiaWinDlg.cpp) мастер добавит инициализацию этой переменной нулевым значением. Рис. 8. Окно добавления переменной, связанной с полем ввода фамилии IDC_NAME
Назначение полей окна мастера добавления переменной приведено в табл.1. Таблица 1
Воспользуйтесь поиском по сайту: ©2015 - 2024 megalektsii.ru Все авторские права принадлежат авторам лекционных материалов. Обратная связь с нами...
|