The best VPN 2023

The Best VPS 2023

The Best C# Book

Client portal project of JHRS development framework

Client portal project of JHRS development framework, In the previous article, I introduced the encapsulation of web api. In fact, I am doing a similar project, which can also be called a front-end and back-end project (the client uses WPF, and the server is web api). Call web api. It is really troublesome if you manually press the function yourself. The URL (or routing) backend of the web api development stage may change the address or add or delete parameters at any time. After encapsulation, the manual work is basically solved, then let’s look at the entry project.

Client portal project

portal
Client portal project of JHRS development framework

If you pull the source code on github , you can see that the JHRS.Shell library is the entry project of the WPF client, a shell program of the entire system, it will load the various subsystems after startup; using this divide-and-conquer method After dividing a large system into multiple subsystems, there must be an entry program to uniformly schedule and load each subsystem. Any function that needs to be implemented outside of each subsystem can be placed in the entry program for processing, such as automatic upgrade functions.

WPF client entry project

Client portal project of JHRS development framework
Client portal project of JHRS development framework

As you can see in the figure above, there are not many directories in the JHRS framework , but you have to understand that this is just a demo project. The real project is much more complicated, but the basic functions of this framework can already meet the needs of development. The function can be expanded by yourself; as long as you understand why you do this, I believe the apes can handle it. Client portal project of JHRS development framework.

In the Shell library, these things are mainly done

  1. App.config saves the configuration information of the entire system
  2. App.xaml can load external resources of the entire system
  3. ShellSwitcher encapsulates the function of closing and opening windows
  4. The Views root directory defines the main form, and the various areas are laid out in the main form
  5. In Login, there are login-related forms and pages, which are used to complete the login and enter the system
  6. Dialogs defines the modal windows that are used uniformly in the entire system, and the display of the message prompt box (the call implementation is encapsulated in the ViewModel base class, because the business needs various prompts in the ViewModel)
  7. In ViewModels are the background business logic of each page or window of the entry program
  8. The images directory contains some pictures that need to be used, you can also move them to external resources; the pictures can also be in svg format

Load external resources uniformly

Client portal project of JHRS development framework.

<prism:PrismApplication x:Class="JHRS.Shell.App" 
                        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
                        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
                        xmlns:local="clr-namespace:JHRS.Shell" 
                        xmlns:prism="http://prismlibrary.com/">
    <Application.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="/JHRS.Wpf;component/Style/TextBoxStyle.xaml" />
                <ResourceDictionary Source="/JHRS.Wpf;component/Style/ButtonStyle.xaml" />
                <ResourceDictionary Source="/JHRS.Wpf;component/Style/PageIcon.xaml" />
                <ResourceDictionary Source="/JHRS.Wpf;component/Style/ComboBox.xaml" />
                <ResourceDictionary Source="/JHRS.Wpf;component/Resources/ConverterResources.xaml" />

                <ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.Light.xaml" />
                <ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.Defaults.xaml" />
                <ResourceDictionary Source="pack://application:,,,/MaterialDesignColors;component/Themes/Recommended/Primary/MaterialDesignColor.DeepPurple.xaml" />
                <ResourceDictionary Source="pack://application:,,,/MaterialDesignColors;component/Themes/Recommended/Accent/MaterialDesignColor.Lime.xaml" />
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Application.Resources>
</prism:PrismApplication>

Client portal project of JHRS development framework

This is the standard operation of WPF to load external resources. For example, ConverterResources.xaml is the Converter (data converter) that WPF needs to use externally defined, and various subsystems can also be used. Client portal project of JHRS development framework.

Unified message prompt box and modal window

jhrs-dialogs
Client portal project of JHRS development framework

In the entry project, you can see in Views/Dialogs that Alert, Confirm and DialogPage are defined, and to implement these functions, you also need to register the dialog service in the startup class (App.xaml.cs) . Client portal project of JHRS development framework. The complete code is as follows:

