Caliburn.Micro - why uses UserControl instead of Window - wpf

My question is exactly like in the title.
I'm starting with Caliburn.Micro for MVVM approach (which also is new for me) and in every tutorial the first step is to remove the default MainWindow.xaml file and create a new UserControl file. Why is that? UserControl does not even accept a Title. Isn't it possible to build application using normal Windows? I already tried that, but with every launch I get error "Cannot find view for ViewModel", although both MainView.xaml and MainViewModel.cs are present. When I created a pair of USerControl and ViewModel for it, everything started to work as expected. So again, why Windows don't work?
It wouldn't really be a problem, but I'm thinking that some additions like Modern UI themes for WPF might not work without a window. I'm not sure of that.
Probably one solution would be to display a defined UserControl View inside of a Window, but it's just a workaround.

You could create your own custom shell window by creating a custom WindowManager:
public class CustomWindowManager : WindowManager
{
protected override Window CreateWindow(object rootModel, bool isDialog, object context, IDictionary<string, object> settings)
{
Window window = new Window();
window.Title = "custom...";
return window;
}
}
...that you register in your bootstrapper:
public class HelloBootstrapper : BootstrapperBase
{
...
protected override void Configure()
{
_container.Singleton<IWindowManager, CustomWindowManager>();
...
}
}

Related

How to customize startup of WPF application?

When a new WPF Application project is created, MainWindow.xaml, App.xaml and their corresponding code behind classes are automatically generated. In the App.xaml there is an attribute that defines which window is going to be run initially and by the default it's StartupUri="MainWindow.xaml"
I have created a new Dispatcher class in the same project. At startup, I want the instance of that class Dispatcher to be constructed and then one of its method to run. That method would actually create and show the MainWindow window. So how do I modify the App.xaml or App.xaml.cs in order to make it happen? Or, if it cannot be done by App, how should I implement it? Thanks.
You can remove the StartupUri attribute from the App.xaml.
Then, by creating an override for OnStartup() in the App.xaml.cs, you can create your new instance of your Dispatcher class.
Here's what my quick app.xaml.cs implementation looks like:
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
new MyClassIWantToInstantiate();
}
}
}
Update
I recently discovered this workaround for a bug if you use this method to customize app startup and suddenly none of the Application-level resources can be found.
Try to use the Startup event (class Application) - MSDN.
You can show MainWindow in this event handler - after you create a Dispatcher instance.
1.In App.xaml, To replace the StartupUri with a subscription to the Startup event.
Use the event in App.xaml.cs .
For instance,
Startup="Application_Startup" in .xaml.
public partial class App : Application
{
private void Application_Startup(object sender, StartupEventArgs e)
{
// Create the startup window
MainWindow wnd = new MainWindow();
// Do stuff here, e.g. to the window
wnd.Title = "Something else";
// Show the window
wnd.Show();
}
}

Should I create separate Bootstrapper for each WPF window?

