Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

лабы / gorev_akhajan_makakshiripov_ehffektivnaja_rabota_s_subd

.pdf
Скачиваний:
52
Добавлен:
26.04.2015
Размер:
3.17 Mб
Скачать

передачи нулевого указателя (null pointer), то его следует передавать как целое число по значению.

Для передачи параметров в Visual Basic следует использовать следующий синтаксис:

[Optional][ByVal | ByRef][ParamArray] varname[( )][As ParamType]

Опции ByVal или ByRef определяют, что параметр будет передан по значению или по ссылке. В следующей таблице показано, к каким результатам приведет передача параметров по значению или по ссылке различного типа:

Тип

По значению

По ссылке

параметра

 

 

Integer

Помещает в стек

Помещает в стек

 

вызовов 16-битовое

вызовов 32-

 

значение.

битовый адрес.

 

 

Этот адрес

 

 

обеспечивает

 

 

ссылку на 16-

 

 

битовое значение

 

 

параметра.

Long

Помещает в стек

Помещает 32-

 

вызовов 32-битовое

битовый адрес в

 

значение. Этот адрес стек.

 

обеспечивает ссылку

 

 

на 32-битовое

 

 

значение параметра.

 

String

Преобразует строку

Помещает 32-

 

символов в формат,

битовый указатель

 

принятый в С, с

в стек.

 

нулевым (CHR(0))

 

 

символом в конце.

 

 

Помещает 32-

 

битовыйый адрес этой строки в стек.

Помимо правильного использования параметров, довольно важным при использовании функций DLL является применение указателя.

Указатель (handle) - это число, уникально идентифицирующее объект.

Среда Windows поддерживает очень много объектов различного типа. Это разнообразные окна, блоки памяти, меню, шрифты, палитры и т. д. Допустим, вы хотите нарисовать голубую линию; тогда вы должны создать объект "голубой карандаш". Функция Windows API CreatePen() вернет указатель на этот "карандаш", который вы затем можете использовать для рисования. Когда вы закончите работу, этот объект можно убрать, передав указатель в функцию

DestroyObject().

Рассмотрим несколько примеров для демонстрации возможностей Windows API. Начнем с таких задач, которые невозможно выполнить стандартными средствами.

Давайте посмотрим, как сделать в Visual FoxPro список, в котором будут появляться все устройства, доступные на данном компьютере. При этом для дисководов гибких дисков должна проверяться их готовность к работе (наличие дискеты). Этот список лучше всего оформить в виде класса. Тогда при добавлении его в форму мы сразу получим требуемый элемент управления.

Список доступных логических устройств для данного компьютера мы можем получить с помощью следующей функции API Windows (в синтаксисе C, как приведено в файле

WIN32API.HLP):

DWORD GetLogicalDriveStrings (DWORD nBufferLength, LPTSTR lpBuffer);

Ⱦɚɧɧɚɹ ɜɟɪɫɢɹ ɤɧɢɝɢ ɜɵɩɭɳɟɧɚ ɷɥɟɤɬɪɨɧɧɵɦ ɢɡɞɚɬɟɥɶɫɬɜɨɦ %RRNV VKRS Ɋɚɫɩɪɨɫɬɪɚɧɟɧɢɟ ɩɪɨɞɚɠɚ ɩɟɪɟɡɚɩɢɫɶ ɞɚɧɧɨɣ ɤɧɢɝɢ ɢɥɢ ɟɟ ɱɚɫɬɟɣ ɁȺɉɊȿɓȿɇɕ Ɉ ɜɫɟɯ ɧɚɪɭɲɟɧɢɹɯ ɩɪɨɫɶɛɚ ɫɨɨɛɳɚɬɶ ɩɨ ɚɞɪɟɫɭ piracy@books-shop.com

Эта функция возвращает список доступных устройств в виде строки символов. Тип устройства можно определить с помощью функции

UINT GetDriveType (LPCTSTR lpRootPathName);

Эта функция возвращает одно из следующих значений:

0 - тип устройства не определен;

1 - нет загрузочного сектора;

2 - устройство для сменного носителя данных;

3 - жесткий диск;

4 - сетевое устройство;

5 - дисковод для лазерных дисков;

6 - виртуальный диск.

Эта функция поможет нам украсить список устройств соответствующим его типу изображением.

