Основы языка Visual FoxPro

Язык VFP это сильно дополненный и расширенный язык xBase. В Visual FoxPro язык программирования объектно-ориентированный, то есть базовой конструкцией языка является понятие класса. Исходный же вариант xBase это чистейший структурный язык, с базовым понятием процедур и функций. Таким образом, современный язык программирования Visual FoxPro допускает совмещать как и программирование "по старинке" описанием массы процедур, так и в стиле ООП, создавая сложную иерархию классов.

Разумный же выбор стиля программирования сделает Ваши программы читабельными, легкими для понимания, с другой стороны, позволит Вам быстро создавать мощные приложения. Как уже говорилось, язык Visual FoxPro сильно перегружен языковыми конструкциями, стандартными функциями и операторами. Это сделано из-за соображений совместимости со старыми версиями FoxPro. Дать полное описание всех конструкций языка представляется невозможным из-за огромного количества материала. Поэтому мы отсылаем читателя к Visual FoxPro Online Documentation справочной базе данных по программированию VFP. Размер этой базы данных около 80 Mb, и ее можно установить при установке самого VFP себе на жесткий диск или же оставить на оригинальном CD-диске. Здесь же мы будем стараться приводить описания только тех языковых конструкций, которые потребуются нам для более полного изложения материала. Если в приводимых программах Вы встретите незнакомую функцию или оператор, попробуйте найти его описание в Visual FoxPro Online Documentation, в крайнем случае, напишите мне, и я обязательно подробно про него расскажу.

Итак, здесь рассматриваются:

1. Понятие типов данных и массивов
2. Венгерская нотация
3. Понятие программной единицы (процедуры) и их типы
4. Операторы
5. Организация процедур и функций
6. Циклы и условные операторы
7. Реакция на события
9. Форма элемент Вашего приложения

Понятие типов данных и массивов

Типы данных.

Если Вы уже имели какой-то опыт в программировании, Вам не нужно особенно объяснять что такое типы данных и с чем их едят. Если же Вы понятия не имеете о данных и об их типах, внимательно прочитайте этот раздел. Здесь же мы рассмотрим основные особенности типов данных в VFP.

Первое, что отличает VFP от других языков программирования, это то, что в VFP все переменные динамические с неявным объявлением типа. То есть, Вы всегда можете создать переменную, некоторое время ее использовать и потом удалить. При создании переменная всегда имеет тип logical со значением false и тип ее фиксируется (определяется) при первом присваивании переменной какого-либо значения. То есть, при первом присваивании переменная меняет свой тип на тип присваиваемых ей данных. Изменить свой тип переменная может только один раз, при первом присваивании. Переменные могут быть следующих типов:

Logical: Логический тип, возможные значения .T. или .F. Точки обязательны это наследство старого языка xBase

Numeric (float): Числовой с плавающей точкой

Character (string): Строковый - строка символов. Раньше максимальная длина строки была равна 255 символов, теперь 64 Kb.

Date: Дата. Значение определяется как дата с установленным разделителем в установленном формате в {} скобках. Например, по умолчанию, {12/31/99}. Более подробно см. команду определения типа даты SET DATE.

DateTime: Дата и время. Тоже что и дата, но хранит также и время. Более подробно мы рассмотрим этот тип позже.

Object: Тип-объект. Переменная такого типа хранит или ссылку на объект или значение NULL В этой таблице перечислены наиболее часто используемые типы.

Кроме того, в некоторых случаях, могут создаваться переменные с любым типом данных, разрешенном в таблицах, то есть, например, memo, general и др. (См. команды SCATTER и GATHER).

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

Объявление типа области действия переменной производится ниже указанными командами. Внимание! Объявление типа области действия переменной всегда должно предшествовать ее первому присваиванию! По умолчанию, переменные без объявления области действия становятся частными (приватными) о чем более подробно будет сказано чуть ниже.

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

PUBLIC gcMyName, gdCurrentDate gcMyName = Иван Никитин gdCurrentDate = date()

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

Локальные переменные существуют и доступны только в том модуле, в котором они объявлены. Эти переменные объявляются командой LOCAL и удаляются из памяти автоматически при завершении выполнения программного модуля. Как мы уже говорили, в VFP все переменные динамические.