using JHRS.Core.Modules;
using JHRS.Reflection;
using JHRS.Shell.ViewModels.Dialogs;
using JHRS.Shell.Views.Dialogs;
using JHRS.Shell.Views.Login;
using Prism.Ioc;
using Prism.Modularity;
using Prism.Regions;
using Prism.Services.Dialogs;
using Prism.Unity;
using System;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Threading;
using Unity;
namespace JHRS.Shell
{
    public partial class App : PrismApplication
  {
    protected override void OnStartup(StartupEventArgs e)
    {
      base.OnStartup(e);
      DispatcherUnhandledException += App_DispatcherUnhandledException;
      AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
      TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException;
    }
    protected override Window CreateShell()
    {
      return Container.Resolve<LoginWindow>();
    }
    protected override void RegisterTypes(IContainerRegistry containerRegistry)
    {
      containerRegistry.RegisterSingleton<PageManager>();
      containerRegistry.RegisterSingleton<UserControlManager>();
      Type[] pages = AppDomainAllAssemblyFinder.FindAll<Page>();
      var pageManager = containerRegistry.GetContainer().Resolve<PageManager>();
      Type[] array = pages;
      foreach (Type item in array)
      {
        containerRegistry.RegisterForNavigation(item, item.FullName);
        FunctionAttribute function = item.GetAttribute<FunctionAttribute>();
        if (function != null)
        {
          pageManager.Add(function.UniqueName, item);
        }
      }
      Type[] controls = AppDomainAllAssemblyFinder.FindAll<UserControl>();
      var controlManager = containerRegistry.GetContainer().Resolve<UserControlManager>();
      Type[] array2 = controls;
      foreach (Type item2 in array2)
      {
        containerRegistry.RegisterForNavigation(item2, item2.FullName);
        QueryLocatorAttribute locator = item2.GetAttribute<QueryLocatorAttribute>();
        if (locator != null)
        {
          controlManager.Add(item2.FullName, new ControlMapping
          {
            ControlType = item2,
            RegionName = locator.RegionName,
            TargetType = locator.Target
          }) ;
        }
      }
      containerRegistry.RegisterDialog<AlertDialog, AlertDialogViewModel>();
      containerRegistry.RegisterDialog<ConfirmDialog, ConfirmDialogViewModel>();
      containerRegistry.RegisterDialog<ErrorDialog, ErrorDialogViewModel>();
      containerRegistry.RegisterDialog<SuccessDialog, SuccessDialogViewModel>();
      containerRegistry.RegisterDialog<WarningDialog, WarningDialogViewModel>();
      containerRegistry.Register(typeof(IDialogWindow), typeof(Views.Dialogs.DialogWindow), "dialog");
      containerRegistry.RegisterDialog<CommonDialogPage, CommonDialogPageViewModel>();
    }
        protected override void ConfigureViewModelLocator()
        {
            base. ConfigureViewModelLocator () ;
        }
        /// <summary>
        /// Register system module
        /// </summary>
        /// <param name="moduleCatalog"></param>
        protected override void ConfigureModuleCatalog ( ModuleCatalog moduleCatalog ) 
        {
            var modules = AppDomainAllAssemblyFinder. FindAll < Module > () ;
            foreach (var item in modules)
            {
                moduleCatalog.AddModule(new ModuleInfo
                {
                    ModuleName = item.Name,
                    ModuleType = item.AssemblyQualifiedName,
                    InitializationMode = InitializationMode.OnDemand
                }) ;
            }
        }
        protected override void ConfigureRegionAdapterMappings(RegionAdapterMappings regionAdapterMappings)
        {
            base.ConfigureRegionAdapterMappings(regionAdapterMappings);
        }
        protected override ModuleCatalog CreateModuleCatalog ()
        {
            return new DirectoryModuleCatalog() { ModulePath = @".\Modules" };
        }
        /// <summary>
        /// Unified exception handling (non-UI thread does not catch exception handling events (for example, a child thread created by yourself))
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
        {
            Exception ex = e.ExceptionObject as Exception;
            if (ex != null)
            {
                MessageBox. Show ( $ "Program component error, reason: {ex.Message}" , "System prompt" , MessageBoxButton. OK , MessageBoxImage. Error ) ;
            }
        }
        /// <summary>
        /// Unified exception handling (the UI thread does not catch exception handling events)
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void App_DispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e)
        {
            Exception ex = e.Exception;
            MessageBox. Show ( $ "The program is running wrong, the reason: {ex.Message}-{ex.InnerException?.Message}" , "System prompt" , MessageBoxButton. OK , MessageBoxImage. Error ) ;
            e. Handled = true ;//Indicates that the exception has been handled and can continue to run
        }
        /// <summary>
        /// Unified exception handling (Task task exception)
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void TaskScheduler_UnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e)
        {
            Exception ex = e.Exception;
            MessageBox. Show ( $ "Error executing task, reason: {ex.Message}" , "System prompt" , MessageBoxButton. OK , MessageBoxImage. Error ) ;
        }
    }
}
Client portal project of JHRS development framework