I am new in WPF and Prism. I'd like to know if I should create new bootstrapper for each new window? For Example I have "Window1" where I select element from ListBox and click button "ShowDetails" and in the new window "Window2" I should see the details of my selection. I have windows and modules for them, but I'd like to know how and where I can register the module "Module2" for "Window2"?
Example of my Bootstrapper.
class Bootstrapper : UnityBootstrapper
{
protected override DependencyObject CreateShell()
{
var mainWindow = new Window1();
mainWindow.Show();
return mainWindow;
}
protected override IModuleCatalog GetModuleCatalog()
{
var moduleCatalog = new ModuleCatalog();
moduleCatalog.AddModule(typeof(Module1));
return moduleCatalog;
}
}
"App.xaml.cs"
public partial class App : Application
{
public App()
{
var bootstrapper = new Bootstrapper();
bootstrapper.Run();
}
}
The Bootstrapper is used usually in the startup class of a WPF Application. Usually this will be the file App.xaml.cs in the standard template, which is the code-behind class of the App.xaml file. You override the method OnStartup and instantiate your Bootstrapper and call its run method. You can delay the startup of the bootstrapper until the override of OnStartup instead of writing this in the constructor of the App.xaml.cs class. You will then use the RegionManager in Prism and define regions in your XAML. If you have multiple independent Windows this is a bit different from the way Prism is intended to be used. There is the concept of a MainWindow or Shell which you define in the CreateShell method of the Bootstrapper class which is available in the Prism source code. Instead, have a main window and define regions and perhaps consider creating a mechanism for displaying additional windows in dialogs. It is possible partition up the MainWindow into multiple regions and inject user controls via the RegionManager. This is done via the activate method of the RegionManager.
Start up by reading the Patterns And Practices Guide and perhaps consider watching the videos of Mike Taulty upon Prism. The first video is here:
Prism & Silverlight: Part 1 - Taking Sketched Code Towards Unity
There are many videos in the video series (10 in total) that will help you get started with PRISM.
An example of how to define a region in XAML is shown next:
<ItemsControl Regions:RegionManager.RegionName="MainRegion" />
A PRISM region can be activated, e.g. through a DelegateCommand or ICommand bound to a button is the following code:
var viewA = new ViewA();
var regionA = (new RegionManager()).Regions["RegionA"];
regionA.Activate(viewA);
You will have to define multiple modules that implement the IModule Interface and add these to your ModuleCatalog as you already have done with ModuleA.

How to use Caliburn Micro in a WinForms app with one WPF form

We have a (massive) legacy WinForms app which, through a menu item, opens up a WPF form. This WPF form will host an Infragistics grid, and some buttons/drop-downs.
This lone WPF form represents the nascent stage of a migration to WPF. Later on, more components of the app will move to WPF, and ultimately the entire app itself.
As part of the migration, we would like to use Caliburn Micro. Hence, it would be nice if we could start by using it with this lone WPF form.
Can someone please provide some pointers on how to use Caliburn Micro with the WPF form?
Or perhaps tell me why it may not make sense to use Caliburn Micro just yet?
The documentation I've read so far involves boot strappers that ensure the application starts with the desired root view model, rather than the scenario above.
Many thanks!
After much Googling and going through the Caliburn Micro source code, I've come up with an approach that works in a sample test application. I can't post the test application here for certain reasons, but here's the approach in a nutshell.
Create a WinForm with a button.
On button click, show a ChildWinForm
In the load handler of the ChildWinForm:
// You'll need to reference WindowsFormsIntegration for the ElementHost class
// ElementHost acts as the "intermediary" between WinForms and WPF once its Child
// property is set to the WPF control. This is done in the Bootstrapper below.
var elementHost = new ElementHost{Dock = DockStyle.Fill};
Controls.Add(elementHost);
new WpfControlViewBootstrapper(elementHost);
The bootstrapper above is something you'll have to write.
For more information about all it needs to do, see Customizing the Bootstrapper from the Caliburn Micro documentation.
For the purposes of this post, make it derive from the Caliburn Bootstrapper class.
It should do the following in its constructor:
// Since this is a WinForms app with some WPF controls, there is no Application.
// Supplying false in the base prevents Caliburn Micro from looking
// for the Application and hooking up to Application.Startup
protected WinFormsBootstrapper(ElementHost elementHost) : base(false)
{
// container is your preferred DI container
var rootViewModel = container.Resolve();
// ViewLocator is a Caliburn class for mapping views to view models
var rootView = ViewLocator.LocateForModel(rootViewModel, null, null);
// Set elementHost child as mentioned earlier
elementHost.Child = rootView;
}
Last thing to note is that you'll have to set the cal:Bind.Model dependency property in the XAML of WpfControlView.
cal:Bind.Model="WpfControls.ViewModels.WpfControl1ViewModel"
The value of the dependency property is used passed as a string to Bootstrapper.GetInstance(Type serviceType, string key), which must then use it to resolve the WpfControlViewModel.
Since the container I use (Autofac), doesn't support string-only resolution, I chose to set the property to the fully qualified name of the view model. This name can then be converted to the type, and used to resolve from the container.
Following up on the accepted answer (good one!), I'd like to show you how to implement the WinForms Bootstrapper in a ViewModel First approach, in a way that:
You won't have to create a WPF Window and,
You won't have to bind directly to a ViewModel from within a View.
For this we need to create our own version of WindowManager, make sure we do not call the Show method on the Window (if applicable to your case), and allow for the binding to occur.
Here is the full code:
public class WinformsCaliburnBootstrapper<TViewModel> : BootstrapperBase where TViewModel : class
{
private UserControl rootView;
public WinformsCaliburnBootstrapper(ElementHost host)
: base(false)
{
this.rootView = new UserControl();
rootView.Loaded += rootView_Loaded;
host.Child = this.rootView;
Start();
}
void rootView_Loaded(object sender, RoutedEventArgs e)
{
DisplayRootViewFor<TViewModel>();
}
protected override object GetInstance(Type service, string key)
{
if (service == typeof(IWindowManager))
{
service = typeof(UserControlWindowManager<TViewModel>);
return new UserControlWindowManager<TViewModel>(rootView);
}
return Activator.CreateInstance(service);
}
private class UserControlWindowManager<TViewModel> : WindowManager where TViewModel : class
{
UserControl rootView;
public UserControlWindowManager(UserControl rootView)
{
this.rootView = rootView;
}
protected override Window CreateWindow(object rootModel, bool isDialog, object context, IDictionary<string, object> settings)
{
if (isDialog) //allow normal behavior for dialog windows.
return base.CreateWindow(rootModel, isDialog, context, settings);
rootView.Content = ViewLocator.LocateForModel(rootModel, null, context);
rootView.SetValue(View.IsGeneratedProperty, true);
ViewModelBinder.Bind(rootModel, rootView, context);
return null;
}
public override void ShowWindow(object rootModel, object context = null, IDictionary<string, object> settings = null)
{
CreateWindow(rootModel, false, context, settings); //.Show(); omitted on purpose
}
}
}
I hope this helps someone with the same needs. It sure saved me.
Here are somethings you can start with
Create ViewModels and inherit them from PropertyChangedBase class provided by CM framework.
If required use the EventAggregator impelmentation for loosly coupled communication \ integration
Implement AppBootStrapper without the generic implementation which defines the root view model.
Now you can use the view first approach and bind the view to model using the Bind.Model attached property on view. I have created a sample application to describe the approach here.

