Giter VIP home page Giter VIP logo

xamformswpf's Introduction

Tutorial Overview

In this tutorial, we will create a cross-platform solution with Xamarin.Forms that includes WPF as a target.

Cross-platform application development should reach the largest potential customer base it can.  With Xamarin.Forms, you can create a solution with projects that target Android, iOS, Windows 10, Windows Mobile, Windows 8.1, and Windows Phone 8.1 out-of-the-box.  Unfortunately, Windows Desktop is not included with this template.  With so many people still using and relying on traditional Windows Desktop applications, many times it is important to provide a solution for that target environment.

Fortunately, with only a few modifications, we can refactor our default Xamarin.Forms solution to include a WPF project.  Furthermore, we will establish a project structure that ensures all the projects can still utilized common code (things like View Models, Services, Models) from a PCL project within the solution.

We will cover the following activities:
  • Creating a new solution from the default Xamarin.Forms project template
  • Adding a Core PCL to our solution, to contain our common code
  • Add some common code to our Core PCL
  • Using a simple IoC container to replace Xamarin.Forms DependencyService class
  • Add a WPF project to our solution to target Windows Desktop
  • Implementing IHelloService in each platform
  • Creating some simple data binding in our MainPage and MainWindow views

Create the XamFormsWpf project

Overview

In this section, we will create a new solution that will contain all our common and platform-specific projects.  We will create our solution using a built-in Xamarin Forms solution template.  I will also briefly go over updating the NuGet dependencies once our solution is created.

Steps

Open a new instance of Visual Studio and select File > New Project.  Search for 'Xamarin Forms' and select 'Blank Xaml App (Xamarin.Forms Portable)' option.

Name the solution 'XamFormsWpf' and click OK. 

At some point during the initialization, you will be asked to determine which version of Windows 10 you want to target.  Go ahead and take the defaults here.

A note about NuGet updates

At the time of this writing, Xamarin Forms and the Android Support Library NuGet packages are out of sync.  Updating Xamarin Forms downgrades the Android Support Libraries, and Upgrading these libraries downgrades Xamarin Forms. 

This results in a never-ending upgrade loop, with some random NuGet errors sprinkled in that you would probably prefer to avoid.  My recommendation is to upgrade Xamarin.Forms, but leave the Android support libraries alone.

From the Visual Studio menu, select Tools > NuGet Package Manger > Manage Packages for Solution.

Select the 'Updates' tab, and check the checkboxes for 'Microsoft.NETCore.UniversalWindowsPlatform' and 'Xamarin.Forms'

*Note - the specific packages to update may be different, as these change over time.

Here is a screenshot of my available Updates at the time of this writing.



Adding a Core PCL to our solution, to contain our common code

Overview

When we created our solution from the Xamarin.Forms template, it created a PCL called XamFormsWpf (Portable).  If you are coming from a Xamarin.Android or Xamarin.iOS background, you might expect this project to contain things like View Models, Models, Services, etc. 

With Xamarin.Forms, this PCL actually contains our common View definitions as well.  Since we cannot reuse the Xamarin.Forms XAML in our WPF project, we will need to create another PCL to hold our common core code.

Steps

Right-click the XamFormsWpf (Portable) project and select 'Rename'.  Change the name to XamFormsWpf.Core.Forms.

Right-click the Solution and select Add > New Project.  Search for 'Portable' and select 'Class Library (Portable for iOS, Android, and Windows)'

Name the new project XamFormsWpf.Core and click OK.

Add references to the new PCL.  Right-click References > Add References.  Under Projects check the check box for XamFormsWpf.Core and click OK.  Do this for each project in the solution.

Once you are finished, go ahead and build to make sure everything is properly configured.  Your solution structure should now look like this



Add some common code to our Core PCL

Overview

The next step is to create some common code in our Core PCL to demonstrate how our Xamarin.Forms and WPF project will utilize it. 

As our example, we will create a single Contact page for each of our apps.  The page will display a contact's First Name, Last Name, and Email Address.  Since Xamarin.Forms DependencyService class won't be available in WPF, we'll need a substitute service locator.  We will create a simple Hello Service interface, with implementations in our Forms and WPF projects, what we'll wire up with simple IoC replacement for DependencyService.

Steps

