Inject Dispatcher into ViewModel - Recompose Unity - property injection - c#

I'm trying to inject the Dispatcher of my ShellPage into my ViewModel with Unity and Prism. Because the dispatcher is available after the shell is being created, I can't register the Dispatcher in time.
First step:
protected override Task OnInitializeAsync(IActivatedEventArgs args)
{
[...]
Container.RegisterInstance<IResourceLoader>(resourceLoader);
Secondly the CreateShell method will be executed.
protected override UIElement CreateShell(Frame rootFrame)
{
var shell = Container.Resolve<ShellPage>();
Container.RegisterType<MyViewModel>(new InjectionProperty(nameof(MyViewModel.Dispatcher), shell.Dispatcher));
Although I inject the Dispatcher into MyViewModel into the propertie Dispatcher is null. Maybe I need something like recomposing in MEF? How can I achieve the propertie injection into MyViewModel?

I think is not possible.
Recomposing like MEF is not supported.
The easy way is creating a helper to get the Dispatcher of the current view and use it in your ViewModels
Please see this example:
http://mikaelkoskinen.net/post/fixing-coredispatcher-invoke-how-to-invoke-method-in-ui-thread-in-windows-8-release-preview
This example is for Windows 8 but you can use it for UWP apps
just when I use this approach I do small changes for UWP apps:
In Onlaunched event in app.xaml.cs
I add UIDispatcher in a different place.
if (rootFrame.Content == null)
{
// When the navigation stack isn't restored navigate to the first page,
// configuring the new page by passing required information as a navigation
// parameter
UIDispatcher.Initialize();
rootFrame.Navigate(typeof(MainPage), e.Arguments);
}
// Ensure the current window is active
Window.Current.Activate();

Related

UWP: Control second window from main window

Inside a UWP app I want to control some animations on a second screen from my main app window.
As far as I can tell I have two options: create a second Window or use the projection feature.
My questions are:
Which option would make more sense / would be easier to implement in this scenario?
How can I react to events from my main window on my second screen?
About Q2:
There are some way to interact with multi thread model. If you write your app based on the MultiView sample, you can use the SecondaryViewsHelper to call method on the another pages, etc. Or, you can call the LaunchUriAsync from each pages. If you regist your app as protocol handler, you can receive the call at OnLaunched method.
This is common for both Projection and Multi-View.
This SO page also helps you :)
Multiple instances of a Windows Universal App (Windows 10)
Edited: Sample - It's used on my uwp app - added.
// This is a method of Application class "F10Client".
// SecondaryViews is a member of this class.
// In my app, this method is called when the app resumes.
public async Task<bool> TogglePrivateMaskForAllPages(bool isMask)
{
bool retVal = true;
if (null != ((F10Client)F10Client.Current).SecondaryViews && 0 < ((F10Client)F10Client.Current).SecondaryViews.Count)
{
foreach (var view in ((F10Client)F10Client.Current).SecondaryViews)
{
// You should use dispatcher to call the page method.
await view.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
var thePage = (ImagePage)((Frame)Window.Current.Content).Content;
// calling the method.
thePage.TogglePrivacyMask(isMask);
});
}
}
return retVal;
}
For my first question, the Guidelines for projection manager helped me choose the right way to go:

How solve circular reference in Caliburn.Micro

