使用 DotNet CLI 创建自定义的 WPF 项目模板-LMLPHP

描述

当我们安装完 DotNetCore 3.0 版本的 SDK 后,我们就可以创建基于 DotNetCore 的 WPF 项目模板,通过如下 CLI 可以方便快捷的创建并运行我们的项目:

dotnet new wpf -n WpfApp
cd WpfApp
dotnet restore
dotnet run

做过 WPF 开发的朋友都知道,这个项目模板肯定不符合我们的预期,我们希望我们的项目模板能够加入 MVVM 的默认代码段,并且能够和 DotNetCore 紧密合作,这样岂不是更加方便了吗? 所以本文使用 MVVM 的一种实现 MvvmLightStd10 来教大家如何创建一个我们理想的项目模板。

操作

首先,我们基于 DotNetCore 3.0 创建一个原始的 WPF 项目模板,然后引用如下库:

  • Microsoft.Extensions.DependencyInjection
  • MvvmLightLibsStd10

可通过执行 cli 命令进行安装

dotnet add package Microsoft.Extensions.DependencyInjection
dotnet add package MvvmLightLibsStd10

然后,尝试修改我们的这个项目,把它改成我们以后期望创建的项目模板的样子。可以参考我的如下修改:

项目结构如下图所示:

使用 DotNet CLI 创建自定义的 WPF 项目模板-LMLPHP

其中,src\Models\DataItem.cs 的示例代码如下所示:

using System;
using System.Collections.Generic;
using System.Text;

namespace WpfApp.Models
{
    public class DataItem
    {
        public string Title { get; private set; }

        public DataItem(string title)
        {
            Title = title;
        }
    }
}

src\Models\IDataService.cs 的示例代码如下所示:

using System;
using System.Collections.Generic;
using System.Text;

namespace WpfApp.Models
{
    public interface IDataService
    {
        void GetData(Action<DataItem, Exception> callback);
    }
}

src\Models\DataService.cs 的示例代码如下所示:

using System;
using System.Collections.Generic;
using System.Text;

namespace WpfApp.Models
{
    public class DataService : IDataService
    {
        public void GetData(Action<DataItem, Exception> callback)
        {
            var item = new DataItem("Hello .NET Core!");
            callback(item, null);
        }
    }
}

src\ViewModels\MainViewModel.cs 的示例代码如下所示:

using GalaSoft.MvvmLight;
using WpfApp.Models;

namespace WpfApp.ViewModels
{
    public class MainViewModel : ViewModelBase
    {
        private readonly IDataService _dataService;

        private string _welcomeTitle;
        public string WelcomeTitle
        {
            get { return _welcomeTitle; }
            set { Set(ref _welcomeTitle, value); }
        }

        public MainViewModel(IDataService dataService)
        {
            _dataService = dataService;
              _dataService.GetData(
                (item, error) =>
                {
                    if (error != null)
                    {
                        return;
                    }

                    WelcomeTitle = item.Title;
                });
        }
    }
}

src\Views\MainView.xaml 的示例代码如下所示:

<Window
    x:Class="WpfApp.Views.MainView"
    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:local="clr-namespace:WpfApp"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    Title="MainWindow"
    Width="800"
    Height="450"
    mc:Ignorable="d">
    <Grid>
        <Label
            HorizontalAlignment="Center"
            VerticalAlignment="Center"
            Content="{Binding WelcomeTitle}"
            FontSize="40" />
    </Grid>
</Window>

src\Views\MainView.xaml.cs 的示例代码如下所示:

using System.Windows;
using WpfApp.ViewModels;

namespace WpfApp.Views
{
    public partial class MainView : Window
    {
        public MainView(MainViewModel vm)
        {
            InitializeComponent();
            this.DataContext = vm;
        }
    }
}

src\App.xaml 的示例代码如下所示:

<Application
    x:Class="WpfApp.App"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:WpfApp" />

src\App.xaml.cs 的示例代码如下所示:

using Microsoft.Extensions.DependencyInjection;
using System.Windows;
using WpfApp.Models;
using WpfApp.ViewModels;
using WpfApp.Views;

namespace WpfApp
{
    public partial class App : Application
    {
        public ServiceProvider ServiceProvider { get; private set; }

        protected override void OnStartup(StartupEventArgs e)
        {
            base.OnStartup(e);

            var serviceCollection = new ServiceCollection();
            ConfigureServices(serviceCollection);

            ServiceProvider = serviceCollection.BuildServiceProvider();

            var mainWindow = ServiceProvider.GetRequiredService<MainView>();
            mainWindow.Show();
        }