WPF window doesn't implement ISynchronizeInvoke.InvokeRequired ERROR

I have this problem that My WPF App in its MainWindow:
public partial class MainWindow : Window, ICallbackNotify
this " ICallbackNotify " is to communicate with MapInfo COM
and to assure The interactivity between UI & MapInfo
public interface ICallbackNotify : ISynchronizeInvoke
{
// Method called by MapInfoCallback class when user chooses custom OLE menuitem
void OnMenuItemClick(uint id);
// Method called by MapInfoCallback class when the status bar text changes
void OnStatusBarTextChanged(string text);
// Method called by MapInfoCallback class when window changes
void OnWindowContentsChanged(uint windowId);
}
The problem is, this code is working just fine in Windows From but when i put it into WPF there is an ERROR msg :
Error 1 'WpfApplication3.MainWindow' does not implement interface member 'System.ComponentModel.ISynchronizeInvoke.InvokeRequired' c:\users\dragon\documents\visual studio 2010\Projects\WpfApplication3\WpfApplication3\MainWindow.xaml.cs 25 26 WpfApplication3
and i cant figure out why or how to solve this problem
Under the hood Windows Forms and WPF are very different.
ISynchronizeInvoke is a Windows Form concept built upon Win32 API that doesn't apply to WPF. In WPF, we use the Dispatcher for communicating between threads.
From your question it sounds like what you want to do is host a WinForms COM control inside a WPF application. In order to bridge these two technologies, Microsoft has a WindowsFormHost control that you can use to contain your component. This blog post has a pretty good write up on that. Checkout this MSDN example: https://msdn.microsoft.com/en-us/library/vstudio/ms751761(v=vs.100).aspx
Update:
It sounds like the problem you are having isn't at runtime but during compilation?? Let slow down and be clear:
In Windows Forms, all user controls and windows implement ISynchronizeInvoke. WPF isn't built on the traditional Win32 WinProc message pump, so they do not implement this interface. If you copy and paste your INotifyCallBack interface and implementation "as is" into the MainWindow.xaml.cs, you will get a compilation error because the base interface ISynchronizeInvoke is not implemented in that class. To bypass the compilation error you will have to implement the ISynchronizeInvoke signature:
public partial class MainWindow : Window, ISynchronizeInvoke
{
public IAsyncResult BeginInvoke(Delegate method, object[] args)
{
throw new NotImplementedException();
}
public object EndInvoke(IAsyncResult result)
{
throw new NotImplementedException();
}
public object Invoke(Delegate method, object[] args)
{
throw new NotImplementedException();
}
public bool InvokeRequired
{
throw new NotImplementedException();
}
}
But guess what? If you do this, it's up to you to provide the proper mapping between ISynchronizeInvoke and the WPF Dispatcher. So don't do this! The above code might compile but it likely won't work correctly (or at all).
Instead, use the Windows Form Integration capabilities provided by Microsoft to host your control inside a WindowsFormHost control. As you need to implement custom code in your INotifyCallback, you'll need to put that code into a custom windows forms user control.
All said, you need to follow the direction provided by the article listed above:
Reference System.Windows.Forms.dll and WindowsFormsIntegration.dll
Add the appropriate namespaces to your MainWindow.xaml
Add the WindowsFormsHost as a container into your MainWindow.xaml, and then put your custom control inside of that.
So instead of putting your ICallbackNotify implementation on the WPF MainWindow, put it in a standard Windows Form UserControl:
namespace YourProject {
using System.ComponentModel;
using System.Windows.Forms;
public class MyCustomMapControl : UserControl, ICallbackNotify
{
// your custom code goes here
}
}