Delete Class1.cs, we won't need the functionality it provides.

In XamFormsWpf.Core:

Create a new folder called Data, add a class to the folder called 'Contact' with the following code.

namespace XamFormsWpf.Core.Data
{
    public class Contact
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string Email { get; set; }
    }
}

Create a new folder called Services, add a new interface to the folder called 'IHelloService' with the following code

namespace XamFormsWpf.Core.Services
{
    public interface IHelloService
    {
        string SayHello();
    }
}

In the Services folder, create a new class called SimpleIoC with the following code. 

There are many IoC containers that are available for use with Xamarin projects, but for the purpose of this demo, I decided to create a quick one myself.  ***This container IS NOT meant for production use, it was created to be simple enough to reduce the learning overhead of working without DependencyService. This will replace Xamarin.Forms DependencyService class, which isn't available in WPF, and will wire up our IHelloService interface with an implementation in our Forms and WPF projects later.

using System;
using System.Collections.Generic;

namespace XamFormsWpf.Core.Services { public sealed class SimpleIoC { private static volatile SimpleIoC _instance; private static object _syncRoot = new object(); private Dictionary<Type, Type> _multiInstance = new Dictionary<Type, Type>(); private Dictionary<Type, object> _singletons = new Dictionary<Type, object>();

    <span style="color:rgb(51,51,51);font-weight:bold">private</span> SimpleIoC() { }
    <span style="color:rgb(51,51,51);font-weight:bold">public</span> <span style="color:rgb(51,51,51);font-weight:bold">static</span> SimpleIoC Container
    {
        get {
            <span style="color:rgb(51,51,51);font-weight:bold">if</span>(_instance == null) {
                lock(_syncRoot) {
                    <span style="color:rgb(51,51,51);font-weight:bold">if</span>(_instance == null)
                        _instance = <span style="color:rgb(51,51,51);font-weight:bold">new</span> SimpleIoC();
                }
            }

            <span style="color:rgb(51,51,51);font-weight:bold">return</span> _instance;
        }
    }

    <span style="color:rgb(51,51,51);font-weight:bold">public</span> <span style="color:rgb(51,51,51);font-weight:bold">void</span> Register&lt;Implementation&gt;()
    {
        RegisterSingleton&lt;Implementation, Implementation&gt;();
    }

    <span style="color:rgb(51,51,51);font-weight:bold">public</span> <span style="color:rgb(51,51,51);font-weight:bold">void</span> RegisterSingleton&lt;Implementation&gt;()
    {
        RegisterSingleton&lt;Implementation, Implementation&gt;();
    }

    <span style="color:rgb(51,51,51);font-weight:bold">public</span> <span style="color:rgb(51,51,51);font-weight:bold">void</span> Register&lt;Interface, Implementation&gt;()
    {
        Validate&lt;Interface, Implementation&gt;();
        _multiInstance.Add(typeof(Interface), typeof(Implementation));
    }

    <span style="color:rgb(51,51,51);font-weight:bold">public</span> <span style="color:rgb(51,51,51);font-weight:bold">void</span> RegisterSingleton&lt;Interface, Implementation&gt;()
    {
        Validate&lt;Interface, Implementation&gt;();
        _singletons.Add(typeof(Interface), Activator.CreateInstance(typeof(Implementation)));
    }

    <span style="color:rgb(51,51,51);font-weight:bold">public</span> Type Resolve&lt;Type&gt;()
    {
        <span style="color:rgb(51,51,51);font-weight:bold">if</span>(_singletons.ContainsKey(typeof(Type))) {
            <span style="color:rgb(51,51,51);font-weight:bold">return</span> (Type)_singletons[typeof(Type)];
        }
        <span style="color:rgb(51,51,51);font-weight:bold">else</span> <span style="color:rgb(51,51,51);font-weight:bold">if</span>(_multiInstance.ContainsKey(typeof(Type))) {
            var theType = _multiInstance[typeof(Type)];
            var obj = Activator.CreateInstance(theType);
            <span style="color:rgb(51,51,51);font-weight:bold">return</span> (Type)obj;
        }
        <span style="color:rgb(51,51,51);font-weight:bold">else</span>
            <span style="color:rgb(51,51,51);font-weight:bold">throw</span> <span style="color:rgb(51,51,51);font-weight:bold">new</span> Exception($<span style="color:rgb(221,17,68)">"Type {typeof(Type).ToString()} is not registered"</span>);
    }

