当前位置: 首页 > news >正文

如何在wpf项目里插入Quill 编辑器

第一步骤,要安装Node.js,下载地址:https://nodejs.org/zh-cn/download

第二步骤,生成quill。

不需要懂前端构建工具链,只是借用 npm 当一个"下载器",比手动去 CDN 上一个个右键另存为更可靠(能保证版本一致、文件完整)。
在任意临时目录执行,我这里直接在桌面执行cmd,不需要在 WPF 项目里

QQ截图20260617122801

 


mkdir quill-temp && cd quill-temp
npm init -y
npm install quill

QQ截图20260617111930
安装完成后你需要的文件就在:

QQ截图20260617122916

 


quill-temp/node_modules/quill/dist/
├── quill.js          ← 主库(未压缩,方便调试)
├── quill.js.map
├── quill.snow.css    ← snow 主题样式(你用的工具栏样式)
├── quill.bubble.css
├── quill.core.css
└── ...

QQ截图20260617112018

在项目中新建QuillEditor 文件夹,并且把 quill.js 和 quill.snow.css 这两个文件复制到里面。这三个文件都要选中 生成操作:内容。

 

QQ截图20260617122321

 

第三步骤,手动新建 editor.html 直接配套使用。

<!DOCTYPE html>
<html lang="zh">
<head><meta charset="UTF-8" /><title>Editor</title><link rel="stylesheet" href="quill.snow.css"><style>html, body {margin: 0;padding: 0;height: 100%;}#editor-container {height: 100%;display: flex;flex-direction: column;}#toolbar {flex-shrink: 0;}#editor {flex: 1;overflow-y: auto;}</style>
</head>
<body><div id="editor-container"><div id="toolbar"></div><div id="editor"></div></div><script src="quill.js"></script><script>// 图片插入交给 C# 端处理,而不是用 Quill 默认的 base64 内嵌function imageHandler() {postToHost({ type: 'requestImage' });}const toolbarOptions = [['bold', 'italic', 'underline', 'strike'],[{ 'script': 'sub' }, { 'script': 'super' }],['clean'],['image']];const quill = new Quill('#editor', {theme: 'snow',modules: {toolbar: {container: toolbarOptions,handlers: {image: imageHandler}}}});// 统一的发送消息函数,脱离 WebView2 环境时不报错function postToHost(msg) {if (window.chrome && window.chrome.webview) {window.chrome.webview.postMessage(msg);}}// C# 端选完图片后调用这个函数把路径插进编辑器function insertImageAtCursor(path) {const range = quill.getSelection(true);quill.insertEmbed(range.index, 'image', path);}// 内容变化时通知 C#(用于双向绑定)
        quill.on('text-change', function () {postToHost({type: 'contentChanged',html: quill.root.innerHTML});});// 提供给 C# 主动调用的取值/赋值方法function getContent() {return quill.root.innerHTML;}function setContent(html) {quill.root.innerHTML = html;}</script>
</body>
</html>

 

第四步骤,在nuget 中添加 WebViewe2 ,WebViewe2 运行基于本机电脑已经按住edge浏览器。


 

用户电脑很可能没有按住WebViewe2,最好采用Fixed Version模式。

考虑到你的目标客户是 B 端机构(学校、培训机构),这些环境往往网络管控严格、不允许随意联网下载组件,Fixed Version 更稳妥。具体做法:

  1. 去 Microsoft 官方 WebView2 下载页面,下载对应你目标系统架构(x64/ARM64)的 Fixed Version 运行时压缩包
  2. 把这个运行时文件夹整个放进你的安装包里
  3. 程序启动时指定运行时路径,而不是用系统全局安装的版本:

 

第五步骤,在nuget 中添加 WebViewe2 

QQ截图20260617123449

 第六步骤,在项目中加入该控件

xaml

<UserControl x:Class="IndividualQAlibrary.Theme.Controls.RichTextBoxMVVMEditorView"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:i="http://schemas.microsoft.com/xaml/behaviors"xmlns:wv2="clr-namespace:Microsoft.Web.WebView2.Wpf;assembly=Microsoft.Web.WebView2.Wpf"mc:Ignorable="d" x:Name="Editor"d:DesignHeight="450" d:DesignWidth="800"><Grid><wv2:WebView2 x:Name="QuillWebView" HorizontalAlignment="Stretch" /></Grid>
</UserControl>