        private void ConfigureServices(ServiceCollection services)
        {
            services.AddTransient<MainView>();
            services.AddTransient<MainViewModel>();

            services.AddScoped<IDataService, DataService>();
        }
    }
}

修改完毕后尝试编译运行我们的项目,确保可以正常编译运行。

之后,在我们的项目根目录 src 下新建一个 .template.config 文件夹,然后在里面新建一个 template.json 文件,进行如下示例配置:

{
    "$schema": "http://json.schemastore.org/template",
    "author": "hippiezhou <hippiezhou@outlook.com>",
    "classifications": ["wpf", "mvvmlight", "Dependency Injection"],
    "name": "wpf mvvmlight: use dotnetcore to create wpf with mvvmlight.",
    "tags": {
        "language": "C#",
        "type": "project"
    },
    "identity": "wpf.mvvmlight",
    "shortName": "wpf-mvvmlight",
    "sourceName": "wpf.mvvmlight",
    "preferNameDirectory": true
}

最后,打开我们的终端,将目录切换至当前项目目录下(就是 .template.config 所在的目录),然后执行下述安装操作

dotnet new -i C:\Users\hippieZhou\Desktop\helloworld\wpfapp

此时,我们的项目模板会被打包到 DotNetCore 的 CLI 中,如下图所示:

使用 DotNet CLI 创建自定义的 WPF 项目模板-LMLPHP

同时,在 C:\Users\hippieZhou.templateengine\dotnetcli\v3.0.100-preview3-010431 目录下的以 templatecache.json 结尾的 JSON 文件内容也会发生修改,会在 TemplateInfo 结点下新增一个如下的节点内容:

 {
      "ConfigMountPointId": "f3861181-7a43-4fc5-ab1c-12d95e734c0a",
      "Author": "hippiezhou <hippiezhou@outlook.com>",
      "Classifications": [
        "wpf",
        "mvvmlight",
        "Dependency Injection"
      ],
      "DefaultName": null,
      "Description": "",
      "Identity": "wpf.mvvmlight",
      "GeneratorId": "0c434df7-e2cb-4dee-b216-d7c58c8eb4b3",
      "GroupIdentity": "",
      "Precedence": 0,
      "Name": "wpf mvvmlight: use dotnetcore to create wpf with mvvmlight.",
      "ShortNameList": [
        "wpf-mvvmlight"
      ],
      "Tags": {
        "language": {
          "Description": null,
          "ChoicesAndDescriptions": {
            "C#": ""
          },
          "DefaultValue": "C#"
        },
        "type": {
          "Description": null,
          "ChoicesAndDescriptions": {
            "project": ""
          },
          "DefaultValue": "project"
        }
      },
      "CacheParameters": {
        "name": {
          "DataType": "string",
          "DefaultValue": null,
          "Description": "The default name symbol"
        }
      },
      "ConfigPlace": "/.template.config/template.json",
      "LocaleConfigMountPointId": "00000000-0000-0000-0000-000000000000",
      "LocaleConfigPlace": null,
      "HostConfigMountPointId": "00000000-0000-0000-0000-000000000000",
      "HostConfigPlace": null,
      "ThirdPartyNotices": null,
      "BaselineInfo": {},
      "HasScriptRunningPostActions": false,
      "ConfigTimestampUtc": null
},

我们可以使用下述操作进行测试一下:

# 使用我们自定义的项目模板,创建 wpf 项目
dotnet new wpf-mvvmlight -n test

cd test

dotnet restore

dotnet run

如果不出意外的话,我们就可以看到这个项目的代码段和我们自定义的模板代码段是一样的。

如果卸载我们的项目模板可以使用如下命令:

dotnet new -u C:\Users\hippieZhou\Desktop\helloworld\wpfapp

关于如何将我们的自定义模板可以上传到 NuGet 供别人下载使用,这里就不做介绍了,具体操作可参考园里介绍如何在 DotNetCore MVC 中打造自己的项目模板方法是一样的。我在本文中的创建的代码模板也不会提交上去,还是等着 MVVMLight 的原作者 Laurent Bugnion 来操刀会好一些。

总结

本文介绍了如何通过 DotNet CLI 来创建自定义的 WPF 项目模板。在实际的使用过程中,CLI 的功能和支持的参数会更多,所以感兴趣的朋友可以自行研究。

相关参考

04-15 12:42