That's my first article here and with it I start a cycle of articles that describe some xaml and mvvm-related features for creating Store applications.
These articles are more practical than theoretical, and as they may seem not too deep and can sometimes be wrong at some points, please don't hesitate to contact me and give advice how to enhance the article or code.
In this tutorial series we'll use Caliburn.Micro Conductor, EventAggregator and some VisualState transitions to build simple, but nice and seamless navigation in Windows Store app. Sample also demonstrates some dependency injection issues. But mostly it's just a demo. Please, don't expect it to follow guidelines strictly.
Navigation scheme we build will include
- Title page that has links to other application pages
- Back button to navigate backwards
- Breadcrumb-styled navigation to navigate between hierarchically linked pages and sections
- Main area to display application pages
- Animated closeable overlay panel to display additional information
In the first part of the journey we'll
Now, let's get started.
We'll proceed with Windows Store Blank app template in Visual Studio 2013, leaving aside universal apps for the moment, as soon it is going to be replaced with universal apps for Windows 10.
Let's start with creating the solution with two projects - one for views and another for view models, getting Caliburn.Micro from Nuget and bootstrapping the application in App.xaml.cs. Instead of simple container I'll use Unity. To simplify type registering I'll use UnityContainerHelper class with some extension methods.
That's how our configure method finally looks, and because we have views and viewmodels splitted between two projects we need to configure type mappings here.
Make sure that App class inherits from CaliburnApplication.
In the ShellView.xaml we'll create a static area that will be present on all pages where we place an improvised navigation menu and an area for displaying active page. Overlay panel will be added later. Menu is constructed dynamically in the ShellViewModel's ObservableCollection<NavigationMenuItem> NavigationMenuItems, so we just bind it.
In the Item template we bind button's Click event to the ViewModel's MenuItemClicked Action with NavigationMenuItem passed as a parameter with the help of Caliburn.Micro notation.
To be able to get navigation messages from other parts of the application we create custom NavigationEvent and handle it in the ShellViewModel. Let it have two parameters for now - Id, it will define what View we'll navigate to and string Parameter - it will contain some additional information for our view model being displayed. In a real application it can contain some other information that is related to out navigation logic.
For displayed pages we create corresponding views and viewmodels and we create a simple ViewModelFactory that we register in App.xaml.cs.
To display several view models simultaneously we need ShellViewModel to inherit from Conductor<IScreen>.Collection.AllActive and have the following properties in the code:
Notice that ActiveItemViewModel property name is the same as x:Name of ContentControl in ShellView.xaml. This is important.
ShellViewModel with menu creation and both ways of handling navigation events - either from clicking the button on navigation menu or coming from event aggregator now looks like:
It's not perfect yet and navigation menu creation can be abstracted away, but for now it's fine.
Notice that here we also use PropertyChanged.Fody. It helps to reduce PropertyChanged notification boilerplate code as it adds it automatically during the compilation step. All you need is to install it from Nuget and modify FodyWeavers.xml to contain PropertyChanged event names.
Now let's add a back-button to our ShellView. It will be invisible when we are on the home page and visible when we navigate to some other view.
To make navigation functionality available we should add navigation stack to our ShellViewModel.
For simplicity we'll use it without any size constraints and clear it when we navigate to the home page
We modify NavigateTo method to clear navigation stack when we are on the home page. In a real application NavigateTo can be further splitted to detect what view model is being activated and modify the behavior according to that.
And NavigateBack Method itself
Now we have simple navigation in a Windows Store app, made with Caliburn.Micro.
You can get full code for that article on the bitbucket. Part 1 code stops on commit "Part 1 complete. Navigation and Back Button".
In the second part we'll add
- create the project, wire it up
- make simple navigation
- add a back button functionality
Now, let's get started.
Creating basic navigation and the main page
Let's start with creating the solution with two projects - one for views and another for view models, getting Caliburn.Micro from Nuget and bootstrapping the application in App.xaml.cs. Instead of simple container I'll use Unity. To simplify type registering I'll use UnityContainerHelper class with some extension methods.
That's how our configure method finally looks, and because we have views and viewmodels splitted between two projects we need to configure type mappings here.
- protected override void Configure()
- {
- ConfigureTypeMappings();
- this.container = new UnityContainer();
- this.container.RegisterContainerControlled<IEventAggregator, EventAggregator>();
- this.container.RegisterPerResolve<ShellViewModel>();
- }
- private void ConfigureTypeMappings()
- {
- var config = new TypeMappingConfiguration
- {
- DefaultSubNamespaceForViews = "Samples.CustomNavigation.Views",
- DefaultSubNamespaceForViewModels = "Samples.CustomNavigation.UILogic.ViewModels"
- };
- ViewLocator.ConfigureTypeMappings(config);
- ViewModelLocator.ConfigureTypeMappings(config);
- }
Make sure that App class inherits from CaliburnApplication.
In the ShellView.xaml we'll create a static area that will be present on all pages where we place an improvised navigation menu and an area for displaying active page. Overlay panel will be added later. Menu is constructed dynamically in the ShellViewModel's ObservableCollection<NavigationMenuItem> NavigationMenuItems, so we just bind it.
In the Item template we bind button's Click event to the ViewModel's MenuItemClicked Action with NavigationMenuItem passed as a parameter with the help of Caliburn.Micro notation.
- <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
- <Grid.RowDefinitions>
- <RowDefinition Height="Auto"/>
- <RowDefinition Height="*"/>
- </Grid.RowDefinitions>
- <!--Menu-->
- <ItemsControl ItemsSource="{Binding NavigationMenuItems}">
- <ItemsControl.ItemsPanel>
- <ItemsPanelTemplate>
- <StackPanel Orientation="Horizontal"/>
- </ItemsPanelTemplate>
- </ItemsControl.ItemsPanel>
- <ItemsControl.ItemTemplate>
- <DataTemplate>
- <Button micro:Message.Attach="[Event Click] = [Action MenuItemClicked($dataContext)]">
- <Button.Content>
- <TextBlock Text="{Binding Name}" Style="{StaticResource SubheaderTextBlockStyle}"/>
- </Button.Content>
- </Button>
- </DataTemplate>
- </ItemsControl.ItemTemplate>
- </ItemsControl>
- <!--Content panel-->
- <ContentControl Grid.Row="1" x:Name="ActiveItemViewModel"
- HorizontalContentAlignment="Stretch"
- VerticalContentAlignment="Stretch"
- />
- <!--TODO Overlay panel-->
- </Grid>
To be able to get navigation messages from other parts of the application we create custom NavigationEvent and handle it in the ShellViewModel. Let it have two parameters for now - Id, it will define what View we'll navigate to and string Parameter - it will contain some additional information for our view model being displayed. In a real application it can contain some other information that is related to out navigation logic.
- public class NavigationEvent
- {
- public string Parameter { get; set; }
- }
For displayed pages we create corresponding views and viewmodels and we create a simple ViewModelFactory that we register in App.xaml.cs.
- public class ViewModelFactory
- {
- public IScreen GetViewModel(string id)
- {
- switch (id)
- {
- case "Active1":
- return new Active1ViewModel();
- case "Active2":
- return new Active2ViewModel();
- case "Home":
- return new HomeViewModel();
- case "Overlay":
- return new OverlayViewModel();
- default:
- throw new ArgumentException("Invalid parameter");
- }
- }
- }
To display several view models simultaneously we need ShellViewModel to inherit from Conductor<IScreen>.Collection.AllActive and have the following properties in the code:
- public IScreen ActiveItemViewModel { get; set; }
- public IScreen OverlayViewModel { get; set; }
Notice that ActiveItemViewModel property name is the same as x:Name of ContentControl in ShellView.xaml. This is important.
ShellViewModel with menu creation and both ways of handling navigation events - either from clicking the button on navigation menu or coming from event aggregator now looks like:
- public class ShellViewModel :
- Conductor<IScreen>.Collection.AllActive,
- IHandle<NavigationEvent>
- {
- private readonly IEventAggregator eventAggregator;
- private readonly ViewModelFactory viewModelFactory;
- public ShellViewModel(IEventAggregator eventAggregator, ViewModelFactory viewModelFactory)
- {
- this.eventAggregator = eventAggregator;
- this.viewModelFactory = viewModelFactory;
- NavigationMenuItems = new ObservableCollection<NavigationMenuItem>();
- this.eventAggregator.Subscribe(this);
- }
- protected override void OnActivate()
- {
- CreateNavigationMenu();
- }
- private void CreateNavigationMenu()
- {
- NavigationMenuItems.Add(new NavigationMenuItem()
- {
- Name = "Home Page",
- NavigationEvent = new NavigationEvent() { Parameter = "Home" }
- });
- NavigationMenuItems.Add(new NavigationMenuItem()
- {
- Name = "Content Page 1",
- NavigationEvent = new NavigationEvent() {Parameter = "Active1"}
- });
- NavigationMenuItems.Add(new NavigationMenuItem()
- {
- Name = "Content Page 2",
- NavigationEvent = new NavigationEvent() { Parameter = "Active2" }
- });
- }
- public ObservableCollection<NavigationMenuItem> NavigationMenuItems { get; set; }
- public IScreen ActiveItemViewModel { get; set; }
- public IScreen OverlayViewModel { get; set; }
- public void MenuItemClicked(NavigationMenuItem item)
- {
- NavigateTo(item.NavigationEvent);
- }
- public void Handle(NavigationEvent navEvent)
- {
- NavigateTo(navEvent);
- }
- private void NavigateTo(NavigationEvent navEvent)
- {
- var viewModel = this.viewModelFactory.GetViewModel(navEvent.Parameter);
- ActiveItemViewModel = viewModel;
- }
- }
It's not perfect yet and navigation menu creation can be abstracted away, but for now it's fine.
Notice that here we also use PropertyChanged.Fody. It helps to reduce PropertyChanged notification boilerplate code as it adds it automatically during the compilation step. All you need is to install it from Nuget and modify FodyWeavers.xml to contain PropertyChanged event names.
- <Weavers>
- <PropertyChanged EventInvokerNames="NotifyOfPropertyChange" />
- </Weavers>
Adding back-button functionality
- <AppBarButton Icon="Back"
- Margin="10,0,10,-20"
- micro:Message.Attach="[Event Click] = [Action NavigateBack]"
- Visibility="{Binding IsBackButtonVisible, Converter={StaticResource BoolToVis}}"
- />
To make navigation functionality available we should add navigation stack to our ShellViewModel.
For simplicity we'll use it without any size constraints and clear it when we navigate to the home page
- private readonly Stack<IScreen> navigationBackStack = new Stack<IScreen>();
We modify NavigateTo method to clear navigation stack when we are on the home page. In a real application NavigateTo can be further splitted to detect what view model is being activated and modify the behavior according to that.
- private void NavigateTo(NavigationEvent navEvent)
- {
- //pushing current view model to navigation stack
- this.navigationBackStack.Push(ActiveItemViewModel);
- var viewModel = this.viewModelFactory.GetViewModel(navEvent.Parameter);
- //clearing stack if we go to the home page
- if (viewModel is HomeViewModel)
- {
- this.navigationBackStack.Clear();
- }
- ActiveItemViewModel = viewModel;
- }
And NavigateBack Method itself
- private void NavigateTo(NavigationEvent navEvent)
- {
- //pushing current view model to navigation stack
- this.navigationBackStack.Push(ActiveItemViewModel);
- var viewModel = this.viewModelFactory.GetViewModel(navEvent.Parameter);
- //clearing stack if we go to the home page
- if (viewModel is HomeViewModel)
- {
- this.navigationBackStack.Clear();
- IsBackButtonVisible = false;
- }
- else
- {
- IsBackButtonVisible = true;
- }
- ActiveItemViewModel = viewModel;
- }
Now we have simple navigation in a Windows Store app, made with Caliburn.Micro.
You can get full code for that article on the bitbucket. Part 1 code stops on commit "Part 1 complete. Navigation and Back Button".
In the second part we'll add
- some async and spinners to indicate loading process
- overlay panel to slide from aside and slide away when closed
No comments:
Post a Comment