I am working with Caliburn.Micro v2.0.1 on a Windows 8.1 Unversal (WinRT) project.
I followed the Caliburn.Micro Working with WinRT example.
My code looks as follows:
App.xaml.cs
protected override void OnLaunched(LaunchActivatedEventArgs args)
{
Initialize();
DisplayRootViewFor<LoginViewModel>();
}
protected override void PrepareViewFirst(Frame rootFrame)
{
_container.RegisterNavigationService(rootFrame);
}
LoginViewModel.cs
public LoginViewModel(INavigationService navigationService, ...)
{
...
}
The issue
The OnLaunched is called first.
Initialize() Configures the WinRT container.
The DisplayRootViewFor<LoginViewModel> invokes an instance of the LoginViewModel and results in a Null exception because NavigationService has not yet been registered by PrepareViewFirst(Frame)
PrepareViewFirst(Frame) is not yet called, having a dependency on the RootFrame that should be configured by OnLaunched
Thus LoginViewModel is dependent on RegisterNavigationService and RegisterNavigationService is dependent on DisplayRootViewFor<LoginViewModel>() which is dependent on LoginViewModel
Is there any way to overcome this circular reference issue?
Register your services in the container before resolving the Views - this way all dependencies are available in the particular Dependency Injection container and you can use ServiceLocator to find them.
Typically I've always done this in the OnStartup() method of App.xaml.cs.
You should register/configure your container at the composition root, the earliest access point of your application.
This point depends on what kind of Application you have:
ASP.NET MVC: Global.asax.cs
Console Application: the Main Method
WPF: Application.OnStartup
WinPhone 7: See https://stackoverflow.com/a/7310492/455493
etc.
Check the Windows 7 lifecycle on http://msdn.microsoft.com/en-us/magazine/hh148153.aspx

Managing simple state in Caliburn Micro WinRT 8.1: navigate to previously active page

I'm creating a WinRT 8.1 app based onto Caliburn.Micro (alpha2) and I'm implementing a simple state management mechanism for it. All it needs is saving a couple of name/value pairs for each of its two or three pages, and restore the current page when resumed. So I'm using the strategy summarized below; given that it seems there is no predefined mechanism for CM/WinRT, it would be interesting to get any community advice on this, or this might hopefully be useful for RT newcomers like me.
1) I define an interface (IHaveSimpleState) to be implemented by VMs having some simple state to be saved and restored. The state is represented by a dictionary where each value is a string, representing any serialized value, and the interface just has 2 methods, one for saving its state into this dictionary, and another to resume it from the dictionary. All my stateful VMs (each corresponding to a view) implement this.
2) In my app.cs (which derives from Caliburn.Application), I create a List<WeakReference<IHaveSimpleState>> to keep track of all the VMs requiring state management: in the GetInstance override which instantiates the VMs (using CM simple container) I add to this list each newly generated instance implementing IHaveSimpleState.
3) for saving state: in the app OnSuspending override, I cycle through all the VMs in this list, and invoke their SaveState method to collect data about their state in a common dictionary. Once the loop is complete, I get ApplicationData.Current.LocalSettings and I copy these data into its Values dictionary, thus effectively saving them.
4) for restoring state: in the app OnResuming override and in the OnActivated override (in the latter case, only if args.PreviousExecutionState is equal to Running, i.e. the app was not terminated by user nor crashed), I invoke a ResumeState method which cycles through all the VMs in the list and invokes their LoadState method to load the state from application data local settings.
All this seems to work fine, I only miss a point: what's the right place to restore the current "page", i.e. to tell Caliburn to navigate to the VM behind the view active at the time of suspension? I tried to do this at the end of my ResumeState method (nr.4), but it seems too early, as when I try to navigate to a VM I get an exception telling me that the corresponding view could not be found. Here is the relevant code for this method:
private void ResumeState()
{
// ... state is a dictionary wrapper class with state data
// restore state for each tracked VM
foreach (WeakReference<IHaveSimpleState> reference in _statefulViewModels)
{
IHaveSimpleState stateful;
if (reference.TryGetTarget(out stateful)) stateful.LoadState(state);
}
// move to the page which was current when the state was saved
string sType = state.Get(APP_CURRENTVM_KEY, null);
if (sType != null)
{
// not so elegant...
INavigationService navigation = IoC.Get<INavigationService>();
Type t = Type.GetType(sType);
navigation.NavigateToViewModel(t);
}
}
I tried this and seems to work, but I feel a bit unsecure about the robustness of my app given that the lifecycle handling in CM for 8.1 does not seem to be clearly documented, so I'd like to get comments or corrections from the community. First override the PrepareViewFirst method:
protected override void PrepareViewFirst(Frame rootFrame)
{
_container.RegisterNavigationService(rootFrame);
}
Then in the OnLaunched override, where before I just called DisplayRootView<MainView>(), I test the args to check whether we are resuming from suspended state, and if so I navigate to the previously active page; else I just go as before:
protected override void OnLaunched(LaunchActivatedEventArgs args)
{
bool bResumed = false;
if (args.PreviousExecutionState == ApplicationExecutionState.Suspended)
{
AppSimpleState state = LoadState();
string sType = state.Get(APP_CURRENTVM_KEY);
if (sType != null)
{
INavigationService navigation = IoC.Get<INavigationService>();
Type t = Type.GetType(sType);
Debug.Assert(t != null);
navigation.NavigateToViewModel(t);
bResumed = true;
} //eif
}
if (!bResumed) DisplayRootView<MainView>();
}

