computery.ru

flashback

 на главную

 заложить
 подписка
 editorial

hardware

 железо
 ликбез
 support

software

 программы
 support

connect

 интернет
 телефония

history

 как это было
 market history
 письма
 случаи
 mustdie
 о журнале
 архив журнала

сервис

 комиссионка
 конференция
 драйверы
 гостевая книга
 отзывы
 почта

реклама

поиск



PageRank

 
Rambler's Top100
 

 


 программы

 

август, 2002

Сиди, слушай

Delphi

Сегодня мы вступим в единоборство с колоссом на силиконовых ногах - корпорацией Microsoft. Мы бросим перчатку в лицо целой команде профи из этой славной корпорации. Мы возьмем за образец какую-нибудь программу, входящую в комплект Windows, и создадим собственный конкурентный аналог.

Я предлагаю разобраться с "Лазерным проигрывателем" - мне он всегда не нравился (а кому он нравится?). Наш проигрыватель должен удобным образом проигрывать CD, опознавать CD, наконец, поддерживать специальную базу данных описаний всех CD.

Думаете, это очень сложно? Ерунда! Ведь этот проект, как всегда, мы выполним с использованием суперпакета Delphi! Итак...

Рисуем интерфейс

1.1. Начните новый проект.

1.2. Установите свойства формы:
Свойство Значение
BorderIcons.biMaximize False
BorderStyle bsSingle
Caption CD-проигрыватель
Height 350
Name MPForm
Width 370

1.3. Разместите на форме (почти под самым заголовком) компонент MediaPlayer (закладка System палитры компонентов).

1.4. Установите следующие свойства компонента Media Player:
Свойство Значение
AutoOpen True
DeviceType dtCDAudio
Name Player

1.5. Дважды щелкните по имени свойства VisibleButtons и установите в False значение следующих подсвойств: btNext, btPrev, btStep, btBack, btRecord. Тем самым вы уберете соответствующие кнопки в компоненте Media Player, размещенном на форме. Зачем?

Кнопка Record (запись) довольно странно будет выглядеть на панели проигрывателя CD-Audio (может, правда, я что-то пропустил, и уже начали выпускать пишущие CD-Audio?). Что касается кнопок "Следующий", "Предыдущий", "Вперед" и "Назад", то они нужны только на панели классического CD-проигрывателя, но, используя Delphi, можно организовать быстрый доступ к нужной песне и более эффективным способом.

1.6. Дважды щелкните по имени свойства ColoredButtons и установите в False значения подсвойств btPause и btEject. На мой взгляд, для цвета пиктограмм на кнопках "Пауза" и "Выброс" более подходит черный цвет, чем предлагаемый по умолчанию желтый и синий (если вы со мной не согласны, оставьте значения этих подсвойств без изменения).

1.7. Сохраните проект на диске в отдельном каталоге, присвоив для модуля (PAS-файл) имя MainForm, а для файла проекта (DPR-файл) имя Player (или любое другое, которое, по вашему мнению, больше подходит для CD-плейера).

Если вам лень читать дальше и вникать в тонкости программирования, то вы можете довольствоваться этим вполне готовым к использованию приложением - правда, правда. Не верите? Тогда запустите программу и убедитесь: только что с помощью Delphi вы создали вполне работающий проигрыватель CD.

Никакого программирования! А что вы хотели, VCL - это вам не шутки! Этакий интеллектуальный конструктор... Правда, не совсем понятно: кому - даже в учебных целях - может понадобиться конструировать такую убогую штуку, как CD-проигрыватель с четырьмя кнопками. Уж если делать, так что-то, не уступающее как минимум CD-проигрывателю, входящему в комплект Windows. Так что продолжим.

1.8. Возле правого края формы разместите кнопку (компонент Button страницы Standard). В ее свойстве Caption введите значение: &Закрыть.

1.9. Щелкните по компоненту Media Player, а затем, нажав Shift, по кнопке, выбрав сразу два компонента.

1.10. Выберите пункт меню Edit > Align... и в окне Alignment, в разделе Vertical установите радиокнопку Tops. Щелкнув OK, вы добьетесь выравнивания кнопки по верхнему краю.

1.11. Не отменяя выделения обоих компонентов, выберите пункт меню Edit > Size... и в разделе Height появившегося окна установите флажок Shrink to smallest. Нажав OK, вы установите высоту панели компонента Media Player равной высоте кнопки (ради удовлетворения собственных эстетических представлений, "советующих" хоть как-то выравнивать компоненты друг относительно друга).

1.12. Дважды щелкните по кнопке "Закрыть" на главной форме проекта и введите Close;.

1.13. Нажмите F12 (чтобы вернуться к шаблону формы). Щелкните по закладке Win32 палитры компонентов VCL, выберите компонент ProgressBar и разместите полоску индикатора под панелью проигрывателя, растянув ее почти во всю ширину формы (если что, сверьтесь с рисунком). Свойству Name присвойте значение ProgressBar.

