Does anyone know if it is possible to share viewmodels between a WPF and Xamarin Forms app? Below is a snippet of a Xamarin Forms viewmodel. There are some interfaces that are unique to the Forms app such as the IPageDialogService and also the way navigation is handled so it doesn't seem as though we can do this but wanted to check.
using System;
using System.Threading.Tasks;
using Prism.Commands;
using Prism.Mvvm;
using Prism.Navigation;
using Prism.Services;
namespace PrismSample.ViewModels
{
public class MainPageViewModel : BindableBase, IInitialize, IConfirmNavigation,
INavigationAware
{
private INavigationService _navigationService { get; }
private IPageDialogService _dialogs { get; }
public MainPageViewModel(INavigationService navigationService, IPageDialogService
dialogs)
{
_navigationService = navigationService;
_dialogs = dialogs;
NavigateCommand = new DelegateCommand<string>(OnNavigateCommandExecuted);
}
}
If your code contains references to any type in Prism.Forms or Prism.Wpf you cannot share it between platforms.
Shared libraries should reference Prism.Core only. You could then create platform specific libraries that references your shared Prism.Core base library and adds the platform specific functionality for Xamarin.Forms and WPF respectively.
Related
Background
Hi all SO viewers. I am normally an Android developer, but now I'm developing a cross platform application targeting WPF and Android. That being said, there's practically no info on how to directly do what I want. So, I ended up finding a 3-part blog series that goes in depth on how to develop a Windows-based cross platform MVVM project. As long as I set the PCL to be compatible with Xamarin.Android, any code that doesn't throw an error SHOULD work once I get to the Android side of things. Here are the links to the blog posts: Blog 1, Blog 2, Blog 3. Again, I do Android, so I am new to doing coding for a WPF Application.
Issue
So my issue today is only dealing with the PCL-WPF side which relates to the above-linked blog post. I followed every single step laid out in the posts as best as I could. The blog uses WinRT and WinPhone as the two target platforms, so I HAD to try figuring out things on my own to make things work on the WPF. Two of the things I had to do was use IsolatedStorage and basically use the WinPhone App.Xaml to make the WPF side build.
I have finished the blog all the way to the end and build succeeds. I even can see my example Debug lines like it talks about at the end of the third blog post. However, when I go to run it, I get the following:
ActivationException was unhandled by user code
An exception of type 'Microsoft.Practices.ServiceLocation.ActivationException' occurred in GalaSoft.MvvmLight.Extras.dll but was not handled in user code
$exception {Microsoft.Practices.ServiceLocation.ActivationException: Type not found in cache: StackOverF.Services.IStorageService.
at GalaSoft.MvvmLight.Ioc.SimpleIoc.DoGetService(Type serviceType, String key, Boolean cache) in c:\MvvmLight\Source\GalaSoft.MvvmLight\GalaSoft.MvvmLight.Extras (PCL)\Ioc\SimpleIoc.cs:line 537
at GalaSoft.MvvmLight.Ioc.SimpleIoc.GetService(Type serviceType) in c:\MvvmLight\Source\GalaSoft.MvvmLight\GalaSoft.MvvmLight.Extras (PCL)\Ioc\SimpleIoc.cs:line 789
at GalaSoft.MvvmLight.Ioc.SimpleIoc.MakeInstanceTClass in c:\MvvmLight\Source\GalaSoft.MvvmLight\GalaSoft.MvvmLight.Extras (PCL)\Ioc\SimpleIoc.cs:line 729} System.Exception {Microsoft.Practices.ServiceLocation.ActivationException}
Is there anything that you guys can tell me that maybe the blog author skipped over that I need to do? Maybe if enough rocks are thrown at this "boulder," it'll crack open...
Clarification
There are basically currently only two projects in my Visual Studio Solution. One is the Portable Class Library. The other is the WPF Application. In the very near future, once I get things working on the WPF side of the equation, I'll use the PCL in Xamarin to reuse the code in an Android project. However, the Android side is not part of my problem here. I'm having the above issue when only dealing with the WPF project.
Code (Last edited Feb 18, 2016)
IMainViewModel.cs
using GalaSoft.MvvmLight.Command;
using StackOverF.Models;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace StackOverF.ViewModels {
public interface IMainViewModel {
ObservableCollection<Workload> Workload { get; }
RelayCommand RefreshCommand { get; }
RelayCommand AddCommand { get; }
}
}
MainViewModel.cs
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Command;
using StackOverF.Models;
using StackOverF.Services;
using System;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace StackOverF.ViewModels {
public class MainViewModel : ViewModelBase,IMainViewModel {
private IDataService dataService;
public MainViewModel(IDataService dataService) {
this.dataService = dataService;
RefreshAsync();
}
private ObservableCollection<Workload> workload = new ObservableCollection<Workload>();
public ObservableCollection<Workload> Workload {
get {
return workload;
}
}
#region Commands
#region Refresh
private RelayCommand refreshCommand;
public RelayCommand RefreshCommand {
get {
return refreshCommand ?? (refreshCommand = new RelayCommand(async () => { await RefreshAsync();}));
}
}
private async Task RefreshAsync() {
workload.Clear();
foreach (Workload listing in await dataService.GetWorkloadAsync()) {
workload.Add(listing);
}
}
#endregion
#region Add
private RelayCommand addCommand;
public RelayCommand AddCommand {
get {
return addCommand ??
(addCommand = new RelayCommand(async () => {
Workload listing = new Workload() { Id = 3, Serial = "relay12" };
await dataService.AddWorkloadAsync(listing);
workload.Add(listing);
}));
}
}
#endregion
#endregion
}
}
LocatorService.cs (DeviceLocatorService, located in WPF Project)
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Ioc;
using Microsoft.Practices.ServiceLocation;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace StackOverF.Services {
public class DeviceLocatorService {
static DeviceLocatorService() {
ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
if (ViewModelBase.IsInDesignModeStatic) {
}
else {
}
if (!SimpleIoc.Default.IsRegistered<IStorageService>())
SimpleIoc.Default.Register<IStorageService, StorageService>();
}
public static void Cleanup() {
}
}
}
LocatorService.cs (LocatorService, located in PCL Project)
using Microsoft.Practices.ServiceLocation;
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Ioc;
using StackOverF.ViewModels;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace StackOverF.Services {
public class LocatorService {
static LocatorService() {
ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
// Services
if (ViewModelBase.IsInDesignModeStatic) {
SimpleIoc.Default.Register<IDataService, Design.DataService>();
}
else {
SimpleIoc.Default.Register<IDataService, Services.DataService>();
}
// View Models
SimpleIoc.Default.Register<IMainViewModel, MainViewModel>();
}
public IMainViewModel MainViewModel {
get {
return ServiceLocator.Current.GetInstance<IMainViewModel>();
}
}
public static void Cleanup() {
}
}
}
It errors (WHILE DEBUGGING ONLY) on the return ServiceLocator.Current.GetInstance<IMainViewModel>(); line.
App.xaml
<Application x:Class="StackOverF.App"
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"
xmlns:services="clr-namespace:StackOverF.Services;assembly=StackOverF.PCL"
xmlns:deviceServices="clr-namespace:StackOverF.Services"
StartupUri="Views/MainWindow.xaml">
<Application.Resources>
<ResourceDictionary>
<deviceServices:DeviceLocatorService x:Key="Locator.WPF" d:IsDataSource="True" />
<services:LocatorService x:Key="Locator" d:IsDataSource="True" />
</ResourceDictionary>
</Application.Resources>
</Application>
The solution to my problem is simpler than one might expect. WPF applications have no problem using MVVM toolkits/frameworks, but they do seem to have a problem with sharing. Since WPF wasn't intended to be a cross-platform-friendly language, the "correct" way to program something like this won't work for it.
The issue comes around trying to include both LocatorService classes in the App.xaml and expecting WPF to run both classes like WinRT or WinPhone might. WPF seems to only refer to a class if the data is needed. Just like in the blog's example, I had in the Main.xaml data bound to the LocatorService class. Since the WPF application only ran that class's code, it'd throw the error.
The solution was to combine the LocatorService files into one file, on the WPF project side. Why on the WPF side you ask? A Portable Class Library should only contain universal code, shareable cross platform.
As I consider, IoC container is trying to resolve MainWindow and in order to make it container resolves IDataService. But IDataService has dependency IStorageService which is not registered in IoC container.
In my point of view, IStorageService is not registered because DeviceLocatorService constructor is never called.
I reckon you have a problem with app.xaml
<Application
x:Class="SampleApp.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:ignore="http://www.ignore.com"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d ignore"
xmlns:services="using:SampleApp.Services"
xmlns:local="using:SampleApp">
<Application.Resources>
<ResourceDictionary>
<services:LocatorService x:Key="Locator" d:IsDataSource="True" />
<services:DeviceLocatorService x:Key="Locator.W8" d:IsDataSource="True" />
</ResourceDictionary>
</Application.Resources>
</Application>
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.
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
}
}
I am using the MVVMLight toolkit for my WPF application.
Now I was going through the demo sample from Lauren's MIX 10.
The sample code is in SL, and makes use of the UnityContainer.
The template provided by MVVMLight toolkit for WPF does not utilizes the unitycontainer concept. How can I make use of the UnityContainer in WPF.
I don't now if my question even makes sense. I do not see any documentation on how to use the ViewModelLocator. Maybe some one can provide a sample or a WPF version of the Demo used by Lauren in MIX
The way I use Unity on WPF (MVVM Light) is like this:
I create a bootstrapper class on the application root, something like:
public class Bootstrapper
{
public IUnityContainer Container { get; set; }
public Bootstrapper()
{
Container = new UnityContainer();
ConfigureContainer();
}
private void ConfigureContainer()
{
Container.RegisterType<IMyRepo, MyRepo>();
Container.RegisterType<MainViewModel>();
}
}
This is my bootstrapper. I register the ViewModels too because is easy create them in the Locator.
Next, I create the boostrapper on the ViewModelLocator's constructor and I resolve every ViewModel here, like:
public class ViewModelLocator
{
private static Bootstrapper _bootStrapper;
static ViewModelLocator()
{
if (_bootStrapper == null)
_bootStrapper = new Bootstrapper();
}
public MainViewModel Main
{
get { return _bootStrapper.Container.Resolve<MainViewModel>(); }
}
}
As you see, my ViewModelLocator is simple, it just create the bootstrapper and resolve the ViewModel, and these VM will resolve their dependencies through the container too :)
Maybe there is a best way to archieve this, but this is a good start indeed.
I would advise to use Managed Extensibility Framework. It's in .NET 4 and I switched myself from unity to MEF. I works very great when your app is growing. You can find lots of info on it by search using google.
Good luck!
I am planning to create a WPF application with a main window which would launch various WinForms. Some of the WinForms use the System.Windows.Forms.Application class (DoEvents, Application.Path, etc). Do you think that there will be a problem in doing this?
Can I still use System.Windows.Forms.Application.DoEvents() from a WinForm that is launched from a WPF application?
The main problem will the ability to instantiate the Windows Forms window and set it's owner to that of the WPF window. The Winforms will want a IWin32Window which a WPF window isn't. To get around this, you need to make a custom class.
I found this code on Mark Rendle's blog (I've copied it here as I had to use the Google Cache to access the page).
LINK - WARNING: May not work
class Shim : IWin32Window
{
public Shim(System.Windows.Window owner)
{
// Create a WindowInteropHelper for the WPF Window
interopHelper = new WindowInteropHelper(owner);
}
private WindowInteropHelper interopHelper;
#region IWin32Window Members
public IntPtr Handle
{
get
{
// Return the surrogate handle
return interopHelper.Handle;
}
}
#endregion
}
and it's method of use:
namespace System.Windows.Forms
{
public static class WPFInteropExtensions
{
public static DialogResult ShowDialog(
this System.Windows.Forms.Form form,
System.Windows.Window owner)
{
Shim shim = new Shim(owner);
return form.ShowDialog(shim);
}
}
}
I haven't tested this code, but reading around the internet, it appears that you can host Winforms windows inside of a WPF app.
I just found this link on MSDN that has a very detailed description of how to interop a Win32 control/window in a WPF application.
Hope these help you out.
I've been doing this sometimes and didn't encounter any problem.
However i don't really recommend it, you should prefer WPF when you are in a WPF Application.
for exemple if you want application path use this :
System.Reflection.Assembly.GetExecutingAssembly().Location