Sunday, November 30, 2014

Франкфурт-на-Майне - немецкий город небоскребов

Сегодня в статье я постараюсь раскрыть красоту такого немецкого мегаполиса, как Франкфурт, который расположен на реке Майн, поэтому он и называется Франкфурт-на-Майне. Его атмосфера мне понравилась намного больше, чем в городе Кёльн, о котором шла речь в предыдущей статье − "Кёльн. Строгий город с готическим дыханием". Мы приехали в Франкфурт с города Кёльн изрядно уставшие и голодные. Наверное, просто сказалось экскурсия по городу Кёльн накануне. Хорошо, что немецкие поезда едут очень тихо и быстро, поэтому дорога не успела нас утомить еще больше.Так вот, повторюсь, что у немцев шикарные поезда:
Жаль, что в Украине таких нет. Путешествие между городами превратилось бы не в муку, а в удовольствие. Первое, что бросается в глаза, как только вы выходите с поезда во Франкфурте − это их вокзал.
Для любителей путешествовать есть приятная новость. Из всех городов, в которых мы побывали (Лёвен, Амстердам, Кёльн, Франкфурт, Мюнхен), Франкфурт – реально самый дешёвый город. Например, недалеко от жд вокзала мы смогли снять номер эконом-класса за 20 евро с человека, при этом в эту стоимость входил также завтрак. Завтрак у немцев однообразный. Во всех городах, в которых мы побывали, он был практически одинаковым. У вас как бы есть шведский стол, с которого вы можете брать что угодно, но на практике выбираете колбасу из нескольких сортов, но которая, к сожалению, мясом и не пахнет; она как искусственная. В Украине мясные изделия делают лучше, чем в Германии. Вы можете взять омлет, если повезет, и в меню отеля, в котором вы остановились, есть это блюдо. Обычно вместо омлета, на выбор предлагаются яйца, сваренные вкрутую. Еще предлагается сыр, который тоже по вкусу не особо впечатляет. И на выбор подается либо чай/кофе, либо же сок. Хлеб у немцев обычно не входит в завтрак. Вместо хлеба может быть булочка, которая по вкусовым свойствам чем-то напоминает пресный хлеб. В целом особо надеяться на вкусные завтраки не советую. Сытные – да, так как наесться ими обычно можно вдоволь.
Так вот, вернемся к вопросу отеля. Возле вокзала можно снять номер от 60-80 долларов на 3-4 человека. Это действительно очень дёшево, как для Германии. Например, нам до вокзала было рукой подать.
Где-то с этого места до отеля было метров 50. На фото слева за деревьями просвечивается здание жд вокзала. Позже мы поняли, почему такая дешевая цена жилья возле вокзала. Дело в том, что, по сути, этот квартал заселяют иностранцы с Турции, Алжира и других стран. Их очень много проживает во Франкфурте, поэтому в билетных автоматах одним из основных языков является турецкий. Вдоль вокзала идет целая улочка самых разных фаст-фудов, но так как мы хотели поесть нормальную пищу, нам пришлось прилично топать. Обидно было, что мы хотели попробовать что-то с национальной кухни, а в этих забегаловках в основном была турецкая, тайская и другая еда, но отнюдь не немецкая. Спустя долгого брожения мы нашли вкусную еду, правда не немецкую, а американскую. 
Так как мы были очень голодны, то заказали ребрышки, которые мне очень понравились.
Цены были вменяемые. Возможно, потому что мы привыкли к сравнению с ценовой политикой ресторанов Бельгии. Немецкой национальной еды мы, к сожалению, так и не отведали во Франкфурте. Разве что испробовали местное пиво, но об этом чуточку позже. Мы хоть и были порядком уставшие, но не прогуляться по этому городу просто не могли. Но так как не все были солидарны во мнении, то один из коллег по работе пошел в отель отдыхать, а мы направились изучать красоту этого города. Первое, что бросается в глаза, когда вы гуляете по центру Франкфурта, – это огромное количество небоскребов. Всюду видны огромные краны. Этот город считается экономической столицей Германии. Когда смотришь вокруг себя и видишь очень много просто огромных зданий, чувствуешь себя ну очень маленьким. 
Но город не везде такой. По ту сторону реки Майн находится совсем другой Франкфурт, без огромных высоток и с не менее симпатичной инфраструктурой. Еще во Франкфурте очень интересные троллейбусы. Они очень похожи друг на друга по цвету. Отличаются только их номера.
Если посмотреть на фото внимательно, то можно увидеть позади троллейбуса памятник евро.  Интересно, что этот памятник установлен перед зданием Центрального Европейского Банка. Да, да, на заднем фоне вот то большое здание является Центральным Европейским Банком. А вот еще одно интересное фото, на котором видны башни-близнецы “Дойче банк”.
К сожалению, мы не подходили к ним ближе, поэтому это единственное фото. Небоскребами Франкфурта можно любоваться очень долго. Что-то в них есть действительно завораживающе-величественное. Но смотреть на город со стороны реки Майн − еще более интересное зрелище. Вот как выглядит набережная реки Майн:
Первое что бросается в глаза, − так это то, что по берегу ходят гуси и мирно пасутся. На берегу возле самой воды лежат и отдыхают люди, кажется, что жизнь течет необычайно медленно, словно замирает на миг.
А вот такая красота открывается нашему взору с другой стороны реки Майн:
Эту фотографию мы сделали с моста, чтобы показать, как величественно выглядит город с этой стороны. Ниже показан вид на набережную с моста.
По другую сторону моста находится “набережная музеев”. Вам не нужно бегать по всему городу, чтобы походить по музеям. Все музеи расположены вдоль набережной. Вот часть из них даже видно на фото.
Вы можете прогуляться по вот такой замечательной аллейке и зайти в музей, который вам придется по душе.
Или же просто полюбоваться замечательными пейзажами, которые открываются вашему взору.
Или можно просто полежать на травке и посмотреть, как пароходы плавают по Майну, либо самому заплатить за экскурсию и покататься по реке.
Выглядит это очень красиво.
Вы можете увидеть знаменитый Франкфуртский собор, а также церковь Святого Николая (у нее бирюзовая крыша на фото).
Рекомендую прогуляться в сторону этой церкви и собора. У вас будет такое ощущение, словно вы попали в другую эпоху. Если вы пройдетесь на главную площадь старого города Франкфурт-на Майне, площадь Рёмерберг, то вы словно окажетесь в каком-то старинном городе века XVIII, наверное. Посреди площади возвышается фонтан правосудия, над фонтаном на постаменте можно увидеть скульптуру самой богини Юстиции. В руках у нее традиционно весы и меч, но вот глаза не закрыты повязкой. Обязательно осмотрите нижнюю часть пьедестала, где можно увидеть несколько женских фигур. Это аллегорические изображения четырех добродетелей: умеренность, справедливость, надежда и любовь. Окружен фонтан черной ажурной решеткой.
А вот фото с площади Рёмерберг.
Здесь очень много сувенирных лавочек, в которых вы можете приобрести на память несколько сувенирчиков. Также здесь можно попробовать фирменное пиво от франкфуртских пивоваров. На вкус это пиво чем-то напоминает старое “жигулёвское” пиво, как описали это пиво коллеги по работе. Меня это пиво тоже особо не впечатлило. Но зато я купил себе на этой площади памятный магнитик, попробовал немецкое мороженное, а также вечером погулял по ночному Франкфурту. Одних только этих фактов достаточно, для того чтобы считать, что путешествие во Франкфурт удалось. И напоследок добавлю фото веселого трамвая, который я запечатлел по дороге в отель.