Caliburn ShowDialog and MessageBox

I'm making a small demo application for MVVM with caliburn.
Now I want to show a MessageBox, but the MVVM way.
For dialogs I created an event, that is handled in the ShellView (the root view)
and just calls WindowManager.ShowDialog with a Dialogs ViewModel type.
Seems to stick to MVVM for me.
But what is the way to show a messagebox and get its result (Okay or cancel)?
I already saw this question, but it contains no answer either.
Mr Eisenberg hisself answers with
"Caliburn has services built-in for calling custom message boxes."
Can anyone tell what he means with that? I don't see it in the samples.
As you mentioned, you just prepare the view model (e.g. ConfirmationBoxViewModel) and an appropriate view. You'll have to create two actions (after inheriting the view model from Screen, which is necessary to use TryClose. You can always implement IScreen instead, but that would be more work):
public void OK()
{
TryClose(true);
}
public void Cancel()
{
TryClose(false);
}
and then in your other view model:
var box = new ConfirmationBoxViewModel()
var result = WindowManager.ShowDialog(box);
if(result == true)
{
// OK was clicked
}
Notice that after the dialog closes, you can access the view model properties if you need to pull additional data from the dialog (e.g. Selected item, display name etc).
In the article A Billy Hollis Hybrid Shell (written by the framework coordinator) the author showed a nice way to handle both dialog and message boxes, but he used dependency injection (you can go without DI of course but it makes things simpler). The main idea is that you can let your main window, the one used as the application shell implement an interface that looks something like this:
public interface IDialogManager
{
void ShowDialog(IScreen dialogModel);
void ShowMessageBox(string message, string title = null, MessageBoxOptions options = MessageBoxOptions.Ok, Action<IMessageBox> callback = null);
}
and then he registers this interface with the IoC container, I guess you can use your imagination from there on and if you don't have time then you can look at the source code that accompanies the article.
When the root/main/shell view-model implements a kind of DialogService interface, every other view-model needing to show dialogs will end up with a dependency on the root view-model. Sometimes this might not be desiderable, e.g. if it could cause a dependency loop:
DialogService (aka RootViewModel) -> SomeViewModel -> RootViewModel.
A more involved approach to break this dependency chain (and actually invert it) is the following:
Implement a behavior that detects Window.OnSourceInitialized event and attach it to main view Window component. That is the event fired when the window handle is available. Upon event, behavior will notify some handler passed in via attached property:
<my:WindowSourceBehavior InitListener="{Binding WindowListener}" />
public class WindowSourceBehavior : Behavior<Window>
{
// ...
// boilerplate code for IWindowListener InitListener dependency property
// ...
attachedWindow.SourceInitialized += (sender, evt) =>
{
// ...
InitListener.SourceInitialized(sender as Window);
}
}
DialogService exposes a handler - or interface - as requested by behavior:
public class DialogService : IWindowListener
{
// ...
public void SourceInitialized(Window rootWindow) { /* ... */ }
}
In root view-model, (indirectly) get the DialogService injected as a dependency. During construction, sets view-model bound property, WindowListener, to the DialogService handler/interface:
public MainViewModel(IWindowListener dialogServiceInDisguise)
{
WindowListener = dialogServiceInDisguise;
}
public IWindowListener WindowListener { get; private set; }
Doing so, the DialogService is able to get a hold of root Window, and whichever view-model needs to show a dialog does not create a(n indirect) dependency on main view-model.

Caliburn Micro WinRT Navigation and Conductors