    <span style="color:rgb(51,51,51);font-weight:bold">private</span> <span style="color:rgb(51,51,51);font-weight:bold">void</span> Validate&lt;Interface, Implementation&gt;()
    {
        <span style="color:rgb(153,153,136);font-style:italic">// This will fail up-front if the class cannot be instantiated correctly</span>
        Activator.CreateInstance&lt;Implementation&gt;();

        <span style="color:rgb(51,51,51);font-weight:bold">if</span>(_multiInstance.ContainsKey(typeof(Interface))) {
             <span style="color:rgb(51,51,51);font-weight:bold">throw</span> <span style="color:rgb(51,51,51);font-weight:bold">new</span> Exception($<span style="color:rgb(221,17,68)">"Type  {_multiInstance[typeof(Interface)].ToString()} is already registered for  {typeof(Interface).ToString()}."</span>);
        }
        <span style="color:rgb(51,51,51);font-weight:bold">else</span> <span style="color:rgb(51,51,51);font-weight:bold">if</span>(_singletons.ContainsKey(typeof(Interface))) {
             <span style="color:rgb(51,51,51);font-weight:bold">throw</span> <span style="color:rgb(51,51,51);font-weight:bold">new</span> Exception($<span style="color:rgb(221,17,68)">"Type {_singletons[typeof(Interface)].ToString()}  is already registered for {typeof(Interface).ToString()}."</span>);
        }
    }
}

}

Create a new folder called ViewModels, add a new class to the folder called 'BaseViewModel' with the following code.  This class will implement INotifyPropertyChanged for our data bound properties in Xamarin.Forms and WPF.

using System.ComponentModel;
using System.Runtime.CompilerServices;

namespace XamFormsWpf.Core.ViewModels { public class BaseViewModel : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged = delegate { }; protected void RaisePropertyChanged([CallerMemberName] string propertyName = "") { PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); }

    <span style="color:rgb(51,51,51);font-weight:bold">protected</span> <span style="color:rgb(51,51,51);font-weight:bold">void</span> RaiseAllPropertiesChanged()
    {
        PropertyChanged(<span style="color:rgb(51,51,51);font-weight:bold">this</span>, null);
    }
}

}

Now add the ContactViewModel class to the ViewModels folder with the following code.  This is the class we will bind our forms to in WPF and Xamarin.Forms.  Notice that this class uses our IoC container to resolve the IHelloService at runtime.  The class also uses our Contact class as a backing object for our data bound contact properties.

using XamFormsWpf.Core.Data;
using XamFormsWpf.Core.Services;

namespace XamFormsWpf.Core.ViewModels { public class ContactViewModel : BaseViewModel { private Contact _contact; private IHelloService _helloService = SimpleIoC.Container.Resolve<IHelloService>();

    <span style="color:rgb(51,51,51);font-weight:bold">public</span> <span style="color:rgb(0,134,179)">string</span> Hello { get { <span style="color:rgb(51,51,51);font-weight:bold">return</span> _helloService.SayHello(); } }

    <span style="color:rgb(51,51,51);font-weight:bold">public</span> <span style="color:rgb(0,134,179)">string</span> ContactFirstName
    {
        get { <span style="color:rgb(51,51,51);font-weight:bold">return</span> _contact.FirstName; }
        <span style="color:rgb(0,134,179)">set</span> {
            _contact.FirstName = value;
            RaisePropertyChanged(nameof(ContactFirstName));
        }
    }
    <span style="color:rgb(51,51,51);font-weight:bold">public</span> <span style="color:rgb(0,134,179)">string</span> ContactLastName
    {
        get { <span style="color:rgb(51,51,51);font-weight:bold">return</span> _contact.LastName; }
        <span style="color:rgb(0,134,179)">set</span> {
            _contact.LastName = value;
            RaisePropertyChanged(nameof(ContactLastName));
        }
    }
    <span style="color:rgb(51,51,51);font-weight:bold">public</span> <span style="color:rgb(0,134,179)">string</span> ContactEmail
    {
        get { <span style="color:rgb(51,51,51);font-weight:bold">return</span> _contact.Email; }
        <span style="color:rgb(0,134,179)">set</span> {
            _contact.Email = value;
            RaisePropertyChanged(nameof(ContactEmail));
        }
    }