В целом, я очень доволен поездкой. И если бы мне еще раз, наверное, на выбор представилась возможность побывать в Кёльне или во Франкфурте-на-Майне, я бы однозначно выбрал Франкфурт, так как хотелось бы посетить хоть парочку музеев и посмотреть на город с какой-либо смотровой вышки. Впечатления остались только позитивные, и я с радостью рекомендую этот город для путешествий. Финальной статьей, которой я хотел бы завершить обзор поездки по Германии, будет материал о городе Мюнхен и его обитателях. Надеюсь, что вам было интересно читать мой обзор о Франкфурте. 

Thursday, November 27, 2014

Динамическая загрузка View в Prism 5

Сегодня мы снова коснемся темы использования регионов в Prism 5. В прошлой теме я затронул использование View Discovery и View Injection в Prism 5. Как я и предполагал, это вызвало вопрос: "Что делать, если нужно в конкретный регион подгрузить представление (view) динамически?". Именно это и будет сегодняшней темой данной статьи, а именно: один из способов динамического управления представлениями с помощью внедрения зависимостей (view injection). Мы не будем злоупотреблять теорией и построим самый пример, так как основной целью является показать, что вы можете без особого труда использовать это в своих программах. В следующих статьях рассмотрим более сложный способ навигации по регионам и управление ими с помощью View-Switching Navigation, хотя мне больше по душе старое название данного типа навигации в Prism 5 – View Based Navigation. Существует два способа работы с представлениями в Prism 5:
  • обнаружение представлений (View Discovery), которое позволяет при старте автоматически указать, как связать конкретный регион с нужным представлением;
  • внедрение представлений (View Injection), которое позволяет программно связать представление с регионом как при старте программы та и динамически во время работы программы.
