Adding breadcrumb navigation to a WPF MVVM app using Caliburn - wpf

I have an existing WPF application following the MVVM pattern with the Caliburn-Micro framework. I have a new requirement to add an interactive breadcrumb to the UI. While I can render the breadcrumb without issue, I am stumped trying to determine how to handle activation when one of the crumbs is clicked by the user.
I have an n-level object graph where each object is a Conductor<T>.Collection.OneActive.
The UI is able to handle basic "navigation" by calling ActivateItem with the item for each conductor to activate. At the each level, I display the list of items and when one is clicked, it is activated. As long as the activated item has child items, the UI displays its list of child items. When one of those is clicked, that child is activated and so on.
I am able to build the breadcrumb trail by crawling this object graph from the active root item down to active child item and so on but I don't know how to re-activate an item when the breadcrumb is clicked because I have a reference to the item, not the conductor that contains/manages that item.
Any ideas?

Just had an idea that may be of some use. It sounds like the conductors are hierarchical so:
Use the event aggregator to fire a simple message type with a property that contains the viewmodel you want to activate
public class BreadcrumbNavigate
{
public IScreen Screen { get; private set; }
public BreadcrumbNavigate(IScreen screen) { Screen = screen; }
}
The conductors should all be listening for these type of messages; when they receive a message they should only respond to it if they are currently active, meaning only the leaf node of the breadcrumb trail will respond to the first message.
If the VM in the message is not a child of the conductor, the conductor should close itself and re-publish the aggregator message so that the next active conductor can handle it (not sure how well this will work since it depends on a conductor closing itself then propagating a message but like I said, it's just an idea).
Eventually the correct conductor somewhere up the tree will handle the message, and that conductor will have a child VM which matches the message - this is the navigation target and that's where the message propagation stops
Edit:
The above won't work due to conductors being active when they have active children - so scrap that!
Can you post more information about your setup?
As far as you've described it, it sounds like a simple breadcrumb nav scenario; in order to go back along the breadcrumb you just need to use TryClose() on the active item of the VM that you want to jump back to:
// on the VM that you want to navigate to...
public void NavigateToMe()
{
// Check if we have a child, if so try and close it...will close all children etc
if (ActiveItem != null)
{
ActiveItem.TryClose();
}
}
Obviously you can just wrap this in a helper method such as
public void NavigateTo(IConductActiveItem vm)
{
var iClose = vm.ActiveItem as IClose;
if(iClose != null)
{
iClose.TryClose();
}
}
So all you need in the breadcrumb is a reference to the VM, on checking it has a closeable child and calling TryClose() all child VMs/conductors should be closed below the selected child - obviously this will only work if the child of the VM is always an item conductor
this is assuming you are just using the following object graph:
Conductor -> Conductor -> Conductor -> Conductor

Related

How to access Parent Form public variables, control properties and methods? (WinForms C#)

I have Parent Form (FrmMainMenu) that has 3 panels. A panel1 docked on top and a panel2 docked on the left. The 3rd panel will be my container for the child Forms. I have a Title label (lblTitle.Text = "Home") on panel1 and buttons on panel2. I'm trying to emulate a Blazor look and feel (navbar and sidebar). My question is how can I access/manipulate the Title label (lblTitle.Text) on my Parent Form (FrmMainMenu) from a Child Form?
1. On Control Properties:
Example Event: When opening the child form I want the (lblTitle.Text) properties be changed according to child form function. ex. (lblTitle.Text = "List of Rooms"). And when closing the child form I want the (lblTitle.Text = "Home") go back to its original Text properties.
2. Methods: I have a method (public void ResetColors()) on my (FrmMainMenu) that can reset the colors on my buttons. Despite being public method my child form cannot access the method. This is also true to public variables.
Any suggestions is appreciated.
I tried converting private methods and variables into public. I also tried changing the control Modifiers to public. I still can't access Parent Form control properties and public methods.
I got it.
((frmMainMenu)this.ParentForm).lblTitle.Text = "List of Rooms";
On closing child form:
((frmMainMenu)this.ParentForm).Reset();
((frmMainMenu)this.ParentForm).lblTitle.Text = "Home";

Prism/MVVM running code after switching a view

I started to use Prism (5.0) some days ago and now I run into a problem I cant solve myself.
I used the quickstart "viewswitchingnavigation".
So I have a shell with a navigation and a main panel/region.
Navigation region holds some radiobuttons to switch the views that are split into view and viewmodel with a service based model.
View A shows a collection of documents with a button each to open a new view B for this document's details.
A button in this detailview should start a part of code. This code moves some data what takes quite a while since the data need to be parsed. Since the user shouldnt wait for a nonreactiong window I want to show some information about the status of the running code. (Example: Getting data 1/3012, which is updated with each new got data piece). This code gets one piece of data at a time so I could send(?) some information to the view to update the status there (if I knew how)
So.
How to implement the button that starts the "external" code?
How to access the current view (e.g. to change the status shown in a loader or in a textbox like destribed above.
You could use a BackgroundWorker class which has built-in progress reporting functionality. Alternatively, you could create your own asynchronous data retreiving mechanism by using async / await and Tasks.
For your progress displaying view you could use the State-Based Navigation provided by Prism. You could display an on-top view with a progress bar or text box showing the progress. As usual, the UI elements of the view should be bound to the view model's properties. To update these UI properties you should use Dispatcher.Invoke(), or SynchronizationContext, or similar sync mechanism, because your progress reporting method (or event) will be called by a background thread.
If you could post your code, then my answer could be more specific.
Not exactly sure what you aim for, though here are two options:
Shall the data begin to be fetched when the View/Windows gets loaded? Then your ViewModels have to implement INavigationAware interface and INavigationAware.OnNavigatedTo (and From) Method). You can mark that method as async to be able to use await within it.
public async void INavigationAware.OnNavigatedTo(NavigationContext navigationContext)
{
await LongRunningTask();
}
public async Task LongRunningTask()
{
foreach(var dataChunk in allData)
{
// Status Text is a property which raises OnPropertyChanged when changed and which you databind to a textbox
StatusText = string.Format("Getting Data {0}", dataChunk.Description);
await ProcessDataChunk(dataChunk);
}
}
Or if you want it happen on an user action, you do it via an async ICommand.
private ICommand processCommand;
public ICommand ProcessCommand)
{
return processCommand??(processCommand = DelegateCommand.FromAsyncHandler(this.LongRunningTask));
}

caliburn micro - run code when view is visible?

I need to run some code (let's say to show message box) right after the view is displayed. I tried to override OnInitialize, OnViewLoaded or OnViewAttached but it's always the same. The View is not fully displayed yet.
I use some animation when displaying view but at the same time need to load some data into grid. If I put data load into OnInitialize, OnViewLoaded or OnViewAttached the animation is not smooth as it's kind of happening the same time when loading data.
Any thoughts?
Give something like this a try - use a couroutine to wait for the animation to complete before binding the grid:
private IEnumerator<IResult> ViewModelStartup()
{
yield return new WaitForAnimation("AnimationName");
BindData();
}
(note - you can load the data async, but just don't assign it)
Then when your form loads:
private void OnViewAttached()
{
Coroutine.BeginExecute(ViewModelStartup(), new ActionExecutionContext() { View = this.GetView() });
}
(the code above might not be 100%... I think View must be FrameworkElement in ActionExecutionContext so cast as needed or create a wrapper class)
The implementation of the WaitForAnimation coroutine would search the view for a named animation and wait for it to complete before firing the callback. You should probably just fire the callback if the animation could not be found. The couroutine could be used on multiple views.
(Coroutines must implement IResult have a look at the docs on the CM codeplex site for info)
http://caliburnmicro.codeplex.com/wikipage?title=IResult%20and%20Coroutines&referringTitle=Documentation

Navigate between views WPF PRISM application

I am working on a WPF PRISM application that has the following structure (I have simplified to explain better without additional layers). I am using Unity as my DI/IOC
AppMain - Bootstrapper
Gui - Views and View Models
Data - Data using EF.
In Gui, I have views names as below:
Home
EmployeesView
OrdersView
Reports
I have three regions in the shell.
MainRegion - Main Content
TopRegion - Navigation Menu
BottomRegion - Status Bar
I am using the following method to register views to the regions
IRegion region = _regionManger.Regions[RegionNames.MainRegion];
var mainView = _container.Resolve<Home>();
region.Add(mainView, ViewNames.HomeViewName);
region.Activate(mainView);
The first of activation happens in the Module Initialize method for Top, Main and Bottom.
After this, I am activating other views when the button are clicked. It is just code behind for now. Sample code here:
IRegion region = _regionManger.Regions[RegionNames.MainRegion];
var reportView = region.GetView(ViewNames.ReportsViewName);
if (reportView == null)
{
reportView = _container.Resolve<ReportsView>();
region.Add(reportView, ViewNames.ReportsViewName);
region.Activate(reportView);
}
else
{
region.RequestNavigate(ViewNames.ReportsViewName);
}
PROBLEM1: Any advise on how this can be done or the way I am doing is fine.
The top menu has Home, Employees, Orders, Reports buttons.
In the home page I have recent orders by the employee in datagrid as readonly.
I would like to double click to navigate to the OrderView and pass the selected order to show to the user. PROBLEM2 I am not sure where to do the navigation for this.
PROBLEM3: Another issue was if set the RegionMemberLifeTime keepAlive false, INavigationAware methods don't fire. If I don't set the KeepAlive to false, the page does not get refreshed because, the view model does not get called.
I need the pages to refresh when it is navigated to and not be stale and also handle any confirm prompts to the user when the view is navigated away from it.
Your help is very much appreciated.
it's certainly too late but…
Problem 1/2: is there a particular reason why you add content to region in module initializer?
the common way is more like -> in xaml:
<ContentControl prism:RegionManager.RegionName="MainRegion" />
and in ModuleInit.cs -> Initialize()
_regionManager.RegisterViewWithRegion("MainRegion", () => _container.Resolve<MainView>());
Problem 3:
the view has to implements INavigationAware, IRegionMemberLifetime
and to swich region, in the viewModel you do:
_regionManager.RequestNavigate("RegionWhatever", new Uri("TestView", UriKind.Relative));
But to work you have to register it in ModulInit.cs as an object with viewName, like that:
_container.RegisterType<Object, TestView>("TestView");
and a contentControl with the correct RegionName define in xaml of course

NullReferenceException thrown in NotifyPropertyChanged when switching views with edit in progress

I have a series of views that are to be displayed depending on the currently selected item in a tree within a parent view. These views are created and registered with the region during the initialization method of the parent view and are being correctly deactivated/activated, therefore giving the effect of swapping in/out the correct view. These views have a single underlying viewmodel as their datacontext, which contains data objects supporting INotifyPropertyChanged.
This solution works if there are no currently outstanding edits in progress within the child view but if there is a edit in progress in a view (i.e. the user has changed the contents of a description but hasn't clicked out of the text box) and that view is deactivated (i.e. the a different tree item is clicked within the parent view, thus causing a de-activation to occur) a NullReferenceException is being thrown in the NotifyPropertyChanged() of the underlying data object attached to the now deactivated view.
What seems to be happening is this:
An edit is started by the user in the child view
The user clicks an item in the tree in the parent view
The controller picks up the change in the selected item in the tree
The current child view is deactivated
The new view is activated
The change from the edit happens to the underlying data object (the set method is getting called)
A change notification event is generated by the data object as a result of this change
A null reference exception is thrown.
Presumably, this change notification event is being sent to the now de-activated view, but the view is not null.
I have not tried this myself, but I believe one solution was to listen for the deactivate event of the view using IActiveAware and cancel any editing.
See if this link helps.

Resources