Классический музыкальный автомат

19 мая 2009 12:09 | Coding4Fun | 0 отзывов

Сегодня мы сделаем музыкальный автомат в классическом стиле (EN), используя WPF, WMP, немного трехмерных эффектов и простой электроники (стоимостью менее $10), а также капельку шаблона «модель-представление-модель представления» (M-V-VM).

Руди Гроблер (Rudi Grobler (EN))

clip_image002

· Сложность: средняя

· Необходимое время: 2-4 часа

· Цена: ~$10 за пять кнопок и разъем LPT

· ПО: Visual C# Express Edition 2008

· Исходные тексты: доступны на CodePlex (EN)

Введение

Пару недель назад я привел сынишку поразвлечься в зал игровых автоматов. В углу я заметил старый музыкальный автомат и начал размышлять над тем, как бы воссоздать его с использованием «настоящего железа» и WPF. В этой статье рассказывается как раз о реализации такого приложения, управляющего аппаратными средствами.

M-V-VM

“Если разработчик привык к WPFи M - V - VM , эти последние становится трудно различить. MVVM — своеобразный общепринятый язык разработчиков WPF, потому что он хорошо приспособлен для платформы WPF, а WPF создавался для упрощения сборки приложений с помощью шаблона M-V-VM (и других). В корпорации Майкрософт M-V-VM использовался для внутренних целей при разработке приложений WPF, например Microsoft Expression Blend, пока основная платформа WPF еще только создавалась. Многие части WPF, например модель контроля без просмотра и шаблоны данных, используют значительное разделение показа от состояния и поведения, применяемое в MVVM.”

Джош Смит (Josh Smith). «Приложения WPF с шаблоном проектирования "модель-представление-модель представления"».

Шаблон M-V-VM помогает отделить логику от интерфейса пользователя, что ОЧЕНЬ важно при реальном управлении ходом программы из внешних источников (например, с помощью кнопок). Наш класс MediaViewModel исключительно прост:

clip_image004

В MediaViewModel имеется набор альбомов, который извлекается при вызове Initialize(), а также некоторые команды.

Работа с мультимедийной библиотекой Windows Media Player

clip_image006

Убедитесь, что в вашей мультимедийной библиотеке Windows Media Player (WMP) есть записи. Я добавил в свою библиотеку несколько альбомов с помощью команды меню «Добавить в библиотеку».

Совет Проверьте наличие всех тегов ID3 и обложек для добавленных альбомов.

Обращение к библиотеке WMP

Для доступа к мультимедийной библиотеке из WMP мы будем использовать некоторые возможности COM:

“Технология MicrosoftCOM ( ComponentObjectModel ) обеспечивает взаимодействие программных компонентов в операционных системах семейства MicrosoftWindows . COMприменяется разработчиками для создания многократно используемых программных компонентов, для связывания компонентов в единое приложение, а также позволяет задействовать возможности служб Windows . Семейство технологий COMвключает COM +, распределенную СОМ ( DistributedCOM , DCOM ) и элементы управления ActiveX .”

Добавить ссылку на COM-компонент не сложней, чем добавить ссылку на сборку .NET. В меню Project (Проект) выберите Add Reference (Добавить ссылку) и щелкните вкладку COM в диалоговом окне Add Reference (Добавление ссылки).

clip_image008

Библиотека WMP представляет собой простой набор элементов, так что иерархию «Альбомы — Дорожки» надо создавать вручную. Вот наша модель:

clip_image010

Мы абстрагируемся от извлечения данных посредством интерфейса IMediaLibraryRepository. Благодаря этому в будущем мы сможем поддерживать другие медиа-источники, например iTunes.

C#:

    1: public interface IMediaLibraryRepository 
    2: { 
    3:     IList<Album> GetAlbums(); 
    4: }

Вот наша реализация для WMP:

C#:

    1: public class WMPMediaLibraryRepository : IMediaLibraryRepository 
    2: { 
    3:     public IList<Album> GetAlbums() 
    4:     { 
    5:         List<Album> Albums = new List(); 
    6:         WindowsMediaPlayer wmp = new WindowsMediaPlayer(); 
    7:         IWMPPlaylist playlist = wmp.mediaCollection.getAll(); 
    8:         for (int i = 0; i < playlist.count; i++) 
    9:         { 
   10:             IWMPMedia media = (IWMPMedia)playlist.get_Item(i); 
   11:             
   12:             Track track = new Track(); 
   13:             track.Title = media.getItemInfo("Title"); 
   14:             track.Location = media.getItemInfo("SourceUrl"); 
   15:             track.Number = media.getItemInfo("OriginalIndex"); 
   16:             string albumName = media.getItemInfo("Album"); 
   17:             
   18:             var album = (from a in Albums 
   19:                             where a.Name == albumName 
   20:                             select a).FirstOrDefault(); 
   21:             
   22:             if (album != null) 
   23:             { 
   24:                 album.Tracks.Add(track); 
   25:             } 
   26:             else 
   27:             { 
   28:                 Album a = new Album(); 
   29:                 a.Name = albumName; 
   30:                 string dir = System.IO.Path.GetDirectoryName(track.Location); 
   31:                 FileInfo file = new FileInfo(System.IO.Path.Combine(dir, "Folder.jpg"));                 
   32:                 if (file.Exists) 
   33:                 { 
   34:                     a.Cover = file.FullName; 
   35:                 } 
   36:                 a.Artist = media.getItemInfo("AlbumArtist"); 
   37:                 a.Tracks.Add(track); 
   38:                 if (albumName != string.Empty) 
   39:                 { 
   40:                     Albums.Add(a); 
   41:                 } 
   42:             } 
   43:         } 
   44:         return Albums; 
   45:     } 
   46: }

