Кладовая программиста. Медиа-комбайн, часть 2.
В прошлый раз мы создали многофункциональный медиа-комбайн. Сегодня мы сделаем из него супермашину, способную если не по удобству, то по возможностям заткнуть за пояс такого гиганта, как Windows Media Player. Давайте немного помечтаем. Что сможет делать наш комбайн? Во-первых, он без лишних проблем откроет файлы форматов dib , jpg , bmp , ico , emf , wmf , avi , wav , mid и уникальный формат плейлистов — list. Во-вторых, он все эти файлы может микшировать в плейлисты, которые, в свою очередь, без проблем можно будет сохранить на винчестере. Комбайн организует слайд-шоу и даже создает презентации с музыкой и видео. Всеми этими процессами несложно управлять с удобной панельки. Есть даже независимое — чтобы перетаскивать с помощью мышки — окошко вывода. Скорость пролистывания слайдов можно изменять. Доставайте из заначки исходники недоделанного комбайна — и продолжим. Если по каким-то причинам вы не читали статью позапрошлого номера “Мании”, не отчаивайтесь — материал ждет вас в разделе “ИнфоБлок” нашего CD- или DVD-диска. Там же разместились исходники комбайна.
Чтобы создать подобное окошко, потребуется не более трех минут.
В дорогу дальнюю… В прошлой статье мы остановились на пункте меню Запустить слайд-шоу. С него и продолжим. Допустим, пользователь желает запустить слайд-шоу. Создайте обработчик события для этого пункта меню (у меня он называется TForm1.N11Click ). Для начала введите глобальную переменную, которая показывает, запущено слайд-шоу или нет. Назовите ее is_show. В глобальный раздел Var добавьте описание этой переменной: is_show:boolean=false; По умолчанию значение переменной установите в “ложь”, ведь изначально слайд-шоу отключено. Помните, в прошлый раз мы решили не вводить дополнительный пункт меню для остановки слайд-шоу? Вместо этого давайте сделаем так, чтобы при его запуске название пункта меню изменилось на Остановить слайд-шоу. Как это сделать? Сначала необходимо “переключить” переменную is_show. То есть если ее значение true , установить его в false и наоборот. Быстрее всего это сделать строчкой: Is_show:= not is_show; Логический оператор not как раз переключает переменные. Теперь, в зависимости от значения переменной is_show , надо задать надпись пункту меню. Давайте создадим элементарную конструкцию выбора case с переменной is_show в качестве селектора. Вариантов всего два: true и false. Если значение переменной is_show равно true , то слайд-шоу уже запущено и его необходимо остановить. Если же false , то слайд-шоу надо, наоборот, запустить. Вот участок кода, который это реализует: case is_show of true:N11.Caption:=‘Остановить слайд-шоу’; false:N11.Caption:=‘Запустить слайд-шоу’; end; Теперь, если слайд-шоу запускается, надо показать первый элемент в списке воспроизведения. Мы уже делали это, когда создавали обработчик для пункта меню Открыть… Зачем делать одну и ту же работу дважды? Скопируйте участок кода, который отвечает за загрузку: **if form1.listbox1.ItemIndex <0 then form1.listbox1.ItemIndex:=0; s:=ExtractFileExt(opendialog1.FileName); if (s=‘.dib’) or (s=‘.jpg’) or (s=‘.jpeg’)
Плеер в действии. Проигрывается трейлер к популярной игре.
or (s=‘.bmp’) or (s=‘.ico’) or (s=‘.emf’) or (s=‘.wmf’) then begin Form2.Hide; Form3.image1.picture.loadfromfile(opendialog1.filename); Form3.Width:=Form3.image1.Picture.Width; Form3.Height:=Form3.image1.Picture.Height; Form3.image1.picture.loadfromfile(opendialog1.filename); Form3.Image1.Visible:=true; Form3.show; end; if (s=‘.avi’) or (s=‘.wav’) or (s=‘.mid’) then begin Form2.Show; Form3.Hide; if s=‘.avi’ then Form3.Show; Form3.Image1.Visible:=false; Form2.Mediaplayer1.Display:=Form3.Panel1; Form2.MediaPlayer1.FileName:=opendialog1.FileName; Form2.MediaPlayer1.Open; Form3.Width:=Form2.MediaPlayer1.DisplayRect.Right-Form2.MediaPlayer1.DisplayRect.left; Form3.Height:=Form2.MediaPlayer1.DisplayRect.Bottom-Form2.MediaPlayer1.DisplayRect.Top; Form2.MediaPlayer1.play; end;В таком виде код еще неработоспособен. В нем нет переменнойs**. Поэтому объявите ее в локальном разделеvar :
В окошке открытия файла теперь можно выбрать одну из четырех групп типов файлов, которые поддерживает программа.
s:string; Осталась лишь одна проблема: откуда брать имя файла, который требуется открыть? Ведь никакого оператора opendialog нет. Если пользователь запустит слайд-шоу, то начаться оно должно с текущей выделенной строки в ListBox1. Как получить индекс выделенной строки? Очень просто: listbox1.ItemIndex. Следовательно, как получить содержимое выделенной строки (по совместительству искомое имя файла)? Еще проще: listbox1.Items.Strings[listbox1.ItemIndex]. И еще один штришок. Вышеописанный код должен сработать только в том случае, если пользователь включит слайд-шоу. Если он его выключит, действия должны быть совсем другие. Поэтому весь код заключите в условие: If is_show then begin // ваш код end; Почему условие выглядит так непривычно? Дело в том, что для переменных типа boolean условие вида переменная=true можно сократить просто до имени переменной. Это очень удобно. Между операторами условия begin и end вставьте скопированный код. На протяжении всего кода замените функцию opendialog1.filename на listbox1.Items.Strings[listbox1.ItemIndex]. После этого скрипт заработает. Основные переключатели Пришло время обеспечить переключение файлов во время слайд-шоу. К картинкам и активным файлам (видео и звук) подход различен. Если текущий файл — картинка, перед запуском следующего пункта Listbox1 надо сделать время задержки (хранится в переменной M ). С видео и звуками дело обстоит по-другому. Следующий файл надо запустить не через какое-то определенное время (откуда мы знаем, как долго длится клип?), а когда текущий клип завершится. Давайте рассмотрим эти два варианта по порядку. Начнем с картинок. Как организовать задержку? Для этого существует специальный невизуальный компонент Timer. Самый первый на вкладке System. Найдите его и поместите в любое место на форме. Соответствующий объект будет называться Timer1. Поначалу таймер должен быть отключен, поэтому его свойство Enabled установите в false. У таймера одно-единственное событие — OnTimer , которое срабатывает, как только время, задающееся свойством Interval , истечет. Не забудьте, что время задается в миллисекундах (одна тысячная секунды). Как только пользователь включает слайд-шоу, нам надо запустить таймер. В обработчике N11Click найдите второе условие (текущий объект — картинка) и после строчки Form3.show пропишите: Timer1.Interval:=M*1000; Timer1.Enabled:=true;
Почему “M*1000”? В миллисекундах время считают только совсем уж крутые программисты. А пользователь, скорее всего, будет вводить время в привычных секундах. Отсюда это простое преобразование. Таймер запущен. Как только он сработает, мы должны его выключить (до поры до времени), выделить следующую строчку в списке и запустить этот файл стандартным куском кода. Создайте обработчик события таймера Timer1Timer и пропишите для него код: Timer1.Enabled:=false; if listbox1.Items.Count-1=listbox1.ItemIndex then listbox1.ItemIndex:=0 else listbox1.ItemIndex:=listbox1.ItemIndex+1; Это еще не все содержимое обработчика Timer1Timer , но к нему мы вернемся после. У вас, скорее всего, возник закономерный вопрос: зачем нужно условие во второй строчке? Почему бы просто не присвоить единицу свойству ItemIndex? Представьте себе, что после нескольких слайдов указатель выделения списка дойдет до его конца. Очередной командой приращения указатель сдвинется… на несуществующую строку. Естественно, возникнет критическая ошибка, и программа вылетит. Поэтому перед приращением необходимо проверить, не добрался ли счетчик до конца списка. Если добрался, то указатель выделения (свойство ItemIndex ) обнуляется, если нет — увеличивается на единицу. Вот и все премудрости. Окольные дорожки Перейдем ко второму варианту. Что, если очередной файл из списка воспроизведения — не картинка, а звуковой или видеоклип? В этом случае необходимо дождаться его завершения, а потом открыть следующий в списке файл. Как узнать, что клип подошел к концу? Есть много способов. Но самый простой, на мой взгляд, — воспользоваться обработчиком события компонента TmediaPlayer — OnNotify. Это событие срабатывает, когда меняется режим работы медиа-плеера. Сам режим можно определить с помощью свойства Mode. Нам интересно только одно его значение — mpStopped. Как вы уже догадались, оно показывает, что плеер остановился, потому что клип закончился.
Все пять форм программы
Когда клип не проигрывается, режимы тоже могут меняться. Поэтому появятся неизбежные ложные срабатывания обработчика. Эти ложные срабатывания надо как-то устранить. Самое простое — ввести еще одну переменную и установить ее в положение true непосредственно перед началом воспроизведения. Внутри обработчика OnNotify необходимо проверить состояние переменной Mode и новой булевой переменной. Если обе проверки проходят — воспроизведение точно прекратилось, и можно переключаться на следующий файл списка. Объявите эту вспомогательную глобальную переменную: video:boolean=false; По умолчанию она “выключена”. Где ее включать? В нашем коде, который отвечает за последовательное воспроизведение. Вновь открывайте обработчик N11Click и во второй условной ветке после строчки Form2.MediaPlayer1.play пишите: video:=true; Теперь предусмотрены почти все варианты действий пользователя, за исключением одного — когда юзер кликает по пункту меню Остановить слайд-шоу. Так как программно этот пункт меню находится в N11Click , надо изменить первое его условие. Найдите предпоследний оператор end обработчика и удалите точку с запятой. Прямо после end пишите: else begin Timer1.Enabled:=false; video:=false; end; Теперь, если переменная is_show не равна true (то есть равна false ), слайд-шоу остановится. Выключите таймер и переменную video. Обработчик N11Click готов. В конечном варианте он выглядит так: procedure TForm1.N11Click(Sender: TObject); var s:string; begin Is_show:= not is_show; case is_show of true:N11.Caption:=‘Остановить слайд-шоу’; false:N11.Caption:=‘Запустить слайд-шоу’; **
Код, обслуживающий форму № 4, запрашивающую у пользователя время задержкиend; if is_show then begin if form1.listbox1.ItemIndex<0 then form1.listbox1.ItemIndex:=0; s:=ExtractFileExt(listbox1.Items.Strings[listbox1.ItemIndex]); if (s=‘.dib’) or (s=‘.jpg’) or (s=‘.jpeg’) or (s=‘.bmp’) or (s=‘.ico’) or (s=‘.emf’) or (s=‘.wmf’) then begin Form2.Hide; Form3.image1.picture.loadfromfile(listbox1.Items.Strings[listbox1.ItemIndex]); Form3.Width:=Form3.image1.Picture.Width; Form3.Height:=Form3.image1.Picture.Height; Form3.image1.picture.loadfromfile(listbox1.Items.Strings[listbox1.ItemIndex]); Form3.Image1.Visible:=true; Form3.show; Timer1.Interval:=M*1000; Timer1.Enabled:=true; end; if (s=‘.avi’) or (s=‘.wav’) or (s=‘.mid’) then begin Form2.Show; Form3.Hide; if s=‘.avi’ then Form3.Show; Form3.Image1.Visible:=false; Form2.Mediaplayer1.Display:=Form3.Panel1; Form2.MediaPlayer1.FileName:=listbox1.Items.Strings[listbox1.ItemIndex]; Form2.MediaPlayer1.Open; Form3.Width:=Form2.MediaPlayer1.DisplayRect.Right-Form2.MediaPlayer1.DisplayRect.left; Form3.Height:=Form2.MediaPlayer1.DisplayRect.Bottom-Form2.MediaPlayer1.DisplayRect.Top; Form2.MediaPlayer1.play; video:=true; end; end else begin Timer1.Enabled:=false; video:=false; end; end;
Вместо того чтобы делать новое окошко О программе…, модно взять стандартное окошко Delphi и лишь слегка отредакти- ровать в нем надписи. В Delph i 6 это делается через опцию File/New/Other/Forms/About box.В обработчиках не хватает только операции запуска плеера. У нас уже есть готовый кусок кода для этого действия. Скопируйте его изN11Click**,начиная со строкиs:=ExtractFileExt(listbox1.Items.Strings[listbox1.ItemIndex])и заканчивая предпоследнимend. Перейдите в обработчик таймера и после последней команды вставляйте скопированный код. Не забудьте в локальном оператореvarобъявить строковую переменнуюs. Перейдите вOnNotifyи пишите:if video and (mediaplayer1.Mode=mpStopped) then begin end; Это условие мы уже детально разобрали. Междуbeginиendвставляйте код. Не забудьте объявить локальную строковую переменнуюs. Код мы поместили в другой модуль, а ссылаемся мы из этого кода на некоторые объекты первого модуля. Значит, второй модуль надо связать с первым. Перед самым обработчиком добавьте строку:uses unit1;Теперь перед всеми упоминаниямиlistbox1иTimer1поставьте выражениеform1.(прописывается слитно). Во второе условие обработчика (которое отвечает за ветвь картинок) вставьте строку:video:=false;Запускайте медиа-комбайн и слайд-шоу. Все должно функционировать.Штрихи к портретуПришло время придать законченный вид комбайну. У нас осталось два необработанных пункта меню:СправкаиО программе. Когда пользователь кликает по пунктуСправка, должен открыться справочный файл программы в форматеhlp(стандартная) илиchm(продвинутая). Создание самого файла справки остается на вашей совести. Для этого существует множество программ, например ориентированный на любителейHTM2CHMили профессиональныйHTML Help Workshop. Задача программиста — прописать в обработчике этого пункта меню открытие файла справки. Соответствующий обработчик выглядит так:procedure TForm1.N13Click(Sender: TObject); begin ShellExecute(Application.Handle,‘open’,PChar(extractfilepath(application.exename)+’ help.hlp’),nil,nil,SW_SHOWNORMAL); end;
Одна из самых длинных процедур в медиа-комбайне. Но не так страшен черт, как его… исходники! Стоит разобрать “монстра” по строчкам, и он становится милым и пушистым.
Он состоит из одной довольно сложной команды. Ее основа — функция ShellExecute , которая позволяет открыть любую папку, документ (в том числе веб-страницу), выполнить некоторые системные команды, а также запустить программу или скрипт. Вот такая функция-многостаночник. Рассмотрим ее поподробнее (не забудьте в текущий раздел uses добавить модуль ShellAPI , которому принадлежит функция). Первый параметр функции ShellExecute — дескриптор программы , которая осуществляет вызов. Пока не будем детально изучать, что такое дескриптор, иначе мы рискуем углубиться в основы API и ядра Windows. Неподготовленный программист там просто потеряется. Но в ближайших номерах мы обязательно рассмотрим ядро и API. Без этих знаний программист никогда не сможет стать профессионалом. Пока же вам будет достаточно знания, что дескриптор — это своеобразный индекс, который указывает на конкретную программу. Зная дескриптор программы, с ней несложно совершить практически любое действие. Получить дескриптор программы можно через свойство Application.Handle. Второй параметр — текстовая строка, содержащая один из трех стандартных методов работы с файловыми объектами: open , print и explore. Open и print работают почти со всеми файлами, а explore —только с папками. Open открывает документ, запускает другую программу или исполняет системную команду, print печатает документ, а explore открывает папку в окне Проводника. Сейчас нам нужно открыть файл справки как документ, поэтому выбираем метод open. Третий параметр — это путь к открываемому документу. Выражение extractfilepath(application.exename) вам уже знакомо — оно возвращает путь до конкретной программы, чтобы можно было адресоваться к файлам, которые лежат в той же папке. Далее следует указание на имя конкретного файла. Почему все выражение упрятано в скобки и перед ним стоит странный операнд Pchar? Дело в том, что Object Pascal (Delphi) — не “родной” язык программирования для Windows. Традиционно программы для него писались на C++. Само ядро
Программа с заполненным плейлистом
и API также написаны по большей части на С++, поэтому унаследовали особенности этого языка. В С++ нет переменных типа String. Зато есть массивы символов (тип Char ). Каждая строка оканчивается так называемым терминатором (знаком #0 ). Поэтому при вызове функций ядра все строки приходится конвертировать в тип PChar (указатель на область памяти, занятый массивом типа Char ). Для этого служит команда Pchar , которой мы воспользовались. В четвертом параметре хранятся параметры запуска. Так как мы запускаем не программу, этот параметр надо забить нулем — nil. Пятый параметр также забиваем nil , потому что в нем должен храниться путь к рабочей папке программы. Последний параметр — переменная, которая показывает, как должно отображаться на экране окно программы или документа после запуска. Нам нужен стандартный запуск, поэтом ставим сюда значение SW_SHOWNORMAL. Код для пункта меню О программе вы уже сможете написать самостоятельно — действуя по аналогии. *** В вашем распоряжении мощнейший медиа-комбайн, который разве что носки не стирает и кашу не варит. Это и мощный слайд-шоу-генератор, и утилита для создания презентаций. Вы ждете домашних заданий? Их есть у нас. В комбайне во многих местах не проверяются вводимые значения. Например, если в окошке задания задержки для слайдов ввести не число, а букву, программа вылетит. Или, если открыть файл не того типа, появится сообщение об ошибке. Перехватить все эти ошибки и поставить надежную “защиту от дурака” — ваша главная задача. Также можете несколько усовершенствовать комбайн, например добавить поддержку формата mp3. Но для этого вам придется обращаться к сторонним компонентам, так как в Delphi такой поддержки нет. В следующей статье, посвященной Delphi, мы займемся программированием оболочки для компакт-диска.