    <span style="color:rgb(51,51,51);font-weight:bold">public</span> ContactViewModel(Contact contact = null)
    {
        _contact = contact == null ? <span style="color:rgb(51,51,51);font-weight:bold">new</span> Contact() : contact;
        RaiseAllPropertiesChanged();
    }
}

}

This is everything we'll need in our Core PCL in order to run the example.  We still have a couple more steps in our View projects to wire up our Views with the View Models and Services, but we'll take care of that next.  Below is a screenshot of what your XamFormsWpf.Core project should look like in the solution window.



Add a WPF project to our solution to target Windows Desktop

Overview

Now it is time to add our WPF project to to the soluion.  In this step, we will add a standard WPF project, and have it reference the XamFormsWpf.Core PCL.

Steps

Right click the solution name and select Add > New Project.  Search for WPF and select WPF Application.  Make sure you select the C# template.  Name the project XamFormsWpf.WPF.


In the new WPF project, add a reference to the XamFormsWpf.Core (Portable) project.


Implementing IHelloService in each platform

Overview

Now we will create a HelloService class in the WPF and Forms projects that provides a custom implementation for WPF and Forms.  We will register this class in each project's App.cs using our SimpleIoC container that replaced DependencyService.

Steps

Create a new class called HelloService in the XamFormsWpf.Core.Forms project with the following code
using XamFormsWpf.Core.Services;

namespace XamFormsWpf { class HelloService : IHelloService { public string SayHello() { return "Hello, Xamarin Forms!"; } } }

Update the App.xaml.cs class in XamFormsWpf.Core.Forms to use our SimpleIoC container to register our HelloWorld service as the implementation.  Replace the App.xaml.cs code with the following code
using XamFormsWpf.Core.Services;
using Xamarin.Forms;

namespace XamFormsWpf { public partial class App : Application { public SimpleIoC IoC = SimpleIoC.Container; public App() { IoC.RegisterSingleton<IHelloService, HelloService>(); InitializeComponent(); MainPage = new MainPage(); }

    <span style="color:rgb(51,51,51);font-weight:bold">protected</span> override <span style="color:rgb(51,51,51);font-weight:bold">void</span> OnStart()
    {
        <span style="color:rgb(153,153,136);font-style:italic">// Handle when your app starts</span>
    }

    <span style="color:rgb(51,51,51);font-weight:bold">protected</span> override <span style="color:rgb(51,51,51);font-weight:bold">void</span> OnSleep()
    {
        <span style="color:rgb(153,153,136);font-style:italic">// Handle when your app sleeps</span>
    }

    <span style="color:rgb(51,51,51);font-weight:bold">protected</span> override <span style="color:rgb(51,51,51);font-weight:bold">void</span> OnResume()
    {
        <span style="color:rgb(153,153,136);font-style:italic">// Handle when your app resumes</span>
    }
}

}

Create a new class in XamFormsWpf.WPF called HelloService with the following code
using XamFormsWpf.Core.Services;

namespace XamFormsWpf.WPF { class HelloService : IHelloService { public string SayHello() { return "Hello, Windows Desktop!"; } } }

In XamFormsWpf.WPF, update the App.xaml.cs file with the following code.
using System.Windows;
using XamFormsWpf.Core.Services;