Client portal project of JHRS development framework

Prism is used in this project, and the custom bullet frame needs to implement the IDialogAware interface. For more detailed content, you can refer to the source code or read this blog post about the Prism dialog box service of the .NET 5 WPF MVVM framework .

Main window

The JRHS framework was originally built to develop the HIS client. Let’s take a look at what the main window looks like. Of course, this is only a demonstration project, not a real HIS system. The HIS system in the real project is much more complicated than this, and the layout is also The traditional style, the top menu, and the Tab tab at the bottom display content. This style of layout is also popular for traditional additions, deletions, and changes.

jhrs-main
Client portal project of JHRS development framework

Xaml file of the main window

Client portal project of JHRS development framework.

<Window x:Class="JHRS.Shell.Views.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:cmmod="clr-namespace:JHRS.Core.Modules;assembly=JHRS.Core" 
        xmlns:b="http://schemas.microsoft.com/xaml/behaviors" 
        xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes" 
        xmlns:prism="http://prismlibrary.com/" 
        xmlns:local="clr-namespace:JHRS.Shell.Views" 
        x:Name="main" 
        prism:ViewModelLocator.AutoWireViewModel="True" 
        WindowStartupLocation="CenterScreen" Icon="/images/jhrs.ico" 
        Title= "The Jianghu Herbalist Management System [First published on: jhrs.com]" Height= "768" Width= "1200" >
    <Window.Resources>
        <ResourceDictionary>
            <Color x:Key="ForeReverseColor">#FFFFFF</Color>
            <Color x:Key="MainColor">green</Color>
            <SolidColorBrush x:Key="DeepForegroundBrush" Color="#e0e0e0" />
            <SolidColorBrush x:Key="ForeReverseBrush.OpacityTwo" Opacity="0.2" Color="{StaticResource ForeReverseColor}" />
            <SolidColorBrush x:Key="ForeReverseBrush.OpacitySix" Opacity="0.6" Color="{StaticResource ForeReverseColor}" />
            <SolidColorBrush x:Key="MainBrush" Color="{StaticResource MainColor}" />
            <SolidColorBrush x:Key="ForeReverseBrush" Color="{StaticResource ForeReverseColor}" />
        </ResourceDictionary>
    </Window.Resources>
    <b:Interaction.Triggers>
        <b:EventTrigger EventName="Closing">
            <b:InvokeCommandAction PassEventArgsToCommand="True" Command="{Binding CloseWindowCommand}" />
        </b:EventTrigger>
    </b:Interaction.Triggers>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="60" />
            <RowDefinition />
        </Grid.RowDefinitions>
        <Grid Background="#FF008000">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="auto" />
                <ColumnDefinition />
                <ColumnDefinition />
            </Grid.ColumnDefinitions>
            <Image Source="/images/mainlogo.png" HorizontalAlignment="Center" />
            <Menu Name="MainMenu" Grid.Column="1" Margin="0,0,220,0" ItemsSource="{Binding Path=MainMenuItemsSource}">
                <ItemsControl.ItemContainerStyleSelector>
                    <local:MenuStyleSelector />
                </ItemsControl.ItemContainerStyleSelector>
                <Menu.Resources>
                    <ResourceDictionary>
                        <Style x:Key="MainMenuStyle" TargetType="{x:Type MenuItem}">
                            <Setter Property="Foreground" Value="#FFFFFF" />
                            <Setter Property="VerticalContentAlignment" Value="Center" />
                            <Setter Property="Padding" Value="23,0" />
                            <Setter Property="FontSize" Value="14" />
                            <Setter Property="Template">
                                <Setter.Value>
                                    <ControlTemplate TargetType="{x:Type MenuItem}">
                                        <Grid>
                                            <Grid Name="MenuContentBorder" Background="{TemplateBinding Background}">
                                                <Rectangle Name="SelectedBackground" Fill="{StaticResource ForeReverseBrush.OpacityTwo}" />
                                                <ContentPresenter ContentSource="Header" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" Margin="{TemplateBinding Padding}" />
                                            </Grid>
                                            <Popup PopupAnimation="Slide" AllowsTransparency="True" IsOpen="{Binding Path=IsSubmenuOpen, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}">
                                                <Border Background="#FFFFFFFF" BorderBrush="{StaticResource DeepForegroundBrush}">
                                                    <ItemsPresenter />
                                                </Border>
                                            </Popup>
                                        </Grid>
                                        <ControlTemplate.Triggers>
                                            <Trigger Property="UIElement.IsMouseOver" Value="True">
                                                <Setter TargetName="SelectedBackground" Property="Visibility" Value="Visible" />
                                            </Trigger>
                                            <Trigger Property="UIElement.IsMouseOver" Value="False">
                                                <Setter TargetName="SelectedBackground" Property="Visibility" Value="Hidden" />
                                            </Trigger>
                                        </ControlTemplate.Triggers>
                                    </ControlTemplate>
                                </Setter.Value>
                            </Setter>
                            <Style.Triggers>
                                <Trigger Property="UIElement.IsMouseOver" Value="True">
                                    <Setter Property="Foreground" Value="{StaticResource ForeReverseBrush}" />
                                    <Setter Property="FontWeight" Value="Bold" />
                                </Trigger>
                            </Style.Triggers>
                        </Style>
                        <Style x:Key="{x:Type MenuItem}" TargetType="{x:Type MenuItem}">
                            <Style.Triggers>
                                <Trigger Property="UIElement.IsMouseOver" Value="true">
                                    <Setter Property="Foreground" Value="{StaticResource ForeReverseBrush}" />
                                </Trigger>
                            </Style.Triggers>
                            <Setter Property="Foreground" Value="{StaticResource ForeReverseBrush.OpacitySix}" />
                            <Setter Property="HorizontalContentAlignment" Value="Center" />
                            <Setter Property="VerticalContentAlignment" Value="Center" />
                            <Setter Property="FontWeight" Value="Normal" />
                            <Setter Property="FontSize" Value="12" />
                            <Setter Property="Template">
                                <Setter.Value>
                                    <ControlTemplate TargetType="{x:Type MenuItem}">
                                        <Border Background="{TemplateBinding Background}" BorderThickness="{TemplateBinding BorderThickness}" BorderBrush="{TemplateBinding BorderBrush}">
                                            <Grid Name="SelectedBackground">
                                                <ContentPresenter Margin="{TemplateBinding Padding}" ContentSource="Header" ContentTemplate="{TemplateBinding HeaderTemplate}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"/>
                                                <b:Interaction.Triggers>
                                                    <b:EventTrigger EventName="MouseLeftButtonUp" >
                                                        <b:InvokeCommandAction Command="{Binding DataContext.SelectedIntoPage,RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}" CommandParameter="{Binding .}"   />
                                                    </b:EventTrigger>
                                                </b:Interaction.Triggers>
                                            </Grid>
                                        </Border>
                                        <ControlTemplate.Triggers>
                                            <Trigger Property="UIElement.IsMouseOver" Value="True">
                                                <Setter TargetName="SelectedBackground" Property="Panel.Background" Value="{StaticResource ForeReverseBrush.OpacityTwo}" />
                                            </Trigger>
                                            <Trigger Property="UIElement.IsMouseOver" Value="False">
                                                <Setter TargetName="SelectedBackground" Property="Panel.Background" Value="{x:Null}" />
                                            </Trigger>
                                        </ControlTemplate.Triggers>
                                    </ControlTemplate>
                                </Setter.Value>
                            </Setter>
                            <Setter Property="MinWidth" Value="{Binding Path=ActualWidth, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=MenuItem}}" />
                            <Setter Property="Height" Value="36" />
                            <Setter Property="Padding" Value="10,0" />
                            <Setter Property="Background" Value="{StaticResource MainBrush}" />
                        </Style>
                        <HierarchicalDataTemplate x:Key="{DataTemplateKey {x:Type cmmod:MenuEntity}}" DataType="{x:Type cmmod:MenuEntity}" ItemsSource="{Binding Path=Children}">
                            <TextBlock Text="{Binding Path=Name}" />
                        </HierarchicalDataTemplate>
                    </ResourceDictionary>
                </Menu.Resources>
                <Menu.Style>
                    <Style TargetType="{x:Type Menu}">
                        <Setter Property="Background" Value="#00FFFFFF" />
                        <Setter Property="Template">
                            <Setter.Value>
                                <ControlTemplate TargetType="{x:Type Menu}">
                                    <ItemsPresenter Name="ItemsPresenter" />
                                </ControlTemplate>
                            </Setter.Value>
                        </Setter>
                        <Setter Property="ItemsPanel">
                            <Setter.Value>
                                <ItemsPanelTemplate>
                                    <StackPanel Orientation="Horizontal" />
                                </ItemsPanelTemplate>
                            </Setter.Value>
                        </Setter>
                    </Style>
                </Menu.Style>
            </Menu>
            <ItemsControl HorizontalAlignment="Right" Grid.Column="2">
                <TextBlock FontSize="24" Foreground="#FFFFFFFF" Margin="2" HorizontalAlignment="Right" Text="{Binding CurrentUser.HospitalName}" />
                <TextBlock FontSize="14" Foreground="#FFFFFFFF" Margin="1" Text="{Binding CurrentUser.ShowText}" />
            </ItemsControl>
        </Grid>
        <TabControl Name="MainTabPanel" BorderBrush="#FFDCDCDC" Grid.Row="1" Margin="5" BorderThickness="0">
            <TabControl.Resources>
                <ResourceDictionary>
                    <Style x:Key="{x:Type TabItem}" TargetType="{x:Type TabItem}">
                        <Setter Property="Template">
                            <Setter.Value>
                                <ControlTemplate TargetType="{x:Type TabItem}">
                                    <Border Name="Border" Height="30" BorderThickness="1,1,1,0" BorderBrush="#FFDCDCDC" CornerRadius="4,4,0,0" Margin="3,0">
                                        <ContentPresenter Name="ContentSite" VerticalAlignment="Center" HorizontalAlignment="Center" ContentSource="Header" Margin="20,2" />
                                    </Border>
                                    <ControlTemplate.Triggers>
                                        <Trigger Property="TabItem.IsSelected" Value="True">
                                            <Setter TargetName="Border" Property="Border.Background" Value="#FF87CEFA" />
                                        </Trigger>
                                        <Trigger Property="TabItem.IsSelected" Value="False">
                                            <Setter TargetName="Border" Property="Border.Background" Value="#FFF8F8FF" />
                                        </Trigger>
                                    </ControlTemplate.Triggers>
                                </ControlTemplate>
                            </Setter.Value>
                        </Setter>
                    </Style>
                </ResourceDictionary>
            </TabControl.Resources>
        </TabControl>
    </Grid>
