Здравствуйте, уважаемые читатели. Сегодня мы посмотрим в сторону кросплатформенного XAML фреймворка под названием OmniXAML. В прошлой статье мы рассматривали возможность написать на кросплатформенном фреймворке Perspex приложение чуть сложнее, чем набросок самых простых
контролов. Чем это у меня закончилось, вы можете посмотреть здесь: "Как я Perspex пытался использовать с MVVM". В
общем, все закончилось печально, и я так и не смог добить до конца примитивную
задачу. Но тут мне на глаза попался другой фреймворк под
названием OmniXAML, который связан с Perspex фреймворком, но который дает немного больше
возможностей развернуться. Немного забегая наперед, скажу, что OmniXAML также не смог справиться с поставленной задачей на
100%, но в отличие от голого Perspex, OmniXAML дает больше возможностей для маневра. Чтобы
не быть голословными, приступим к реализации. Давайте создадим новое приложение WPF Application, которое назовем “OmnimaxlSample”.
Следующим делом нам
нужно установить с помощью NuGet Package Manager
пакеты OmniXAML и OmniXaml.Wpf
Нам понадобятся оба пакета, потому что мы создали
свое приложение как WPF Application, поэтому его нужно немного допилить,
чтобы оно могло работать с OmniXAML. В отличие от Perspex, фреймворк OmniXAML уже вышел
с бета-версии, и его можно спокойно использовать. После того как вы установите OmniXAML, обратите внимание на количество библиотек, которые вам будут
загружены.
Их намного меньше, чем количество библиотек с того же Perspex, что мне очень нравится. Но хватит уже хвалить данный
фреймворк; приступим к его реализации. Первым
делом нам нужно проделать небольшую хитрость, чтобы избавиться от файла MainWindow.xaml.cs. Для этого нужно произвести все действия, которые
описаны в инструкции. Если же английский язык для вас проблематичный, то ниже будет небольшой перевод.
Выбираем наш файл MainWindow.xaml и в окне свойств и
выставляем свойство “Copy to Output Directory” в “Copy always”. Затем удаляем файл MainWindow.xaml.cs. Далее открываем файл MainWindow.xaml и заменяем его
содержимое на следующее:
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
Title="MainWindow" Height="350" Width="525">
<TextBlock Text="Hello
World!" />
</Window>
Переходим в App.xaml и удаляем свойство StartupUri. После этого переходим в App.xaml.cs и переопределяем метод OnStartup, как показано ниже.
protected override void
OnStartup(StartupEventArgs e)
{
var xamlLoader = new WpfXamlLoader();
Window mainWindow;
using (var
stream = new FileStream("MainWindow.xaml", FileMode.Open))
{
mainWindow = (Window)xamlLoader.Load(stream);
}
mainWindow.Show();
}
Добавляем namespace на библиотеку OmniXaml.Wpf
using
OmniXaml.Wpf;
После этого
собираем наш проект и запускаем.
P.S. На данном этапе у
вас может случиться небольшое затруднение. Например, у меня Visual Studio 2015 категорически
отказывалась копировать MainWindow.xaml в папку дебага. Пришлось для теста перенести это
файл вручную.
Теперь после того
как мы запустили наш простой пример, настало время немного его усложнить с
использованием MVVM. Нам же нужно, чтобы пример хоть немного соответствовал реалиям.
В OmniXaml нет классов для работы с командами, которые будут нам нужны для того, чтобы обрабатывать нажатие на кнопки. Тут есть два выхода.
Первый – написать свою реализацию, наследуясь от класса ICommand. Второй – для более ленивых, к которым отнесу себя,
пожалуй, и я, – скачать какую-то готовую библиотеку, в которой это реализовано. Например,
я для этих целей скачал себе Prism.Mvvm через NuGet Package Manager.
Самое время приступить к реализации. Добавим в наш проект следующую
структуру папок: ViewModes, Views и Services.
Затем в папку Models добавим интерфейс
IBook, в котором мы
реализуем все поля которые будут нести информацию о книге.
public interface IBook
{
string Author { get;
set;
}
string Title { get;
set;
}
DateTime Year { get;
set;
}
string SN { get;
set;
}
int Count { get;
set;
}
}
Затем в этой же
папке добавим реализацию самой книги – класс Book.
public class Book : ViewModel, IBook
{
private string _author;
public string Author
{
get
{ return _author; }
set
{
_author = value;
OnPropertyChanged();
}
}
private string _title;
public string Title
{
get
{ return _title; }
set
{
_title = value;
OnPropertyChanged();
}
}
private DateTime _year;
public DateTime Year
{
get
{ return _year; }
set
{
_year = value;
OnPropertyChanged();
}
}
private string _sn;
public string SN
{
get
{ return _sn; }
set
{
_sn = value;
OnPropertyChanged();
}
}
private int
_count;
public int
Count
{
get
{ return _count; }
set
{
_count = value;
OnPropertyChanged();
}
}
}
Здесь в качестве
модели и наследовали наш класс от класса ViewModel (странное имя для класса, у которого единственное, что реализовано, – это метод OnPropertyChanged), для
того чтобы у нас была доступна функция OnPropertyChanged.
Реализовываем интерфейс, который будет возвращать нам необходимые данные по
книгам. Для этого в папку Services добавим интерфейс IBookService.
public interface IBookService
{
void GetData(Action<ObservableCollection<IBook>, Exception> callback);
IBook FindBook(IBook findBook);
void CreateNewBook();
void
RemoveBook(IBook book);
}
В эту же папку и
добавим реализацию, которая будет доставать нам все эти данные.
public class BookService : IBookService
{
#region Variable
private ObservableCollection<IBook> _books;
#endregion
#region Constructor
public BookService()
{
_books = new ObservableCollection<IBook>();
_books.Add(new Book { Author = "Jon
Skeet", Title = "C# in Depth", Count = 3, SN = "ISBN: 9781617291340", Year = new DateTime(2013, 9, 10) });
_books.Add(new Book { Author = "Martin
Fowler", Title = "Refactoring: Improving the Design of
Existing Code", Count
= 2, SN = "ISBN-10:
0201485672", Year
= new DateTime(1999, 7, 8) });
_books.Add(new Book { Author = "Jeffrey
Richter", Title = "CLR via C# (Developer Reference)", Count = 5, SN = "ISBN-10: 0735667454", Year = new DateTime(2012, 12, 4) });
}
#endregion
#region Public Methods
public void
GetData(Action<ObservableCollection<IBook>, Exception>
callback)
{
callback(_books, null);
}
public IBook FindBook(IBook findBook)
{
if
(findBook == null)
return null;
return _books.FirstOrDefault(book => book.Author == findBook.Author
&&
book.Title == findBook.Title
&&
book.SN == findBook.SN
&&
book.Year == findBook.Year);
}
public void
CreateNewBook()
{
_books.Add(new Book { Author = "Test1", Title = "Test1", Count = 5, SN = "ISBN-10: 0735667454", Year = DateTime.Now });
}
public void
RemoveBook(IBook book)
{
if
(book == null)
return;
_books.Remove(book);
}
#endregion
}
Как видите, этот
интерфейс просто хардкодит некоторые данные, которые будут отправляться наружу. Теперь реализуем нашу модель представления. Она практично не
меняется у меня с примера к примеру, и я уже подумываю, что нужно придумать какой-то
другой тестовый пример, а не все время толкаться с книгами. Назовем
нашу модель представления MainViewModel и добавим ее в
папку ViewModels.
public class MainViewModel : ViewModel
{
#region [ vars ]
private readonly IBookService _bookService;
#endregion
#region [ .ctor ]
/// <summary>
/// Initializes a new
instance of the MainViewModel class.
/// </summary>
public MainViewModel(IBookService dataService)
{
_bookService = dataService;
_bookService.GetData(
(items, error) =>
{
Books = items;
});
}
#endregion
#region Public Properties
public IBook FavoriteBook { get;
set;
}
public ObservableCollection<IBook> Books { get;
set;
}
private string Name { get; set; }
private IBook _selectedBook;
public IBook SelectedBook
{
get
{ return _selectedBook; }
set
{
_selectedBook = value;
OnPropertyChanged();
RemoveBookCommand.RaiseCanExecuteChanged();
}
}
#endregion
#region Command
private DelegateCommand _addBookCommand;
public DelegateCommand AddBookCommand
{
get
{
return _addBookCommand ?? (_addBookCommand = new DelegateCommand(AddNewBook));
}
}
private void
AddNewBook()
{
_bookService.CreateNewBook();
}
private DelegateCommand _removeBookCommand;
public DelegateCommand RemoveBookCommand
{
get
{
return _removeBookCommand ?? (_removeBookCommand = new DelegateCommand(RemoveBook, CanRemoveBook));
}
}
private void
RemoveBook()
{
Books.Remove(SelectedBook);
}
public bool
CanRemoveBook()
{
return SelectedBook != null;
}
#endregion
}
Здесь также все на
уровне примитивов. Сама модель представления только оперирует списком
полученных книг, и в ней реализованы две команды: добавить новую книгу и удалить
старую.
У нас осталась самая важная миссия — это реализация нашего представления. Поэтому
открываем нашу форму MainWindow.xaml и правим код следующим образом:
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<ListView Name="libary" Grid.Column ="0" ItemsSource="{Binding Books}" SelectedItem="{Binding SelectedBook}">
<ListView.ItemTemplate>
<DataTemplate>
<WrapPanel>
<TextBlock Text="Author:
" />
<TextBlock Text="{Binding Author}" FontWeight="Bold" />
<TextBlock Text=",
" />
<TextBlock Text="Caption:
" />
<TextBlock Text="{Binding Title}" FontWeight="Bold" />
<TextBlock Text="Count:
" />
<TextBlock Text="{Binding Count}" FontWeight="Bold" />
</WrapPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Grid Grid.Column="1">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid Grid.Row="0" DataContext="{Binding SelectedBook}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Text="Author"
/>
<TextBox Grid.Row="0" Grid.Column="1" Text="{Binding Author}" />
<TextBlock Grid.Row="1" Grid.Column="0" Text="Title"
/>
<TextBox Grid.Row="1" Grid.Column="1" Text="{Binding Title}" />
<TextBlock Grid.Row="2" Grid.Column="0" Text="Serial
Number" />
<TextBox Grid.Row="2" Grid.Column="1" Text="{Binding SN}"
/>
<TextBlock Grid.Row="3" Grid.Column="0" Text="Count"
/>
<TextBox Grid.Row="3" Grid.Column="1" Text="{Binding Count}" />
</Grid>
<StackPanel Grid.Row="1" Orientation="Horizontal">
<Button Content="Add new book" Margin="3" Command="{Binding AddBookCommand}" />
<Button Content="Remove book" Margin="3" Command="{Binding RemoveBookCommand}"/>
</StackPanel>
</Grid>
</Grid>
</Window>
В дизайнере это будет
выглядеть вот так:
Тут немного
пришлось подумать, как сделать так, чтобы оно все заработало. Детальный список, который вы видите с правой стороны, я обычно реализовываю следующим образом:
<Grid Grid.Row="0" DataContext="{Binding ElementName=libary, Path=SelectedItem}">
Но так как OmniXAML не хотел такую строку воспринимать и находить мой
выбранный элемент, пришлось схитрить и написать вот так:
<Grid Grid.Row="0" DataContext="{Binding SelectedBook}">
Мы забыли еще один
штрих – установить DataContext для главного окна. Переходим
в класс App.xaml.cs и
изменяем метод OnStartup следующим образом:
protected override void
OnStartup(StartupEventArgs e)
{
var xamlLoader = new WpfXamlLoader();
Window mainWindow;
using (var
stream = new FileStream("MainWindow.xaml", FileMode.Open))
{
mainWindow = (Window)xamlLoader.Load(stream);
}
mainWindow.DataContext = new MainViewModel(new BookService());
mainWindow.Show();
}
Мне не хотелось для такого простого примера ставить какой-то IoC контейнер ,поэтому получилось
так, как вы видите в примере.
Теперь можно
запустить наш пример и посмотреть, что все работает.
Итоги
Сегодня мы рассмотрели использование кросплатформенного фреймворка OmniXAML – фреймворка самого по себе интересного, но особых идей, как его и где использовать, мне не пришло; тем более, заявленная работа с Perspex не работает нормально. Из чистого любопытства я попробовал допилить кусок на Perspex. Возможно, со временем он действительно займет
свою нишу в мире разработки ПО, а пока же пусть просто полежит в сторонке.
Исходники к статье
вы можете скачать по ссылке: OmnimaxlSample.
No comments:
Post a Comment