Category Archives: .NET
Для демонстрации возможностей console framework я долго думал над тем, чтобы написать какое-нибудь небольшое приложение, которые с одной стороны было бы достаточно функциональным (чтобы продемонстрировать возможности тулкита), а с другой – максимально простым (чтобы показать, как просто с этим тулкитом работать). Рассматривались варианты блокнота, телефонной книжки итд, но недавно я увидел проект cmdradio и понял, что это именно то, что нужно – достаточно завернуть консольный плеер радио в приятный простой UI, и даже писать ничего не придётся. Благо код оригинальной программы был настолько прост, что состоял из одного файла в 400 строк. И вот за пару дней была написана обёртка над ней – cmdradio-visual. Выглядит это следующим образом:
А кода буквально пара строк:
<Window Title="cmdradio" xmlns:x="http://consoleframework.org/xaml.xsd" xmlns:cmdradio="clr-namespace:cmdradio;assembly=cmdradio" MaxWidth="80"> <Panel> <Panel Orientation="Horizontal"> <GroupBox Title="Genres"> <Panel Orientation="Vertical"> <ComboBox ShownItemsCount="20" MaxWidth="30" SelectedItemIndex="{Binding Path=SelectedGenreIndex, Mode=OneWayToSource}" Items="{Binding Path=Genres, Mode=OneWay}"/> <Panel Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,1,0,0"> <TextBlock Text="Volume"/> <cmdradio:VolumeControl Percent="{Binding Path=Volume}" Margin="1,0,0,0" Width="20" Height="1"/> </Panel> </Panel> </GroupBox> <GroupBox Title="Control"> <Panel Margin="1"> <Button Name="buttonPlay" Caption="Play" HorizontalAlignment="Stretch"/> <Button Name="buttonPause" Caption="Pause" HorizontalAlignment="Stretch"/> <Button Name="buttonStop" Caption="Stop" HorizontalAlignment="Stretch"/> <Button Name="buttonExit" Caption="Exit" HorizontalAlignment="Stretch"/> </Panel> </GroupBox> </Panel> <TextBlock HorizontalAlignment="Stretch" Text="{Binding Path=Status, Mode=OneWay}"/> </Panel> </Window> |
WindowsHost windowsHost = ( WindowsHost ) ConsoleApplication.LoadFromXaml( "cmdradio.WindowsHost.xml", null ); PlayerWindowModel playerWindowModel = new PlayerWindowModel( ); Window playerWindow = (Window)ConsoleApplication.LoadFromXaml("cmdradio.PlayerWindow.xml", playerWindowModel); Player player = new Player( ); playerWindow.FindChildByName< Button >( "buttonPlay" ).OnClick += ( sender, eventArgs ) => { player.cmd = new string[] { "play", ( string ) playerWindowModel.Genres[playerWindowModel.SelectedGenreIndex] }; player.Play(); }; playerWindow.FindChildByName< Button >( "buttonPause" ).OnClick += ( sender, eventArgs ) => { player.ReadCmd( new string[] {"pause"} ); }; playerWindow.FindChildByName< Button >( "buttonStop" ).OnClick += ( sender, eventArgs ) => { player.ReadCmd( new string[] {"stop"} ); }; playerWindow.FindChildByName< Button >( "buttonExit" ).OnClick += ( sender, eventArgs ) => { ConsoleApplication.Instance.Exit( ); }; windowsHost.Show( playerWindow ); foreach ( string s in player.GetGenres( )) { playerWindowModel.Genres.Add( s ); } playerWindowModel.PropertyChanged += ( sender, eventArgs ) => { if ( eventArgs.PropertyName == "Volume" ) { player.ReadCmd( new string[] { "volume", string.Format( "{0}", playerWindowModel.Volume ) }); } }; playerWindowModel.Status = player.Status; player.PropertyChanged += ( sender, eventArgs ) => { if ( eventArgs.PropertyName == "Status" ) { playerWindowModel.Status = player.Status; } }; ConsoleApplication.Instance.Run(windowsHost); |
По-моему, ещё никогда писать TUI-приложения не было так просто. Архив с программой можно скачать здесь.
Используя стандартную конструкцию {Binding ElementName=myElement}
, никогда не задумывался о том, как происходит разрешение элемента по имени, если этот элемент определён дальше. А когда сам стал писать XAML-подобный механизм, озадачился. Ведь парсер должен строить граф объектов последовательно, сверху вниз, читая атрибуты и содержимое тегов и тут же их применяя к текущему конструируемому объекту. Но как в таком случае разруливать ссылки на элементы, до которых парсер ещё не добрался ? Неужели WPF обрабатывает расширения разметки отдельно, уже после построения графа объектов ? Это было бы весьма нелогично. Документация по этому вопросу отсутствовала, было только описание интерфейса IXamlNameResolver с сигнатурами методов, по которым можно догадаться о том, что метод Resolve может возвращать ссылки на не до конца инициализированные объекты, а заказывать нужные элементы можно через GetFixupToken.
Поискав на stackoverflow с полчаса, я нашёл замечательнейший ответ на свой вопрос.
Работает всё следующим образом. Классы, зарегистрированные в качестве расширений разметки, вызываются тотчас же для преобразования строки в объект. Метод ProvideValue может вернуть либо готовый объект, либо FixupToken, получаемый от сервиса IXamlNameResolver. Если ProvideValue возвращает FixupToken, то ProvideValue этого расширения разметки будет вызван ещё раз, когда все требуемые элементы будут созданы. А для отлова случаев, когда искомого элемента вообще нет в разметке, код ProvideValue должен перед вызовом GetFixupToken проверять значение свойства IsFixupTokenAvailable. Если оно равно False, граф объектов уже создан полностью, и метод должен как-то обработать ошибку (выбросить исключение, как правило), если нужного элемента среди созданных объектов нет.
В свете недавних новостей решил наконец оформить своё поделие в open-source библиотеку и выложить, как это у меня принято, на bitbucket. Код этот я писал уже довольно давно, когда плотно работал с WPF, помнится, тогда мы использовали аналогичную разработку моего коллеги Рината Зарипова. На тот момент она показалась мне слишком усложнённой, и спустя некоторое время я решил написать свою, более простую реализацию. Из ринатовской концепции я позаимствовал гениальную идею о преобразовании методов в команды, за что ему респект и уважуха. Такой крутой фишки не было ни в одном из существовавших тогда MVVM-фреймворков. Сейчас – не знаю, может быть где-то и появилась, хотя навряд ли.
Пример использования с комментариями прилагается, сам код библиотеки откомментирован тоже, но недостаточно. Надо будет заняться этим более плотно. Если в будущем буду пересекаться с WPF, то обязательно попутно сделаю это.
0