Как мы убедились, VFP очень бережно работает с памятью, создавая переменные частными по умолчанию и удаляя их из памяти при завершении модуля, в котором они были созданы. Иногда, правда, приходятся самим удалить переменные. Это можно сделать командой RELEASE. Как мы отмечали выше, переменные не могут менять свой тип. Но, используя удаление переменной из памяти, можно создавать такую же переменную, но с другим типом. Я не представляю себе, для чего это нужно реально, но все же вот пример, как это сделать (После символов && - примечания): PUBLIC gMultiTypeVar && Это глобальная переменная

gMultiTypeVar = 1.00 && Ее тип числовойRelease gMultiTypeVar
gMultiTypeVar = "Hello" && Теперь это строка
Release gMultiTypeVar
gMultiTypeVar = {01/01/2000} && А сейчас - дата

Венгерская нотация

В Вашем приложении могут быть сотни и тысячи переменных. Чтобы избежать путаницы и долгих размышлений при отладке программы типа: "A[i,j] что за массив и чего он здесь вообще делает?" была предложена и успешно используется стройная система именования переменных и других объектов программы.

По родине предложившего эту систему программиста она названа Венгерская нотация. Система именования очень проста. Прилагаемо к VFP, имя переменной начинается с 2-х буквенного префикса, 1-я буква которого область действия переменной, 2-я буква ее тип. То есть:

lc - (local character) локальная символьная
ld - (local date) локальная типа дата
gn - (global numeric) глобальная числовая
ga - (global array) глобальный массив и так далее.

Кроме того, желательно все объекты и классы также объявлять согласно венгерской нотации, но об этом позже. Само имя переменной должно быть максимально информативным. Например, хорошо объявленные переменные:

ldCurrentDate
gaMyCustomers
gnAppVersion

Всячески следует избегать имен переменных типа A[i,j], K1, L2 R0 и т.п. так часто приводимых в старых учебниках по программированию. Единственным исключением, пожалуй, являются переменные циклов I и J. Это "вечные" имена для создания циклов.

Понятие программной единицы (процедуры) и их типы

Как мы уже говорили, Ваше приложение состоит из какого-то определенного набора программных единиц, или модулей (этот термин авторский, "чистого" понятия модуля в VFP нет). В VFP есть несколько типов программных единиц. На наш взгляд все исполняемые программные единицы в VFP можно отнести к двум большим группам:

Программные единицы
Формы

В отличие от старых версий FoxPro, VFP имеет понятие формы определенного описания окна (формы), которое может самостоятельно исполнятся. В старых версиях FoxPro существовало понятие Экрана (screen).Это не одно и то же, что и форма.

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

Таким образом, в отличие от экранов FoxPro форма может быть самостоятельно исполнена без генерации программного модуля. Более подробно мы рассмотрим формы позже, а пока перейдем к другой группе программных единиц.

Любая программная единица это набор операторов VFP, выполнение которых приводит к желаемому результату. В зависимости от назначения и способа построения программные единицы делятся на следующие группы:

Программы и процедуры
Процедурные файлы и описание классов
Функции, определенные пользователем (UDF)
Хранимые процедуры и триггеры
Программы запросов
Программы меню

Четкого различия на уровне операторов между всеми этими группами нет. Просто они по разному строятся (например, запросы и меню стоятся автоматически), и соответственно имеют разное назначение. Здесь мы пока не приводим подробное описание каждой группы, лишь обозначим общую идеологию этого построения и типы файлов, в которых сохраняются эти программные единицы. Любая программная единица хранится в файле. Стандартным расширением для такого файла является расширение PRG. При выполнении или при построении проекта этот файл компилируется в p-код и затем выполняется. Компиляция проходит очень быстро и обычно пользователь VFP даже не замечает время, потраченное VFP на компиляцию при выполнении. Соответственно, при выполнении построенного проекта компиляция уже не нужна, так как проект строится на основе уже скомпилированных модулей.