Cannot show up WPF application when setting MainWindow manually and composing application (MEF)

I got my hands om MEF for a week now and I am trying to build up a WPF application that loads imported controls from MEF.
I created a WPF application project and removed the default window and application start up URI. Then I handled the application startup event to compose the application:
public partial class App : Application, IPartImportsSatisfiedNotification
{
{...}
private void App_Startup(object sender, StartupEventArgs e)
{
this.Compose();
}
public void Compose()
{
try
{
globalCatalog.Catalogs.Add(new DirectoryCatalog(extensionsDirectoryPath));
CompositionContainer container = new CompositionContainer(globalCatalog);
container.ComposeParts(this);
}
catch (Exception ex)
{
// Do something
}
}
{...}
}
Actually, when debugging and watching objects after imports are satisfied, everything has hierarchically composed fine like I wanted. But when I try to show up the MainWindow of the application an exception is thrown on MainWindow.Show() call:
"Specified element is already the logical child of another element. Disconnect it first."
Though my code in OnImportsSatisfied method seems fine as it is working when not using MEF mecanism:
public void OnImportsSatisfied()
{
Window mainWindow = new Window();
mainWindow.Content = this.importedControl;
this.MainWindow = mainWindow;
this.MainWindow.Show();
}
I insist on the fact that this works perfectly when not importing controls with MEF. What is surprising is that this code does not work too:
Window mainWindow = new Window();
//mainWindow.Content = this.importedControl;
this.MainWindow = mainWindow;
this.MainWindow.Show();
So I suspect that ComposeParts is doing a bit more than what it says as it is the only member acting on my actual application instance.
Hope someone can help me (Glenn?).
Thanks.
Edit:
I discovered that when I remove the IPartImportsSatisfiedNotification interface from my parts, no exception is thrown and the window shows up. But of course the window is empty as I need this OnImportsSatisfied method to set the DataContext of the window to its associated imported view model.
The sample applications of the WPF Application Framework (WAF) show how to use MEF within a WPF application.
I finally discovered that I was importing my WPF user controls by using the default ImportAttribute constructor, which in fact will make a shared instance of the class if the creation policy is not specified during export. And as many of my controls were implementing the same interface and I was binding them in my views, I was actually trying to add this shared user control instance to different visual elements, which is not permited by WPF (and so the exception).
I marked my imports using the RequiredCreationPolicy set to NonShared and everything got back in order! That was all about learning MEF...

Resources