(Даже если вы и не согласны с моим решением изменить чудесное, данное Delphi, имя ProgressBar1, на безликое, ничего не выражающее, простецкое ProgressBar, не советую восстанавливать справедливость - в этом случае вам придется самостоятельно изменять соответствующие фрагменты кода, который будет рассмотрен ниже).

1.14. Под компонентом ProgressBar разместите две метки (компонент Label вкладки Standard) - одна под другой. Первой дайте имя (свойство Name) Author, а второй - Album. Эти метки будут отображать информацию о названии диска и его авторе (или исполнителе).

Выделите обе метки и очистите свойство Caption - Delphi позволяет устанавливать общее свойство для нескольких компонентов, а свойство Caption имеется у обоих меток, так что очистить его одним махом не составляет труда).

1.15. Откройте вкладку Win32 палитры компонентов, найдите на ней компонент Status Bar и дважды щелкните по нему. Ого! Delphi автоматом разместила строку статуса в нижней части формы. Ее свойству Name присвойте значение Status.

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

Дважды щелкните по компоненту Status Bar, чтобы открыть диалоговую панель для манипуляций с этим компонентом (заголовок этого окна сейчас: Editing Status.Panels). Теперь три раза щелкните по кнопке "Add New" - в специальном окне этой панели появилось три строки: "0 - TStatusPanel", "1 - TStatusPanel" и "2 - TStatusPanel".

Да, вы совершенно правы - каждая строка представляет из себя один из трех распилов на "бруске". Их нумерация начинается с нуля.

Щелкните по строке "0 - TstatusPanel" и ее свойству Width присвойте значение 25. Для второй строки точно также задайте ширину в 40 пикселей.
Можете закрыть окно Editing Status.Panels - оно нам больше не понадобится.

1.17. Теперь выберите уже известный нам (см. статью "Килограмм пиктограмм" в Upgrade № 25 (63)) компонент StringGrid - сетка строк (закладка Additional).

Разместите его на форме только что размещенными метками и установите значения для его следующих свойств:
Свойство Значение
ColCount 3
Color clInfoBk
DefaultRowHeight 18
FixedCols 0
Name Songs
RowCount 10
ScrollBars ssVertical

Тем самым вы установили трехколоночную сетку из 10 строк цвета стандартной контекстной подсказки Windows (clInfoBk). Высота строк равна 18 пикселей, ни одной колонки не зафиксировано, разрешен только вертикальный ползунок.

1.18. Измените ширину списка строк так, чтобы она равнялась ширине компонента ProgressBar (см. п. 1.13), а высоту - так, чтобы были видны все десять строк. Теперь измените ширину колонок, используя разделительные линии на первой фиксированной строке (точно так же, как изменяется ширина колонок в программе MS Excel, то есть при помощи технологии Drag&Drop).

На мой субъективный взгляд, ширина колонок списка строк должна хоть немного соответствовать ширине "распилов" на бруске статуса (см. пп. 1.17, 1.18), иначе говоря, первая колонка (номер трека) должна быть самой узкой, вторая (время звучания) - чуть шире, а третья - занимать всю оставшуюся ширину списка строк (как на иллюстрации). Поэкспериментируйте, короче...

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

1.19. Дважды щелкните по свойству Options компонента StringGrid и установите в False подсвойства goVertLine и goRangeSelect (отмена вертикальных разделяющих линий в списке строк и запрет одновременного выбора нескольких строк).

Подсвойству goRowSelect присвойте значение True. Это подсвойство отвечает за внешний вид активной строки списка. В данном случае выделяться будут все три колонки сразу.

1.20. И наконец, откройте вкладку System палитры компонентов и разместите в любом месте формы (симпатичнее всего - на списке строк) два компонента Timer. Они автоматически получат имена, соответственно: Timer1 и Timer2, которые можно оставить без изменения.

Назначение компонента Timer (а также количество этих компонентов, использованных в проекте) будет разъяснено чуть ниже. Свойство Interval (частота "тиканья" в миллисекундах) обоих компонентов сделайте равным 100. Для компонента Timer2 установите свойство Enabled в False, чтобы сделать его "не тикающим" в начале выполнения программы.

1.21. Сохраните проект на диске (обратите, внимание: на панели Delphi есть две кнопки сохранения - Save file и Save All. Первая сохраняет текущую форму, а вторая - проект в целом, то есть все редактируемые формы проекта).

Запускаем музыку

Итак, у нас имеется заготовка формы с девятью размещенными на ней компонентами: Media Player, Button, два компонента Label, ProgressBar, StringGrid, StatusBar и два компонента Timer. А теперь нужно заставить их как-то взаимодействовать друг с другом. Как ни хотелось бы порадовать кое-кого из читателей, но, увы, без написания кода не обойтись...

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

2.1. Нажмите F12, чтобы перейти в окно Редактора кода (Code Editor).

2.2. Переместитесь в начало модуля и добавьте в секцию импортируемых модулей (uses) новый модуль: MMSystem.

2.3. Переместитесь к описанию формы и вставьте после служебного слова type перед описанием формы (класс TMPForm) следующую строку: TPlayerStatus = (NoDisk, InsertingDisk, Played, Paused, Stopped);