Как говорят в народе, “меньше слов, больше дела”; приступим к реализации. Итак, что же будет собой представлять наша программа. Это будет приложение, в котором мы создадим в главном окне регион “MainRegion”, а также отдельно два UserControl, один из которых будет загружен при старте программы в наш регион, а второй будет загружен динамически по нажатию кнопки в первом контроле. Вроде ничего сложного нет, поэтому приступаем к реализации. Для этого заходим в Visual Studio и создаем новый WPF проект с выбором фреймворка 4.5 для того чтобы использовать призм 5-й версии. Назовем наш проект “DynamicViewInjection”, чтобы он отражал суть нашей работы.
Ниже на рисунке показано стартовое окно со всеми настройками. Затем сразу через Manager NuGet Packages загрузим Prism и Prism.UnityExtensions, для того чтобы в качестве загрузчика приложения использовать UnityBootstrapper, который работает совместно с IoC контейнером Unity.
Ребята с Patterns & Practices старались так сильно все разбить, чтобы библиотеки Prism 5 можно было использовать по отдельности, что это привело к тому, что если мы ставим сразу данную библиотеку целиком и плюс к этому ставим UnityBootstrapper загрузчик, то в итоге получим ни много ни мало 11 библиотек.
Лично мне не очень нравится такое огромное количество библиотек. Но если учесть, что в Prism 5 есть практически все для построения корпоративных приложений, то такое количество может быть оправдано. Я немного отошел от темы, поэтому вернемся к разметке нашего проекта. Поскольку с Prism 5 использование MVVM – это,по сути, просто необходимость, иначе если вы не используете MVVM, тогда не совсем понятно, зачем вам вообще нужен Prism. Это я веду к тому, что структура папок будет разбита согласно концепции Prism + MVVM. У меня это выглядит так:
Чтобы не расписывать отдельно каждую папку, я решил сделать это в виде рисунка. Поскольку мы работаем в призме, нам необходимо и оперировать терминами призма. Главное окно в Prism 5 называется оболочкой Shell. Поэтому мы перенесем наше окно сразу в папку Views и переименуем его с “MainWindow” на “Shell”. После этого нужно перейти в разметку App.xaml и удалить StartupUri, который запускает главное окно. Главное окно у нас будет запускаться с помощью загрузчика (bootstrapper) призма. Для этого мы сделаем небольшую заготовку, в которой дальше вставим код запуска нашего bootstrapper. Переходим в файл App.xam.cs и переопределяем метод OnStartup.
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
{
    protected override void OnStartup(StartupEventArgs e)
    {
        base.OnStartup(e);
    }
}
Если вы все проделали правильно, как минимум ваш проект должен собраться. Если что-то не получилось, то, в принципе, ничего страшного. Просто откройте эту статью: "Введение в Prism 5. Bootstrapper" и шаг за шагом делайте то, что там прописано. Теперь наша задача – добавить загрузчик, в котором мы пропишем инициализацию нашего приложения. Для этого в проект добавим новый класс Bootstrapper.cs. Реализация этого класса приведена ниже.
public class Bootstrapper : UnityBootstrapper
{
    protected override DependencyObject CreateShell()
    {
        return Container.Resolve<Shell>();
    }

    protected override void InitializeShell()
    {
        Application.Current.MainWindow = (Window)Shell;
        Application.Current.MainWindow.Show();
    }
}
Затем переходим в наш класс App.xaml.cs и добавляем в заготовку метода OnStartup следующий код:
protected override void OnStartup(StartupEventArgs e)
{
    var bootstrapper = new Bootstrapper();
    bootstrapper.Run();
}
После этого, если вы запустите проект, то должны увидеть на экране пустое окно.
Теперь самое время приступить к реализации контролов, которые будут заполнять наш регион. Для начала сделаем разметку в нашей оболочке Shell.xaml. В ней нет ничего сложного, просто ContentControl, который будет отображать наше содержимое.
<Window x:Class="DynamicViewInjection.Shell"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:regions="http://www.codeplex.com/CompositeWPF"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <ContentControl regions:RegionManager.RegionName="MainRegion" />
    </Grid>