C# 

using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Text.Json;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using Microsoft.Web.WebView2.Core;
using Microsoft.Win32;
using System.IO;
using System.Text.Json;
using Microsoft.Web.WebView2.Core;namespace IndividualQAlibrary.Theme.Controls
{public class BridgeMessage{public string Type { get; set; }public string Html { get; set; }}public partial class RichTextBoxMVVMEditorView : UserControl {public RichTextBoxMVVMEditorView(){InitializeComponent();Loaded += OnLoaded;}private async void OnLoaded(object sender, RoutedEventArgs e){// 触发 CoreWebView2 初始化,必须 await 完成await QuillWebView.EnsureCoreWebView2Async(null);QuillWebView.CoreWebView2.Settings.AreDevToolsEnabled = true;// 此时 CoreWebView2 才不为 null
            SetupVirtualHost();SetupMessageBridge();// 用伪域名加载页面,而不是 file:// 或本地路径QuillWebView.Source = new Uri("https://quill.localapp/editor.html");}private void SetupVirtualHost(){var assetsPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory,"QuillEditor");  // 改成实际的文件夹名
QuillWebView.CoreWebView2.SetVirtualHostNameToFolderMapping("quill.localapp",assetsPath,CoreWebView2HostResourceAccessKind.Allow);}private void SetupMessageBridge(){QuillWebView.CoreWebView2.WebMessageReceived += OnWebMessageReceived;}private string PickAndSaveImageFile(){// TODO: 实现弹出文件选择框、保存图片到本地目录的逻辑return null;}private async void OnWebMessageReceived(object sender, CoreWebView2WebMessageReceivedEventArgs e){string json = e.WebMessageAsJson;var msg = JsonSerializer.Deserialize<BridgeMessage>(json,new JsonSerializerOptions { PropertyNameCaseInsensitive = true });switch (msg.Type){case "requestImage":var localPath = PickAndSaveImageFile();if (localPath != null){var safePath = JsonSerializer.Serialize(localPath);await QuillWebView.CoreWebView2.ExecuteScriptAsync($"insertImageAtCursor({safePath})");}break;case "contentChanged":// TODO: 确认 ViewModel 类型后再启用这段同步逻辑// if (DataContext is XxxViewModel vm)// {//     vm.RichContent = msg.Html;// }
                    UpdateContentFromJs(msg.Html);break;}}public static readonly DependencyProperty RichContentProperty =DependencyProperty.Register(nameof(RichContent),typeof(string),typeof(RichTextBoxMVVMEditorView),new FrameworkPropertyMetadata(string.Empty,FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,OnRichContentChanged));public string RichContent{get => (string)GetValue(RichContentProperty);set => SetValue(RichContentProperty, value);}private bool _isUpdatingFromJs; // 防止 JS→C#→JS 的循环回写private static async void OnRichContentChanged(DependencyObject d, DependencyPropertyChangedEventArgs e){var control = (RichTextBoxMVVMEditorView)d;if (control._isUpdatingFromJs) return; // ViewModel 改值是因为 JS 推过来的,不要再推回去if (control.QuillWebView.CoreWebView2 != null){await control.SetEditorContentAsync((string)e.NewValue);}}// JS 端内容变化时调用这个,更新依赖属性,但要标记来源避免死循环private void UpdateContentFromJs(string html){_isUpdatingFromJs = true;RichContent = html;_isUpdatingFromJs = false;}public async Task SetEditorContentAsync(string html){if (QuillWebView.CoreWebView2 == null) return;var safeHtml = JsonSerializer.Serialize(html ?? string.Empty);await QuillWebView.CoreWebView2.ExecuteScriptAsync($"setContent({safeHtml})");}}}

引用

   xmlns:controls="clr-namespace:IndividualQAlibrary.Theme.Controls"<controls:RichTextBoxMVVMEditorView   
Grid.Row="2" Margin="20,0,20,0" Grid.ColumnSpan="2"/><TextBlock  Grid.Row="3"  Margin="20,0,0,0"  VerticalAlignment="Center" FontSize="16"  Foreground="#3E4A58" FontWeight="Bold" Grid.ColumnSpan="2">正确答案·选项 :</TextBlock><controls:RichTextBoxMVVMEditorView  

效果

QQ截图20260617133724

 

http://www.jsqmd.com/news/1030003/

相关文章:

  • 官方最新发布2026年:安徽六安电大中专计算机应用专业,衔接大专升学跳板 - 我叫小周
  • ZigBee ZCL温控器集群开发实战:从原理到NXP SDK实现
  • 6 月 17 日无锡黄金回收暗藏哪些陷阱?一文讲清不踩雷 - 热点速览
  • 深圳黄金回收哪家靠谱?2026全市星级测评榜单,本地人变现放心选 - zzlzzl6688
  • 2026年遂川县做广告哪家强?2026精选优质厂家推荐 - 品牌2026
  • 2026年6月最新浪琴中国官方售后电话网点服务热线客服地址 - 浪琴服务中心
  • 红河哈尼族彝族自治州黄金回收实体店怎么选?这份清单帮你货比三家 - 奢金汇
  • Java毕设选题推荐:基于 SpringBoot+Vue 的数码产品展示与购物结算系统设计 移动端适配型数码电商购物平台设计与实现【附源码、mysql、文档、调试+代码讲解+全bao等】
  • 北京亨得利官方维修中心地址唯一认证:2026年最新华贸中心店实地探访(附全国10城地址+仿冒网点识别指南) - 亨得利腕表维修中心
  • 2026年贵阳全屋舒适系统安装选购指南:地暖、空调、新风、净水一站式对标 - 年度推荐企业名录
  • 2026武汉黄金回收避坑实录:哪家靠谱?收收金到底值不值得选? - 热点速览
  • 2026国内知名农膜行业盘点:雷马散光膜跻身国内十大农膜品牌 - 速递信息
  • 从零搭建微信小程序后端:域名、备案、HTTPS 证书与 Nginx(以 perfecttools.top 为例)
  • 2026年6月最新江诗丹顿中国官方售后电话热线客服地址服务网点 - 江诗丹顿服务中心
  • 2026定西人卖黄金前必看!实地走访一区六县,这份“找谁靠谱”回收指南请查收 - 博客万
  • 2026年长沙高端系统门窗定制怎么选|断桥铝隔音窗品牌深度测评与避坑指南 - 优质企业观察收录
  • 淮北市闲置黄金变现多少钱?本地5家回收门店最新报价参考 - 奢金汇
  • 【亨得利官方硬核打假】从“黑榜”到“真相”:别再被骗!亨得利服务中心官方电话、门店、账号全公开 - 亨得利官方维修中心
  • 2026年贵阳全屋舒适家居系统:地暖、新风、净水、空调一体化选购指南 - 年度推荐企业名录
  • 2026北京黄金回收机构梯队排名|分级选店不踩坑 全域变现评级指南 - 博客万
  • 只有转账记录能起诉欠款吗?律所胜诉策略分析 - 品牌2026
  • 2026长沙高端系统门窗定制:断桥铝隔音方案与全屋定制品牌深度横评 - 优质企业观察收录
  • 3个简单步骤永久保存微信聊天记录:WeChatMsg完整指南与年度报告生成秘籍
  • 2026年6月最新卡地亚中国官方售后电话热线客服地址服务网点 - 卡地亚服务中心
  • 2026羊绒裤品牌排行榜:昭乌达稳居榜首,八大实力品牌逐一解析 - 936品牌测评网
  • 2026石家庄黄金回收避坑指南:这3个套路不避开,吃亏的是自己 - 奢侈品回收测评
  • 泸州汽车灯光升级行业实力门店解析:欧特车灯深耕行业二十年,以专业工艺树立行业标准 - 热点速览
  • 2026武汉 KTV 设备打包回收实测分享 - LYL仔仔
  • 2015+充电协议解析
  • 宏昌天马:深耕专用运输装备赛道三大核心产品对标高端制造标准 - 速递信息