Компонент Media Player имеет свойство Mode, которое показывает его текущее состояние. Однако, проверяя это свойство, невозможно поймать тот драматический момент, когда диск только-только вставлен в CD-ROM - это свойство фиксирует либо отсутствие диска, либо его присутствие (и одно из нескольких состояний: не проигрывается, проигрывается и т. п.).

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

2.4. В секции private описания формы вместо комментария { private declarations } введите строки кода, данные во врезке "Объявления" на предыдущей странице.

2.5. Перейдите в секцию реализации модуля, найдите строку и введите под директивой компилятору {$R *.DFM} текст врезки "Вспомогательные методы".

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

StartPos, Tracks и TrackLength - свойства компонента Media Player (напомню, в данном проекте он называется Player), хранящие информацию соответственно о: текущем треке, количестве треков на диске и длине указанного трека. Длина трека в свойстве TrackLength хранится в так называемом упакованном формате MSF (минуты, секунды, фреймы).

У компонента Media Player еще имеется свойство TimeFormat, которое устанавливает формат, описывающий каждый трек CD. Но вот на свойство TrackLength TimeFormat никакого воздействия не оказывает. Почему это происходит - корпорация Borland умалчивает.

Так что придется, чтобы распаковать этот формат и извлечь из него количество минут и секунд, воспользоваться функциями MCI_MSF_MINUTE и MCI_MSF_SECOND соответственно, описанными в модуле MMSystem (см. 2.2). Используя эти две функции, значение свойства Max компонента ProgressBar устанавливается равным количеству секунд звучания текущего трека.

2.6. Теперь займемся таймерами. Компонент Timer обеспечивает возможность периодического выполнения какого-то однотипного действия с указанной в его свойстве Interval частотой. Таймер можно использовать для контроля текущего состояния CD-ROM (через повторяющееся с частотой 0,01 сек. обращение к компоненту Media Player).

А для чего понадобилось два таймера? Для упрощения кода лучше разделить две операции: проверку текущего состояния Media Player и вывод информации о текущем треке (индикатор и счетчик секунд в реале будут показывать текущее состояние выполняющейся композиции).

Дважды щелкните по компоненту Timer2 - он будет отвечать за индикацию состояния композиции, воспроизводящейся в данный момент - и введите между строками begin - end код, приведенный во врезке "Timer2".

Свойство Position компонента Media Player хранит информацию о текущем звуке в несколько ином формате - TMSF (трек, минуты, секунды, фреймы) и для ее извлечения понадобились функции, аналогичные тем, что были использованы в процедуре InitProgressBar (см. 2.5).

Доступ к отдельным фрагментам панели состояния (StatusBar) осуществляется через массив Panels, одно из полей которого (Text) хранит отображаемую на панели состояния информацию.

2.7. Теперь опишем метод, который позволит загружать информацию о композициях CD (о его треках). В описании формы введите под строкой public объявление следующего метода: procedure AddSongsToList ( Sng: TStringGrid );

Почему именно в секции public, а не private? Вы узнаете об этом чуть позже. Перейдите в конец модуля и перед финальной end. (с точкой) введите текст врезки "Получение информации о треках".