</Window>
Затем переходим в папку Regions и добавляем туда новый UserControl с именем FirstView. Реализация этого окна приведена ниже.
<UserControl x:Class="DynamicViewInjection.Views.Regions.FirstView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             mc:Ignorable="d"
             d:DesignHeight="300" d:DesignWidth="300">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
            <RowDefinition Height="50"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="*" />
        </Grid.ColumnDefinitions>

        <Border Background="Gold" Margin="2" Grid.Column="0" Grid.ColumnSpan="2" Grid.Row="0">
            <StackPanel Orientation="Horizontal">
                <Button Content="Button1" Margin="3,2"/>
                <Button Content="Button2" Margin="3,2"/>
            </StackPanel>
        </Border>

        <Grid Grid.Column="0" Grid.ColumnSpan="2" Grid.Row="1" Background="ForestGreen">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto" />
                <ColumnDefinition Width="*" />
            </Grid.ColumnDefinitions>
            <ComboBox Grid.Column="0" />
        </Grid>

        <StackPanel Grid.Column="0"  Grid.Row="2" Background="DodgerBlue" Margin="5,3"/>
        <StackPanel Grid.Column="1"  Grid.Row="2" Background="DodgerBlue" Margin="5,3"/>

        <StackPanel Grid.Column="0"  Grid.Row="3" Grid.ColumnSpan="2" Background="Firebrick" Margin="5,10"/>
    </Grid>
</UserControl>
Внешний вид контрола приведен ниже.
Я старался эмулировать окно, как в реальном приложении, поэтому цветами выделил те области, вместо которых при нормальной разметке можно использовать регионы для заполнения.
Ну и добавим второй контрол, с названием которого тоже не будем сильно изобретать  и назовем его просто: SecondView.
<UserControl x:Class="DynamicViewInjection.Views.Regions.SecondView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             mc:Ignorable="d"
             d:DesignHeight="300" d:DesignWidth="300">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*" />
        </Grid.ColumnDefinitions>

        <Grid Grid.Row="0" Background="ForestGreen">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto" />
                <ColumnDefinition Width="*" />
            </Grid.ColumnDefinitions>
            <ComboBox Grid.Column="0" />
        </Grid>

        <StackPanel Grid.Column="0"  Grid.Row="1" Background="DodgerBlue" Margin="5,3"/>

        <Border Background="Gold" Margin="2" Grid.Row="1" Height="25" HorizontalAlignment="Left" VerticalAlignment="Top">
            <StackPanel Orientation="Horizontal">
                <Button Content="Button1" Margin="3,2"/>
                <Button Content="Button2" Margin="3,2"/>
                <Button Content="Button3" Margin="3,2"/>
            </StackPanel>
        </Border>
    </Grid>
</UserControl>
В этом контроле для разнообразия я добавил три кнопки, чтобы показать, что это другое представление.
Теперь сделаем так, чтобы первый контрол по умолчанию загружался в наш главный регион, который мы добавили в Shell.xaml с названием “MainRegion”. Для этого нам необходимо перейти в папку Modules и добавить новый класс MainModule.cs.
public class MainModule : IModule
{
    private readonly IRegionManager _regionManager;
    public readonly IUnityContainer _container;

    public MainModule(IUnityContainer container, IRegionManager regionManager)
    {
        _container = container;
        _regionManager = regionManager;
    }

    public void Initialize()
    {
        _regionManager.Regions["MainRegion"].Add(_container.Resolve<FirstView>());
    }
}
Теперь для того чтобы это чудо заработало, нам необходимо добавить инициализацию нашего модуля в созданный ранее бутстраппер. В нем добавится всего лишь один переопределённый метод InitializeModules.
public class Bootstrapper : UnityBootstrapper
{
    protected override DependencyObject CreateShell()
    {
        return Container.Resolve<Shell>();
    }

    protected override void InitializeShell()
    {
        Application.Current.MainWindow = (Window)Shell;
        Application.Current.MainWindow.Show();
    }