namespace XamFormsWpf.WPF { /// <summary> /// Interaction logic for App.xaml /// </summary> public partial class App : Application { SimpleIoC IoC = SimpleIoC.Container; protected override void OnStartup(StartupEventArgs e) { IoC.Register<IHelloService, HelloService>(); } } }


Creating some simple data binding in MainPage and MainWIndow

Overview

The final step is to make use of our ContactViewModel in our Forms and and WPF XAML views.  The Xamarin.Forms and WPF XAML definitions are not interchangeable, meaning we will need to use the different view definitions on each platform, but with our project structure, we can reuse all of our Core PCL content.

First we will update our MainPage.xaml file in our Forms PCL, then we'll move on to provide the same implementation in our WPF project.

Steps

Open the MainPage.xaml file in your XamFormsWpf.Core.Forms PCL and add the following code.

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:XamFormsWpf"
             x:Class="XamFormsWpf.MainPage">
  <StackLayout>
    <Label Text="{Binding Hello}" />
    <StackLayout Orientation="Horizontal">
      <Label Text="First Name" />
      <Entry Text="{Binding ContactFirstName, Mode=TwoWay}" />
    </StackLayout>
    <StackLayout Orientation="Horizontal">
      <Label Text="Last Name" />
      <Entry Text="{Binding ContactLastName, Mode=TwoWay}" />
    </StackLayout>
    <StackLayout Orientation="Horizontal">
      <Label Text="Email" />
      <Entry Text="{Binding ContactEmail, Mode=TwoWay}" />
    </StackLayout>
  </StackLayout>
</ContentPage>
Now open the MainPage.xaml.cs code-behind file and add the following code.  This will setup some simple controls, a sample Contact, and demonstrate data binding between the two.
using Xamarin.Forms;
using XamFormsWpf.Core.Data;
using XamFormsWpf.Core.ViewModels;

namespace XamFormsWpf { public partial class MainPage : ContentPage { readonly ContactViewModel _contactViewModel; public MainPage() { // Test contact var contact = new Contact { FirstName = "Jimmy", LastName = "Smith", Email = "[email protected]" };

        _contactViewModel = <span style="color:rgb(51,51,51);font-weight:bold">new</span> ContactViewModel(contact);
        BindingContext = _contactViewModel;
        InitializeComponent();
    }
}

}

The final step is to implement similar view controls in the WPF project.  In XamFormsWpf.WPF, open MainWindow.xaml and add the following code
<Window x:Class="XamFormsWpf.WPF.MainWindow"
        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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:XamFormsWpf.WPF"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <StackPanel>
            <TextBlock Text="{Binding Hello}" />
            <StackPanel Orientation="Horizontal">
                <TextBlock Text="First Name" />
                <TextBox Text="{Binding ContactFirstName, Mode=TwoWay}" />
            </StackPanel>
            <StackPanel Orientation="Horizontal">
                <TextBlock Text="Last Name" />
                <TextBox Text="{Binding ContactLastName, Mode=TwoWay}" />
            </StackPanel>
            <StackPanel Orientation="Horizontal">
                <TextBlock Text="Email" />
                <TextBox Text="{Binding ContactEmail, Mode=TwoWay}" />
            </StackPanel>
        </StackPanel>
    </Grid>
</Window>

Now open the MainWindow.xaml.cs code-behind file and add the following code.  This will setup some simple controls, a sample Contact, and demonstrate data binding between the two.
using System.Windows;
using XamFormsWpf.Core.Data;
using XamFormsWpf.Core.ViewModels;

namespace XamFormsWpf.WPF { /// <summary> /// Interaction logic for MainWindow.xaml /// </summary> public partial class MainWindow : Window { readonly ContactViewModel _contactViewModel; public MainWindow() { var contact = new Contact { FirstName = "Jimmy", LastName = "Smith", Email = "[email protected]" };

        _contactViewModel = <span style="color:rgb(51,51,51);font-weight:bold">new</span> ContactViewModel(contact);
        DataContext = _contactViewModel;

        InitializeComponent();
    }
}

}



Conclusion

That's all there is to it! You can now build and run the app by selecting a project as the default startup project.  Below are screenshots of the application running on Android and (of course) WPF.

Android:


WPF:

To summarize, in this tutorial, we've gone through the steps necessary to add support for a WPF project to a Xamarin.Forms solution.  We also created a very simple implementation as an example.  We covered the following topics:
  • Creating a new solution from the default Xamarin.Forms project template
  • Adding a Core PCL to our solution, to contain our common code
  • Add some common code to our Core PCL
  • Using a simple IoC container to replace Xamarin.Forms DependencyService class
  • Add a WPF project to our solution to target Windows Desktop
  • Implementing IHelloService in each platform
  • Creating some simple data binding in our MainPage and MainWindow views

As usual, styling was omitted from this tutorial, but now that you understand these core concepts, you can work with the existing solution to add some styling of your own.

I truly hope you found this tutorial to be both clear and informative, and please feel free to leave feedback.

xamformswpf's People

Contributors

c0d3name avatar

Watchers

James Cloos avatar  avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.