I have a WinRT app which is up and running nicely using the caliburn.micro INavigationService (FrameAdapter). The problem is all of the pages in my app are full pages and they repeat a lot of view xaml markup. What I would like to do would be to have more of a "MasterPages" type of architecture where I have a shell view which defines the main layout and contains a ContentControl and uses the Conductor pattern to switch out the contents of the ContentControl. I have had success with this sort of architecture in the past with WPF and Silverlight so I assume it is possible in WinRT.
The problem is there seems to be a disconnect between the navigation infrastructure (using the FrameAdapter) and the Conductor infrastructure using a ContentControl bound to the ActiveItem of the conductor (like the SimpleNavigation sample).
In the conductor scenario I use ActivateItem:
ActivateItem(new MyViewModel());
but with the INavigationService I use NavigateToViewModel:
navigationService.NavigteToViewModel<MyViewModel>();
and the two don't seem to be connected as far as I can tell.
One idea I had was to create a ConductorNavigationService which implements INavigationService and basically handles the creation and activation of the child screens. While it seems possible it doesn't seem very straight forward so I figured I would check to see if there is an already supported way to do this in caliburn.micro.
Ok my understanding might be a little strained as I've not used WinRT or the INavigationService but I assume Frame is part of RT and INavigationService provides viewmodel resolution and navigation for the frames.
My other assumption is that your frame is a bit like your conductor already, and when you call 'Navigate()' on the frame, it just replaces the content of the frame with the newly specified content. If this is the case then CM is doing view first resolution for viewmodels.
Since you want to go the conductor route, it sounds like you want to ditch the CM implementation of INavigationService and just roll your own to handle the INavigationService navigation methods (e.g. skip the Frames Navigate() method).
A quick look at the CM source reveals that all NavigationService is doing is handling the Navigate events on the frame and then doing VM resolution and setting up the view (something that conductor probably already does). All you would need to do is ensure that your INavigationService implementation just loads the specified view into the shell instead of navigating the frame
You could just need to steal the constructor code for NavigationService and change the implementation of Navigate(), then just call ActivateItem(x) on your shell where x is the instance of the VM. CM will take care of the rest (I think CM boostrapper will already setup your root 'Frame' too so you shouldn't need to worry about that).
e.g.
An implementation may look more like this (bear in mind this is just something I thrown together and may be barefaced lies!):
public class NewFrameAdapter : INavigationService
{
private readonly Frame frame;
private readonly IConductActiveItem shell;
private event NavigatingCancelEventHandler ExternalNavigatingHandler = delegate { };
public NewFrameAdapter(Frame frame)
{
this.frame = frame;
// Might want to tighten this up as it makes assumptions :)
this.shell = (frame as FrameworkElement).DataContext as IConductActiveItem;
}
public bool Navigate(Type pageType)
{
// Do guardclose and deactivate stuff here by looking at shell.ActiveItem
// e.g.
var guard = shell.ActiveItem as IGuardClose;
if (guard != null)
{
var shouldCancel = false;
guard.CanClose(result => { shouldCancel = !result; });
if (shouldCancel)
{
e.Cancel = true;
return;
}
}
// etc
// Obviously since the guard is probably async (assume it is, if not you are ok to continue!) you'd have to not call this code right
// here but I've just stuck it in here as an example
// edit: looking at the code above (the guard code) it looks like this is all sync so the below code should be fine
// You might get away with calling shell.ActivateItem(pageType) as I'm not sure
// if the viewmodel binder in RT would resolve this all for you, but if it doesnt...
// Init the view and then resolve the VM type
ViewLocator.InitializeComponent(pageType);
var viewModel = ViewModelLocator.LocateForView(pageType);
// Activate the VM in the shell)
shell.ActivateItem(viewModel);
}
It shouldn't be too difficult to roll this your own way. Does this help you at all?
Then your XAML would be pretty simple:
<Frame blah blah>
<SomeStaticContent />
<ContentControl x:Name="ActiveItem" /> <!-- The dynamic bit... -->
<SomeMoreStaticContent />
</Frame>
I'm thinking that this will probably be a hybrid of view-first and viewmodel-first since your root Frame will be using view-first, and your conductor will be using ActivateItem() which takes a viewmodel and then resolves the view when the binder kicks in, but if my assumptions are ok, it should work

Categories

Resources