Наборы готовых "кирпичиков" процедур можно объединять в файлы (об этом см. ниже), что дает нам процедурные файлы. Кроме того, есть большой класс програмных элементов, именуемый User defined function (UDF), но об этом ниже.

Хранимые процедуры это такие же наборы процедур, хранимых в базе данных. Об этом см. раздел "Понятие базы данных". Программы запросов и программы меню строятся автоматически и соответственно имеют расширения QPR и MPR. Как правило, Вам не нужно их менять или править. Более подробно об этом мы рассмотрим в разделах применение RQBE и Построение меню.

Операторы

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

Операторы (команды) VFP могут быть как простыми, так и очень сложными и длинными, равно как и команда, выполняемая оператором, может быть простой, а может быть и весьма сложной и емкой. Конечно же, здесь мы не ставим перед собой цель подробно объяснить все команды VFP, для этого у нас попросту нет ни времени, ни сил, ни объема, и, в конце концов, для этого существует Help (справочная информация), но несколько простых оператором мы рассмотрим.

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

SELECT ;
Name, Address ;
FROM Customers ;
INTO TABLE Forieners ;
WHERE not Country = Россия

Организация процедур и функций

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

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

Самая верхняя процедура обычно называется главной (стартовой) программой и, собственно, и есть основной единицей Вашего приложения (хотя это и необязательно, как мы покажем ниже). При вызовах процедуры может случиться так, что какая-то процедура (или функция) вызывает саму себя. Это называется рекурсией. С рекурсией в VFP следует обращаться крайне осторожно. VFP поддерживает до 128 рекурсивных вызовов, не более, поэтому мы настоятельно рекомендуем не пользоваться рекурсивными вызовами.

Процедура может принимать при ее вызове параметры от вызывающей программы. Параметры могут объявляться как частные переменные (ключевое слово PARAMETERS) или как локальные переменные (ключевое слово LPARAMETERS). Как именно объявлять параметры дело Вашего вкуса. Число передаваемых параметров может быть меньше, чем число объявленных параметров. В этом случае "лишние" параметры процедуры инициализируются в значение false (.F.). Пример оформления процедуры:

  Procedure MyProc
parameter nScope
? "Число pi="+str(nScope)
return

Соответственно вызов этой процедуры выглядит так:

  Do MyProc with 3.14  

Программа, дойдя до оператора Do MyProc, произведет поиск файла myproc.prg (или откомпилированного myproc.fxp) и произведет вызов этой процедуры. При вызове переменной-параметру nScope будет передано значение 3.14, и начнет исполняться процедура, пока не будет достигнут конец файла myproc.prg или не будет встречен оператор RETURN. При этом управление будет передано на следующий оператор после Do MyProc.

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

Do MyProc with 3.14 in MyProcFile,

где MyProcFile имя процедурного файла.

Функции очень похожи на процедуры, за исключением двух ключевых моментов:

Функции могут вызываться из выражения, а не только прямым вызовом DO;
Функции могут возвращать результат своей работы;

То есть, можно сказать и так, если процедура что-то выполняет, то функция что-то вычисляет и сообщает об этом вызывающей программе. Конечно же, это деление весьма условно, и, как мы убедимся, более удобно использовать функции.

Итак, функция начинается ключевым словом FUNCTION и завершается словом RETURN. После RETURN обычно следует переменная или выражение, которое будет возвращено вызывающей программе. Если это выражение отсутствует или отсутствует само ключевое слово RETURN, то предполагается возврат логического выражения .T. (истина).

Пример функции, которая вычисляет что-то:

  FUNCTION MyFUNC  
  PARAMETER X, Y, Z  
  LOCAL Result  
  Result=x+y+z  
  RETURN Result  

Вызов этой функции может быть таким:

MyVar = MyFunc(MyParam1, MyParam2, MyParam3)

или таким:

Something = MyFunc(Param1) + MyFunc(Param2)

или даже так:

=MyFunc()

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

Число переданных параметров в функции всегда можно узнать с помощью функции PARAMETERS(), или проверить параметры функцией EMPTY(), которая вернет .T., если выражение, переданное в нее, является пустым (то есть, .F. или пустая строка или 0 или пустая дата или NULL)