Комментарии в тексте метода (начинаются с символов //), думаю, делают излишними объяснение алгоритма.
2.8. А теперь потребуется создать механизм проверки каждого вновь вставленного в CD-ROM диска.

Поскольку опознавать диск придется при помощи таймера, то во избежание зависания при слишком долгой проверке, лучше всего воспользоваться механизмом сообщений Windows. В чем преимущество такого способа?

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

Несколько секунд - ерунда, когда речь идет о подсчете ежегодной прибыли корпорации Microsoft, а если на несколько секунд прервется исполнение вашей любимой песни? Вот то-то! Совсем другое дело - сообщение: программа с помощью специальной функции посылает ядру Windows сообщение, а уж как и в какой момент им распорядиться - Windows решает сама.

Переместитесь в верхнюю часть модуля и после секции uses (перед словом type) введите следующий текст:
const
WM_SCANCD = WM_USER + 1;
WM_UNKNOWNCD = WM_USER + 2;

Таким незамысловатым способом вы объявили два внутренних сообщения: сообщение о начале сканирования диска и сообщение, которое программа пошлет системе в том случае, если просмотренный диск окажется ей неизвестным.

2.9. Теперь перейдите к описанию формы и в секции private после описания трех служебных методов (см. 2.4) введите объявление для двух обработчиков выше объявленных сообщений (врезка "Обработка сообщений").

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

Самым верным решением, наверное, будет сканирование CD в тот момент, когда он только вставлен в устройство чтения (это мы уже выяснили). Для этого надо осуществлять периодический опрос текущего состояния CD-ROM. И тут нам на помощь приходит таймер. Поскольку компонент Timer2 уже занят (см. 2.6), используем Timer1.

2.11. Нажмите F12, чтобы перейти к шаблону формы и дважды щелкните по размещенному на ней компоненту Timer1. В шаблоне обработчика между парой begin - end (кстати, эта пара называется инструктивными скобками) поместите код, данный во врезке "Timer1".
Первоначально проверяется свойство Mode компонента Player (наш CD-проигрыватель), и в зависимости от его значения устанавливается значения переменной CurStatus.

Если оказывается, что диск находится в устройстве чтения, а устройство готово (состояние mpStopped), то проверяется предыдущее значение CurStatus. Если оно равно NoDisk, то программа приходит к очень логичному выводу, что диск только что вставлен и устанавливает CurStatus в InsertingDisk. Вот оно - мы поймали момент, когда диск только вставлен.

Ну а теперь уж совсем просто: в зависимости от текущего состояния CD-ROM выполняются те или иные действия. Если диск только-только вставлен, ядру ОС посылается сообщение WM_SCANCD - о начале проверки диска на предмет поиска информации о нем.

Сообщение посылается при помощи стандартной функции SendMessage, известной историкам еще со времен Windows 3.0.

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

Второй параметр - само сообщение; третий и четвертый параметры могли бы нести какую-нибудь информацию (и эту информацию можно было бы получить в обработчике, проанализировав структуру TMessage), но нам это сейчас ни к чему, а потому с легким сердцем мы поместили туда нули.

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

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

А дальше?

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

Объявления

private
Trk, Min, Sec: word; // Трек и его длительность (мин., сек.)
CD: TStringList; // Описание дисков
CurStatus: TPlayerStatus; // Текущий статус CD-ROM
procedure ClearSongsList;
procedure EmptyIndicat;
procedure InitProgressBar;

 

Вспомогательные методы

procedure TMPForm.ClearSongsList;
{Очистить список строк}
begin
with Songs do
begin
RowCount:=1; Cells[0,0]:=''; Cells[1,0]:=''; Cells[2,0]:='';
end;
Author.Caption:= ''; Album.Caption:= '';
end;

procedure TMPForm.EmptyIndicat;
{Очистить строку статуса}
begin
Status.Panels [0].Text:= '00'; Status.Panels [1].Text:= '00:00';
Status.Panels [2].Text:= '';
ProgressBar.Position:= 0; ProgressBar.Max:= 100;
end;

procedure TMPForm.InitProgressBar;
{Настройка индикатора звучания трека}
var Len: longint;
begin
with Player, ProgressBar do
if ( StartPos > 0 ) AND ( StartPos <= Tracks ) then
begin
Len:= TrackLength [ StartPos ]; Position:= 0;
Max:= MCI_MSF_MINUTE ( Len ) * 60 + MCI_MSF_SECOND ( Len );
// Вывод названия песни в строке статуса
Status.Panels [ 2 ].Text:= Songs.Cells [ 2, StartPos - 1 ];
end;
end;

 

Получение информации о треках

procedure TMPForm.AddSongsToList ( Sng: TStringGrid );
var I: longint; M, S: word;
begin
with Sng do
begin
// Установить количество строк в Sng равным количеству треков CD
RowCount:= Player.Tracks;
// Для каждого трека:
for I:= 1 to Player.Tracks do
begin
// Получить число минут
M:= MCI_MSF_MINUTE ( Player.TrackLength [ I ] );
// Получить число секунд
S:= MCI_MSF_SECOND ( Player.TrackLength [ I ] );
// Первая колонка - номер трека
Cells [ 0, I - 1 ]:= Format ( '%.2d', [ I ] );
// Вторая - время звучания
Cells [ 1, I - 1 ]:= Format ( '%.2d:%.2d', [ M, S ] );
// Третья пока пустая
Cells [ 2, I - 1 ]:= '';
end;
// Позиционировать указатель на первую строку
Row:= 0;
end;
end;

 

Обработка сообщений

procedure ScanCD ( var Msg: TMessage ); message WM_SCANCD;
procedure UnknownCD ( var Msg: TMessage ); message WM_UNKNOWNCD;

 

О сообщениях Windows

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

Все сообщения в Windows являются обычными двухбайтовыми числами: от 0000h до FFFFh (в 16-ричной системе счисления). Сообщения в интервале от 0401h до 7FFFh зарезервированы Microsoft для нужд прикладных программ. Для граничного интервала 0400h в Windows имеется специальная константа WM_USER. Таким образом, прикладной программист может описывать внутренние (для собственной программы) сообщения, добавляя некие фиксированные числа к константе WM_USER.

Но просто описать сообщение мало - нужно еще создать для него обработчик. В Object Pascal системы Delphi заголовок обработчика сообщения имеет следующую структуру: первым стоит служебное слово PROCEDURE, затем имя обработчика с единственным параметром MSG класса TMessage. Завершается обработчик служебным словом MESSAGE, после которого должно стоять уникальное число - собственно сообщение, описанное выше программистом с использованием константы WM_USER.

Для посылки сообщения используется стандартная функция, входящая в состав Windows API - SendMessage.

 

Заглушки

procedure TMPForm.ScanCD ( var Msg: TMessage );
begin end;
procedure TMPForm.UnknownCD ( var Msg: TMessage );
begin end;

 

Timer2

with Player, Status do
begin
Trk:= MCI_TMSF_TRACK ( Position );
Min:= MCI_TMSF_MINUTE ( Position );
Sec:= MCI_TMSF_SECOND ( Position );
// Закончилась одна песня и началась другая?
if ( Min = 0 ) AND ( Sec = 0 ) AND ( Mode = mpPlaying ) then
begin InitProgressBar; Songs.Row:= Trk - 1; end;
ProgressBar.Position:= Min * 60 + Sec;
// Информация в панели состояния
Panels [ 0 ].Text:= Format ( '%.2d', [ Trk ] );
Panels [ 1 ].Text:= Format ( '%.2d:%.2d', [ Min, Sec ] );
end;

 

Timer1

// Определить текущее состояние CD-ROM
with Player, Status do
case Mode of
mpOpen: CurStatus:= NoDisk; // Открыт (нет диска)
mpStopped: if CurStatus = NoDisk then // Диск есть, но не играет
CurStatus:= InsertingDisk
else if CurStatus <> Paused then
CurStatus:= Stopped;
mpPlaying: begin // Играет
CurStatus:= Played;
// Если список строк еще не заполнен
if Songs.RowCount = 1 then
AddSongsToList ( Songs );
end;
end;
// Активизировать второй таймер, если CD находится в устройстве чтения
Timer2.Enabled:= CurStatus <> NoDisk;
// Выполнить действия для каждого из текущих состояний диска
case CurStatus of
NoDisk: begin
ClearSongsList;
EmptyIndicat;
end;
InsertingDisk: begin
AddSongsToList ( Songs );
// Послать сообщение о начале сканирования
SendMessage ( Handle, WM_SCANCD, 0, 0 );
end;
end;

Краткое содержание предыдущей части. Некто, воодушевленный своими первыми удачными опытами в деле осваивания пакета Delphi 6.0, самонадеянно решает бросить перчатку в лицо разработчикам из корпорации Microsoft.

С этой целью этот самый некто решает создать собственный CD-проигрыватель, способный не только воспроизводить AudioCD, но и - внимание! - автоматически опознавать вставленный компакт, и, в случае такой надобности, позволять сохранять описание компакт-диска в импровизированной базе данных.

В проекте участвовали: MediaPlayer - компонент, отвечающий за работу с мультимедийными штучками, Timer (две штуки) - компонент, отвечающий за тиканье внутренних часов программы, а также разная вспомогательная мелюзга, как то: кнопка (Button), 2 метки (Label), индикатор затяжного процесса (ProgressBar) и панель статуса (StatusBar).

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

Быстро оправившись от удара, некто стоически заканчивает разработку проекта и предлагает всем желающим ознакомиться со второй частью своего дневника, в котором он - потомкам в назидание - скрупулезно записывал все свои манипуляции над проектом CD-player. Итак...

Show Must Go On!

2.12. Откройте ранее сохраненный проект CD-player. Для этого удобно выбрать пункт File > Reopen и в списке проектов выбрать нужный (если вы не работали с каким-то другим проектом, то искомый расположен в первой строке списка под номером 0). Теперь к нашему проекту необходимо добавить еще одну форму, которая будет использоваться для ввода информации о диске.

2.12.1. Выберите пункт File > New Form (или щелкните соответствующую кнопку на toolbar) и сохраните новый модуль на диске под именем CDdescript.pas.

2.12.2. Для новой формы установите следующие свойства: BorderIcons.byMinimize = False,
BorderIcons.byMaximize = False,
BorderStyle = bsDialog,
Caption = Описание диска,
Height = 305,
Name = DescriptionForm,
Width = 376.

2.12.3. Разместите на форме одну под другой три метки (компонент Label, страница Standard), назвав их, соответственно, (изменив их свойства Caption): "&Автор", "&Название" и "&Композиции" (если вас не устраивают такие названия, можете присвоить другие - что фантазия подскажет, например "Группешник", "Сидюшник" и "Хитюшники").

2.12.4. Справа от первых двух меток разместите два компонента Edit (на той же вкладке), свойству Name первого присвойте значение CDAuthor, а второго - CDName (а вот тут уже без самодеятельности - эти названия не вздумайте изменять) и очистите у обоих свойство Text.

2.12.5. Под меткой "Композиции" разместите уже хорошо вам известный компонент StringGrid (без труда найдя его на странице Additional). Присвойте следующие значения его свойствам:
ColCount = 3,
Color = clInfoBk,
DefaultRowHeight = 18,
FixedCols = 2,
FixedRows = 0,
Name = List,
Options.goFixedVertLine = False,
Options.goVertLine = False,
Options.goRangeSelect = False,
Options.goDrawFocusSelect = True,
Options.goEditing = True,
RowCount= 1,
ScrollBars = ssVertical.

Прежде чем устанавливать свойство FixedRows, измените ширину колонок. Свойство Options.goEditing устанавливает в True для того, чтобы иметь возможность непосредственно редактировать строку сетки. Поскольку свойство FixedCols равно 2, что делает две левых колонки недоступными, то редактировать можно будет лишь колонку названий композиций (а что нам еще нужно?).

2.12.6. У меток "Автор", "Название" и "Композиции" (или что там вы напридумывали) свойству FocusControl присвойте значения (выбрав из раскрывающегося списка), соответственно: CDAuthor, CDName и List. Если эти свойства не установить - на работоспособности программы это не отразится, но будет потеряна возможность выбора строк редактирования при помощи клавиатурных эквивалентов (Alt+А и пр.).

А хорошо разработанный интерфейс должен позволять выполнять любое действие как при помощи мыши, так и при помощи клавиатуры! Расширьте компоненты Edit, чтобы выровнять их по правому краю сетки строк.

2.12.7. Под списком строк разместите компонент Panel (страница Standard), очистите ее свойство Caption и установите следующие свойства (превратив, таким образом, в разделяющую бороздку):
BevelInner = bvRaised
BevelOuter = bvLowered
Height = 3
Установите ширину Panel равной ширине сетки строк.

2.12.8. Под "бороздкой" разместите две кнопки (компонент Button страницы Standard). Свойству Caption левой кнопки присвойте значение "&Сохранить", второй - "&Отмена". Для свойства ModalResult кнопки "Сохранить" из раскрывающегося списка выберите значение mrOk, для второй - mrCancel. Наконец, свойство Cancel кнопки "Отмена" установите в True (теперь отменить действие можно будет и нажатием на Esc).

2.12.9. Щелкните один раз по любому участку форму, не занятому компонентами. В Инспекторе объектов (Object Inspector) перейдите к закладке Events и дважды щелкните по свойству OnActivate (только щелкать надо не по названию, а по пустой строке ввода - она же раскрывающийся список, расположенной слева от названия свойства). В шаблон созданного обработчика введите между строками begin и end текст из врезки "Формирование сетки".

2.12.10. Переместитесь к строке {$R *.DFM} (директива компилятору для подключения внешнего описания формы) и под ней на новой строке введите:
uses MainForm;

2.12.11. Щелкните по закладке MainForm Редактора Кода (Code Editor). Здесь также найдите строку {$R *.DFM} и под ней введите: 
uses CDdescript;

Чуете? Таким нехитрым способом вы осуществили взаимную ссылку одного модуля на другой (а того, в свою очередь, на первый). Но делать это можно лишь в секции реализации модуля (под строкой {$R *.DFM}). Если такую штуку проделать в секции интерфейса, то компилятор выдаст сообщение об ошибке.

Теперь пара слов о методе AddSongsToListглавной формы MPForm, вернее не о самом методе, а о том, для чего он помещен в секцию public описания формы. Дело в том, что этот метод нам понадобился для заполнения сетки строк второй формы (см.2.12.9). Если бы его описание было помещено в секции private главной формы, то вторая форма его бы не "увидела", ибо в эту секцию помещаются внутренние методы и свойства, недоступные извне.

Для этого, кстати, в модуле CDdescript и понадобилось подключение модуля MainForm. Обычно этого не требуется (кстати замечу: если вы забудете выполнить подключения, умница Delphi заметит это при компиляции и сама предложит выполнить связь с модулем дополнительной формы).

2.12.12. Выровняйте все компоненты второй формы, сверившись с рисунком и сохраните проект.

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

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

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

В модуле MainForm переместитесь на самый верх к секции объявления констант (const) и под объявлением сообщений введите такой текст:
CH_TILDA = '~';
CH_ZERO = '^';
CH_VERT = '|';
FILE_NAME = 'cd.txt';

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

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

2.14. Переместитесь в секцию реализации и сразу после строки uses CDdescript; введите код из врезки "Описание компакт-диска".

Функция FoundPos носит вспомогательное значение и используется в GetSubString для поиска в строке позиции символа-разделителя. Функция GetSubString возвращает из строки S подстроку номер Num, обрамленную разделителем Delim.

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

2.15. Договоримся о структуре строки, хранящейся в файле. Пусть она состоит из пяти полей, разделенных символом CH_TILDA:
1. Общая длина диска (свойство Length компонента MediaPlayer).
2. Количество треков (свойство Tracks компонента MediaPlayer).
3. Название диска (присваивается пользователем).
4. Автор/исполнитель (присваивается пользователем).
5. Секция описания композиций.

В секции описания композиций одно описание от другого отделяется символом CH_ZERO и состоит из трех, разделенных символом CH_VERT полей:
1) длина трека (в формате "минуты:секунды");
2) название композиции;
3) зарезервировано (вдруг вы доработаете проект и захотите хранить еще какую-нибудь информацию о композиции, например, ее текст.Между прочим, почему бы нет?).