</Window>

Client portal project of JHRS development framework

If you want to make it more exquisite, you have to work hard by the artist. This demo form is so ugly, I am embarrassed to take it out, but it is not easy to explain if it is not taken out to take a look. Client portal project of JHRS development framework.

C# code behind the main window

Client portal project of JHRS development framework

using CommonServiceLocator;
using JHRS.Core.Events;
using JHRS.Core.Modules;
using Prism.Events;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace JHRS.Shell.Views
{
    /// <summary>
    /// Interactive logic of MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            PageEvent pageEvent = ServiceLocator.Current.TryResolve<IEventAggregator>().GetEvent<PageEvent>();
            pageEvent.Subscribe((p) =>
            {
                MenuEntity menu = p Menu ;
                AddPage(menu.Name, p.Page);
            }) ;
        }
        private void AddPage(string name, Page page)
        {
            TabItem tabItem = MainTabPanel.Items.OfType<TabItem>().FirstOrDefault(item => item.Header.ToString() == name);
            if (tabItem == null)
            {
                tabItem = new TabItem () 
                {
                    Header = name,
                } ;
                var pageFrame = new Frame () ; 
                pageFrame.Focusable = false;
                pageFrame.BorderThickness = new Thickness(0);
                pageFrame.Margin = new Thickness(20);
                pageFrame.Navigate(page);
                tabItem.Content = pageFrame;
                MainTabPanel.Items.Add(tabItem);
            }
            MainTabPanel.SelectedItem = tabItem;
        }
    }
    public static class ControlHelper
    {
        public static T FindVisualParent<T>(DependencyObject sender) where T : DependencyObject
        {
            do
            {
                sender = VisualTreeHelper.GetParent(sender);
            }
            while (sender != null && !(sender is T));
            return sender as T;
        }
    }
    /// <summary>
    /// Main menu style selector
    /// </summary>
    public class MenuStyleSelector : StyleSelector
    {
        public override Style SelectStyle(object item, DependencyObject container)
        {
            MenuEntity functionItem = item as MenuEntity;
            if (functionItem.IsGroup)
                return ControlHelper.FindVisualParent<Menu>(container).Resources["MainMenuStyle"] as Style;
            else
                return null;
        }
    }
}

Client portal project of JHRS development framework

Write at the end

So far, I have basically finished introducing the entry project. I think I need to confess. If you don’t understand, you can pull the source code on github and compile it to see the effect. Generally speaking, it plays a coordinating role, unifying the auxiliary functions, without the need for the various subsystems or their own separate realization. Client portal project of JHRS development framework.

The next article will introduce how the various subsystems of the JHRS development framework are integrated .

Related reading in this series

  1. WPF enterprise development framework building guide, 2020 from entry to give up
  2. Introduction JHRS WPF development framework basic library
  3. JHRS WPF framework reference library introduction
  4. How does JHRS WPF framework call Web API
  5. Client portal project of JHRS development framework
  6. How to integrate the various subsystems of the JHRS development framework
  7. How to design a reasonable ViewModel base class in JHRS development framework
  8. Encapsulation of public component user control of JHRS development framework
  9. Some principles of cataloging documents followed by the recommendations of the JHRS development framework
  10. WPF data verification of JHRS development framework
  11. The solution of JHRS development framework’s ViewModel mutual parameter transfer and pop-up frame return parameter
  12. JHRS development framework of stepping on the hole (final chapter)

Leave a Comment