В последнем примере видно как функция вызывается без фиксации возвращаемого результата (кстати говоря, тут и сам знак = не нужен, но так читабельнее). То есть в этом случае, возвращаемый результат нигде не сохраняется. В этом случае, функция ведет себя как типичная процедура, выполняя что-то, а не вычисляя. Кстати, большинство функциональных возможностей VFP реализовано именно как функции.

Далее мы увидим, что методы классов тоже реализуются именно как функции. Функции, определенные Вами, в VFP называются User Defined Function, то есть функции, определенные пользователем. UDF это очень мощный инструмент структурного программирования.

Циклы и условные операторы

Если Вы знакомы со структурным программированием, то Вам особенно не нужно объяснять, что такое циклы и условные операторы. Но все же вкратце на этом остановимся.

Условный оператор позволяет выполнять тот или иной блок программы в зависимости от какого-то условия:

IF lExpression <часть кода 1> ELSE <часть кода 2>
ENDIF

lExpression любое логическое выражение. Если оно истинно, то выполняется "часть кода 1", иначе "часть кода 2". В VFP существует масса функций, результат которых является логическим и поэтому может использоваться в этой конструкции.

Циклы позволяют выполнить какую либо часть кода несколько раз. Соответственно, в VFP существуют несколько видов циклов:

Циклы с предусловием
Циклы перечисления
Циклы "для каждого"

Циклов с послесловием, таких как Паскалевский цикл repeat until, в VFP нет. Но, при желании, их можно легко реализовать. Циклы с предусловием это циклы, выполняющиеся до тех пор, пока условие выполнения истинно. Проверка условия происходит перед очередным циклом, отсюда и название.

Такой цикл строится такой конструкцией :

DO WHILE lExpression
<операторы тело цикла>
ENDDO

Цикл перечисления это цикл, в котором происходит последовательное наращивание (или уменьшение) какого либо значения, то есть его перечисление.

Типичный цикл перечисления:

FOR I = nMin TO nMax STEP nStep
<операторы тело цикла>
ENDFOR

В этом случае, происходит последовательное увеличение переменной цикла (в данном примере I) от минимального значения nMin, до максимального nMax, с шагом приращения nStep.

Циклы "для каждого" очень удобно использовать для последовательного просмотра массивов или коллекций (о них мы поговорим позже). В этом случае, произойдет выполнение операторов цикла для каждого элемента этого массива или коллекции:

Приведем два примера использования этого цикла. Первый пример создается массив и затем последовательно выводятся его элементы оператором (?) :

  DIMENSION cMyArray(3)  
  cMyArray[1] = 'A'  
  cMyArray[2] = 'B'  
  cMyArray[3] = 'C'  
  FOR EACH cMyVar IN cMyArray  
  ? cMyVar  
  ENDFOR  

В следующем примере с помощью OLE Automation создается объект книги Excel и последовательно выводятся имена листов этой книги:

  oExcel = CREATE("Excel.Application")  
  oExcel.Workbooks.ADD  
  FOR EACH oMyVar IN oExcel.sheets  
  ? oMyVar.name  
  ENDFOR  

Во всех циклах можно прервать выполнение цикла оператором EXIT или прекратить выполнение текущей итерации (перейти на следующую итерацию) с помощью команды LOOP.

Реакция на события

Как мы уже отмечали, VFP реализует систему, управляемую событиями. Поговорим об этом более подробно. Первые языки программирования и первые подходы к программированию реализовали модель, управляемую данными. То есть программа чего-то там обрабатывала, и в зависимости от данных (исходных, вычисленных, сохраненных и проч.) выполняла тот или иной свой блок.

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

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

Соответственно, суть системы управляемой событиями это описание реакции системы на определенные события. Нетрудно видеть, что алгоритма здесь нет, потому что мы не можем предсказать события. Мы можем лишь заявить следующее: "В случае того-то и того-то система поведет себя так-то и так-то". Таким образом, программирование системы заключается в описании реакции на события. Это очень важный момент в понимании подобных систем. Основное отличие от обычных подходов в том, что мы описываем только те события, которые нас интересуют, оставляя для остальных стандартную (ранее описанную) реакцию. То есть, если при создании обычных программ, Вы были обязаны описать все функциональные блоки Вашей программы, то в данном случае, Вы описываете лишь то, что отличает Вашу программу от стандартного поведения системы, как бы говоря: "А в остальном веди себя, как и прежде".

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

