如和在wpf桌面应用程序中集成TinyMCE编辑器

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

第二步骤, 

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

QQ截图20260617122801

 

在命令行中输入
npm install @tinymce/tinymce-vue tinymce

 

QQ截图20260617170711

 


安装完成后你需要的文件就在:

QQ截图20260617170955

 

 在wp项目下新建 文件夹,TinyMCEEdtior,将桌面TinyMCEEdtior-temp\node_modules 下的整个tinymce复制到 项目中TinyMCEEdtior文件夹下。

QQ截图20260617171318

 

 QQ截图20260617171400

QQ截图20260617171434

//这段代码将 在程序编译时候,把TinyMCEEdtior项目文件夹下的文件,全部复制到Bin 文件夹里。   
<ItemGroup> <Content Include="TinyMCEEdtior\**\*.*"> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> </Content> </ItemGroup> <ItemGroup> <Content Update="TinyMCEEdtior\tinymce\editor.html"> <CopyToOutputDirectory>Always</CopyToOutputDirectory> </Content> </ItemGroup>

 

 

 

 

 

第三步骤,tinymce文件夹下 手动新建 editor.html ,然后黏贴以下代码

QQ截图20260617171627

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8" />
  <meta http-equiv="X-UA-Compatible" content="IE=edge" />
  <title>题库编辑器</title>
  <style>
    html, body {
      margin: 0;
      padding: 0;
      height: 100%;
    }
    #editor-container {
      height: 100%;
    }
  </style>
</head>
<body>
  <div id="editor-container">
    <textarea id="editor"></textarea>
  </div>

  <script src="tinymce.min.js"></script>

  <script>
      tinymce.init({
          selector: '#editor',
          license_key: 'gpl',
          base_url: '.',
          suffix: '.min',
          height: '100%',
          menubar: false,
          plugins: 'lists table image link code',

          // 工具栏里加上自定义按钮 mylocalimage
          toolbar: 'undo redo | bold italic underline | bullist numlist | table image link mylocalimage | code',

          branding: false,
          promotion: false,

          automatic_uploads: true,
          file_picker_types: 'image',
          file_picker_callback: function (callback, value, meta) {
              if (meta.filetype === 'image') {
                  if (window.chrome && window.chrome.webview) {
                      window.__imagePickedCallback = callback;
                      window.chrome.webview.postMessage(JSON.stringify({ type: 'pickImage' }));
                  }
              }
          },

          setup: function (editor) {
              // 注册自定义工具栏按钮
              editor.ui.registry.addButton('mylocalimage', {
                  icon: 'image',
                  tooltip: '插入本地图片',
                  onAction: function () {
                      if (window.chrome && window.chrome.webview) {
                          // 直接发消息,不经过 Insert/Edit Image 对话框
                          window.chrome.webview.postMessage(JSON.stringify({ type: 'pickImageDirect' }));
                      }
                  }
              });

              editor.on('init', function () {
                  if (window.chrome && window.chrome.webview) {
                      window.chrome.webview.postMessage(JSON.stringify({ type: 'ready' }));
                  }
              });

              editor.on('change keyup', function () {
                  var content = editor.getContent();
                  if (window.chrome && window.chrome.webview) {
                      window.chrome.webview.postMessage(JSON.stringify({ type: 'contentChanged', content: content }));
                  }
              });
          }
      });
  </script>
</body>
</html>

 

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


 

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

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

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

 

 

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="EditorWebView" />
    </Grid>
</UserControl>

 

C# 

using System;
using System.IO;
using System.Text.Json;
using System.Windows;
using System.Windows.Controls;
using Microsoft.Web.WebView2.Core;
using Microsoft.Win32;

namespace IndividualQAlibrary.Theme.Controls
{
    public class BridgeMessage
    {
        public string Type { get; set; }
        public string Html { get; set; }
    }

    public partial class RichTextBoxMVVMEditorView : UserControl
    {
        private readonly string _imageStoreDir =
            Path.Combine(AppContext.BaseDirectory, "QAImages");

        public RichTextBoxMVVMEditorView()
        {
            InitializeComponent();
            Loaded += async (_, _) =>
            {
                System.Diagnostics.Debug.WriteLine("【Loaded】事件触发");
                await EditorWebView.EnsureCoreWebView2Async();
                System.Diagnostics.Debug.WriteLine("【CoreWebView2】初始化完成");

                Directory.CreateDirectory(_imageStoreDir);
                var tinymceDir = Path.Combine(AppContext.BaseDirectory, "TinyMCEEdtior", "tinymce");

                EditorWebView.CoreWebView2.SetVirtualHostNameToFolderMapping(
                    "tinymce-app", tinymceDir, CoreWebView2HostResourceAccessKind.Allow);
                EditorWebView.CoreWebView2.SetVirtualHostNameToFolderMapping(
                    "qa-images", _imageStoreDir, CoreWebView2HostResourceAccessKind.Allow);

                System.Diagnostics.Debug.WriteLine("【虚拟域名映射】完成");

                EditorWebView.CoreWebView2.WebMessageReceived += async (sender, e) =>
                {
                    var json = e.TryGetWebMessageAsString();
               
                    if (string.IsNullOrWhiteSpace(json)) return;

                    BridgeMessage msg;
                  
                    try
                    {
                        msg = JsonSerializer.Deserialize<BridgeMessage>(json, new JsonSerializerOptions
                        {
                            PropertyNameCaseInsensitive = true
                        });
                     
                    }
                    catch (Exception ex)
                    {
             
                        return;
                    }
                    catch { return; }

                    if (msg?.Type == "pickImage")
                    {
                        var virtualUrl = PickAndSaveImage();
                        if (virtualUrl == null) return;
                        var fileName = virtualUrl.Substring(virtualUrl.LastIndexOf('/') + 1);
                        var script = $"window.__imagePickedCallback && window.__imagePickedCallback('{virtualUrl}', {{ title: '{fileName}' }});";
                        await EditorWebView.CoreWebView2.ExecuteScriptAsync(script);
                    }
                    else if (msg?.Type == "pickImageDirect")
                    {
                       
                        var virtualUrl = PickAndSaveImage();
                
                        if (virtualUrl == null) return;
                        var script = $"tinymce.activeEditor.insertContent('<img src=\"{virtualUrl}\" alt=\"\" />');";
                        await EditorWebView.CoreWebView2.ExecuteScriptAsync(script);
                     
                    }
                };

    
                EditorWebView.CoreWebView2.Navigate("https://tinymce-app/editor.html");
             
            };
        }

        private string PickAndSaveImage()
        {
            try
            {
              

                var dialog = new OpenFileDialog
                {
                    Filter = "图片文件|*.png;*.jpg;*.jpeg;*.gif;*.bmp",
                    Title = "选择图片"
                };

                var ownerWindow = Window.GetWindow(this);
                bool? result = ownerWindow != null
                    ? dialog.ShowDialog(ownerWindow)
                    : dialog.ShowDialog();

   

                if (result != true) return null;

                var ext = Path.GetExtension(dialog.FileName);
                var newFileName = $"{Guid.NewGuid()}{ext}";
                var targetPath = Path.Combine(_imageStoreDir, newFileName);
                File.Copy(dialog.FileName, targetPath, overwrite: true);

                return $"https://qa-images/{newFileName}";
            }
            catch (Exception ex)
            {
          
                return null;
            }
        }
    }
}

 

引用

   
        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截图20260617172035

 

posted @ 2026-06-17 17:22  小林野夫  阅读(9)  评论(0)    收藏  举报
原文链接:https://www.cnblogs.com/cdaniu/