Теперь у нас имеется все, чтобы наконец-то описать текст обработчиков сообщений WM_SCANCD и WM_UNKNOWNCD. Найдите в модуле MainForm заглушки обработчиков. Вместо строк begin - end метода TMPForm.ScanCD введите код из врезки "База компакт-дисков".

В этом методе организован цикл WHILE DO по всем строкам, загруженным в список CD. В каждой очередной строке получается информация (с помощью функции GetSubString) о длине CD и количестве композиций. Если она совпадает с той, что в данный момент хранится в компоненте MediaPlayer (Player), организуется еще один - внутренний цикл WHILE DO, в котором также с помощью GetSubString получается информация о времени звучания каждой композиции и сверяется со значением массива TrackLength, который хранит информацию о композициях текущего диска.

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

Ха-ха, нет, в этом случае программа с помощью стандартной функции MessageDlg информирует пользователя о ситуации и спрашивает его разрешение добавить описание диска в базу.

2.16. Перейдите в главной форме к заглушке TMPForm.UnknownCD и введите вместо строк begin - end код из врезки "Ввод описания".

Метод ShowModal - стандартный метод любой формы, предназначенный для вывода ее на экран. Метод возвращает mrOk, если пользователь нажал кнопку Ok (кнопка, свойство которой ModalResult, равна mrOk - см. 2.12.8) в вызываемой форме.