* Старт <инициализация Вашего приложения>
READ EVENTS
* Завершение
CLEAR EVENTS

И все. Где именно это размещать мы покажем позже. А пока просто примите это к сведению.

Наиболее ярким примером средств управления событиями, за исключением классов, пожалуй, является меню VFP. Система программирования VFP является весьма мощной, настолько, что Вы всегда можете не просто создать меню в своем приложении, но и изменить, дополнить или кардинальным способом поменять меню самого VFP. Это делается с помощью команд управления меню, которых довольно много. Однако, к счастью, концепция RAD, и именно построитель меню позволяет Вам создать сложное, полнофункциональное меню, вообще не прибегая к написанию кода. Сам процесс построения меню мы рассмотрим позже, здесь лишь отметим, что его результатом обычно является полученная программа (файл с расширением *.MPR) , которая будучи однократно запущенной, довольно быстро завершит свое выполнение и начнет отслеживать события (мышку, горячие клавиши) как обычное Windows-меню.

Кроме этого примера, можно привести команды VFP ON :

ON KEY LABEL - отслеживает нажатия на клавиши или мышь
ON ERROR - отслеживает ошибки выполнения

Попробуйте следующее. В среде VFP в командном окне введите строку:

On error MessageBox( Ошибка! )

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

Функция LocFile() возвращает имя выбранного пользователем файла, но если пользователь отказывается от выбора, то происходит ошибка. Попробуем ее отследить:

  local lcSelectedFile, llError, lcOnError  
  llError = .F.  
  lcOnError = ON("ERROR")  
  On Error llError = .T.  
  lcSelectedFile = locfile("readme.txt", "txt, me", "Файл")  
  On Error &lcOnError  
  if llError MessageBox("Файл не выбран")  
  else  
  Modify file (lcSelectedFile)  
  endIf  

Комментарии к примеру: В переменной lcSelectedFile сохраняется полное имя выбранного файла, переменная llError сначала устанавливается в .F. и, в случае ошибки в .T., lcOnError сохраняет предыдущую установку ON ERROR, которая считывается функцией ON( ERROR ), восстанавливается установка макроподстановкой &lcOnError, файл открывается с помощью команды Modify file (lcSelectedFile). И еще комментарий, не нужно принимать этот пример в качестве оптимального решения. На самом деле все можно сделать куда более элегантнее, это всего лишь пример перехвата ошибки (реакции на ошибку) в процессе выполнения программы.

Форма элемент Вашего приложения

Форма это не программа. В старых версиях FoxPro существовало понятие экрана, то есть программы, строящей какое либо окно, набор окон или оформление экрана со всеми элементами управления. Такие программы автоматически создавались при построении проекта или с помощью команды Generate в меню. В любом случае, после изменения в образе этого экрана, Вы должны были снова сгенерировать экранную программу (файл SPR), чтобы изменения вступили в силу. Как уже отмечалось выше, Visual FoxPro это объектно-ориентированная среда, и здесь уже появляется понятие формы.

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

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

 

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

Итак, форма это не программа, а класс. Класс формы хранится в файле с расширением SCX (SCT дополнительный файл). Экземпляр формы создается командой DO FORM, при этом, соответственно выполняются события (методы) формы, которые возникают при ее загрузке. При выполнении формы (если это не указана специально) создается объектная переменная. Этим процессом можно управлять, указывая имя переменной в команде DO FORM:

* Объявим переменную для формы
public goMyForm
* Запустим форму, в переменной будет создан объект
DO FORM MyForm.scx NAME goMyForm
* а теперь закроем форму, обращаясь к ее методу
goMyForm.Release

Иван Никитин

Автор публикации

не в сети 3 месяца

admin

Комментарии: 1Публикации: 123Регистрация: 10-12-2000
Материалы по теме
Оставить комментарий
//////////////// ///////////////
Авторизация
*
*
Генерация пароля