Для определения готовности дисковода к записи нам потребуется еще одна функция, которая переопределяет реакцию операционной системы на критические ошибки, например, на отсутствие дискеты в дисководе при попытке записи:

UINT SetErrorMode(UINT fuErrorMode);

Эта функция возвращает предыдущую установку реакции на ошибки. В Visual FoxPro нет такого многообразия типов данных, как в Си ++, поэтому тип возвращаемого этими тремя функциями значения DWORD (двойное слово) или UINT (целое без знака) заменим имеющим 4

бита INTEGER.

Теперь, проведя необходимую теоретическую подготовку, можно приступать к практическому созданию требуемого класса.

В Project Manager при активной вкладке Classes щелкнем на кнопке New и в диалоговом окне New Class укажем название создаваемого класса, класс, на основании которого он создается, и имя библотеки, где он будет храниться. Будем надеяться, что к этому моменту у вас уже есть собственный набор классов и класс для списка в том числе.

Дадим этому классу имя lstDrives и установим для него следующие свойства:

BoundColumn = 2 (две колонки);

ColumnLines = .F. (без разграничительных линий);

FontSize = 12 (размер шрифта, обычно лучше использовать 10, но отдельные буквы можно увеличить).

Код для создания требуемого списка в событии Init может выглядеть следующим образом:

*Определяем функцию для получения списка доступных

*устройств

DECLARE INTEGER GetLogicalDriveStrings IN Win32API AS GetDrive;

INTEGER, STRING

*Определяем функцию для определения типа устройства

DECLARE INTEGER GetDriveType IN Win32API AS GetType; STRING

*Описываем локальные переменные

LOCAL cLetter, nLetter, NType, nDrivesNum

*Резервируем строку для размещения списка доступных

*устройств

lpString=SPACE(200)

*Определяем размер буфера для строки nBuffSize=LEN(lpString)

*Получаем список устройств

= GetDrive(nBuffSize,@lpString)

*Определяем количество устройств nDrivesNum = OCCURS(":",lpString)

*Получаем их имена

FOR nLetter = 1 TO nDrivesNum

cLetter = UPPER(" " + SUBSTR(lpString,AT(":",lpString,nLetter)-1,1)) * Определяем тип устройства

www.books-shop.com