Если это произошло, новое описание сохраняется в списке строк (CD.Add), затем весь список сохраняется в файле на диске (CD.SaveToFile) и вновь посылается сообщение (SendMessage) WM_SCANCD,в результате чего в главной форме будет выведена только что введенная информация. При формировании сохраняемой строки используется еще не описанный нами метод DescriptionCD.

2.17. Введите в разделе private описания главной формы следующие строки:
procedure ShowSongsName 
( TotSongs: word; S: string );
function DescriptionCD
( Name, Album: string; Song: TStringGrid ): string;
Затем переместитесь к концу модуля и введите текст, который приведен во врезке "Метод DescriptionCD".
Здесь формируется структурированная строка с использованием всех тех разделителей, которые мы обсудили выше (см. п. 2.15).
2.18. Начиная с новой строки введите текст метода "Заполнение сетки" (см. одноименную врезку).

Этот метод используется (см. 2.15) для заполнения сетки строк главной формы названиями композиций.

2.19. Нажмите F12, чтобы перейти к шаблону, и сделайте активной саму форму. На странице Events Инспектора объектов дважды щелкните по событию OnCreate и введите следующий текст в созданном шаблоне (между строками begin и end):
CurStatus:= NoDisk;
CD:= TStringList.Create;
// Загрузить описание дисков из файла
CD.LoadFromFile ( FILE_NAME );
// Очистить индикаторы
EmptyIndicat;

