http://www.nadomnik.by.ru/


Сиди, слушай

Дмитрий Румянцев

Сегодня мы вступим в единоборство с колоссом на силиконовых ногах - корпорацией 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;

Продолжение следует...

Источник: http://www.computery.ru/upgrade/

 


Copyright © "Internet Zone", http://www.izcity.com/, info@izcity.com