    protected override void InitializeModules()
    {
        IModule mainModule = Container.Resolve<MainModule>();
        mainModule.Initialize();
    }
}
Теперь запустим наш проект, чтобы посмотреть, что наше окно отображает уже заполненный регион. Теперь пришло время для написания логики. Мы сделаем так, чтобы нажатие на первую кнопку контрола FirstView загружало в регион контрол SecondView, а нажатие на вторую кнопку в SecondView, наоборот, загружало FirstView. То есть, контролы загружают друг друга. Для этого нам понадобится реализовать модель представления. Для этого нужно перейти в папку ViewModels и добавить класс FirstViewModel для имплементации логики FirstView, и аналогично − класс SecondViewModel для второго контрола. Ниже приведена реализация FirstViwModel.
public class FirstViewModel : BindableBase
{
    #region Private Variables
    private readonly IRegionManager _regionManager;
    private readonly IUnityContainer _unityContainer;
    #endregion

    #region Constructor
    public FirstViewModel(IRegionManager regionMananger, IUnityContainer container)
    {
        _regionManager = regionMananger;
        _unityContainer = container;
        ButtonCommand = new DelegateCommand(ShowSecondView);
    }
    #endregion

    #region Commands
    public DelegateCommand ButtonCommand { get; private set; }

    #endregion

    #region Private Methods
    private void ShowSecondView()
    {
        var view = _unityContainer.Resolve<SecondView>();
        view.DataContext = _unityContainer.Resolve<SecondViewModel>();
        _regionManager.Regions["MainRegion"].Add(view);
        _regionManager.Regions["MainRegion"].Activate(view);
    }
    #endregion
}
Ничего сложного в данной модели представления для вас не должно быть. Здесь, по сути, только одна команда ButtonCommand, которая реализует метод ShowSecondViewдля того чтобы с помощью view injection заменить в нашем регионе представление на другое. И реализация SecondViewModel выглядит очень похоже.
public class SecondViewModel : BindableBase
{
    #region Private Variables
    private readonly IRegionManager _regionManager;
    private readonly IUnityContainer _unityContainer;
    #endregion

    #region Constructor
    public SecondViewModel(IRegionManager regionMananger, IUnityContainer container)
    {
        _regionManager = regionMananger;
        _unityContainer = container;
        ButtonCommand = new DelegateCommand(ShowFirstView);
    }
    #endregion

    #region Commands
    public DelegateCommand ButtonCommand { get; private set; }

    #endregion

    #region Private Methods
    private void ShowFirstView()
    {
        var view = _unityContainer.Resolve<FirstView>();
        view.DataContext = _unityContainer.Resolve<FirstViewModel>();
        _regionManager.Regions["MainRegion"].Add(view);
        _regionManager.Regions["MainRegion"].Activate(view);
    }
    #endregion
}
У этих классов настолько подобная логика, что для таких целей можно вынести всю логику в базовый класс и просто в производных переопределить метод, который реализует view injection. После того как мы создали наши модели, нам нужно при старте нашего приложения для первого представления FirstView установить DataContext как FirstViewModel. Для этого переходим в наш класс MainModule, в котором осуществляется начальная привязка, и переписываем метод Initialize() вот так:
public void Initialize()
{
    var view = _container.Resolve<FirstView>();
    view.DataContext = _container.Resolve<FirstViewModel>();
    _regionManager.Regions["MainRegion"].Add(view);
}
Теперь осталась самая простая и самая легкая часть, а именно связать кнопки с соответствующими командами с моделей представлений. Первой под нашу правку попадает контрол FirstView. В нем ищем, где находятся кнопки, и для первой кнопки устанавливаем байндинг.
StackPanel Orientation="Horizontal">
    <Button Content="Button1" Margin="3,2" Command="{Binding ButtonCommand}"/>
    <Button Content="Button2" Margin="3,2"/>
</StackPanel>
Теперь переходим на контрол SecondView, ищем там кнопки и теперь уже для второй кнопки устанавливаем байндинг.
<StackPanel Orientation="Horizontal">
    <Button Content="Button1" Margin="3,2"/>
    <Button Content="Button2" Margin="3,2" Command="{Binding ButtonCommand}"/>
    <Button Content="Button3" Margin="3,2"/>
</StackPanel>
После этого вы можете запустить ваше приложение, и у вас должна заработать смена представлений.
Надеюсь, что это небольшое введение поможет вам использовать динамическую подгрузку представлений в ваши регионы, используя для этого view injection. Информацию для понимания, в какую сторону нужно копать глубже, я очень старался донести.