Снова о COM-портах
Модератор: Дед Пахом
Правила форума
При написании вопроса или обсуждении проблемы, не забывайте указывать версию Clarion который Вы используете.
А так же пользуйтесь спец. тегами при вставке исходников!!!
При написании вопроса или обсуждении проблемы, не забывайте указывать версию Clarion который Вы используете.
А так же пользуйтесь спец. тегами при вставке исходников!!!
Я дико извиняюсь за очередное поднятие этой темы.
Но никак у меня не получается создать хотя бы простенькую программку, которая могла бы отправлть в модем AT-команду и получать оттуда то, что модем возвращает.
Я уже неделю бьюсь с API-функциями WriteFile и ReadFile, но у меня так ничего и не получается. В частности, не могу понять, как заранее сообщить функции ReadFile количество байт, которые нужно прочитать. Ну... и другие трудности.
Тем более, что Кларионовские прототипы этих функция разные источники дают по-разному - трудно понять, где правда.
Дайте кто-нибудь небольшой РАБОТАЮЩИЙ примерчик по использованию модемных команд (32-битный вариант). Буду признателен.
P.S. Ссылки на CLACOM не предлагать.
Написал: Booroondook(77)
Но никак у меня не получается создать хотя бы простенькую программку, которая могла бы отправлть в модем AT-команду и получать оттуда то, что модем возвращает.
Я уже неделю бьюсь с API-функциями WriteFile и ReadFile, но у меня так ничего и не получается. В частности, не могу понять, как заранее сообщить функции ReadFile количество байт, которые нужно прочитать. Ну... и другие трудности.
Тем более, что Кларионовские прототипы этих функция разные источники дают по-разному - трудно понять, где правда.
Дайте кто-нибудь небольшой РАБОТАЮЩИЙ примерчик по использованию модемных команд (32-битный вариант). Буду признателен.
P.S. Ссылки на CLACOM не предлагать.
Написал: Booroondook(77)
параметром nNumberOfBytesToRead, надо полагать.Я уже неделю бьюсь с API-функциями WriteFile и ReadFile, но у меня так ничего и не получается. В частности, не могу понять, как заранее сообщить функции ReadFile количество байт, которые нужно прочитать.
правда по WinAPI в MSDN.Тем более, что Кларионовские прототипы этих функция разные источники дают по-разному - трудно понять, где правда.
прям вот так сишного кода и накидать?Дайте кто-нибудь небольшой РАБОТАЮЩИЙ примерчик по использованию модемных команд (32-битный вариант). Буду признателен.
OVERLAPPED используешь? DCB устанавливаешь? таймауты устанавливаешь?
показал бы хоть свой код, а то так не поймешь, что у тебя не работает. тем более, что в MSDN есть рабочие примеры.
^^^^^^ ROFL! =8-3P.S. Ссылки на CLACOM не предлагать.
Фр> WBR, Booroondook
--
Best regards,
Maxim Yemelyanov,
Enigma Soft Company
phone: +380 572 177977
WEB: http://enigmasoft.com.ua
e-mail: clalist@enigmasoft.com.ua
ICQ: 12253836
Написал: ClaList(2)
"Большое спасибо", Максим!
Поучать всегда легче, чем помогать.
Возможно, в других вопросах, я сильней Вас, и мог бы там Вас поучать, как Вы сейчас пытаетесь меня.
Насчет параметра NumberOfBytesToRead - я понимаю, что параметр назвать можно как угодно.
Вопрос в том - откуда я могу заранее знать, сколько байт считается функцией.
Я ведь пробовал наугад указывать заведомо большее число байт - ни к чему хорошему это ни привело.
Поэтому я и прошу работающий примерчик.
Причем, не на C++ (как в MSDN), а на Кларионе.
И насчет правды - в MSDN действительно есть все - жаль только, что не для Клариона.
Поучать всегда легче, чем помогать.
Возможно, в других вопросах, я сильней Вас, и мог бы там Вас поучать, как Вы сейчас пытаетесь меня.
Насчет параметра NumberOfBytesToRead - я понимаю, что параметр назвать можно как угодно.
Вопрос в том - откуда я могу заранее знать, сколько байт считается функцией.
Я ведь пробовал наугад указывать заведомо большее число байт - ни к чему хорошему это ни привело.
Поэтому я и прошу работающий примерчик.
Причем, не на C++ (как в MSDN), а на Кларионе.
И насчет правды - в MSDN действительно есть все - жаль только, что не для Клариона.
Каждый в чем-то сильнее другого.
Я задал вопросы и попросил показать код. Чтобы указать, где необходимо внести правки. Как вариант. Как второй вариант - я мог бы привести свой код. Но он на си, а Вы с си, как написали ниже, не дружите.
А сколько должно быть считано - от протокола обмена зависит. Это модем, или какая-то желязяка со специфическим протоколом обмена? Железяки тоже бывают разные, если не считал ответ и не ответил за 200мс, то гайки, начинай обмен снова.
Тогда откуда у вас информация о функции ReadFile ? Если не знаете, как пользоваться, почему бы не уточнить в документации? Вижу 2 проблемы - отсутствие оной документации вообще, либо незнание английского в достаточной мере для понимания. Возможно тогда вопрос должен был быть задан по другому: где взять русский перевод MSDN?
--
Best regards,
Maxim Yemelyanov
(Добавление)
Добрый день!
На сайте Гундорова Сергея http://pisoft.ru/verstak/ есть ряд статей Пола Атрайда. Почитай, может поможет
С уважением Мартюшев Леонид
mailto:leonid@opfr.komi.com
(Добавление)
Некоторое время назад я кидал в рассылку реально работающий пример работы с СОМ-портом. На clarionlife.net его нет к сожалению.
Попоздже закину.
С уважением, Андрей Истомин
Ну это не мне, а тому, кто вопрошал, но думаю, если примерчик будет на http://clarionlife.net, то хорошо
С уважением Мартюшев Леонид
mailto:leonid@opfr.komi.com
Написал: ClaList(2)
Я задал вопросы и попросил показать код. Чтобы указать, где необходимо внести правки. Как вариант. Как второй вариант - я мог бы привести свой код. Но он на си, а Вы с си, как написали ниже, не дружите.
Я говорил об MSDN. Описание функции ReadFile.Насчет параметра NumberOfBytesToRead - я понимаю, что параметр назвать можно как угодно.
Код: Выделить всё
BOOL ReadFile(
HANDLE hFile, // handle to file
LPVOID lpBuffer, // data buffer
DWORD nNumberOfBytesToRead, // number of bytes to read
LPDWORD lpNumberOfBytesRead, // number of bytes read
LPOVERLAPPED lpOverlapped // overlapped buffer
);
считается 0..nNumberOfBytesToRead байт, в зависимости от к-ва байт во входном буфере и таймаутов чтения.Вопрос в том - откуда я могу заранее знать, сколько байт считается функцией.
А сколько должно быть считано - от протокола обмена зависит. Это модем, или какая-то желязяка со специфическим протоколом обмена? Железяки тоже бывают разные, если не считал ответ и не ответил за 200мс, то гайки, начинай обмен снова.
Как говорится, чтение документации с выражением - $50 в час.Я ведь пробовал наугад указывать заведомо большее число байт - ни к чему хорошему это ни привело.
Тогда откуда у вас информация о функции ReadFile ? Если не знаете, как пользоваться, почему бы не уточнить в документации? Вижу 2 проблемы - отсутствие оной документации вообще, либо незнание английского в достаточной мере для понимания. Возможно тогда вопрос должен был быть задан по другому: где взять русский перевод MSDN?
На кларионе у меня нет.Поэтому я и прошу работающий примерчик.
Причем, не на C++ (как в MSDN), а на Кларионе.
В MSDN описывает интерфейсы к ОС, которыми можно пользоваться из клариона, делфи, си и всего, что компилит под винду.И насчет правды - в MSDN действительно есть все - жаль только, что не для Клариона.
--
Best regards,
Maxim Yemelyanov
(Добавление)
Добрый день!
На сайте Гундорова Сергея http://pisoft.ru/verstak/ есть ряд статей Пола Атрайда. Почитай, может поможет
С уважением Мартюшев Леонид
mailto:leonid@opfr.komi.com
(Добавление)
Некоторое время назад я кидал в рассылку реально работающий пример работы с СОМ-портом. На clarionlife.net его нет к сожалению.
Попоздже закину.
С уважением, Андрей Истомин
Ну это не мне, а тому, кто вопрошал, но думаю, если примерчик будет на http://clarionlife.net, то хорошо
С уважением Мартюшев Леонид
mailto:leonid@opfr.komi.com
Написал: ClaList(2)
Спасибо за ссылку на статью Paul Attryde.
Почитаю.
Собственно говоря, задача такая - послать в мобильный телефон запрос в виде произвольной AT-команды и получить в ответ возвращаемую информацию. Т.е., грубо говоря, задача эмуляции терминала.
Почему я прошу дать реально работающий пример. Потому что ни в MSDN, ни в статьях различных... нигде не дается последовательность вызова этих АПИшных функций.
Ну хорошо, я инициализировал порт (это мне удалось). Создал DCB. С этим тоже все нормально. Настроил параметры порта (скорость, четность и т.д.).
Потом, по идее наждо делать WriteFile. Сделал. Удачно.
А дальше-то что?
Я пробую сразу сделать ReadFile, но ничего толкового не возвращается.
Я подозреваю, что между WriteFile и ReadFile нужно вызывать еще какие-то функции.
Но какие?
Ни в одном источнике я не смог найти связанной информации, описывающей нужную последовательность вызова функций - только какие-то разрозненные обрывки, описывающие отдельные функции.
Написал: Booroondook(77)
Почитаю.
Собственно говоря, задача такая - послать в мобильный телефон запрос в виде произвольной AT-команды и получить в ответ возвращаемую информацию. Т.е., грубо говоря, задача эмуляции терминала.
Почему я прошу дать реально работающий пример. Потому что ни в MSDN, ни в статьях различных... нигде не дается последовательность вызова этих АПИшных функций.
Ну хорошо, я инициализировал порт (это мне удалось). Создал DCB. С этим тоже все нормально. Настроил параметры порта (скорость, четность и т.д.).
Потом, по идее наждо делать WriteFile. Сделал. Удачно.
А дальше-то что?
Я пробую сразу сделать ReadFile, но ничего толкового не возвращается.
Я подозреваю, что между WriteFile и ReadFile нужно вызывать еще какие-то функции.
Но какие?
Ни в одном источнике я не смог найти связанной информации, описывающей нужную последовательность вызова функций - только какие-то разрозненные обрывки, описывающие отдельные функции.
Написал: Booroondook(77)
Можно делать обмен синхронный, можно асинхронный. Лови кусок кода на си, надеюсь, поможет разобраться что за чем должно вызываться.
На самом деле в случае синхронного обмена без колбеков все _намного_ проще. Но я не смог найти этот код
Идея такая: после открытия порта стартует апишный поток, который мониторит порт и вызывает колбековую функцию, передавая в нее принятые данные.
--
Best regards,
Maxim Yemelyanov
Написал: ClaList(2)
На самом деле в случае синхронного обмена без колбеков все _намного_ проще. Но я не смог найти этот код