Вновь перейдите к Инспектору Объектов, создайте шаблон для события OnClose и введите следующую строку:
CD.Free;

2.20. И наконец, еще несколько коротких обработчиков. Дело в том, что, несмотря на имеющийся список треков, до сих пор еще нельзя запускать произвольную композицию. Чтобы можно было двойным щелчком по любой строке (или нажатием Enter) запускать нужную песню, перейдите к шаблону главной формы, сделайте активным сетку строк и создайте шаблон для обработчика события OnDblClick (двойной щелчок мышью). Введите в шаблоне код приведенный во врезке с соответствующим названием -"Обработка двойного щелчка".

Теперь для этой же сетки строк создайте обработчик события OnKeyPress и введите:
// Если нажаты Enter или пробел
if Key IN [ #13, #32 ] then
SongsDblClick ( Sender );
Теперь снова перейдите к шаблону главной формы и дважды щелкните по компоненту MediaPalyer.

В созданном шаблоне введите следующее:
// Кнопка Play не нажата
if Button <> btPlay then
DoDefault:= true
else
SongsDblClick ( Songs );

2.21. Последнее. Выберите пункт меню File > New. В появившемся окне New Items на странице New дважды щелкните по пиктограмме Text. Выберите пункт File > Save As и сохраните пустой текстовый файл в том же каталоге, что и весь проект под именем, присвоенном константе FILE_NAME (см. 2. 13).

Сохраните проект. Перед этим рекомендую изменить пиктограмму проекта - я полагаю, что при помощи программы, рассмотренной в статье Килограмм пиктограмм (Upgrade #63) вы уже создали свою замечательную коллекцию.

Наконец-то!

Уф-ф-ф! Это все - можете слушать и попутно составлять описание вашей аудиотеки.
Можно, конечно, задаться вопросом: зачем нужно было конструировать CD-плейер, когда в поставке Windows уже имеется один.

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

Ну, и в-третьих, только что созданный проект лучше, чем его "майкрософтовский" аналог тем, что можно расширять его свойства.

Как? А для этого нужно продолжать изучение библиотеки визуальных компонентов среды Delphi. Так что следите за публикациями. 

Формирование сетки

 
CDAuthor.Text:= ''; CDName.Text:= ''; List.RowCount:= 1;
List.Cells [0,0]:= ''; List.Cells [1,0]:= ''; List.Cells [2,0]:= '';
// Заполнить сетку строк информацией о треках
MPForm.AddSongsToList ( List );

 

Описание компакт-диска

 
function FoundPos ( St, Dlm: string ): integer;
var StPtr, DlmPtr: PChar;
begin
St:= St + #0;
Dlm:= Dlm + #0;
StPtr:= @St [ 1 ];
DlmPtr:= @Dlm [ 1 ];
DlmPtr:= StrPos ( StPtr, DlmPtr );
Result:= DlmPtr - StPtr + 1;
end;

function GetSubString ( S, Delim: string; Num: integer ): string;
var I, P1, P2: integer;
begin
Result:= ''; P1:= FoundPos ( S, Delim );
if P1 > 0 then
if Num = 1 then
Result:= Copy ( S, 1, P1 - 1 )
else
begin
// Найти левый обрамляющий символ
I:= 1;
while ( I <= Num - 1 ) AND ( P1 > 0 ) do
begin
P1:= FoundPos ( S, Delim );
if P1 > 0 then S:= Copy ( S, P1 + 1, Length ( S ) );
Inc ( I );
end;
if P1 > 0 then
begin
// Найти правый обрамляющий символ
P2:= FoundPos ( S, Delim );
if P2 > 0 then S:= Copy ( S, 1, P2 - 1 );
if Length ( S ) > 0 then Result:= S
else Result:= '';
end
else Result:= '';
end
else
if Num = 1 then Result:= S
else Result:= '';
end;

 

База компакт-дисков

 
var I, J, L, Len, TotSongs: integer; Found: boolean; S, S2, S3: string;
begin
Found:= false; I:= 0; L:= CD.Count - 1;
// Просмотреть все строки
while ( NOT Found ) AND ( I <= L ) do
begin
S:= CD [ I ]; Len:= StrToInt ( GetSubString ( S, CH_TILDA, 1 ) );
TotSongs:= StrToInt ( GetSubString ( S, CH_TILDA, 2 ) );
if ( Len = Player.Length ) AND ( TotSongs = Player.Tracks ) then
begin
S2:= GetSubString ( S, CH_TILDA, 5 ); Found:= true; J:= 1;
while Found AND ( J <= TotSongs ) do
begin
S3:= GetSubString ( S2, CH_ZERO, J );
Found:= ( Player.TrackLength [ J-1 ] =
StrToInt ( GetSubString (S3,CH_VERT,1) ) );
Inc ( J );
end;
end;
Inc ( I );
end;
if Found then
begin
Author.Caption:= GetSubString ( S, CH_TILDA, 3 );
Album.Caption:= GetSubString ( S, CH_TILDA, 4 );
ShowSongsName ( TotSongs, S2 );
end
else
if MessageDlg ( 'Неизвестный CD. Добавить описание CD в базу?',
mtConfirmation, [mbYes, mbNo], 0 ) = mrYes then
SendMessage ( Handle, WM_UNKNOWNCD, 0, 0 );
end;

 

Ввод описания

 
begin
with DescriptionForm do
// Пользователь нажал OK?
if ShowModal = mrOk then
begin
CD.Add ( DescriptionCD ( CDAuthor.Text, CDName.Text, List ) );
CD.SaveToFile ( FILE_NAME );
SendMessage ( Handle, WM_SCANCD, 0, 0 );
end;
end;

 

Метод DescriptionCD

 
function TMPForm.DescriptionCD ( Name, Album: string; Song: TStringGrid ): string;
var I: word;
begin
Result:= Format ( '%d', [Player.Length] ) + CH_TILDA;
Result:= Result + Format ( '%d', [Player.Tracks] ) + CH_TILDA;
Result:= Result + Name + CH_TILDA;
Result:= Result + Album + CH_TILDA;
with Song do
// Просмотреть все строки сетки
for I:= 1 to RowCount do
begin
Result:= Result + Format ('%d',[Player.TrackLength[I-1]])+CH_VERT;
// Добавить название композиции и пустой зарезервированный блок
Result:= Result + Cells [2,I-1] + CH_VERT + CH_VERT + CH_ZERO;
end;
end;

 

Заполнение сетки

 
procedure TMPForm.ShowSongsName;
var I: word; Sng: string;
begin
with Songs do
for I:= 1 to TotSongs do
begin
Sng:= GetSubString ( S, CH_ZERO, I );
Cells [ 2, I - 1 ]:= GetSubString ( Sng, CH_VERT, 2 );
end;
end;

 

Обработка двойного щелчка

 
with Player do
begin
// Позиционировать согласно указанной строки
StartPos:= ( Sender AS TStringGrid ).Row + 1;
// Эмулировать нажатие кнопки Play
Play;
end;
InitProgressBar;

 

 


<< на главную

Дмитрий Румянцев
rd9145@mail.ru


 



При перепечатке материалов сайта ссылка на UPGRADE обязательна. 
Имена и фамилии авторов изменять не рекомендуется.

  programmer: 
  Илья Васильев
новые поступления: 
vano@veneto.ru
PR-менеджер: 
Екатерина Кожанова
  newswriters:
  Николай Барсуков
  Александр Савицкий
тех. поддержка по софту:
stnvidnoye@mail.ru;
problem@veneto.ru
менеджер тестовой лаборатории:
testlab@veneto.ru
(495) 246-7468
  content: egor_be 
 
тех. поддержка по железу:
problem@veneto.ru
отдел рекламы: (495) 745-6898, 510 58 31
Виноградов Павел, Илья Саньков
директор отдела распространения Ирина Агронова agronova@veneto.ru: (495) 681-7837, 684-5285

© © 2000-2006 Upgrade