nType = GetType(ALLTRIM(cLetter + ":\"))

*Добавляем обозначение устройства (первая колонка)

*и его тип (вторая колонка) в список

This.AddItem(cLetter, nLetter, 1) This.List(nLetter, 2) = STR(nType)

*Задаем соответствующее изображение

DO CASE

CASE NType = 2

This.Picture(nLetter) = "FLOPPY.BMP"

CASE NType = 3

This.Picture(nLetter) = "DRIVE.BMP"

CASE NType = 4

This.Picture(nLetter) = "NET.BMP"

CASE NType = 5

This.Picture(nLetter) = "CDROM.BMP"

ENDCASE

ENDFOR

Все необходимые файлы изображений вы найдете на дискете. Нам их пришлось создать заново, так как обширная библиотека изображений, поставляемая с Visual FoxPro, содержала подходящие изображения только в формате ICO 32x32. В подобных списках лучше смотрятся изображения 16x16. Впрочем, это легко сделать с помощью утилиты Imagedit из состава Visual FoxPro. Здесь только необходимо учесть, что при выводе изображения Visual FoxPro очень своеобразно обходится с белым цветом, считая, что его вообще нет. Если вы выведете изображение, содержащее белый цвет, например, на серую кнопку, то те места изображения, которые были белые, станут серыми. Для сохранения белого цвета для каждого изображения надо создать двойника - маску (файл с расширением MSK), в котором на месте белого цвета должен располагаться черный. В качестве примера этой операции можно руководствоваться изображениями, которые использует Wizard (Мастер) для графических кнопок управления в форме.

В событии Click класса запишем следующий код:

LOCAL cLetter

cLetter = This.DisplayValue + ":"

*Если устройством является дисковод для гибких дисков,

*то проверяем наличие

*дискеты с помощью метода IsDiskIn

IF ALLTRIM(This.Value) = "2"

IF This.IsDiskIn(cLetter) = .T. This.Parent.cmdOk.Enabled = .T. ELSE

= MESSAGEBOX("Вставьте дискету в выбранное устройство!",0,; "Не готов дисковод")

IF This.IsDiskIn(cLetter) = .T. This.Parent.cmdOk.Enabled = .T.

ELSE

This.Parent.cmdOk.Enabled = .F. ENDIF

ENDIF

RETURN ENDIF

This.Parent.cmdOk.Enabled = .T.

Для определения наличия дискеты в дисководе создадим в нашем классе путем выбора команды New Method в меню Class специальный метод IsDiskIn() и запишем в него следующий код:

LPARAMETERS cDrive

LOCAL lOldError

*Регистрируем функцию SetErrorMode, которая определяет,

*как ОС реагирует на некоторые критические ошибки,

*в том числе дисковые операции

DECLARE INTEGER SetErrorMode IN Win32API INTEGER

* Определяем наличие и сохраняем имя обработчика ошибок lOldError = ON('ERROR')

www.books-shop.com

ON ERROR lDiskError = .T.

*Задание функции SetErrorMode с аргументом 1 позволяет

*приложению самому обрабатывать критические ошибки

*вместо ОС

nOldErrorState = SetErrorMode(1)

*По умолчанию считаем, что ошибки нет, если не так,

*значение будет изменено

lDiskError = .f.

*Пытаемся найти NUL файл в загрузочной области

*указанного устройства

lDriveState = FILE(cDrive + "\NUL") IF .NOT. lDiskError

IF lDriveState lDriveOk = .t.

ELSE

lDriveOk = .f. ENDIF

ELSE

lDriveOk = .f. ENDIF

*Восстанавливаем прежний обработчик ошибок

IF .NOT. EMPTY(lOldError) ON ERROR DO (lOldError)

ELSE

ON ERROR ENDIF

*Восстанавливаем реакцию на ошибки ОС nRestState = SetErrorMode(nOldErrorState) RETURN lDriveOk

После этого класс списка можно помещать в любой требуемой форме.

Во втором примере использования функций Windows API рассмотрим возможность ускорения выполнения графических операций.

Сначала создадим форму, в которой будем выводить зигзагообразную линию средствами Visual FoxPro (используя метод Line). Для этого в методе Paint формы запишем следующий код:

nY = 1

FOR nX = 5 TO 375 STEP 5 ThisForm.Line(nX, nY*100) nY = -nY

ENDFOR

Теперь попробуем такую же линию нарисовать средствами Windows API. В методе Paint второй формы запишем:

***Возвращает указатель на контекст указанного окна

DECLARE INTEGER GetDC IN WIN32API INTEGER

*HWND Указатель окна

***Стирает контекст указанного окна

DECLARE INTEGER ReleaseDC IN WIN32API INTEGER, INTEGER

*HWND Указатель окна

*HDC Указатель контекста устройства

***Рисует линию с текущей позиции до указанных координат

DECLARE INTEGER LineTo IN WIN32API INTEGER, INTEGER, INTEGER

*HDC Указатель контекста устройства

*int nXEnd x-координата конечной точки линии

*int nYEnd y-координата конечной точки линии

***Обновляет текущее положение указанной точки

DECLARE INTEGER MoveToEx IN WIN32API INTEGER, INTEGER, INTEGER, STRING

*HDC Указатель контекста устройства

*int X x-координата нового текущего положения

*int Y y-координата нового текущего положения

*LPPOINT адрес предыдущего положения (NULL in FoxPro)

***Функция GetFocus не имеет параметров и возвращает HWND активного окна

DECLARE INTEGER GetFocus IN WIN32API

www.books-shop.com

mynull = .NULL. pmhand = GetFocus()

IF pmhand<<>>0 && Если есть открытое активное окно hmdc = GetDC(pmhand)

= MoveToEx(hmdc, 0, 0, mynull) nY = 1

FOR nX = 5 TO 375 STEP 5

= LineTo(hmdc, nX, nY*100) nY = -nY

ENDFOR

= ReleaseDC(pmhand, hmdc) ELSE

WAIT WINDOW "Нет активного окна" ENDIF

Запустите обе формы и вы убедитесь, что второй вариант работает в несколько раз быстрее. Для рисования линии средствами Windows API нам пришлось использовать четыре функции. Передаваемые параметры перечислены в строчках комментария после объявления функций.

В этом примере мы используем новое понятие - контекст устройства (device context). Что это такое?

Контекст устройства - это совокупность параметров, описывающих условия работы с каким-либо устройством.

Использование контекста устройства позволяет программисту сосредоточиться на выполнении какого-то действия (например, на написании кода для рисования) и избежать имеющих вспомогательное значение операций. Для рисования такими вспомогательными операциями могут быть установленное разрешения экрана, число поддерживаемых данным устройством цветов и т. д. Если вы пишете код для рисования красного прямоугольника, то можете быть уверены, что Windows обеспечит рисование этого объекта с учетом возможностей того устройства, которое выбрано для его отображения, будь то экран, принтер или плоттер.

С точки зрения программиста такой подход обозначает рисование в объекте, называемом контекстом устройства. При этом контекст устройства может рассматриваться как "черный ящик", обладающий определенными характеристиками. Важнейшие из них:

Система координат, которая может быть представлена такими фиксированными единицами, как сантиметры, дюймы или пикселы, или комбинированными единицами.

Параметры рисуемой линии (объект Pen) определяют цвет, ширину и стиль линии.

Параметры закрашивания (объект Brush) определяют цвета, используемые для заполнения фона, и палитру для графических команд.

Шрифт (объект Font) описывает шрифт, используемый при работе команд вывода данных.

Взаключение обсуждения проблем, связанных с использованием функций Windows API, затронем еще один важный вопрос. Он связан с тем, что многие функции Windows API в качестве параметров используют структуры.

При использовании Visual Basic у программиста не появится никаких проблем - это средство разработки поддерживает структуры. Visual FoxPro не поддерживает структур и программисту придется формировать структуру в виде символьной строки. В качестве примера такой работы приведем фрагмент программного кода для соединения с сервером по телефонной линии. Само соединение предварительно создается в специальной утилите внешнего доступа Dial-Up Networking. Windows хранит параметры созданного соединения в регистре. В Visual FoxPro требуемый фрагмент будет выглядеть так:

DECLARE INTEGER RasDial IN rasapi32 ; STRING DialExtetions, ;

STRING cPhonebookfile, ;

STRING @ cParameters, ; INTEGER nCallBack, ; STRING cCallBack, ; STRING @ nConnHandle

nConnHandle = CHR(0)+CHR(0)+CHR(0)+CHR(0) && Указатель соединения

www.books-shop.com

cConnName = "RcomPPP" && Имя соединения cPhone = "2594277"

&& Номер телефона, подключенного к серверу cUserName = "Administrator" && Имя пользователя cPassWord = "AndreyG" && Пароль для соединения cDomain = "" && Имя домена Windows NT

* Формируем структуру

cParam = CHR(28)+CHR(4)+CHR(0)+CHR(0)+; PADR(cConnName+CHR(0), 256+1)+; PADR(CHR(0), 128+1)+;

PADR(CHR(0), 128+1)+; PADR(cUserName+CHR(0), 256+1)+; PADR(cPassword+CHR(0), 256+1)+; PADR(cDomain+CHR(0), 15+1)+" "

* Выполняем соединение

nRes = RasDial(NULL, NULL, @cParam, 0, NULL, @nConnHandle)

Равный по функциональности фрагмент, написанный на Visual Basic, будет выглядеть так:

Private nConnHandle As Long, nRes As Long

'Описываем структуру параметров соединения

Private Type DIALPARAMS dwSize As Long

szEntryName(256 + 1) As String szPhoneNumber(128 + 1) As String szCallbackNumber(128 + 1) As String szUserName(256 + 1) As String szPassword(256 + 1) As String szDomain(15 + 1) As String

End Type

Private ConnParams As DIALPARAMS

'Функция для установки соединения

Private Declare Function RasDialA Lib "rasapi32" _ (DialExtetions, _

cPhonebookfile, _

ByRef Parameters As DIALPARAMS, _ nCallBack As Long, _

cCallBack, _

ByRef hConnHandle As Long) As Long

`Присваиваем значения параметрам nRes = 0

nConnHandle = 0

`Это значение dwSize устанавливается при внешнем доступе из Windows 95 ConnParams.dwSize = 1052

' Наименование соединения в регистре Windows ConnParams.szEntryName(0) = "R" ConnParams.szEntryName(1) = "c" ConnParams.szEntryName(2) = "o" ConnParams.szEntryName(3) = "m" ConnParams.szEntryName(4) = "P" ConnParams.szEntryName(5) = "P" ConnParams.szEntryName(6) = "P" ConnParams.szEntryName(7) = Chr(0)

' Номер телефона берем из регистра

ConnParams.szPhoneNumber(0) = Chr(0) ConnParams.szCallbackNumber(0) = Chr(0) ConnParams.szUserName(0) = "A" ConnParams.szUserName(1) = Chr(0) ConnParams.szPassword(0) = Chr(0) ConnParams.szDomain(0) = Chr(0)

`Выполняем соединение

nRes = RasDialA(Null, Null, ConnParams, 0, Null, nConnHandle)

9.2. Конструируем форму

www.books-shop.com

При описании различных действий, возникающих при работе с данными, мы уже многократно говорили о формах, которые составляют основу интерфейса любого приложения для обработки данных.

В этом параграфе мы более детально разберем процесс проектирования форм для работы с данными при использовании различных СУБД.

Формы предназначены для представления, ввода и редактирования данных удобным и привычным для пользователя способом. Помимо этого в формы можно включать различные объекты, реагирующие на события (системные или пользовательские). С помощью этих объектов можно разрабатывать простые и интуитивно понятные интерфейсы пользователя для управления данными.

Несмотря на то, что вы полностью можете описать форму с помощью программирования, как правило, более простым и быстрым способом будет визуальный. При этом Конструктор формы возьмет на себя труд перевода ваших пожеланий на соответствующий язык программирования. Язык, который используется в Visual Basic, а значит, и в Access, в отличие от Visual FoxPro не является объектно-ориентированным, тем не менее он объектный. Таким образом, какой бы путь создания формы вы ни выбрали, работа будет заключаться в создании объектов и обеспечении требуемой функциональности за счет использования их свойств и методов. Большинство свойств позволяют указать действия, которые должны выполниться при наступлении событий, доступных для конкретного объекта. Используя Конструктор формы, с помощью окна Свойства (Properties) мы получаем легкий доступ к любому из свойств и можем описать действия объекта в ответ на события. В коде событий мы можем использовать методы объектов, например, для перемещения на него курсора (придания статуса активного объекта в форме).

Например, в форме "Прием заказов" мы будем использовать командные кнопки, комбинированный список и текстовое поле. Для переноса элементов управления на проектируемую форму можно использовать панель инструментов с элементами управления. Для установки некоторых визуальных свойств формы, например цвета фона и шрифта, можно использовать панель инструментов для форматирования. При этом очень часто использованию панелей инструментов есть хорошая альтернатива в виде всплывающего меню, вызываемого при нажатии правой кнопки мыши и являющееся контекстным по отношению к выделенному элементу.

Формы при работе с данными используют свой набор данных. Организация процесса взаимодействия формы с данными происходит посредством установки свойства RecordSource (Источник данных) и связанных с конкретными полями источника данных объектов. Если вы хотите отображать с помощью элементов управления данные из полей, которые не относятся к источнику данных, то прямое указание в свойстве ControlSource (Данные) ни к чему не приведет. Вам необходимо организовать синхронизацию записей между данными, входящими в источник данных, и данными, не входящими, и, естественно, использовать несвязанное поле для отображения информации, не относящейся к источнику данных. Если вы ничего не поняли из этой фразы, то правильно сделали. Как правило, так не поступают, потому что подобный путь требует сложного и не оправдывающего себя кодирования. Лучший способ - это создание запроса, который одновременно может отобразить данные из нескольких таблиц. Помимо этого используйте подчиненные формы. При этом необходимо иметь поля, по которым вы можете связать данные в основной и подчиненной формах. Еще проще вызывать форму, содержащую нужные вам, но хранящиеся в другом источнике данные, динамически, с помощью реакции на события. Во многих случаях этого более чем достаточно.

Иногда, при решении более сложных задач, когда вам необходимо, чтобы никакие обстоятельства, вплоть до перебоев в энергопитании, не смогли нарушить установленные правила взаимодействий между данными, вам необходимо использовать транзакции. Для использования транзакций в Access необходимо работать с такими наборами данных, создание и доступ к которым осуществляются с помощью методов объектов доступа к данным (DAO).

Опишем форму, которую мы будем создавать в качестве примера.

На основе таблицы Order_ создается форма для принятия заказа. Ни одно из полей в таблице Order_ пользователь непосредственно не заполняет. Путем использования комбинированных списков и вызываемых форм он заполняет другие таблицы, при этом автоматически заполняются поля в новой записи таблицы Order_. Перед началом редактирования новой записи начинается транзакция.

Поле key_order заполняется автоматически, так как имеет тип Счетчик. Поле key_salman заполняется именем текущего пользователя приложения (для примера на Access) или выбирается из списка продавцов (для примера на Visual FoxPro). Заказчик выбирается из списка заказчиков по фамилии, имени и адресу или заносится вновь (для примера на Access), при этом необходимо вызвать форму для заполнения данных о заказчике. При вызове формы для заполнения данных о заказчике также начнется транзакция, но не вложенная, а параллельная, так как данные о заказчике будут сохраняться в любом случае, для того чтобы потом "засыпать" его проспектами и приглашениями на презентации новых моделей автомобилей.

После этого, на основе данных, хранящихся в таблице Model, подбирается модель и, если она

www.books-shop.com

есть на складе и удовлетворяет заказчика, то заполняется запись в таблице Account. После этого завершается транзакция и запись вносится в таблицу физически.

Создание формы "Прием заказов" на Visual FoxPro

Начнем с систематизации данных о способах создания новой формы в Visual FoxPro. Таких способов достаточно много:

Использование Мастера форм обсуждалось ранее. Это эффективный способ создания простых форм или "заготовок" для более сложных форм.

Выбор команды New из меню File главного меню Visual FoxPro. В открывшемся диалоговом окне New необходимо щелкнуть на кнопке Form.

Выполнение команды CREATE FORM.

В Project Manager выделение пункта Form и выбор New.

Независимо от выбранного способа загружается Конструктор формы, окно которого с основными визуальными инструментами представлено на рис. 9.1. При этом в главном меню Visual FoxPro появляется меню Form, с помощью которого можно выполнить следующие действия:

Рис. 9.1.

New property - позволяет добавить свое свойство для создаваемой формы.

New method - позволяет добавить свой метод для создаваемой формы.

Edit Property/Method - позволяет удалить добавленные свойства и методы из описания формы или изменить комментарий.

Include File - позволяет определить файл, содержащий директивы компилятора.

Create Form Set - создает набор форм - объект, который будет являться контейнером по отношению к нескольким формам и панелям инструментов.

www.books-shop.com

Remove Form Set - удаляет набор форм, если он создан.

Add New Form - позволяет добавить форму в набор форм.

Remove Form - удаляет форму из набора форм.

Quick Form - запускает Построитель формы (Form Builder), с помощью которого можно быстро создать простую форму для работы с данными из одной таблицы и в последующем использовать как заготовку в Конструкторе формы. Построитель формы представляет собой упрощенную версию Мастера формы.

Run Form - запускает разрабатываемую форму.

Для ускорения разработки формы служат несколько панелей инструментов.

Панель инструментов Form Designer предназначена для быстрого перехода или запуска различных элементов Конструктора формы. Она появляется автоматически при запуске Конструктора формы. Назначение кнопок на этой панели описано на рис. 9.2.

Рис. 9.2.

Панель инструментов Layout предназначена для быстрого изменения расположения объектов на завершающем этапе создания формы. Ее возможности будут вам ясны, если вы посмотрите на рис. 9.3. Перед выполнением какого-либо действия предварительно нужно выбрать объект или группу объектов, удерживая клавишу Shift и щелкая на них мышкой. Операции выравнивания, которые выполняются относительно группы объектов, за образец берут самый большой размер, иначе перед выполнением операции выравнивания необходимо удерживать клавишу Ctrl. Если выбранное действие не дало желаемого результата или вовсе привело к нежелательным последствиям, не стоит отчаиваться, вспомните про кнопку Undo на стандартной панели инструментов Visual FoxPro.

www.books-shop.com

Рис. 9.3. Панель инструментов Layout

Панель инструментов Form Controls позволяет включать в разрабатываемую или редактируемую форму тот или иной элемент управления. На рис. 9.4 описаны кнопки данной панели инструментов.

Рис. 9.4.

www.books-shop.com

Соседние файлы в папке лабы