Remember the days were you were a beginner. Or memorize, if you still are one. You have never learned enough. Think of yourself as you were a beginner, every day. Always try to see technologies from a beginners mind. You can accept corrections to your software better and leave the standard path if you need it more easily. There are some good ideas even from people who don’t have your experience.
基础实例 Demo 可以参照以下这篇博文,
基于.Net CEF 实现 Vue 等前端技术栈构建 Windows 窗体应用-CSDN博客文章浏览阅读389次。基于 .Net CEF 库,能够使用 Vue 等前端技术栈构建 Windows 窗体应用https://blog.csdn.net/weixin_47560078/article/details/133974513?spm=1001.2014.3001.5501原理非常简单,基于 .Net CEF 实现,用到的库为 CefSharp。
从 Sqlite 数据库中加载数据,并渲染到前端,使用 Echart 进行可视化。
Vite + Vue3 + TS + Echarts 5.4.3 + ElementUI(plus) + .NET Framework 4.7.2,开发环境为 Win10,VS2019,VS Code。
# 创建 vite vue
cnpm create vite@latest
# element-plus 国内镜像 https://element-plus.gitee.io/zh-CN/
# 安装 element-plus
cnpm install element-plus --save
按需引入 element plus,
# 安装导入插件
cnpm install -D unplugin-vue-components unplugin-auto-import
在 main.ts 引入 element-plus 和样式,
// app\src\main.ts
import { createApp } from 'vue'
//import './style.css'
import App from './App.vue'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
createApp(App).use(ElementPlus).mount('#app')
配置 vite,
// app\vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
vue(),
AutoImport({
resolvers: [ElementPlusResolver()],
}),
Components({
resolvers: [ElementPlusResolver()],
}),
],
})
cnpm install @element-plus/icons-vue
cnpm install echarts
折线图示例,
// src\components\MyChart.vue
效果,
MyChart 页面使用页签切换不同的图表,不同的图表封装为单独的一个组件,由 MyChart 页面按需引入,
// src\components\MyChart.vue
// src\components\MyPictorialBar.vue
// src\components\MyStackedLine.vue
echarts 官方提供了五个工具,但是导出图片在 CEF 中是不可用的,其他工具都能正常使用,因此需要重写导出图片方法,思路如下:
1、前端获取 echarts 的图表 canvas
2、将 canvas 转为 Base64 字符串传给 .NET 方法
3、C# 解析 Base64 保存为图片
toolbox: {
feature: {
// saveAsImage: {},
myTool1: {
show: true,
title: 'saveAsImage',
icon: 'path://M160 832h704a32 32 0 1 1 0 64H160a32 32 0 1 1 0-64zm384-253.696 236.288-236.352 45.248 45.248L508.8 704 192 387.2l45.248-45.248L480 584.704V128h64v450.304z',
onclick: function () {
saveChartAsImage();
}
},
restore: {},
dataView: {},
dataZoom: {},
}
},
// 手动触发保存图像
const saveChartAsImage = () => {
console.log(myChart.getDataURL());
}
注意,这里 canvas 有一个跨域访问的问题,需要配置服务端允许跨域访问,否则会报错,
// InitCefSettings 允许跨域访问
settings.CefCommandLineArgs.Add("disable-web-security");
# 安装 eslint
cnpm i -D eslint @babel/eslint-parser
# 初始化配置
npx eslint --init
# 安装依赖
cnpm i @typescript-eslint/eslint-plugin@latest eslint-plugin-vue@latest @typescript-eslint/parser@latest
# 安装插件
cnpm i -D vite-plugin-eslint
配置 vite,
// vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
import eslintPlugin from 'vite-plugin-eslint'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
vue(),
// ESLint 插件配置
eslintPlugin({
include: ['src/**/*.js', 'src/**/*.vue', 'src/*.js', 'src/*.vue']
}),
AutoImport({
resolvers: [ElementPlusResolver()],
}),
Components({
resolvers: [ElementPlusResolver()],
}),
],
})
配置 eslint 规则,
// .eslintrc.cjs
module.exports = {
"env": {
"browser": true,
"es2021": true,
"node": true
},
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:vue/vue3-essential"
],
"overrides": [
{
"env": {
"node": true
},
"files": [
".eslintrc.{js,cjs}"
],
"parserOptions": {
"sourceType": "script"
}
}
],
"parserOptions": {
"ecmaVersion": "latest",
"parser": "@typescript-eslint/parser",
"sourceType": "module"
},
"plugins": [
"@typescript-eslint",
"vue"
],
"rules": {
"@typescript-eslint/no-explicit-any": 1,
"no-console": 1,
"no-debugger": 1,
"no-undefined": 1,
"no-undef":1,
}
}
补充:打包时需要修改 build 指令,
调用 .NET exportSeriesData 方法获取图表数据,
// src\api\DataUtil.ts
export const exportSeriesData = async (): Promise => {
await CefSharp.BindObjectAsync("exportDataUtil")
return exportDataUtil.exportSeriesData()
}
调用 .NET saveAsImage 方法获取将 Base64 字符保存为图片,
// src\api\ImageUtil.ts
export const SaveAsImage = async (data:string): Promise => {
await CefSharp.BindObjectAsync("imageUtil")
return imageUtil.saveAsImage(data)
}
项目的初始化结构非常简单,与 WinForm 项目结构相似,App.xaml 是程式的主入口,MainWindow.xaml 是自定义窗口,App.xaml.cs(WPF) 与 Program.cs(WinForm) 相似,MainWindow(WPF) 与 Form1(相似)。
CefSharp.Wpf
可以直接在 MainWindow.xaml 中添加,
// MainWindow.xaml
也可以通过控件添加,
// MainWindow.xaml
// CefWpfApp\MainWindow.xaml.cs
using CefSharp;
using CefSharp.JavascriptBinding;
using CefSharp.Wpf;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace CefWpfApp
{
///
/// MainWindow.xaml 的交互逻辑
///
public partial class MainWindow : Window
{
///
/// ChromiumWebBrowser
///
private static ChromiumWebBrowser browser;
public MainWindow()
{
InitializeComponent();
AddChromiumWebBrowser();
}
///
/// Create a new instance in code or add via the designer
///
private void AddChromiumWebBrowser()
{
browser = new ChromiumWebBrowser("http://wpf.test");
this.grid.Children.Add(browser);
}
}
}
效果,
using System;
using System.Windows.Threading;
namespace CefWpfApp.Dispatchers
{
public static class DispatcherExtensions
{
///
/// Executes the Action asynchronously on the UI thread, does not block execution on the calling thread.
///
/// the dispatcher for which the update is required
/// action to be performed on the dispatcher
public static void InvokeOnUiThreadIfRequired(this Dispatcher dispatcher, Action action)
{
if (dispatcher.CheckAccess())
{
action.Invoke();
}
else
{
dispatcher.BeginInvoke(action);
}
}
}
}
直接在项目属性中配置,或者在 MainWindow 中使用 Icon 属性,
///
/// 自定义右键菜单
///
public class CustomContextMenuHandler : IContextMenuHandler
{
///
/// 上下文菜单列表,在这里加菜单
///
///
///
///
///
///
void IContextMenuHandler.OnBeforeContextMenu(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model)
{
if (model.Count > 0)
{
// 添加分隔符
model.AddSeparator();
}
model.AddItem((CefMenuCommand)29501, "Show DevTools");
}
///
/// 上下文菜单指令,这里实现菜单要做的事情
///
///
///
///
///
///
///
///
bool IContextMenuHandler.OnContextMenuCommand(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, IContextMenuParams parameters, CefMenuCommand commandId, CefEventFlags eventFlags)
{
if (commandId == (CefMenuCommand)29501)
{
browser.GetHost().ShowDevTools();
return true;
}
return false;
}
void IContextMenuHandler.OnContextMenuDismissed(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame)
{
}
bool IContextMenuHandler.RunContextMenu(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model, IRunContextMenuCallback callback)
{
// 必须返回 false
return false;
}
}
///
/// Create a new instance in code or add via the designer
///
private void AddChromiumWebBrowser()
{
browser = new ChromiumWebBrowser("http://wpf.test");
// 配置右键菜单
browser.MenuHandler = new CustomContextMenuHandler();
this.grid.Children.Add(browser);
}
直接引入图片,并将文件的属性设置为 “SplashScreen”,
或者在 App.xaml.cs 中重写 OnStartUp 方法,
///
/// 重写启动方法
///
///
protected override void OnStartup(StartupEventArgs e)
{
SplashScreen splashScreen = new SplashScreen("chromium-256.ico");
// true => SplashScreen 自动关闭
// false => SplashScreen 不关闭
splashScreen.Show(false);
// 配置 SplashScreen 关闭时间,1s
// SplashScreen 的关闭时间是基于主窗口加载完成的时间。
// 如果主窗口的加载时间比 1 秒要短,那么 SplashScreen 将会在主窗口加载完成后立即关闭。
splashScreen.Close(new TimeSpan(0, 0, 1));
base.OnStartup(e);
}
注意:
1、Show 方法参数需要设置为 false 才不会自动关闭界面,如果主窗口的加载时间比 1 秒要短,那么 SplashScreen 将会在主窗口加载完成后立即关闭。
2、引入资源为 “Resource” 类型,在 Resources 文件夹下添加资源。
///
/// 状态更新处理
///
///
///
private void OnLoadingStateChanged(object sender, LoadingStateChangedEventArgs args)
{
if (args.IsLoading)
{
// 在 UI 线程上执行操作
Application.Current.Dispatcher.InvokeOnUiThreadIfRequired(() =>
{
// 加载中
isLoading.Visibility = Visibility.Visible;
});
}
else
{
Application.Current.Dispatcher.InvokeOnUiThreadIfRequired(() =>
{
// 隐藏进度条
isLoading.Visibility = Visibility.Collapsed;
});
}
}
// 加载状态变更
browser.LoadingStateChanged += OnLoadingStateChanged;
效果,
可以使用 Windows Forms 控件,但是它的风格跟外观不一定能跟 WPF 兼容,
using Ookii.Dialogs.Wpf;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace CefWpfApp.Dialogs
{
public class CustomerFolderBrowserDialog
{
///
/// 委托实现,显示文件夹浏览器,返回选中的文件夹路径
///
///
public static string ShowFolderBrowserDialog_WPF()
{
VistaFolderBrowserDialog folderDialog = new VistaFolderBrowserDialog
{
Description = "请选择文件夹",
UseDescriptionForTitle = true,
SelectedPath = @"D:\AwsomeWorkSpace\CEFWPFAPP\bin\Debug",
};
if (folderDialog.ShowDialog() == true)
{
return folderDialog.SelectedPath;
}
return "";
}
///
/// 委托实现,显示文件夹浏览器,返回选中的文件夹路径
///
///
public static string ShowFolderBrowserDialog_WinForms()
{
FolderBrowserDialog folderBrowserDialog = new FolderBrowserDialog()
{
Description = "请选择文件夹",
ShowNewFolderButton = true,
SelectedPath = @"D:\AwsomeWorkSpace\CEFWPFAPP\bin\Debug",
};
if (folderBrowserDialog.ShowDialog() == DialogResult.OK)
{
return folderBrowserDialog.SelectedPath;
}
return "";
}
}
}
为了更符合 WPF ,采用第三方 Ookii.Dialogs.Wpf 库,
在 MainWindow.xaml.cs 中创建异步方法 GetSelectedFolderPath(),
///
/// 委托
///
///
public delegate string MyFolderBrowserDialog();
///
/// 获取文件夹路径
///
public Task GetSelectedFolderPath()
{
// 使用TaskCompletionSource来创建一个未完成的任务
var tcs = new TaskCompletionSource();
// 在 UI 线程上执行操作
Application.Current.Dispatcher.InvokeOnUiThreadIfRequired(() =>
{
// 执行回调方法,并获取结果
string result = CustomerFolderBrowserDialog.ShowFolderBrowserDialog_WPF();
// 将结果设置到任务完成源,并标记任务为成功
tcs.SetResult(result);
});
// 返回任务对象
return tcs.Task;
}
效果,
// ImageUtil.cs
using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Threading.Tasks;
namespace CefWpfApp.Utils
{
public class ImageUtil
{
public async Task
注意,这里 SaveAsImage 方法的返回类型是 Task
CefSharpSettings.ConcurrentTaskExecution = true;
否则会报错,
创建两个实体类,SeriesEntity 用于返回 Echarts 图表数据实体,StackedLineEntity 用于返回数据库实体,
using System.Collections.Generic;
namespace CefWpfApp.Entity
{
public class SeriesEntity
{
public string name { get; set; }
public string type { get; set; }
public string stack { get; set; }
public List data { get; set; }
}
}
namespace CefWpfApp.Entity
{
public class StackedLineEntity
{
public int Email { get; set; }
public int Unio_Ads { get; set; }
public int Video_Ads { get; set; }
public int Direct { get; set; }
public int Search_Engine { get; set; }
public StackedLineEntity(int Email, int Unio_Ads, int Video_Ads, int Direct, int Search_Engine)
{
this.Email = Email;
this.Unio_Ads = Unio_Ads;
this.Video_Ads = Video_Ads;
this.Direct = Direct;
this.Search_Engine = Search_Engine;
}
}
}
工具类封装了连接字符串方法、创建数据库文件方法、生成模拟数据方法、查询数据表方法、处理空值方法,
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SQLite;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace CefWpfApp.Utils
{
public class MySqliteUtil
{
///
/// 生成模拟数据
///
public static void GenerateStackedLineEntityData()
{
string connectionString = CreateConnectionString();
using (SQLiteConnection connection = new SQLiteConnection(connectionString))
{
connection.Open();
// 创建表
using (var command = new SQLiteCommand("CREATE TABLE IF NOT EXISTS StackedLine (Id INTEGER PRIMARY KEY, Email INTEGER, Unio_Ads INTEGER, Video_Ads INTEGER, Direct INTEGER, Search_Engine INTEGER)", connection))
{
command.ExecuteNonQuery();
}
// 插入数据
for (int i = 0; i < 7; i++)
{
using (var command = new SQLiteCommand("INSERT INTO StackedLine (Email, Unio_Ads, Video_Ads, Direct, Search_Engine) VALUES (@Email, @Unio_Ads, @Video_Ads, @Direct, @Search_Engine)", connection))
{
command.Parameters.AddWithValue("@Email", Math.Abs(Guid.NewGuid().GetHashCode() % 64));
command.Parameters.AddWithValue("@Unio_Ads", Math.Abs(Guid.NewGuid().GetHashCode() % 128));
command.Parameters.AddWithValue("@Video_Ads", Math.Abs(Guid.NewGuid().GetHashCode() % 64));
command.Parameters.AddWithValue("@Direct", Math.Abs(Guid.NewGuid().GetHashCode() % 128));
command.Parameters.AddWithValue("@Search_Engine", Math.Abs(Guid.NewGuid().GetHashCode() % 64));
command.ExecuteNonQuery();
}
}
}
}
///
/// 创建数据库文件
///
///
public static void CreateDBFile(string fileName)
{
string path = System.Environment.CurrentDirectory + @"/Data/";
if (!Directory.Exists(path))
{
Directory.CreateDirectory(path);
}
string databaseFileName = path + fileName;
if (!File.Exists(databaseFileName))
{
SQLiteConnection.CreateFile(databaseFileName);
}
}
///
/// 生成连接字符串
///
///
private static string CreateConnectionString()
{
SQLiteConnectionStringBuilder connectionString = new SQLiteConnectionStringBuilder();
connectionString.DataSource = @"IkunDB.db";
return connectionString.ToString();
}
///
/// 对插入到数据库中的空值进行处理
///
///
///
public static object ToDbValue(object value)
{
if (value == null)
{
return DBNull.Value;
}
else
{
return value;
}
}
///
/// 对从数据库中读取的空值进行处理
///
///
///
public static object FromDbValue(object value)
{
if (value == DBNull.Value)
{
return null;
}
else
{
return value;
}
}
///
/// 执行非查询的数据库操作
///
/// 要执行的sql语句
/// 参数列表
/// 返回受影响的条数
public static int ExecuteNonQuery(string sqlString, params SQLiteParameter[] parameters)
{
string connectionString = CreateConnectionString();
using (SQLiteConnection connection = new SQLiteConnection(connectionString))
{
connection.Open();
using (SQLiteCommand command = connection.CreateCommand())
{
command.CommandText = sqlString;
foreach (SQLiteParameter parameter in parameters)
{
command.Parameters.Add(parameter);
}
return command.ExecuteNonQuery();
}
}
}
///
/// 执行查询并返回查询结果第一行第一列
///
/// SQL语句
/// 参数列表
///
public static object ExecuteScalar(string sqlString, params SQLiteParameter[] parameters)
{
string connectionString = CreateConnectionString();
using (SQLiteConnection connection = new SQLiteConnection(connectionString))
{
connection.Open();
using (SQLiteCommand command = connection.CreateCommand())
{
command.CommandText = sqlString;
foreach (SQLiteParameter parameter in parameters)
{
command.Parameters.Add(parameter);
}
return command.ExecuteScalar();
}
}
}
///
/// 查询多条数据
///
/// SQL语句
/// 参数列表
/// 返回查询的数据表
public static DataTable GetDataTable(string sqlString, params SQLiteParameter[] parameters)
{
string connectionString = CreateConnectionString();
using (SQLiteConnection connection = new SQLiteConnection(connectionString))
{
connection.Open();
using (SQLiteCommand command = connection.CreateCommand())
{
command.CommandText = sqlString;
foreach (SQLiteParameter parameter in parameters)
{
command.Parameters.Add(parameter);
}
DataSet ds = new DataSet();
SQLiteDataAdapter adapter = new SQLiteDataAdapter(command);
adapter.Fill(ds);
connection.Close();
return ds.Tables[0];
}
}
}
}
}
MySqliteService 调用数据库工具类获取图表数据并处理为 series 格式,
using CefWpfApp.Entity;
using CefWpfApp.Utils;
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SQLite;
namespace CefWpfApp.Service
{
public class MySqliteService
{
///
/// 返回图表数据信息
///
public static Object GetSeriesData()
{
List seriesEntities = new List(5);
string[] series = new string[] { "Email", "Union Ads", "Video Ads", "Direct", "Search Engine" };
List stackedLineEntities = GetStackedLineEntities();
for (int i = 0; i < series.Length; i++)
{
SeriesEntity seriesEntity = new SeriesEntity();
seriesEntity.name = series[i];
seriesEntity.type = "line";
seriesEntity.stack = "Total";
List data = new List(7);
switch (series[i])
{
case "Email":
{
for (int j = 0; j < stackedLineEntities.Count; j++)
{
data.Add(stackedLineEntities[j].Email);
}
break;
}
case "Union Ads":
{
for (int j = 0; j < stackedLineEntities.Count; j++)
{
data.Add(stackedLineEntities[j].Unio_Ads);
}
break;
}
case "Video Ads":
{
for (int j = 0; j < stackedLineEntities.Count; j++)
{
data.Add(stackedLineEntities[j].Video_Ads);
}
break;
}
case "Direct":
{
for (int j = 0; j < stackedLineEntities.Count; j++)
{
data.Add(stackedLineEntities[j].Direct);
}
break;
}
case "Search Engine":
{
for (int j = 0; j < stackedLineEntities.Count; j++)
{
data.Add(stackedLineEntities[j].Search_Engine);
}
break;
}
}
seriesEntity.data = data;
seriesEntities.Add(seriesEntity);
}
return seriesEntities;
}
///
/// 查询数据
///
///
public static List GetStackedLineEntities()
{
List stackedLineEntities = new List();
string sqlQuery = "SELECT * FROM 'StackedLine' ORDER BY Id DESC LIMIT 7;";
SQLiteParameter[] parameters = new SQLiteParameter[]{ };
DataTable dt = MySqliteUtil.GetDataTable(sqlQuery, parameters);
for (int i = 0; i < dt.Rows.Count; i++)
{
int Email = int.Parse(dt.Rows[i]["Email"].ToString());
int Unio_Ads = int.Parse(dt.Rows[i]["Unio_Ads"].ToString());
int Video_Ads = int.Parse(dt.Rows[i]["Video_Ads"].ToString());
int Direct = int.Parse(dt.Rows[i]["Direct"].ToString());
int Search_Engine = int.Parse(dt.Rows[i]["Search_Engine"].ToString());
StackedLineEntity stackedLineEntity = new StackedLineEntity(Email, Unio_Ads, Video_Ads, Direct, Search_Engine);
stackedLineEntities.Add(stackedLineEntity);
}
return stackedLineEntities;
}
}
}
ExportDataUtil 调用 MySqliteService 导出数据给 JS,
using CefWpfApp.Service;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace CefWpfApp.Utils
{
public class ExportDataUtil
{
///
/// 导出图表数据
///
///
public Object ExportSeriesData()
{
return MySqliteService.GetSeriesData();
}
}
}
///
/// 导出类方法
///
public static void ExposeDotnetClass()
{
browser.JavascriptObjectRepository.ResolveObject += (sender, e) =>
{
// 注册 ImageUtil 实例
DoRegisterDotNetFunc(e.ObjectRepository, e.ObjectName, "imageUtil", new ImageUtil());
// 注册 ExportDataUtil 实例
DoRegisterDotNetFunc(e.ObjectRepository, e.ObjectName, "exportDataUtil", new ExportDataUtil());
// 注册其他实例 ...
};
browser.JavascriptObjectRepository.ObjectBoundInJavascript += (sender, e) =>
{
var name = e.ObjectName;
Debug.WriteLine($"Object {e.ObjectName} was bound successfully.");
};
}
///
/// 注册 DoNet 实例
///
/// IJavascriptObjectRepository param >
/// < param name="eventObjectName">事件对象名
/// 方法名 param >
/// < param name="objectToBind">需要绑定的DotNet对象
private static void DoRegisterDotNetFunc(IJavascriptObjectRepository repo, string eventObjectName, string funcName, object objectToBind)
{
if (eventObjectName.Equals(funcName))
{
if (!IsSetNameConverter)
{
repo.NameConverter = new CamelCaseJavascriptNameConverter();
IsSetNameConverter = true;
}
BindingOptions bindingOptions = null;
bindingOptions = BindingOptions.DefaultBinder;
//repo.NameConverter = null;
//repo.NameConverter = new CamelCaseJavascriptNameConverter();
repo.Register(funcName, objectToBind, isAsync: true, options: bindingOptions);
}
}
///
/// Create a new instance in code or add via the designer
///
private void AddChromiumWebBrowser()
{
// 本地代理域
browser = new ChromiumWebBrowser("http://wpf.test");
// 菜单
browser.MenuHandler = new CustomContextMenuHandler();
// 加载状态变更
browser.LoadingStateChanged += OnLoadingStateChanged;
// 导出 .Net 方法
ExposeDotnetClass();
this.contentGrid.Children.Add(browser);
}
前端打包,
npm run build
然后将打包文件引入 .NET 项目中,注册本地域,
// 本地代理域
settings.RegisterScheme(new CefCustomScheme
{
SchemeName = "http",
DomainName = "wpf.test",
SchemeHandlerFactory = new FolderSchemeHandlerFactory(rootFolder: @"..\..\..\CefWpfApp\Web",
hostName: "wpf.test", //Optional param no hostname/domain checking if null
defaultPage: "index.html") //Optional param will default to index.html
});
配置本地网页的访问域、缓存目录、跨域等信息,
// CefWpfApp\App.xaml.cs
using CefSharp;
using CefSharp.SchemeHandler;
using CefSharp.Wpf;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
namespace CefWpfApp
{
///
/// App.xaml 的交互逻辑
///
public partial class App : Application
{
///
/// 主窗口实例
///
public static MainWindow mainWindow;
public App()
{
// 初始化 CEF
InitCefSettings();
// 实例化主窗口
mainWindow = new MainWindow();
this.MainWindow = mainWindow;
// 显示主窗口
this.MainWindow.Show();
}
///
/// 重写启动方法
///
///
protected override void OnStartup(StartupEventArgs e)
{
SplashScreen splashScreen = new SplashScreen("chromium-256.ico");
// true => SplashScreen 自动关闭
// false => SplashScreen 不关闭
splashScreen.Show(false);
// 配置 SplashScreen 关闭时间,1s
// SplashScreen 的关闭时间是基于主窗口加载完成的时间。
// 如果主窗口的加载时间比 1 秒要短,那么 SplashScreen 将会在主窗口加载完成后立即关闭。
splashScreen.Close(new TimeSpan(0, 0, 1));
base.OnStartup(e);
}
///
/// 初始化 CEF 配置
///
private static void InitCefSettings()
{
#if ANYCPU
CefRuntime.SubscribeAnyCpuAssemblyResolver();
#endif
// Pseudo code; you probably need more in your CefSettings also.
var settings = new CefSettings()
{
//By default CefSharp will use an in-memory cache, you need to specify a Cache Folder to persist data
CachePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "CefSharp\\Cache")
};
//Example of setting a command line argument
//Enables WebRTC
// - CEF Doesn't currently support permissions on a per browser basis see https://bitbucket.org/chromiumembedded/cef/issues/2582/allow-run-time-handling-of-media-access
// - CEF Doesn't currently support displaying a UI for media access permissions
//
//NOTE: WebRTC Device Id's aren't persisted as they are in Chrome see https://bitbucket.org/chromiumembedded/cef/issues/2064/persist-webrtc-deviceids-across-restart
settings.CefCommandLineArgs.Add("enable-media-stream");
//https://peter.sh/experiments/chromium-command-line-switches/#use-fake-ui-for-media-stream
settings.CefCommandLineArgs.Add("use-fake-ui-for-media-stream");
//For screen sharing add (see https://bitbucket.org/chromiumembedded/cef/issues/2582/allow-run-time-handling-of-media-access#comment-58677180)
settings.CefCommandLineArgs.Add("enable-usermedia-screen-capturing");
// 允许跨域访问
settings.CefCommandLineArgs.Add("disable-web-security");
// 本地代理域
settings.RegisterScheme(new CefCustomScheme
{
SchemeName = "http",
DomainName = "wpf.test",
SchemeHandlerFactory = new FolderSchemeHandlerFactory(rootFolder: @"..\..\..\CefWpfApp\Web",
hostName: "wpf.test", //Optional param no hostname/domain checking if null
defaultPage: "index.html") //Optional param will default to index.html
});
//Perform dependency check to make sure all relevant resources are in our output directory.
Cef.Initialize(settings, performDependencyCheck: true, browserProcessHandler: null);
// 允许 JS 调用 Task 类型返回值的方法
CefSharpSettings.ConcurrentTaskExecution = true;
}
}
}
如果不引入静态资源,不想在本地代理 Web ,则可以通过配置远程 URL 来实现前后端分离部署,
npm run dev
开启前端,注意,这里 ROOT_PATH URL 前缀跟 Local 相同,用于访问 public 下的静态资源(图片/JSON),如果是在 .NET 中代理,则需要保持跟 CEF 配置相同,例如 “http://wpf.test”。
注释掉 CEF 配置,
配置构造方法参数,
# https://www.sqlite.org/download.html
https://www.sqlite.org/2023/sqlite-tools-win-x64-3440000.zip
https://www.sqlite.org/2023/sqlite-dll-win-x64-3440000.zip
将解压后的两个文件夹整合到一个文件夹 Sqlite3 作为安装目录,然后配置 PATH 环境变量,在命令提示符打开,输入,
sqlite3
看到上面的结果就代表安装完成。
安装 System.Data.Sqlite 包,这里使用的版本是 1.0.118,
// Sqlite 增删查改示例
using System;
using System.Data.SQLite;
namespace SQLiteExample
{
class Program
{
static void Main(string[] args)
{
// 连接数据库 example.db
using (var connection = new SQLiteConnection("Data Source=example.db;Version=3;"))
{
connection.Open();
// 创建表
using (var command = new SQLiteCommand("CREATE TABLE IF NOT EXISTS Users (Id INTEGER PRIMARY KEY, Name TEXT, Age INTEGER)", connection))
{
command.ExecuteNonQuery();
}
// 插入数据
using (var command = new SQLiteCommand("INSERT INTO Users (Name, Age) VALUES (@name, @age)", connection))
{
command.Parameters.AddWithValue("@name", "John");
command.Parameters.AddWithValue("@age", 25);
command.ExecuteNonQuery();
}
// 查询数据
using (var command = new SQLiteCommand("SELECT * FROM Users", connection))
{
using (var reader = command.ExecuteReader())
{
while (reader.Read())
{
int id = Convert.ToInt32(reader["Id"]);
string name = reader["Name"].ToString();
int age = Convert.ToInt32(reader["Age"]);
Console.WriteLine($"ID: {id}, Name: {name}, Age: {age}");
}
}
}
// 更新数据
using (var command = new SQLiteCommand("UPDATE Users SET Age = @age WHERE Name = @name", connection))
{
command.Parameters.AddWithValue("@age", 30);
command.Parameters.AddWithValue("@name", "John");
command.ExecuteNonQuery();
}
// 删除数据
using (var command = new SQLiteCommand("DELETE FROM Users WHERE Name = @name", connection))
{
command.Parameters.AddWithValue("@name", "John");
command.ExecuteNonQuery();
}
}
}
}
}
# 打开数据库
.open IkunDB.db
# 查询数据库
.databases
# 查询所有表
.tables
# 查询某个表数据
SELECT * FROM tableName;
# 退出
.quit
1、WPF 介绍 | Microsoft Learn
2、在 Visual Studio 2019 中创建第一个 WPF 应用 - .NET Framework | Microsoft Learn
3、Quick Start For MS .Net 4.x · cefsharp/CefSharp Wiki · GitHub
4、一个 Vue 3 UI 框架 | Element Plus
5、基于.Net CEF 实现 Vue 等前端技术栈构建 Windows 窗体应用-CSDN博客
6、使用 ElementUI 组件构建 Window 桌面应用探索与实践(WinForm)-CSDN博客
7、Apache ECharts
8、Examples - Apache ECharts
9、快速上手 - Handbook - Apache ECharts
10、【精选】WPF:Loading等待动画、加载动画_wpf 加载动画_pandawangyt的博客-CSDN博客
11、Documentation - Apache ECharts
12、SQLite Home Page
13、SQLite 教程 | 菜鸟教程