Советы тем, кто программирует на VB & VBA
Совет 335. Как передать параметры между процедурами
Совет 336. Формирование сокращенного имени каталога
Совет 337. Инициализация ActiveX DLL
Совет 338. Как определить события инициализации и завершения работы ActiveX DLL
Совет 339. Как определить полное имя Host-приложения
Совет 340. Как ввести табуляцию в Textbox
Совет 341. Динамическая передача данных из списка в текстовое поле
Совет 342. Как переписать удаленные файлы в Recycle Bin
Совет 343. Поддержка контекстного меню для Rich Textbox
Совет 344. Дайте возможность пользователям изменять размеры элементов управления
Совет 345. В циклах лучше используйте GetInputState
Совет 346. Работа с Интернетом в VB-приложениях
Совет 347. Как определить текущие размеры экрана
Совет 348. Как установить связь с Microsoft Excel с помощью OLE DB
Совет 349. Простой ввод данных в элементе управления MSFlexGrid
Совет 350. Десять советов «как не нужно поступать при работе с ASP»
Совет 335. Как передать параметры между процедурами
Мы уже отмечали (см. КомпьютерПресс № 11’99), что передача данных между формами Visual Basic представляет некоторую проблему в связи с тем, что эти формы не могут хранить у себя глобальные переменные. К приведенным ранее рекомендациям по организации информационного взаимодействия форм предлагаем еще один вариант, который использует процедуры Property Let и Get, реализующие новые свойства формы.
Например, вы хотите, щелкнув мышью на форме Form1, загрузить форму Form2 и передать ей некоторый числовой параметр.
Для этого включите в Form2 следующий код:
Private myVar as Variant Public Property Get PassVar() As Variant PassVar = myVar End Property Public Property Let PassVar(ByVal vNewValue As Variant) If IsNumeric(vNewValue) Then myVar = vNewValue End Property
Разумеется, вы можете установить более точный тип свойства, но такие изменения нужно сделать в обеих процедурах — Get и Let. Обратите также внимание, что мы включили код проверки для определения, является ли передаваемое значение числом.
Теперь, чтобы записать или прочитать значение свойства, используйте следующий код в форме Form1:
Private Sub Command1_Click() With Form2 .PassVar = 25 'записать в Form2 .Show ' показать Form2 Debug.Print .PassVar ' прочитать значение End With End Sub
Совет 336. Формирование сокращенного имени каталога
Довольно обычной является ситуация, когда нужно выводить на форму (например, в виде метки) имя каталога, но при этом длина его полного названия выходит за пределы отведенного места. В этом случае можно использовать прием, когда из названия удаляют имена каталогов, начиная от корневого. То есть реализуется такой вариант вывода:
d:\My-sites\Visualmy\kolesov\kolesov.htm ' полное имя d:\...almy\kolesov\kolesov.htm ' имя, выданное на экран
Для преобразования имен можно использовать следующий код:
Public Function PathNameShort$(Lens%, PathNameFull$) ' формирование сокращенного имени каталога 'ВХОД: ' Lens - макс. длина названия ' PathNameFull$ - полное имя каталога 'ВЫХОД: ' PathNameSmall$ - сокращенное имя для вывода If Len(PathNameFull$) <= Lens% Then ' поместилось полностью PathNameShort$ = PathNameFull$ Else ' убираем лишние символы PathNameShort$ = Left$(PathNameFull$, 3) + "..." + _ Right$(PathNameFull$, Lens% - 6) End If End Function
В данном случае пользователь сам задает максимальное число символов в имени. Но как согласуется число символов с размером той же метки? Здесь возможны два варианта ответа:
- В принципе, можно написать код, который в цикле будет определять физическую длину выводимого текста (об этом см. совет 147), сравнивать с длиной окна и удалять из имени по одному символу, пока не будет получена заданная длина текста.
- Установив для метки шрифт с равномерной шириной символов (например, Courier New), экспериментальным путем можно легко определить, сколько символов будет помещаться в нем. В этом случае функция PathNameShort подойдет без изменений.
Совет 337. Инициализация ActiveX DLL
Как сделать, чтобы при загрузке ActiveX DLL была бы выполнена динамическая инициализация ее данных, объектов или какие-то другие операции, например анализ наличия необходимых системных ресурсов? Для этого нужно просто создать в DLL модуль кода BAS, включить в него процедуру Main и указать ее в качестве стартовой в окне свойств проекта. При загрузке библиотеки управление будет передано именно этой подпрограмме.
Совет 338. Как определить события инициализации и завершения работы ActiveX DLL
Например, нужно контролировать время работы ActiveX DLL, и вы хотите, чтобы она сама записывала куда-то время своей загрузки и выгрузки. Учитывая предыдущий совет, с загрузкой дело обстоит очень просто — нужно использовать подпрограмму Main. А как быть с выгрузкой? Можно реализовать и такой вариант.
Создайте тестовую ActiveX DLL с именем LbEvent.DLL, в которой находится несколько объектов (классов), в том числе clsTest. Теперь добавьте к этому проекту вспомогательный модуль класса libEvent и добавьте такой код в его событийные процедуры:
Private Sub Class_Initialize() MsgBox "Запуск DLL" End Sub Private Sub Class_Terminate() MsgBox "Выгрузка DLL" End Sub
А в стартовую подпрограмму Main добавьте следующий код:
Private Sub Main() ' инициализация DLL MsgBox "Sub Main - DLL загружена" Static Lib As LibEvent Set Lib = New LibEvent End Sub
При запуске библиотеки создается статический экземпляр класса LibEvent (и выполняется его процедура Initialize). Поскольку он статический, то не может быть закрыт до тех пор, пока библиотека не будет выгружена. В момент выгрузки выполнится процедура Terminate.
Сформируйте DLL и затем создайте тестовое приложение (не забудьте сделать в окне Reference ссылку на LbEvent.DLL):
Dim MyLib As Object Private Sub Form_Click() Set MyLib = New Lbivent.clsTest End Sub
После запуска приложения щелкните мышью по его форме: для создания объекта clsTest выполнится загрузка библиотеки, и вы увидите сначала сообщение «Sub Main — библиотека загружена» (выполнилась процедура Main), а затем «Запуск DLL» (инициализация модуля LibEvent). Закройте форму, и вы увидите сообщение «Выгрузка DLL».
Совет 339. Как определить полное имя Host-приложения
Предположим, вы создали собственный элемент управления (UserControl) и хотите, чтобы он смог узнать каталог, где находится его родительское приложение (откуда он был вызван в данный момент). В общем случае можно просто предусмотреть передачу этих сведений с помощью установки соответствующих свойств элемента управления. Но иногда бывает удобнее использовать код, который определит полный путь родительского EXE-модуля, независимо от того, захотела вызывающая программа сделать такую установку или нет.
Для этого включите в свой элемент управления следующий код:
Private Declare Function GetModuleFileName Lib _ "kernel32" Alias "GetModuleFileNameA" (ByVal _ hModule As Long, ByVal lpFileName As String, ByVal _ nSize As Long) As Long Private Sub UserControl_Paint() ' определить каталог родительского приложения Dim AppPath As String Const MAX_PATH = 260 UserControl.Cls AppPath = Space$(MAX_PATH) If GetModuleFileName(0, AppPath, Len(AppPath)) Then AppPath = Left$(AppPath, InStr(AppPath, vbNullChar) - 1) UserControl.Print AppPath Else UserControl.Print "Не смогли определить!" End If End Sub
Совет 340. Как ввести табуляцию в Textbox
При работе с элементом управления Rich Textbox порой бывает необходимо использовать табуляторы при вводе данных. Если Rich Textbox является единственным элементом на форме, то нажатие клавиши Tab как раз вставляет табулятор в текст. Но если на форме находится несколько элементов управления, то нажатие клавиши Tab будет приводить к перемещению фокуса между ними. Как же быть? Очень просто — нажимайте Ctrl+Tab и табулятор будет вставляться в любом случае.
Вставлять табуляторы в обычное текстовое поле можно с помощью того же Ctrl+Tab, написав такой код обработки нажатия этих клавиш:
Private Sub TextBox1_KeyDown(KeyCode As Integer, Shift As Integer) ' вставка табулятора при нажатии Ctrl+Tab If (KeyCode = vbKeyTab) and (Shift = 2) Then TextBox1.SelText = vbTab KeyCode = 0 End If End Sub
Правда, в этом случае в поле Text1 введенный табулятор будет просто отображен специальным символом (жирная вертикальная полоска), так как данный элемент управления не знает, как нужно реагировать на этот код. Такую обработку символа табуляции придется писать вручную, например вставляя вместо табулятора заданное число пробелов:
TextBox1.SelText = Space(4)
Совет 341. Динамическая передача данных из списка в текстовое поле
Задача формулируется следующим образом: во время перемещения по списку вы хотите, чтобы в текстовом окне отображался текущий элемент списка. В совете 123 мы рассматривали аналогичный случай, когда хотели увидеть полностью текст, который не помещается по ширине списка. Там мы использовали динамическую установку свойства List1.ToolTipText, которое можно легко заменить на Text1.Text.
Однако тогда решение было связано со слежением за положением указателя мыши, а сейчас мы предлагаем использовать таймер. Рассмотрим это на примере работы с комбинированным списком. Установите на форме таймер с интервалом, например, в четверть секунды и в отключенном режиме по умолчанию. Далее напишите следующий код:
Private Sub Combo1_DropDown() ' при открытии списка таймер запускается Timer.Enable = True End Sub Private Timer1_Timer() ' каждые 250 мс выполняется обновление: Text1.Text = Combo1.List(ListIndex) End Sub Private Sub Combo1_Click() ' по щелчку список закрывается Timer.Enable = False ' таймер отключается Text1.Text = Combo1.List(ListIndex) ' поле обновляется End Sub
Совет 342. Как переписать удаленные файлы в Recycle Bin
Оператор Kill просто удаляет файлы. А как сделать, чтобы записать эти файлы в «Корзину»? Для этого используйте следующий код:
Private Declare Function SHFileOperation Lib _ "shell32.dll" (ByRef lpFileOp As _ SHFILEOPSTRUCT) As Long Private Const ERROR_SUCCESS = 0& Private Const FO_COPY = &H2 Private Const FO_DELETE = &H3 Private Const FO_MOVE = &H1 Private Const FO_RENAME = &H4 Private Const FOF_ALLOWUNDO = &H40 Private Const FOF_CONFIRMMOUSE = &H2 Private Const FOF_FILESONLY = &H80 Private Const FOF_MULTIDESTFILES = &H1 Private Const FOF_NOCONFIRMATION = &H10 Private Const FOF_NOCONFIRMMKDIR = &H200 Private Const FOF_RENAMEONCOLLISION = &H8 Private Const FOF_SILENT = &H4 Private Const FOF_SIMPLEPROGRESS = &H100 Private Const FOF_WANTMAPPINGHANDLE = &H20 Private Type SHFILEOPSTRUCT hwnd As Long wFunc As Long pFrom As String pTo As String fFlags As Integer fAnyOperationsAborted As Long hNameMappings As Long lpszProgressTitle As String ' можно использовать только ' FOF_SIMPLEPROGRESS End Type Next create a function called Recycle, like so Public Sub Recycle(ByVal FileName As String) ' вызов Recycle Dim CFileStruct As SHFILEOPSTRUCT With CFileStruct .hwnd = Me.hwnd .fFlags = FOF_ALLOWUNDO .pFrom = FileName .wFunc = FO_DELETE End With If SHFileOperation(CFileStruct) <> ERROR_SUCCESS Then ' произошла ошибка End If End Sub
Чтобы протестировать процедуру, создайте какой-нибудь текстовый файл и удалите его с помощью такого кода:
Private Sub Command1_Click() Recycle "c:\test.txt" End Sub
Совет 343. Поддержка контекстного меню для Rich Textbox
VB предлагает достаточно простые способы создания контекстных меню, но некоторые элементы управления, в частности Rich Textbox, не включают их поддержку. Чтобы исправить это упущение, можно использовать событие MouseDown.
Для тестирования такой ситуации добавьте к форме элемент управления Rich Textbox, затем с помощью Menu Editor создайте меню из пары строк команд. Введите такой код в событийную процедуру:
Private Sub RichTextBox1_MouseDown(Button As Integer, _ Shift As Integer, X As Single, Y As Single) If Button = vbRightButton Then Me.PopupMenu Menu End Sub
Запустите проект, щелкните правой кнопкой мыши на элементе Rich Textbox и убедитесь, что появилось меню.
Совет 344. Дайте возможность пользователям изменять размеры элементов управления
Вы можете предоставить пользователям своего приложения возможность изменять размеры элемента управления с помощью мыши — аналогично тому, как вы это делаете в режиме разработки. Для этого необходимо только вызвать две простые функции Windows API — ReleaseCapture и SendMessage. Если задать диапазоны, в которых может двигаться мышь (например, X > 0 и X < 100), событие MouseDown активизирует эти функции и меняет размеры элемента управления при перемещении мыши. Предположим, что вы поместили на форму картинку (PictureBox). Теперь напишите для нее следующий код:
Private Declare Function ReleaseCapture Lib "user32" () As Long Private Declare Function SendMessage Lib "user32" Alias _ "SendMessageA" (ByVal hWnd As Long, ByVal wMsg As Long, _ ByVal wParam As Long, lParam As Any) As Long Private Const WM_NCLBUTTONDOWN = &HA1 ' В API Viewer есть и другие константы ' Заданные здесь константы используются ' только для изменения правой и левой границ Private Const HTLEFT = 10 Private Const HTRIGHT = 11 Private Sub Picture1_MouseDown(Button As Integer, _ Shift As Integer, X As Single, Y As Single) Dim nParam As Long With Picture1 ' Здесь вы можете задать любые координаты If (X > 0 And X < 100) Then nParam = HTLEFT ' и здесь тоже ElseIf (X > .Width - 100 And X < .Width) Then nParam = HTRIGHT End If If nParam Then Call ReleaseCapture Call SendMessage(.hWnd, WM_NCLBUTTONDOWN, nParam, 0) End If End With End Sub Private Sub Picture1_MouseMove(Button As Integer, _ Shift As Integer, X As Single, Y As Single) Dim NewPointer As MousePointerConstants ' Здесь вы можете задать любые координаты If (X > 0 And X < 100) Then NewPointer = vbSizeWE ' и здесь тоже ElseIf (X > Picture1.Width - 100 And X < Picture1.Width) Then NewPointer = vbSizeWE Else NewPointer = vbDefault End If If NewPointer <> Picture1.MousePointer Then Picture1.MousePointer = NewPointer End If End Sub
Совет 345. В циклах лучше используйте GetInputState
Некоторые разработчики предлагают использовать в циклах функцию DoEvents, чтобы приложение могло реагировать на какие-либо события. Однако это не всегда целесообразно. Так, если цикл короткий, функция DoEvents и не нужна, а если длинный, то использование этой функции может нанести ущерб быстродействию приложения. А что же делать, если необходимо, чтобы пользователь имел возможность щелкнуть мышью на кнопке Cancel (Отмена) или выполнить какое-либо другое действие в процессе выполнения цикла?
Решение этой проблемы возможно путем использования функции Windows API GetInputState, которая возвращает 1, если пользователь щелкает на кнопке мышью или нажимает клавишу на клавиатуре. Применение этой функции требует намного меньше ресурсов, чем при работе с DoEvents, поэтому цикл выполняется быстрее.
Однако если щелчок мыши или нажатие клавиши инициирует выполнение некоторого события, то следует вызывать функцию DoEvents. Другими словами, вызывайте DoEvents только в том случае, если вам действительно необходимо обработать это событие. При этом вы можете уменьшить использование ресурсов, осуществляя проверку каждые x итераций (конкретное число зависит от того, сколько времени занимает выполнение цикла):
Option Explicit Private Declare Function GetInputState Lib "user32" () As Long Private m_UserCancel As Boolean Private Sub cmdCancel_Click() m_UserCancel = True End Sub Private Sub cmdGo_Click() Dim lCounter As Long m_UserCancel = False Me.MousePointer = vbHourglass For lCounter = 0 To 10000000 ' любой длинный цикл, ' который необходимо прервать If lCounter Mod 100 Then If GetInputState <> 0 Then ' событие, инициируемое щелчком мыши ' или нажатием клавиши, находится в 'очереди сообщений, поэтому вызываем ' функцию DoEvents,чтобы начать обработку ' этого события DoEvents If m_UserCancel Then Exit For End If End If Next lCounter Me.MousePointer = vbDefault End Sub
Совет 346. Работа с Интернетом в VB-приложениях
Создавая любое современное клиентское Web-приложение, вы, вероятно, захотите, чтобы у пользователи имели при работе с ним имели выход в Интернет. Для того чтобы реализовать такую возможность, разработчики обычно используют элемент управления WebBrowser (для его установки выберите команду Project|Components, а затем — элемент Microsoft Internet Controls). Однако у пользователей при этом обязательно должен быть установлен Internet Explorer, иначе приложение не запустится. Чтобы решить подобную проблему, удалите элемент управления WebBrowser из приложения, а затем загрузите его динамически, выяснив, что у пользователя установлен Internet Explorer. Для этого напишите следующий код:
Option Explicit Private ie As VBControlExtender Private Sub Form_Load() On Error GoTo IEMissing Set ie = Form1.Controls.Add("Shell.Explorer", "wcIE") ie.Visible = True IEMissing: ' обработка ошибки End Sub Private Sub Form_Resize() If Not (ie Is Nothing) Then ie.Move 0, 0, Me.ScaleWidth, Me.ScaleHeight End If End Sub
Вы можете совершать самые разнообразные операции с этим объектом, например менять его видимость, но при этом вам не будут доступны уникальные свойства и методы Internet Explorer. Так, если вы введете obj.Navigate sMyURL, VB даст сообщение о том, что данный объект не поддерживает указанное свойство или метод. Секрет заключается в том, что свойство Object объектной переменной следует использовать только подобным образом:
Private Sub Form_Activate() If Not (ie Is Nothing) Then ie.object.Navigate "http://www.visual.2000.ru" End If End Sub
Совет 347. Как определить текущие размеры экрана
Последние модели видеодрайверов позволяют менять разрешение экрана без перезагрузки компьютера. К сожалению, объект Screen не всегда правильно возвращает размеры экрана — он запоминает только те значения, которые были установлены на момент запуска приложения. Представляется, что такое поведение зависит от драйвера, однако подобная ситуация может быть вызвана и работой операционной системы (этого никогда не случится, если на вашей машине установлена Windows 98, но не Windows NT).
Поэтому если необходимо определить размерность экрана в любой другой момент работы приложения, а не только при выполнении события Form_Load, пользуйтесь функциями API, как показано ниже:
Option Explicit Private Type RECT Left As Long Top As Long Right As Long Bottom As Long End Type Private Declare Function GetDesktopWindow Lib _ "user32" () As Long Private Declare Function GetWindowRect Lib "user32" _ (ByVal hWnd As Long, lpRect As RECT) As Long Public Function ScreenWidth() As Single Dim R As RECT GetWindowRect GetDesktopWindow(), R ScreenWidth = R.Right * Screen.TwipsPerPixelX End Function Public Function ScreenHeight() As Single Dim R As RECT GetWindowRect GetDesktopWindow(), R ScreenHeight = R.Bottom * Screen.TwipsPerPixelY End Function
Совет 348. Как установить связь с Microsoft Excel с помощью OLE DB
В документации Microsoft говорится, что можно установить связь с Excel 97 или Excel 2000 с помощью провайдера Microsoft.Jet.OLEDB 4.0. Однако если при этом вы используете элемент управления Microsoft ADO Data Control (Adodc), то могут возникнуть проблемы.
Продемонстрируем это на следующем примере. Поместите на форму элемент управления Adodc, во вкладке General окна свойств установите переключатель Use Connection String и щелкните кнопку Build. Затем выберите необходимую базу данных, установив Excel-файл в качестве файла базы данных. Теперь, если вы щелкните кнопку Test Connection, чтобы проверить, установилась ли связь, VB выдаст сообщение об ошибке, в котором говорится о невозможности установления связи с базой данных из-за того, что файл имеет неизвестный формат.
Однако у вас есть выход. Подтвердите получение сообщения об ошибке и вернитесь во вкладку General окна свойств. В поле ввода Use Connection String в конце строки, используемой для связи с Excel, допишите следующий код:
Extended Properties = Excel 8.0;
Тогда полная строка будет выглядеть так:
Provider = Microsoft.Jet.OLEDB/4.0; Data Source = FileName; _ Extended Properties = Excel 8.0;
Теперь, если вы щелкнете кнопку Build, а затем — Test Connection, то ошибки не будет, и связь с базой данных Excel будет успешно установлена.
Совет 349. Простой ввод данных в элементе управления MSFlexGrid
Существует возможность осуществлять ввод данных в элемент управления MSFlexGrid, не используя другие дополнительные компоненты. Это можно реализовать с помощью событий KeyPress и KeyUp, как показано ниже. Для начала поместите на форму элемент управления MSFlexGrid, присвойте ему имя FlxGrdDemo, а затем введите следующий код:
Option Explicit Private Sub FlxGrdDemo_KeyPress(KeyAscii As Integer) Select Case KeyAscii Case vbKeyReturn ' Когда пользователь нажимает ' клавишу Enter, этот код ' осуществляет переход к ' следующей ячейке или ряду With FlxGrdDemo If .Col + 1 <= .Cols - 1 Then .Col = .Col + 1 ElseIf .Row + 1 <= .Rows - 1 Then .Row = .Row + 1 .Col = 0 Else .Row = 1 .Col = 0 End If End With Case vbKeyBack ' Удаляет предыдущий символ при ' нажатии клавиши Backspace With FlxGrdDemo If Trim(.Text) <> " " Then _ .Text = Mid(.Text, 1, Len(.Text) - 1) End With Case Is < 32 ' Не разрешает вводить непечатные символы Case Else ' Разрешает печатать все With FlxGrdDemo .Text = .Text & Chr(KeyAscii) End With End Select End Sub Private Sub FlxGrdDemo_KeyUp(KeyCode As Integer, _ Shift As Integer) Select Case KeyCode Case vbKeyC And Shift = 2 ' Ctrl + C ' Копирует символы Clipboard.Clear Clipboard.SetText FlxGrdDemo.Text KeyCode = 0 Case vbKeyV And Shift = 2 ' Ctrl + V ' Вставляет символы FlxGrdDemo.Text = Clipboard.GetText KeyCode = 0 Case vbKeyX And Shift = 2 ' Ctrl + X ' Вырезает символы Clipboard.Clear Clipboard.SetText FlxGrdDemo.Text FlxGrdDemo.Text = " " KeyCode = 0 Case vbKeyDelete ' Удаляет символы FlxGrdDemo.Text = " " End Select End Sub
Вы можете также установить свойство FillStyle как FlexFillRepeat, что позволяет вносить изменения во все выделенные ячейки.
Совет 350. Десять советов «как не нужно поступать при работе с ASP»
Следуя им, можно создавать более эффективные и надежные ASP-приложения.
- Не следует часто переключаться от HTML-текста к коду скрипта и обратно. При создании HTML-текста с использованием большого числа изменяемых фрагментов применяйте последовательность методов Response.Write.
- Не нужно на одной ASP-странице использовать коды JScript и VBScript. Такое смешение разных языков ограничивает число откомпилированных страниц, которые IIS может хранить в кэш-памяти.
- Не забывайте отключить режим отладки в отлаженных Web-приложениях. При включенном режиме IIS работает как однопотоковое приложение, снижая общую производительность системы.
- Не используйте большие или вложенные Include-файлы. В любом случае старайтесь применять Include-файлы только для загрузки процедур, которые действительно нужны для данной ASP-страницы.
- Не используйте повторно один и тот же объект ADO Command для двух разных запросов на одной странице. Например, система может просто «рухнуть», если вы будет применять такой объект для работы с двумя разными параметризованными хранимыми процедурами.
- Не используйте OLE DB Provider for ODBC Drivers, если у вас есть возможность доступа к базе данных через ее «родной» OLE DB-провайдер. Последний всегда работает надежнее и быстрее.
- Не следует применять метод Response.Redirect для перехода к другой ASP-странице, находящейся на этом же сайте. Если вы работаете с IIS 5.0, то лучше воспользоваться новыми методами Server.Transfer и Server.Execute.
- Не создавайте наборы записей (recordsets) ADO с большей, чем это нужно, функциональностью. Чем больше функциональности — тем меньше эффективности (по объему кода и быстродействию). Например, если вы не собираетесь модифицировать данные, задайте режим «только для чтения», если будете просматривать их только в одном направлении — «только вперед» и т.д.
- Не храните большие массивы в виде переменных Application и Session. IIS копирует массив полностью, когда вы обращаетесь только к одному его элементу из скрипта. Понятно, что время копирования массива пропорционально его размеру.
- Не используйте серверные скрипты (server-side scripts) для выполнения операций, которые можно проделать с помощью скриптов на клиентской машине. Например, проверку значений, вводимых в поля формы, лучше сделать непосредственно в браузере.
КомпьютерПресс 12'2000