Идея такая: после открытия порта стартует апишный поток, который мониторит порт и вызывает колбековую функцию, передавая в нее принятые данные.
Код: Выделить всё
/*!WinAPI-обертка для #WatcherThread_inner, чтобы WatcherThread_inner был членом класса*/
static u_long WINAPI WatcherThreadProc(void* lpData)
{
TComm *comm = reinterpret_cast<TComm*>(lpData);
comm->WatcherThread_inner();
return 0;
}
/* функция потока. код выглядит уродливо, т.к. писался давно, когда я не умел
программировать еще больше, чем не умею сейчас. но работает.
*/
void TComm::WatcherThread_inner()
{
#define maxBuffer 100
u_long dwEvMask,dwBytesRead;
char sBuf[maxBuffer+1];
OVERLAPPED OvlWatcher;
HANDLE Handles[2]; // будем ловить 2 события
if (!SetCommMask( hComm, EV_RXCHAR)) return;
if (!(OvlWatcher.hEvent = CreateEvent(NULL,1,0,NULL))) return;
Handles[0] = hExitClose; // событие принудительного закрытия порта
Handles[1] = OvlWatcher.hEvent; // а также событие появления данных в порту
while (!IsClose())
{
// ловим событие на порту
if (! WaitCommEvent( hComm, &dwEvMask, &OvlWatcher ))
{
switch( WaitForMultipleObjects(2,(HANDLE*)&Handles, FALSE, INFINITE) )
{
// выход, прерываем цикл (break тут - выход из switch, а не while)
case WAIT_OBJECT_0: continue;
case WAIT_OBJECT_0+1: // поймали событие в порту
// анализируем состояние, заполняя структуру OVERLAPPED
if (!GetOverlappedResult( hComm, &OvlWatcher, &dwBytesRead, FALSE))
continue;
ResetEvent(OvlWatcher.hEvent); // сбрасываем event в этой структуре
break;
}
}
if (IsClose()) break; // флаг закрытия
// на самом деле, ловится много событий.
// если событие == пришли данные
if (dwEvMask==EV_RXCHAR)
do
{
dwBytesRead = ReadBlockDirect( sBuf, maxBuffer); // вызывает ReadFile
if (dwBytesRead<0) break;
Queue->Write(sBuf, dwBytesRead ); // засемафорено пишем в очередь
if (port_callback) // если зарегистрирован колбек,
port_callback(sBuf, dwBytesRead); // то вызываем его
sBuf[dwBytesRead]=0;
// пишем в лог. log - экземпляр класса с перегруженным манипулятором endl.
log << sBuf << endl(DET_DEBUG);
}
while(dwBytesRead==maxBuffer);
SetEvent(hEvent);
}//while
CloseHandle( OvlWatcher.hEvent );
}
Best regards,
Maxim Yemelyanov
Написал: ClaList(2)
Спасибо. Буду пробовать разбираться.
Я, кстати, времени не терял, а пытался писать в соответствии со статьей Paul Attryde.
Вот, что у меня получилось (естественно, здесь не весь код и опущены декларации всякие):
Вот у меня почему-то GetCommState возвращает StopBits=0, хотя я задаю 1
И самое главное - WaitCommEvent выдает 0.
Кстати, WriteFile работает нормально - это было проверено утилитой мониторинга порта.
И кроме того, несмотря на то, что эта программа не возвращает отклик от модема, этот отклик "застревает" в модеме. Ибо можно после посыла строки вфйти из программы, а потом запустить другую программу эмуляции терминала (не мою), и там на команду 'AT' получить отклик на ту команду, что посылал я из своей программы.
Я подозреваю, что у меня функции вызываются не в том порядке. Или какие-то пропущены.
Написал: Booroondook(77)
Я, кстати, времени не терял, а пытался писать в соответствии со статьей Paul Attryde.
Вот, что у меня получилось (естественно, здесь не весь код и опущены декларации всякие):
Код: Выделить всё
PortNumber=GETINI('Communications','PortNumber',4,'.\Wcomm.ini') !Читаем номер порта из ИНИ-файла
Loc:InBuffer=GETINI('Communications','InBuffer',1024,'.\Wcomm.ini') !Входной буфер
Loc:OutBuffer=GETINI('Communications','OutBuffer',4096,'.\Wcomm.ini') !Выходной буфер
DCB:BaudRate=GETINI('Communications','BaudRate',57600,'.\Wcomm.ini') !Скорость
DCB:Parity=GETINI('Communications','Parity',0,'.\Wcomm.ini') !Четность в виде числа
CASE DCB:Parity !Преобразование четности из числа в букву
OF 1
Loc:ParityS='Y'
ELSE
Loc:ParityS='N'
END
DCB:ByteSize=GETINI('Communications','ByteSize',8,'.\Wcomm.ini') !Размер байта
DCB:StopBits=GETINI('Communications','StopBits',1,'.\Wcomm.ini') !Стоповые биты
CTO:ReadIntervalTimeout=GETINI('Communications','TimeOut',5000,'.\Wcomm.ini') !Тайм-ауты - все по 5 секунд
CTO:ReadTotalTimeoutMultiplier=GETINI('Communications','TimeOut',5000,'.\Wcomm.ini')
CTO:ReadTotalTimeoutConstant=GETINI('Communications','TimeOut',5000,'.\Wcomm.ini')
CTO:WriteTotalTimeoutMultiplier=GETINI('Communications','TimeOut',5000,'.\Wcomm.ini')
CTO:WriteTotalTimeoutConstant=GETINI('Communications','TimeOut',5000,'.\Wcomm.ini')
Loc:PortString = 'COM' & CLIP(PortNumber) !Текстовое имя порта
IF Loc:PortHandle < 1
Loc:PortHandle = CreateFile(Address(Loc:PortString), GENERIC_READ + GENERIC_WRITE, 0, 0, OPEN_EXISTING, 0, NULL)
END
IF Loc:PortHandle=-1
MESSAGE('Невозможно создать порт' & '<10,13>' & Loc:PortHandle)
RETURN
ELSE
!MESSAGE(Loc:PortHandle)
END
IF Loc:PortHandle <> -1
!Формируем строку порта вида "9600,N,8,1"
Loc:ControlString=DCB:BaudRate & ',' & Loc:ParityS & ',' & DCB:ByteSize & ',' & DCB:StopBits
MESSAGE('Loc:ControlString: ' & Loc:ControlString, 'ControlString')
IF BuildCommDCB(Address(Loc:ControlString), Address(DCB)) <> 0
Loc:ReturnCode=SetCommState(Loc:PortHandle, Address(DCB))
IF ~Loc:ReturnCode
MESSAGE(Loc:ReturnCode)
END
Loc:ReturnCode=SetCommTimeOuts(Loc:PortHandle,Address(COMMTIMEOUTS)) !Тайм-ауты. А надо ли их настраивать?
IF ~Loc:ReturnCode
MESSAGE(Loc:ReturnCode)
END
Loc:ReturnCode=SetupComm(Loc:PortHandle,Loc:InBuffer,Loc:OutBuffer)
IF ~Loc:ReturnCode
MESSAGE(Loc:ReturnCode)
END
ELSE
MESSAGE('ERROR')
END
END
Loc:ReturnCode=GetCommState(Loc:PortHandle,Address(DCB))
IF ~Loc:ReturnCode
MESSAGE('GetCommState: ' & Loc:ReturnCode,'GetCommState')
ELSE
MESSAGE( !
'BaudRate: ' & DCB:BaudRate & '<13,10>' & !
'Parity: ' & DCB:Parity & '<13,10>' & !
'ByteSize: ' & DCB:ByteSize & '<13,10>' & !
'StopBits: ' & DCB:StopBits !
,'GetCommState' )
END
Loc:OutString='AT +MPBR=?'
Loc:Len=LEN(Clip(Loc:OutString))
Loc:ReturnCode = WriteFile(Loc:PortHandle, Address(Loc:OutString), Loc:Len, Address(Loc:BytesWritten), NULL)
IF Loc:Len <> Loc:BytesWritten
MESSAGE('Не записали все байты, которые хотели...')
ELSE
!MESSAGE('Записано байт: ' & Loc:BytesWritten & ', код возврата: ' & Loc:ReturnCode, 'WriteFile')
END
Loc:ReturnCode=SetCommMask(Loc:PortHandle, !
EV_RXCHAR + !
EV_RXFLAG + !
EV_TXEMPTY + !
EV_CTS + !
EV_DSR + !
EV_RLSD + !
EV_BREAK + !
EV_ERR + !
EV_RING + !
EV_PERR + !
EV_RX80FULL + !
EV_EVENT1 + !
EV_EVENT2 ) !Устанавливаем маску на все события
IF ~Loc:ReturnCode
MESSAGE('SetCommMask: ' & Loc:ReturnCode,'SetCommMask')
END
Loc:ReturnCode = WaitCommEvent(Loc:PortHandle, Address(COMMMASK), NULL) !возвращает не ноль, если все нормально
IF ~Loc:ReturnCode
MESSAGE('WaitCommEvent: ' & Loc:ReturnCode,'WaitCommEvent')
END
Loc:ReturnCode = ClearCommError(Loc:PortHandle, Address(Loc:PortErrorCode), Address(ComStat))
IF ~Loc:ReturnCode
MESSAGE('ClearCommError: ' & Loc:ReturnCode,'ClearCommError')
END
MESSAGE('InQue: ' & CS:InQue & '<13,10>OutQue: ' & CS:OutQue)
Loc:BytesToRead=CS:InQue
Loc:ReturnCode = ReadFile(Loc:PortHandle, Address(Loc:TempInString), Loc:BytesToRead, Address(Loc:BytesRead), NULL)
IF ~Loc:ReturnCode
MESSAGE('Loc:ReturnCode: ' & Loc:ReturnCode,'ReadFile')
ELSE
IF ~Loc:BytesRead
MESSAGE('Loc:BytesRead: ' & Loc:BytesRead,'ReadFile')
ELSE
Loc:InString = Loc:InString & Loc:TempInString & '<13,10,13,10>' !Дописывание двойного перевода строки
END
DISPLAY()
И самое главное - WaitCommEvent выдает 0.
Кстати, WriteFile работает нормально - это было проверено утилитой мониторинга порта.
И кроме того, несмотря на то, что эта программа не возвращает отклик от модема, этот отклик "застревает" в модеме. Ибо можно после посыла строки вфйти из программы, а потом запустить другую программу эмуляции терминала (не мою), и там на команду 'AT' получить отклик на ту команду, что посылал я из своей программы.
Я подозреваю, что у меня функции вызываются не в том порядке. Или какие-то пропущены.
Написал: Booroondook(77)
Кхе-кхе. А вот это зря. Смысл таймаутов - коэффициенты умножения и сложения. С таким раскладом винда будет ждать ответа от модема Loc:BytesToRead * 5 + 5 секунд. При килобайтном буфере получим 85 минут.....!Тайм-ауты - все по 5 секунд
Честно говоря, ни разу не пользовался BuildCommDCB, использую SetCommState, там можно прочитать/установить поля побитно.IF BuildCommDCB(Address(Loc:ControlString), Address(DCB)) <> 0
Вот тут непонятно. Раз ты открыл порт без OVERLAPPED, то зачем тебе WaitCommEvent? проще войти в ReadFile и заблокироваться до получения данных.Loc:ReturnCode = WaitCommEvent(Loc:PortHandle, Address(COMMMASK), NULL) !возвращает не ноль, если все нормально
Если же хочешь варианта работы без блокирования на чтении из порта, то используй FILE_FLAG_OVERLAPPED при открытии порта и WaitCommEvent, чтобы узнать, когда данные пришли.
А что возвращает и какой GetLastError после вызова ReadFile?Кстати, WriteFile работает нормально - это было проверено утилитой мониторинга порта.
--
Best regards,
Maxim Yemelyanov
Написал: ClaList(2)
GetLastError возвращает тоже 0.
Я же говорю - у меня подозрение (наверное, 100%-ное), что я все эти функции вызываю не в том порядке, как нужно.
Может, какие-то вообще забыл вызвать.
Я пользовался для справки несколькими источниками - везде совершенно разные рекомендации, везде совершенно разные описания API-функций (указывают разные типы данных) в нотации Клариона.
Короче, истину найти совершенно невозможно.
Собственно, поэтому я и просил дать работающий примерчик терминала на Кларионе - где просто посылается АТ-команда и появляется ответ от модема.
Собственно, я даже где-то откопал подобный примерчик - он называется WComm32.zip. Написан Игорем Куклиным 20 октября 1999 г.
Там по идее есть все, что мне нужно.
Но... Если его скомпилировать под С5/С55/С6, то программа просто молча вываливается при вызове терминала (там терминал вызывается пунктом меню в главном окне приложения).
А если скомпилировать под С4, то даже запускается окно терминала, но через 2-3 секунды программа сваливается по GPF.
Я попытался найти причины такого поведения, но моих знаний тут явно не хватило.
Может быть, прислать вам этот примерчик, а вы посмотрите?
Написал: Booroondook(77)
Я же говорю - у меня подозрение (наверное, 100%-ное), что я все эти функции вызываю не в том порядке, как нужно.
Может, какие-то вообще забыл вызвать.
Я пользовался для справки несколькими источниками - везде совершенно разные рекомендации, везде совершенно разные описания API-функций (указывают разные типы данных) в нотации Клариона.
Короче, истину найти совершенно невозможно.
Собственно, поэтому я и просил дать работающий примерчик терминала на Кларионе - где просто посылается АТ-команда и появляется ответ от модема.
Собственно, я даже где-то откопал подобный примерчик - он называется WComm32.zip. Написан Игорем Куклиным 20 октября 1999 г.
Там по идее есть все, что мне нужно.
Но... Если его скомпилировать под С5/С55/С6, то программа просто молча вываливается при вызове терминала (там терминал вызывается пунктом меню в главном окне приложения).
А если скомпилировать под С4, то даже запускается окно терминала, но через 2-3 секунды программа сваливается по GPF.
Я попытался найти причины такого поведения, но моих знаний тут явно не хватило.
Может быть, прислать вам этот примерчик, а вы посмотрите?
Написал: Booroondook(77)