[feat]增加WinForm客户端例程,主要为了测试同步调用异步方法等场景大石头 authored at 2024-11-23 22:58:12
diff --git a/NewLife.Remoting.sln b/NewLife.Remoting.sln
index 3bb1f98..f06905b 100644
--- a/NewLife.Remoting.sln
+++ b/NewLife.Remoting.sln
@@ -30,6 +30,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IoTZero", "Samples\IoTZero\
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ZeroServer", "Samples\ZeroServer\ZeroServer.csproj", "{95AA14E4-6771-487B-886E-251C785624E6}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Zero.Desktop", "ZeroClient\Zero.Desktop.csproj", "{C9E9BE11-9B06-483D-86C1-3C11D1A5907A}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -68,6 +70,10 @@ Global
{95AA14E4-6771-487B-886E-251C785624E6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{95AA14E4-6771-487B-886E-251C785624E6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{95AA14E4-6771-487B-886E-251C785624E6}.Release|Any CPU.Build.0 = Release|Any CPU
+ {C9E9BE11-9B06-483D-86C1-3C11D1A5907A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {C9E9BE11-9B06-483D-86C1-3C11D1A5907A}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C9E9BE11-9B06-483D-86C1-3C11D1A5907A}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {C9E9BE11-9B06-483D-86C1-3C11D1A5907A}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -77,6 +83,7 @@ Global
{8351AC88-1955-48AD-B6F4-26959E1A0C4C} = {8DB8495C-1EB6-41AE-83DD-55DBBB0E6FA2}
{1DEAF969-F093-4D99-80C9-1CEA3BD06ABB} = {8DB8495C-1EB6-41AE-83DD-55DBBB0E6FA2}
{95AA14E4-6771-487B-886E-251C785624E6} = {8DB8495C-1EB6-41AE-83DD-55DBBB0E6FA2}
+ {C9E9BE11-9B06-483D-86C1-3C11D1A5907A} = {8DB8495C-1EB6-41AE-83DD-55DBBB0E6FA2}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {323831A1-A95B-40AB-B9AD-36A0BC10C2CB}
diff --git a/NewLife.Remoting/NewLife.Remoting.csproj b/NewLife.Remoting/NewLife.Remoting.csproj
index ce8f1d1..b51f91a 100644
--- a/NewLife.Remoting/NewLife.Remoting.csproj
+++ b/NewLife.Remoting/NewLife.Remoting.csproj
@@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
- <TargetFrameworks>net45;net461;netstandard2.0;netstandard2.1;net5.0;net6.0;net7.0;net8.0</TargetFrameworks>
+ <TargetFrameworks>net45;net461;netstandard2.0;netstandard2.1;net5.0;net6.0;net7.0;net8.0;net9.0</TargetFrameworks>
<AssemblyTitle>协议通信库</AssemblyTitle>
<Description>提供高性能RPC客户端服务端,提供Http/WebSocket客户端服务端,提供应用级客户端</Description>
<Company>新生命开发团队</Company>
diff --git a/ZeroClient/app.manifest b/ZeroClient/app.manifest
new file mode 100644
index 0000000..2255841
--- /dev/null
+++ b/ZeroClient/app.manifest
@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="utf-8"?>
+<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
+ <assemblyIdentity version="1.0.0.0" name="MyApplication.app"/>
+ <trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
+ <security>
+ <requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
+ <!-- UAC 清单选项
+ 如果想要更改 Windows 用户帐户控制级别,请使用
+ 以下节点之一替换 requestedExecutionLevel 节点。
+
+ <requestedExecutionLevel level="asInvoker" uiAccess="false" />
+ <requestedExecutionLevel level="requireAdministrator" uiAccess="false" />
+ <requestedExecutionLevel level="highestAvailable" uiAccess="false" />
+
+ 指定 requestedExecutionLevel 元素将禁用文件和注册表虚拟化。
+ 如果你的应用程序需要此虚拟化来实现向后兼容性,则移除此
+ 元素。
+ -->
+ <requestedExecutionLevel level="asInvoker" uiAccess="false" />
+ </requestedPrivileges>
+ </security>
+ </trustInfo>
+
+ <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
+ <application>
+ <!-- 设计此应用程序与其一起工作且已针对此应用程序进行测试的
+ Windows 版本的列表。取消评论适当的元素,
+ Windows 将自动选择最兼容的环境。 -->
+
+ <!-- Windows Vista -->
+ <supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}" />
+
+ <!-- Windows 7 -->
+ <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}" />
+
+ <!-- Windows 8 -->
+ <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" />
+
+ <!-- Windows 8.1 -->
+ <supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" />
+
+ <!-- Windows 10 -->
+ <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
+
+ </application>
+ </compatibility>
+
+ <!-- 指示该应用程序可感知 DPI 且 Windows 在 DPI 较高时将不会对其进行
+ 自动缩放。Windows Presentation Foundation (WPF)应用程序自动感知 DPI,无需
+ 选择加入。选择加入此设置的 Windows 窗体应用程序(面向 .NET Framework 4.6)还应
+ 在其 app.config 中将 "EnableWindowsFormsHighDpiAutoResizing" 设置设置为 "true"。
+
+ 将应用程序设为感知长路径。请参阅 https://docs.microsoft.com/windows/win32/fileio/maximum-file-path-limitation -->
+ <!--
+ <application xmlns="urn:schemas-microsoft-com:asm.v3">
+ <windowsSettings>
+ <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>
+ <longPathAware xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">true</longPathAware>
+ </windowsSettings>
+ </application>
+ -->
+
+ <!-- 启用 Windows 公共控件和对话框的主题(Windows XP 和更高版本) -->
+ <!--
+ <dependency>
+ <dependentAssembly>
+ <assemblyIdentity
+ type="win32"
+ name="Microsoft.Windows.Common-Controls"
+ version="6.0.0.0"
+ processorArchitecture="*"
+ publicKeyToken="6595b64144ccf1df"
+ language="*"
+ />
+ </dependentAssembly>
+ </dependency>
+ -->
+
+</assembly>
diff --git a/ZeroClient/appsettings.json b/ZeroClient/appsettings.json
new file mode 100644
index 0000000..368e81f
--- /dev/null
+++ b/ZeroClient/appsettings.json
@@ -0,0 +1,24 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft": "Warning",
+ "Microsoft.Hosting.Lifetime": "Information"
+ }
+ },
+ "AllowedHosts": "*",
+ //"StarServer": "http://s.newlifex.com:6600",
+ //"RedisCache": "server=127.0.0.1:6379;password=;db=3",
+ //"RedisQueue": "server=127.0.0.1:6379;password=;db=5",
+ "ConnectionStrings": {
+ "Membership": "Data Source=..\\Data\\Membership.db;Provider=SQLite",
+ "Log": "Data Source=..\\Data\\Log.db;Provider=SQLite",
+ "Zero": "Data Source=..\\Data\\Zero.db;Provider=SQLite"
+
+ // 各种数据库连接字符串模版,连接名Zero对应Zero.Data/Projects/Model.xml中的ConnName
+ //"Zero": "Server=.;Port=3306;Database=zero;Uid=root;Pwd=root;Provider=MySql",
+ //"Zero": "Data Source=.;Initial Catalog=zero;user=sa;password=sa;Provider=SqlServer",
+ //"Zero": "Server=.;Database=zero;Uid=root;Pwd=root;Provider=PostgreSql",
+ //"Zero": "Data Source=Tcp://127.0.0.1/ORCL;User Id=scott;Password=tiger;Provider=Oracle"
+ }
+}
diff --git a/ZeroClient/ClientSetting.cs b/ZeroClient/ClientSetting.cs
new file mode 100644
index 0000000..7959ffb
--- /dev/null
+++ b/ZeroClient/ClientSetting.cs
@@ -0,0 +1,37 @@
+using System.ComponentModel;
+using NewLife;
+using NewLife.Configuration;
+using NewLife.Remoting.Clients;
+
+namespace Zero.Desktop;
+
+[Config("ClientSetting")]
+public class ClientSetting : Config<ClientSetting>, IClientSetting
+{
+ #region 属性
+ /// <summary>语音提示。默认true</summary>
+ [Description("语音提示。默认true")]
+ public Boolean SpeechTip { get; set; } = true;
+
+ /// <summary>证书</summary>
+ [Description("证书")]
+ public String Code { get; set; }
+
+ /// <summary>密钥</summary>
+ [Description("密钥")]
+ public String Secret { get; set; }
+
+ /// <summary>服务地址端口。默认为空,子网内自动发现</summary>
+ [Description("服务地址端口。默认为空,子网内自动发现")]
+ public String Server { get; set; } = "";
+ #endregion
+
+ #region 加载/保存
+ protected override void OnLoaded()
+ {
+ if (Server.IsNullOrEmpty()) Server = "http://s.newlifex.com:6600";
+
+ base.OnLoaded();
+ }
+ #endregion
+}
\ No newline at end of file
diff --git a/ZeroClient/FrmMain.cs b/ZeroClient/FrmMain.cs
new file mode 100644
index 0000000..de453b7
--- /dev/null
+++ b/ZeroClient/FrmMain.cs
@@ -0,0 +1,139 @@
+using System.Reflection;
+using NewLife;
+using NewLife.Log;
+using NewLife.Reflection;
+using NewLife.Remoting;
+using NewLife.Threading;
+
+namespace Zero.Desktop;
+
+public partial class FrmMain : Form
+{
+ public FrmMain()
+ {
+ InitializeComponent();
+ }
+
+ private void FrmMain_Load(Object sender, EventArgs e)
+ {
+ var asm = AssemblyX.Create(Assembly.GetExecutingAssembly());
+ Text = String.Format("{2} v{0} {1:HH:mm:ss}", asm.FileVersion, asm.Compile, Text);
+
+ richTextBox1.UseWinFormControl();
+
+ _timer = new TimerX(OnBindConn, null, 1_000, 3_000);
+ }
+
+ private TimerX _timer;
+ private ApiClient _client;
+ private String _lastConns;
+ private void OnBindConn(Object state)
+ {
+ //var keys = DAL.ConnStrs.Keys;
+ //var ks = keys.Join(",");
+ //if (ks == _lastConns) return;
+ //_lastConns = ks;
+
+ //cbConns.DataSource = keys;
+ }
+
+ private void btnOpen_Click(Object sender, EventArgs e)
+ {
+ var server = txtServer.Text;
+ if (server.IsNullOrEmpty()) return;
+
+ var btn = sender as Button;
+ var btn2 = btnOpenAsync;
+ if (btn.Text == "打开")
+ {
+ var client = new ApiClient(server)
+ {
+ Log = XTrace.Log,
+ EncoderLog = XTrace.Log,
+ SocketLog = XTrace.Log
+ };
+ client.Open();
+
+ var rs = client.Invoke<String[]>("api/all", null);
+ cbApi.DataSource = rs;
+
+ txtServer.Enabled = false;
+ groupBox2.Enabled = true;
+ btn.Text = "关闭";
+ btn2.Text = "异步关闭";
+
+ _client = client;
+ }
+ else
+ {
+ _client.Close(btn.Text);
+
+ txtServer.Enabled = true;
+ groupBox2.Enabled = false;
+ btn.Text = "打开";
+ btn2.Text = "异步打开";
+ }
+ }
+
+ private async void btnAsyncOpen_Click(object sender, EventArgs e)
+ {
+ var server = txtServer.Text;
+ if (server.IsNullOrEmpty()) return;
+
+ var btn = btnOpen;
+ var btn2 = sender as Button;
+ if (btn2.Text == "异步打开")
+ {
+ var client = new ApiClient(server)
+ {
+ Log = XTrace.Log,
+ EncoderLog = XTrace.Log,
+ SocketLog = XTrace.Log
+ };
+ client.Open();
+
+ var rs = await client.InvokeAsync<String[]>("api/all", null);
+ cbApi.DataSource = rs;
+
+ txtServer.Enabled = false;
+ groupBox2.Enabled = true;
+ btn.Text = "关闭";
+ btn2.Text = "异步关闭";
+
+ _client = client;
+ }
+ else
+ {
+ _client.Close(btn.Text);
+
+ txtServer.Enabled = true;
+ groupBox2.Enabled = false;
+ btn.Text = "打开";
+ btn2.Text = "异步打开";
+ }
+ }
+
+ private void listBox1_SelectedIndexChanged(Object sender, EventArgs e)
+ {
+ //var table = listBox1.SelectedItem as IDataTable;
+ //if (table == null) return;
+
+ //var sql = $"select * from {table.TableName}";
+ //var ds = _dal.Select(new SelectBuilder(sql), 0, 1000);
+
+ //dataGridView1.DataSource = ds.Tables[0];
+ //dataGridView1.Refresh();
+ }
+
+ private void btnCall_Click(object sender, EventArgs e)
+ {
+ var act = cbApi.Text.Substring(" ", "(");
+ var rs = _client.Invoke<String>(act, null);
+ }
+
+ private async void btnCallAsync_Click(object sender, EventArgs e)
+ {
+ var act = cbApi.Text.Substring(" ", "(");
+ var rs = await _client.InvokeAsync<String>(act, null);
+ }
+}
\ No newline at end of file
diff --git a/ZeroClient/FrmMain.Designer.cs b/ZeroClient/FrmMain.Designer.cs
new file mode 100644
index 0000000..cfda26e
--- /dev/null
+++ b/ZeroClient/FrmMain.Designer.cs
@@ -0,0 +1,238 @@
+namespace Zero.Desktop
+{
+ partial class FrmMain
+ {
+ /// <summary>
+ /// Required designer variable.
+ /// </summary>
+ private System.ComponentModel.IContainer components = null;
+
+ /// <summary>
+ /// Clean up any resources being used.
+ /// </summary>
+ /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing && (components != null))
+ {
+ components.Dispose();
+ }
+ base.Dispose(disposing);
+ }
+
+ #region Windows Form Designer generated code
+
+ /// <summary>
+ /// Required method for Designer support - do not modify
+ /// the contents of this method with the code editor.
+ /// </summary>
+ private void InitializeComponent()
+ {
+ groupBox1 = new GroupBox();
+ btnOpenAsync = new Button();
+ txtServer = new TextBox();
+ btnOpen = new Button();
+ label1 = new Label();
+ groupBox2 = new GroupBox();
+ textBox2 = new TextBox();
+ label3 = new Label();
+ btnCall = new Button();
+ cbApi = new ComboBox();
+ label2 = new Label();
+ groupBox3 = new GroupBox();
+ richTextBox1 = new RichTextBox();
+ btnCallAsync = new Button();
+ groupBox1.SuspendLayout();
+ groupBox2.SuspendLayout();
+ groupBox3.SuspendLayout();
+ SuspendLayout();
+ //
+ // groupBox1
+ //
+ groupBox1.Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right;
+ groupBox1.Controls.Add(btnOpenAsync);
+ groupBox1.Controls.Add(txtServer);
+ groupBox1.Controls.Add(btnOpen);
+ groupBox1.Controls.Add(label1);
+ groupBox1.Location = new Point(11, 10);
+ groupBox1.Margin = new Padding(3, 2, 3, 2);
+ groupBox1.Name = "groupBox1";
+ groupBox1.Padding = new Padding(3, 2, 3, 2);
+ groupBox1.Size = new Size(1134, 75);
+ groupBox1.TabIndex = 0;
+ groupBox1.TabStop = false;
+ groupBox1.Text = "数据库连接";
+ //
+ // btnOpenAsync
+ //
+ btnOpenAsync.Location = new Point(630, 19);
+ btnOpenAsync.Margin = new Padding(3, 2, 3, 2);
+ btnOpenAsync.Name = "btnOpenAsync";
+ btnOpenAsync.Size = new Size(106, 45);
+ btnOpenAsync.TabIndex = 4;
+ btnOpenAsync.Text = "异步打开";
+ btnOpenAsync.UseVisualStyleBackColor = true;
+ btnOpenAsync.Click += btnAsyncOpen_Click;
+ //
+ // txtServer
+ //
+ txtServer.Location = new Point(96, 28);
+ txtServer.Name = "txtServer";
+ txtServer.Size = new Size(346, 26);
+ txtServer.TabIndex = 3;
+ txtServer.Text = "tcp://127.0.0.1:5500";
+ //
+ // btnOpen
+ //
+ btnOpen.Location = new Point(489, 19);
+ btnOpen.Margin = new Padding(3, 2, 3, 2);
+ btnOpen.Name = "btnOpen";
+ btnOpen.Size = new Size(106, 45);
+ btnOpen.TabIndex = 2;
+ btnOpen.Text = "打开";
+ btnOpen.UseVisualStyleBackColor = true;
+ btnOpen.Click += btnOpen_Click;
+ //
+ // label1
+ //
+ label1.AutoSize = true;
+ label1.Location = new Point(19, 31);
+ label1.Name = "label1";
+ label1.Size = new Size(60, 20);
+ label1.TabIndex = 1;
+ label1.Text = "连接:";
+ //
+ // groupBox2
+ //
+ groupBox2.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right;
+ groupBox2.Controls.Add(btnCallAsync);
+ groupBox2.Controls.Add(textBox2);
+ groupBox2.Controls.Add(label3);
+ groupBox2.Controls.Add(btnCall);
+ groupBox2.Controls.Add(cbApi);
+ groupBox2.Controls.Add(label2);
+ groupBox2.Enabled = false;
+ groupBox2.Location = new Point(11, 90);
+ groupBox2.Margin = new Padding(3, 2, 3, 2);
+ groupBox2.Name = "groupBox2";
+ groupBox2.Padding = new Padding(3, 2, 3, 2);
+ groupBox2.Size = new Size(1134, 202);
+ groupBox2.TabIndex = 1;
+ groupBox2.TabStop = false;
+ groupBox2.Text = "内容区";
+ //
+ // textBox2
+ //
+ textBox2.Location = new Point(96, 79);
+ textBox2.Name = "textBox2";
+ textBox2.Size = new Size(346, 26);
+ textBox2.TabIndex = 4;
+ //
+ // label3
+ //
+ label3.AutoSize = true;
+ label3.Location = new Point(19, 82);
+ label3.Name = "label3";
+ label3.Size = new Size(69, 20);
+ label3.TabIndex = 3;
+ label3.Text = "参数1:";
+ //
+ // btnCall
+ //
+ btnCall.Location = new Point(489, 67);
+ btnCall.Name = "btnCall";
+ btnCall.Size = new Size(106, 45);
+ btnCall.TabIndex = 2;
+ btnCall.Text = "调用";
+ btnCall.UseVisualStyleBackColor = true;
+ btnCall.Click += btnCall_Click;
+ //
+ // cbApi
+ //
+ cbApi.FormattingEnabled = true;
+ cbApi.Location = new Point(96, 35);
+ cbApi.Name = "cbApi";
+ cbApi.Size = new Size(346, 28);
+ cbApi.TabIndex = 1;
+ //
+ // label2
+ //
+ label2.AutoSize = true;
+ label2.Location = new Point(19, 38);
+ label2.Name = "label2";
+ label2.Size = new Size(60, 20);
+ label2.TabIndex = 0;
+ label2.Text = "接口:";
+ //
+ // groupBox3
+ //
+ groupBox3.Anchor = AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right;
+ groupBox3.Controls.Add(richTextBox1);
+ groupBox3.Location = new Point(14, 296);
+ groupBox3.Margin = new Padding(3, 2, 3, 2);
+ groupBox3.Name = "groupBox3";
+ groupBox3.Padding = new Padding(3, 2, 3, 2);
+ groupBox3.Size = new Size(1128, 400);
+ groupBox3.TabIndex = 2;
+ groupBox3.TabStop = false;
+ groupBox3.Text = "日志";
+ //
+ // richTextBox1
+ //
+ richTextBox1.Dock = DockStyle.Fill;
+ richTextBox1.Location = new Point(3, 21);
+ richTextBox1.Margin = new Padding(3, 2, 3, 2);
+ richTextBox1.Name = "richTextBox1";
+ richTextBox1.Size = new Size(1122, 377);
+ richTextBox1.TabIndex = 0;
+ richTextBox1.Text = "";
+ //
+ // btnCallAsync
+ //
+ btnCallAsync.Location = new Point(630, 67);
+ btnCallAsync.Name = "btnCallAsync";
+ btnCallAsync.Size = new Size(106, 45);
+ btnCallAsync.TabIndex = 5;
+ btnCallAsync.Text = "异步调用";
+ btnCallAsync.UseVisualStyleBackColor = true;
+ btnCallAsync.Click += btnCallAsync_Click;
+ //
+ // FrmMain
+ //
+ AutoScaleDimensions = new SizeF(10F, 20F);
+ AutoScaleMode = AutoScaleMode.Font;
+ ClientSize = new Size(1155, 707);
+ Controls.Add(groupBox3);
+ Controls.Add(groupBox2);
+ Controls.Add(groupBox1);
+ Margin = new Padding(3, 2, 3, 2);
+ Name = "FrmMain";
+ StartPosition = FormStartPosition.CenterScreen;
+ Text = "零代客户端";
+ Load += FrmMain_Load;
+ groupBox1.ResumeLayout(false);
+ groupBox1.PerformLayout();
+ groupBox2.ResumeLayout(false);
+ groupBox2.PerformLayout();
+ groupBox3.ResumeLayout(false);
+ ResumeLayout(false);
+ }
+
+ #endregion
+
+ private GroupBox groupBox1;
+ private GroupBox groupBox2;
+ private Label label1;
+ private Button btnOpen;
+ private GroupBox groupBox3;
+ private RichTextBox richTextBox1;
+ private TextBox txtServer;
+ private Button btnCall;
+ private ComboBox cbApi;
+ private Label label2;
+ private TextBox textBox2;
+ private Label label3;
+ private Button btnOpenAsync;
+ private Button btnCallAsync;
+ }
+}
\ No newline at end of file
diff --git a/ZeroClient/FrmMain.resx b/ZeroClient/FrmMain.resx
new file mode 100644
index 0000000..8b2ff64
--- /dev/null
+++ b/ZeroClient/FrmMain.resx
@@ -0,0 +1,120 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+ <!--
+ Microsoft ResX Schema
+
+ Version 2.0
+
+ The primary goals of this format is to allow a simple XML format
+ that is mostly human readable. The generation and parsing of the
+ various data types are done through the TypeConverter classes
+ associated with the data types.
+
+ Example:
+
+ ... ado.net/XML headers & schema ...
+ <resheader name="resmimetype">text/microsoft-resx</resheader>
+ <resheader name="version">2.0</resheader>
+ <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+ <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+ <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+ <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+ <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+ <value>[base64 mime encoded serialized .NET Framework object]</value>
+ </data>
+ <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+ <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+ <comment>This is a comment</comment>
+ </data>
+
+ There are any number of "resheader" rows that contain simple
+ name/value pairs.
+
+ Each data row contains a name, and value. The row also contains a
+ type or mimetype. Type corresponds to a .NET class that support
+ text/value conversion through the TypeConverter architecture.
+ Classes that don't support this are serialized and stored with the
+ mimetype set.
+
+ The mimetype is used for serialized objects, and tells the
+ ResXResourceReader how to depersist the object. This is currently not
+ extensible. For a given mimetype the value must be set accordingly:
+
+ Note - application/x-microsoft.net.object.binary.base64 is the format
+ that the ResXResourceWriter will generate, however the reader can
+ read any of the formats listed below.
+
+ mimetype: application/x-microsoft.net.object.binary.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.soap.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.bytearray.base64
+ value : The object must be serialized into a byte array
+ : using a System.ComponentModel.TypeConverter
+ : and then encoded with base64 encoding.
+ -->
+ <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+ <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+ <xsd:element name="root" msdata:IsDataSet="true">
+ <xsd:complexType>
+ <xsd:choice maxOccurs="unbounded">
+ <xsd:element name="metadata">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" />
+ </xsd:sequence>
+ <xsd:attribute name="name" use="required" type="xsd:string" />
+ <xsd:attribute name="type" type="xsd:string" />
+ <xsd:attribute name="mimetype" type="xsd:string" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="assembly">
+ <xsd:complexType>
+ <xsd:attribute name="alias" type="xsd:string" />
+ <xsd:attribute name="name" type="xsd:string" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="data">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+ <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+ <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="resheader">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" />
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:choice>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:schema>
+ <resheader name="resmimetype">
+ <value>text/microsoft-resx</value>
+ </resheader>
+ <resheader name="version">
+ <value>2.0</value>
+ </resheader>
+ <resheader name="reader">
+ <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <resheader name="writer">
+ <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+</root>
\ No newline at end of file
diff --git a/ZeroClient/Program.cs b/ZeroClient/Program.cs
new file mode 100644
index 0000000..14dd759
--- /dev/null
+++ b/ZeroClient/Program.cs
@@ -0,0 +1,73 @@
+using System.Text;
+using NewLife;
+using NewLife.Log;
+using NewLife.Model;
+using Stardust;
+
+namespace Zero.Desktop;
+
+internal static class Program
+{
+ /// <summary>
+ /// The main entry point for the application.
+ /// </summary>
+ [STAThread]
+ static void Main()
+ {
+ // 支持GB2312编码
+ Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
+
+ XTrace.UseWinForm();
+ MachineInfo.RegisterAsync();
+
+ StartClient();
+
+ var set = ClientSetting.Current;
+
+ // 启用语音提示
+ StringHelper.EnableSpeechTip = set.SpeechTip;
+
+ if (set.IsNew) "学无先后达者为师,欢迎使用新生命零代客户端!".SpeechTip();
+
+ // To customize application configuration such as set high DPI settings or default font,
+ // see https://aka.ms/applicationconfiguration.
+ ApplicationConfiguration.Initialize();
+ //Application.EnableVisualStyles();
+ //Application.SetCompatibleTextRenderingDefault(false);
+ //Application.SetHighDpiMode(HighDpiMode.SystemAware);
+ Application.Run(new FrmMain());
+ }
+
+ static StarFactory _factory;
+ static StarClient _Client;
+ private static void StartClient()
+ {
+ var set = ClientSetting.Current;
+ var server = set.Server;
+ if (server.IsNullOrEmpty()) return;
+
+ XTrace.WriteLine("初始化服务端地址:{0}", server);
+
+ _factory = new StarFactory(server, null, null)
+ {
+ Log = XTrace.Log,
+ };
+
+ var client = new StarClient(server)
+ {
+ Code = set.Code,
+ Secret = set.Secret,
+ ProductCode = _factory.AppId,
+ Setting = set,
+
+ Tracer = _factory.Tracer,
+ Log = XTrace.Log,
+ };
+
+ client.Open();
+
+ Host.RegisterExit(() => client.Logout("ApplicationExit"));
+
+ _Client = client;
+ }
+}
\ No newline at end of file
diff --git a/ZeroClient/Zero.Desktop.csproj b/ZeroClient/Zero.Desktop.csproj
new file mode 100644
index 0000000..5eae30c
--- /dev/null
+++ b/ZeroClient/Zero.Desktop.csproj
@@ -0,0 +1,37 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+ <PropertyGroup>
+ <OutputType>WinExe</OutputType>
+ <TargetFramework>net9.0-windows</TargetFramework>
+ <AssemblyTitle>客户端桌面应用</AssemblyTitle>
+ <Description>CS架构的客户端桌面应用,给用户提供便捷操作,可对接硬件</Description>
+ <Company>新生命开发团队</Company>
+ <Copyright>©2002-2024 NewLife</Copyright>
+ <VersionPrefix>1.0</VersionPrefix>
+ <VersionSuffix>$([System.DateTime]::Now.ToString(`yyyy.MMdd`))</VersionSuffix>
+ <Version>$(VersionPrefix).$(VersionSuffix)</Version>
+ <FileVersion>$(Version)</FileVersion>
+ <AssemblyVersion>$(VersionPrefix).*</AssemblyVersion>
+ <Deterministic>false</Deterministic>
+ <OutputPath>..\Bin\Desktop</OutputPath>
+ <AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
+ <ImplicitUsings>enable</ImplicitUsings>
+ <LangVersion>latest</LangVersion>
+ <UseWindowsForms>true</UseWindowsForms>
+
+ <ApplicationVisualStyles>true</ApplicationVisualStyles>
+ <ApplicationUseCompatibleTextRendering>false</ApplicationUseCompatibleTextRendering>
+ <ApplicationHighDpiMode>SystemAware</ApplicationHighDpiMode>
+ <ApplicationDefaultFont>Microsoft Sans Serif, 8.25pt</ApplicationDefaultFont>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <PackageReference Include="NewLife.Stardust" Version="3.1.2024.1004" />
+ <PackageReference Include="System.Speech" Version="8.0.0" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <ProjectReference Include="..\NewLife.Remoting\NewLife.Remoting.csproj" />
+ </ItemGroup>
+
+</Project>
\ No newline at end of file