Команды

Взаимодействие между View и ViewModel осуществляется посредством ICommand. Мы будем использовать RelayCommand.

RelayCommand — это ICommand, чьи делегаты могут быть подключены к Execute и CanExecute.

MediaViewModel предоставляет следующие команды:

· NextAlbum;

· PreviousAlbum;

· SongUp;

· SongDown;

· PlaySong.

Для создания RelayCommand нам потребуются делегаты CanExecute и Execute.

C#:

    1: private bool SongUpCanExecute(object parameter) 
    2: { 
    3:     return (TracksCollectionView.CurrentPosition > 0); 
    4: } 
    5:  
    6: private void SongUpExecute(object parameter) 
    7: { 
    8:     if (TracksCollectionView != null) 
    9:     { 
   10:         TracksCollectionView.MoveCurrentToPrevious(); 
   11:     } 
   12: }

А теперь можно реализовать команду.

C#:

    1: private ICommand songUp; 
    2: public ICommand SongUp 
    3: { 
    4:     get 
    5:     { 
    6:         if (songUp == null) 
    7:         { 
    8:             songUp = new RelayCommand(SongUpExecute, SongUpCanExecute); 
    9:         } 
   10:         return songUp; 
   11:     }
   12: }

RelayCommand как нельзя лучше соответствует шаблону M-V-VM, поскольку вся ее реализация и логика выполнения может быть инкапсулирована в ViewModel. Что касается View, они затем просто привязываются к командам.

Дополнительные сведения о собственной реализации ICommand см. в моей статье The Power of ICommand (EN).

Трехмерный WPF-элемент управления в виде книги

“Великая библиотека в Александрии была основана в 300 году до н. э. для великой задачи: собрать знания всего мира в одном месте; в свои лучшие времена библиотека хранила около 750 тысяч свитков. В современном мире Британская библиотека содержит одну из самых выдающихся коллекций; среди двадцати миллионов книг и рукописей, хранящихся здесь, есть уникальные редчайшие экземпляры. Здесь находится «Бриллиантовая сутра», самая первая напечатанная книга; первый атлас Европы Меркатора; Евангелие из Линдисфарна; личная записная книжка Леонардо да Винчи; Великая хартия вольностей и Синайский кодекс (Codex Sinaiticus), одна из двух самых ранних христианских Библий. Такие уникальные вещи должны, конечно, храниться с предельной аккуратностью. Если они вообще выставляются для общего доступа, они надежно защищены стеклом, а прямое взаимодействие с ними разрешено только небольшой группе людей.

К счастью, теперь эти творения впервые оцифровываются, чтобы стать достоянием широкой общественности. И что еще лучше, оцифрованные версии превращаются в захватывающие интерактивные композиции с комментариями специалистов, в которых книги становятся живыми. В сотрудничестве с английским разработчиком программного обеспечения Британская библиотека разработала приложение Turning the Pages («Перелистывая страницы») на основе Windows, которое предоставляет точные трехмерные виртуальные копии наиболее ценных экземпляров из коллекции библиотеки”.

Тим Снит (Tim Sneath). «Перелистывая страницы при помощи WPF».

clip_image002[4]

Благодаря использованию бесплатного элемента управления, созданного Мицуру Фурута (Mitsuru Furuta (EN)) на CodePlex (EN)), мы добавим в нашу библиотеку возможность перелистывать страницы, как в книге!

Для использования соответствующего элемента управления нам нужна ссылка на WPMMitsuControls.dll, а также надо добавить следующее пространство имен:

    1: xmlns:controls="clr-namespace:WPFMitsuControls;assembly=WPFMitsuControls"

Поскольку Book порожден от ItemsControl, мы можем привязать его к любому набору с помощью ItemsSource и изменить ItemTemplate (аналогично ListBox, ListView и т. д.).

    1: <controls:Book ItemsSource="...">
    2:     <controls:Book.ItemTemplate>
    3:         ...
    4:     </controls:Book.ItemTemplate>
    5: </controls:Book>
Обращение к параллельному порту

clip_image002[6]

clip_image004[5]

Для получения сигналов от клавиатуры мы будем использовать регистр состояния (базовый адрес + 1). Для доступа к клавиатуре нам нужен драйвер ввода-вывода, в качестве которого я использовал inpout32.dll. Он реализован как win32 dll, следовательно нам придется использовать Р/Invoke.

C#:

    1: public class PortAccess 
    2: { 
    3:     [DllImport("inpout32.dll", EntryPoint = "Out32")] 
    4:     public static extern void Output(int adress, int value); 
    5:     
    6:     [DllImport("inpout32.dll", EntryPoint = "Inp32")] 
    7:     public static extern int Input(int adress); 
    8: }

Для получения дополнительных сведений о работе с LPT-портом в управляемом коде, ознакомьтесь со следующей статьей из CodeProject:

· CodeProject: I/O Ports Uncensored - 1 - Controlling LEDs (Light Emiting Diodes) with Parallel Port (EN).

Немного электроники

clip_image002[8]

Вот обобщенная схема подключения моей клавиатуры:

clip_image004[7]

Вы можете использовать другие кнопки, которые найдете в своем магазине (вот кнопки в магазине RadioShack).

WindowWithKeypadSupport

Осталось описать реагирование на нажатие кнопок. Я создаю собственный класс WindowWithKeypadSupport, производный от WPF Window. В WindowWithKeypadSupport есть фоновый поток, отслеживающий LPT-порт. При нажатии кнопки он выдает событие PreviewKeypadDown.

C#:

    1: public class WindowWithKeypadSupport : Window 
    2: { 
    3:     public static readonly RoutedEvent PreviewKeypadDownEvent; 
    4:     
    5:     static WindowWithKeypadSupport() 
    6:     {
    7:         WindowWithKeypadSupport.PreviewKeypadDownEvent = 
    8:             EventManager.RegisterRoutedEvent( "PreviewKeypadDown", 
    9:             RoutingStrategy.Bubble, typeof(KeypadEventHandler), 
   10:             typeof(WindowWithKeypadSupport)); 
   11:     } 
   12:     
   13:     public event RoutedEventHandler PreviewKeypadDown 
   14:     { 
   15:         add 
   16:         { 
   17:             base.AddHandler(WindowWithKeypadSupport.PreviewKeypadDownEvent, value); 
   18:         }         
   19:         remove 
   20:         { 
   21:             base.RemoveHandler(WindowWithKeypadSupport.PreviewKeypadDownEvent, value); 
   22:         } 
   23:     } 
   24:     // Остаток класса опущен для простоты 
   25: } 

Для инициации события нам надо вызвать поток, реализующий интерфейс пользователя.

C#:

    1: Dispatcher.BeginInvoke(DispatcherPriority.Background,
    2:     (SendOrPostCallback)delegate 
    3:     { 
    4:         KeypadEventArgs e = new KeypadEventArgs(
    5:             WindowWithKeypadSupport.PreviewKeypadDownEvent, this); 
    6:         e.Key = CreateKeyFromIOPortValue(keyValue); 
    7:         base.RaiseEvent(e); 
    8:     }, null);

Вот пример создания экземпляра WindowWithKeyadSupport:

    1: <controls:WindowWithKeypadSupport x:Class="ClassicJukebox.MainWindow"
    2:     xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
    3:     xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
    4:     xmlns:controls="clr-namespace:ClassicJukebox"
    5:     PreviewKeypadDown="WindowWithKeypadSupport_PreviewKeypadDown" />

СоветМожно также реагировать и на нажатие клавиш компьютерной клавиатуры. Используйте стрелку влево, вправо, вверх и вниз для управления музыкальным автоматом.

И вот что получилось:

clip_image002[10]

Завершение

WPF предоставляет очень развитую инфраструктуру для создания без особых усилий приложений, в которых используются и трехмерные эффекты, и работа с устройствами. Архитектура связывания и поддержка команд обеспечивают прекрасные возможности разделения функций.

Но важнейшая возможность WPF — это то, что от создания приложений получаешь одно удовольствие!

Об авторе

Основная сфера интересов Руди Гроблера (его повседневная деятельность) связана с разработкой встраиваемых систем. За последние десять лет Руди программировал самые различные устройства: ЖК-дисплеи, подключенные через параллельные порты, устройства приема счетов, RoboHum, считывающие устройства смарт-карт, удаленный Wii, устройства сбора данных, насосы на автозаправках и многое другое. Примерно два года назад ему достался экземпляр книги Чарльза Петцольда (Charles Petzold) о WPF и у него началась новая любовь…

Его публикации можно найти на https://dotnet.org.za/rudi (EN).