【AI插件开发】Notepad++ AI插件开发实践(代码篇):从Dock窗口集成到功能菜单实现

news2025/4/17 14:00:43

一、引言

上篇文章已经在Notepad++的插件开发中集成了选中即问AI的功能,这一篇文章将在此基础上进一步集成,支持AI对话窗口以及常见的代码功能菜单:

  • 显示AI的Dock窗口,可以用自然语言向 AI 提问或要求执行任务
  • 选中代码后使用,AI 会详细解释代码功能
  • 需要 AI 帮助改进或修复代码时使用
  • 自动生成代码注释
  • 选中即问,直接把选中内容丢给AI
  • 参数设置,基于插件配置切换AI平台等参数,提供对话框设置及调整平台参数

本篇的主要难点在于如何集成Dock窗口,是自己裸写Window窗口,还是找第三方库,或者从Notepad++的源码中剥离相关代码?

最终选择的是从Notepad++的源码中剥离相关代码的方案,但是直接剥离很难,发现依赖越来越多,因此需要在剥离的基础上做一些改造,删减非必须的功能,比如NppDarkMode裁剪。

:项目已开源、镜像,欢迎使用及指正

二、Notepad++的源码中剥离Dock窗口

Notepad++文件

1. 剥离后的Dock文件列表

Common.h
Docking.h
DockingDlgInterface.h
dockingResource.h
dpiManagerV2.cpp
dpiManagerV2.h
NppDarkMode.cpp
NppDarkMode.h
StaticDialog.cpp
StaticDialog.h
Window.h

2. 要点说明

直接从Notepad++中拷贝上述文件代码到项目中引用的话,会发现还要包含其他文件,然后试着把别的文件引入的时候,后面发现引入的文件越来越多,所以需要对部分文件进行裁剪,主要裁剪的代码是NppDarkMode.cpp。 我把NppDarkMode.cpp的代码全删了,然后自己实现了项目中调用了的函数,采集后的NppDarkMode.cpp文件内容如下:

#include "NppDarkMode.h"

enum class SystemVersion
{
	Unknown,
	Windows10,
	Windows11
};

SystemVersion GetWindowsVersion()
{
	// 使用RtlGetVersion替代已废弃的GetVersionEx
	typedef NTSTATUS(WINAPI* RtlGetVersionPtr)(PRTL_OSVERSIONINFOW);

	OSVERSIONINFOW osInfo = { 0 };
	HMODULE hMod = GetModuleHandleW(L"ntdll.dll");
	if (hMod)
	{
		auto RtlGetVersion = reinterpret_cast<RtlGetVersionPtr>(
			GetProcAddress(hMod, "RtlGetVersion"));
		if (RtlGetVersion) {
			osInfo.dwOSVersionInfoSize = sizeof(osInfo);
			if (RtlGetVersion(&osInfo) == 0)
			{ // STATUS_SUCCESS
				// Windows 11的版本号为10.0.22000+
				if (osInfo.dwMajorVersion == 10 &&
					osInfo.dwMinorVersion == 0)
				{
					if (osInfo.dwBuildNumber >= 22000)
					{
						return SystemVersion::Windows11;
					}
					else if (osInfo.dwBuildNumber >= 10240)
					{
						return SystemVersion::Windows10;
					}
				}
			}
		}
	}
	return SystemVersion::Unknown;
}

bool NppDarkMode::isWindows10() { return GetWindowsVersion() == SystemVersion::Windows10; }
bool NppDarkMode::isWindows11() { return GetWindowsVersion() == SystemVersion::Windows11; }
void NppDarkMode::setDarkTitleBar(HWND hwnd) {}

3. AI窗口实现

现在只需要引入DockingDlgInterface.h文件,实现一个基于DockingDlgInterface的窗口即可,其中AiAssistWnd.h

// AiAssistWnd.h
#pragma once
#include "DockingDlgInterface.h"
#include "PluginInterface.h"

class AiAssistWnd : public DockingDlgInterface {
public:
    AiAssistWnd(HINSTANCE hInst, const NppData& nppData);
    ~AiAssistWnd();

    // 必需实现的虚函数
    virtual void init();
    virtual INT_PTR run_dlgProc(UINT message, WPARAM wParam, LPARAM lParam);

    // 功能接口
    void updateModelList(const std::vector<std::wstring>& models);
    void appendAnswer(const std::wstring& answer);
    void clearConversation();

private:
    void initControls();
    void layoutControls(int width, int height);
    void handleUserInput();

    // Npp
    HINSTANCE _hInst;
    NppData _nppData;

    // 控件句柄
    HWND _hModelCombo = nullptr;
    HWND _hInputEdit = nullptr;
    HWND _hAnswerView = nullptr;

    // 配置参数
    const int CONTROL_MARGIN = 5;
    const int COMBO_HEIGHT = 25;
    const int INPUT_HEIGHT = 80;

    // 字体资源
    HFONT _hFont = nullptr;
    HFONT _hBoldFont = nullptr;

    // 控件ID定义
    enum ControlID {
        IDC_MODEL_COMBO = 2000,
        IDC_INPUT_EDIT,
        IDC_ANSWER_VIEW
    };
};

代码实现:

// AiAssistWnd.cpp
#include "AiAssistWnd.h"
#include "resource.h"
#include <richedit.h>
#include <commctrl.h>
#include <format>

AiAssistWnd::AiAssistWnd(HINSTANCE hInst, const NppData& nppData)
    : DockingDlgInterface(IDD_DIALOG_AI_ASSIST), _hInst(hInst), _nppData(nppData)
{
    this->_hParent = _nppData._nppHandle;
}

AiAssistWnd::~AiAssistWnd()
{
    if (_hFont) DeleteObject(_hFont);
    if (_hBoldFont) DeleteObject(_hBoldFont);
}

void AiAssistWnd::init()
{
    DockingDlgInterface::init(_hInst, _hParent);

    // 注册Dock窗口
    tTbData tbData = { 0 };
    DockingDlgInterface::create(&tbData);
    tbData.uMask = DWS_DF_CONT_RIGHT | DWS_ICONTAB;;
    tbData.pszModuleName = L"AI Assistant";;
    tbData.dlgID = _dlgID;
    ::SendMessage(_nppData._nppHandle, NPPM_DMMREGASDCKDLG, 0, (LPARAM)&tbData);

    // 初始化UI控件
    initControls();

    // 设置初始大小
    RECT rc;
    GetClientRect(_hSelf, &rc);
    ::SetWindowPos(_hSelf, nullptr, rc.left, rc.top, 300, rc.bottom, SWP_NOZORDER | SWP_NOMOVE);

    layoutControls(rc.right, rc.bottom);
}

void AiAssistWnd::initControls()
{
    // 创建控件
    _hModelCombo = ::CreateWindowExW(0, WC_COMBOBOX, L"",
        CBS_DROPDOWNLIST | WS_CHILD | WS_VISIBLE | WS_TABSTOP,
        0, 0, 0, 0, _hSelf, (HMENU)IDC_MODEL_COMBO, _hInst, nullptr);

    _hInputEdit = ::CreateWindowExW(WS_EX_CLIENTEDGE, L"EDIT", L"",
        WS_CHILD | WS_VISIBLE | ES_MULTILINE | ES_AUTOVSCROLL | WS_VSCROLL,
        0, 0, 0, 0, _hSelf, (HMENU)IDC_INPUT_EDIT, _hInst, nullptr);

    // 使用RichEdit 4.1
    LoadLibraryW(L"Msftedit.dll");
    _hAnswerView = ::CreateWindowExW(WS_EX_CLIENTEDGE, MSFTEDIT_CLASS, L"",
        WS_CHILD | WS_VISIBLE | ES_MULTILINE | ES_READONLY | WS_VSCROLL | WS_HSCROLL,
        0, 0, 0, 0, _hSelf, (HMENU)IDC_ANSWER_VIEW, _hInst, nullptr);

    // 初始化字体
    _hFont = CreateFontW(14, 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE,
        DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS,
        DEFAULT_QUALITY, DEFAULT_PITCH, L"Segoe UI");

    _hBoldFont = CreateFontW(14, 0, 0, 0, FW_SEMIBOLD, FALSE, FALSE, FALSE,
        DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS,
        DEFAULT_QUALITY, DEFAULT_PITCH, L"Segoe UI");

    // 应用字体
    SendMessageW(_hModelCombo, WM_SETFONT, (WPARAM)_hFont, TRUE);
    SendMessageW(_hInputEdit, WM_SETFONT, (WPARAM)_hFont, TRUE);
    SendMessageW(_hAnswerView, WM_SETFONT, (WPARAM)_hFont, TRUE);
}

INT_PTR AiAssistWnd::run_dlgProc(UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message) {
    case WM_SIZE:
        if (wParam != SIZE_MINIMIZED) {
            RECT rc;
            GetClientRect(_hSelf, &rc);
            layoutControls(rc.right, rc.bottom);
        }
        return TRUE;

    case WM_COMMAND:
        if (HIWORD(wParam) == EN_MAXTEXT && LOWORD(wParam) == IDC_INPUT_EDIT) {
            handleUserInput();
            return TRUE;
        }
        break;

    case WM_NOTIFY:
        // 处理其他通知消息
        break;

    case WM_CTLCOLOREDIT:
        // 设置输入框背景色
        if ((HWND)lParam == _hInputEdit) {
            SetBkColor((HDC)wParam, RGB(255, 255, 255));
            return (INT_PTR)GetStockObject(WHITE_BRUSH);
        }
        break;
    }

    return DockingDlgInterface::run_dlgProc(message, wParam, lParam);
}

void AiAssistWnd::layoutControls(int width, int height)
{
    int yPos = CONTROL_MARGIN;
    int nHeight = CONTROL_MARGIN;

    // 回答区域
    int answerHeight = height - 3*CONTROL_MARGIN - COMBO_HEIGHT - INPUT_HEIGHT;
    ::MoveWindow(_hAnswerView,
        CONTROL_MARGIN, yPos,
        width - 2 * CONTROL_MARGIN, answerHeight, TRUE);
    yPos += CONTROL_MARGIN + answerHeight;

    // 模型选择框
    ::MoveWindow(_hModelCombo,
        CONTROL_MARGIN, yPos,
        width - 2 * CONTROL_MARGIN, COMBO_HEIGHT, TRUE);
    yPos += COMBO_HEIGHT + CONTROL_MARGIN;

    // 输入框
    ::MoveWindow(_hInputEdit,
        CONTROL_MARGIN, yPos,
        width - 2 * CONTROL_MARGIN, INPUT_HEIGHT, TRUE);
    yPos += INPUT_HEIGHT + CONTROL_MARGIN;
}

void AiAssistWnd::updateModelList(const std::vector<std::wstring>& models)
{
    SendMessageW(_hModelCombo, CB_RESETCONTENT, 0, 0);
    for (const auto& model : models) {
        SendMessageW(_hModelCombo, CB_ADDSTRING, 0, (LPARAM)model.c_str());
    }
    if (!models.empty()) {
        SendMessageW(_hModelCombo, CB_SETCURSEL, 0, 0);
    }
}

void AiAssistWnd::appendAnswer(const std::wstring& answer)
{
    // 添加时间戳
    SYSTEMTIME st;
    GetLocalTime(&st);
    std::wstring timestamp = std::format(L"[{:02}:{:02}:{:02}] ",
        st.wHour, st.wMinute, st.wSecond);

    // 设置富文本格式
    CHARFORMAT2W cf = { sizeof(CHARFORMAT2W) };
    cf.dwMask = CFM_COLOR | CFM_BOLD;
    cf.crTextColor = RGB(0, 128, 0);
    cf.dwEffects = CFE_BOLD;
    SendMessageW(_hAnswerView, EM_SETCHARFORMAT, SCF_SELECTION, (LPARAM)&cf);
    SendMessageW(_hAnswerView, EM_REPLACESEL, FALSE, (LPARAM)timestamp.c_str());

    // 恢复默认格式
    cf.dwMask = CFM_COLOR;
    cf.crTextColor = RGB(0, 0, 0);
    cf.dwEffects = 0;
    SendMessageW(_hAnswerView, EM_SETCHARFORMAT, SCF_SELECTION, (LPARAM)&cf);
    SendMessageW(_hAnswerView, EM_REPLACESEL, FALSE, (LPARAM)answer.c_str());

    // 自动滚动到底部
    SendMessageW(_hAnswerView, WM_VSCROLL, SB_BOTTOM, 0);
}

void AiAssistWnd::clearConversation()
{
    if (!IsWindow(_hAnswerView))
        return;

    // 使用SETTEXTEX结构清空内容
    SETTEXTEX st = {
        ST_DEFAULT,    // 标志位
        1200           // 使用UTF-16编码
    };

    // 方法1:直接设置空文本(保留格式)
    SendMessageW(_hAnswerView, EM_SETTEXTEX, (WPARAM)&st, (LPARAM)L"");

    // 方法2:通过选择全部删除(更彻底)
    // SendMessageW(_hAnswerView, EM_SETSEL, 0, -1);   // 全选
    // SendMessageW(_hAnswerView, EM_REPLACESEL, 0, (LPARAM)L""); // 替换为空

    // 可选:重置滚动条位置
    SendMessageW(_hAnswerView, WM_VSCROLL, SB_TOP, 0);

    // 可选:清除Undo缓冲区
    SendMessageW(_hAnswerView, EM_EMPTYUNDOBUFFER, 0, 0);
}
void AiAssistWnd::handleUserInput()
{
    // 获取输入文本
    int len = GetWindowTextLengthW(_hInputEdit) + 1;
    std::wstring input(len, L'\0');
    GetWindowTextW(_hInputEdit, &input[0], len);
    input.resize(len - 1); // 移除结尾的null字符

    if (!input.empty()) {
        // TODO: 触发AI处理逻辑
        appendAnswer(L"Received: " + input);

        // 清空输入框
        SetWindowTextW(_hInputEdit, L"");
    }
}

三、主要菜单功能实现

1.定义菜单

// PluginDefinition.h
//-----------------------------------------------//
//-- STEP 2. DEFINE YOUR PLUGIN COMMAND NUMBER --//
//-----------------------------------------------//
//
// Here define the number of your plugin commands
//
const int nbFunc = 6;

//
// Your plugin command functions
//
//
// 参数设置
void PluginConfig();
// 打开Ai助手窗口
void OpenAiAssistWnd();
// 解读代码
void ReadCode();
// 代码优化
void OptimizeCode();
// 添加代码注释
void AddCodeComment();
// 选中即问
void AskBySelectedText();

2.初始化菜单

// PluginDefinition.cpp
//
// Initialization of your plugin commands
// You should fill your plugins commands here
void commandMenuInit()
{

    //--------------------------------------------//
    //-- STEP 3. CUSTOMIZE YOUR PLUGIN COMMANDS --//
    //--------------------------------------------//
    // with function :
    // setCommand(int index,                      // zero based number to indicate the order of command
    //            TCHAR *commandName,             // the command name that you want to see in plugin menu
    //            PFUNCPLUGINCMD functionPointer, // the symbol of function (function pointer) associated with this command. The body should be defined below. See Step 4.
    //            ShortcutKey *shortcut,          // optional. Define a shortcut to trigger this command
    //            bool check0nInit                // optional. Make this menu item be checked visually
    //            );

    // 初始化数据
    g_pNppImp = new NppImp(g_nppData);

    // 初始化菜单
    ShortcutKey* pSck = new ShortcutKey[nbFunc];
    g_pShortcutKeys = pSck;
    size_t nCid = 0;
    setCommand(nCid, L"参数配置", PluginConfig, NULL, false); ++nCid;
    pSck[nCid] = { false, true, false, 'K' };
    setCommand(nCid, L"显示窗口", OpenAiAssistWnd, pSck + nCid, false); ++nCid;
    pSck[nCid] = { false, true, false, 'J' };
    setCommand(nCid, L"解读代码", ReadCode, pSck + nCid, false); ++nCid;
    pSck[nCid] = { false, true, false, 'Y' };
    setCommand(nCid, L"优化代码", OptimizeCode, pSck + nCid, false); ++nCid;
    pSck[nCid] = { false, true, false, 'Z' };
    setCommand(nCid, L"代码注释", AddCodeComment, pSck + nCid, false); ++nCid;
    pSck[nCid] = { false, true, false, 'A' };
    setCommand(nCid, L"选中即问", AskBySelectedText, pSck + nCid, false); ++nCid;
}

3.打开AI窗口

// PluginDefinition.cpp
// 打开Ai助手窗口
void OpenAiAssistWnd()
{
    // 初始化Dock窗口
    if (g_pAiWnd == nullptr && g_hModule != nullptr && g_nppData._nppHandle != nullptr)
    {
        g_pAiWnd = new AiAssistWnd((HINSTANCE)g_hModule, g_nppData);
        g_pAiWnd->init();
    }
    if (g_pAiWnd)
    {
        g_pAiWnd->display(true);
    }
}

四、效果展示

1.插件菜单

插件菜单

2.AI窗口

Ai窗口

五、结语

到这里,已经在Notepad++中支持AI对话窗口了,已经有一点Cursor的AI编辑器的意思了,下一步将进一步完善功能实现,包括配置加载、配置窗口以及界面功能实现。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2330783.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Vue3在ZKmall开源商城前端的应用实践与技术创新

ZKmall开源商城作为一款企业级电商解决方案&#xff0c;其前端架构基于Vue3实现了高效、灵活的开发模式&#xff0c;结合响应式设计、组件化开发与全链路性能优化&#xff0c;为多端协同和复杂业务场景提供了先进的技术支持。以下从技术架构、核心特性、性能优化等维度解析Vue3…

SpringAI+MCP协议 实战

文章目录 前言快速实战Spring AISpring AI 集成 MCP 协议Spring Mcp Client 示例Spring Mcp Server 示例 前言 尽管Python最近成为了编程语言的首选&#xff0c;但是Java在人工智能领域的地位同样不可撼动&#xff0c;得益于强大的Spring框架。随着人工智能技术的快速发展&…

[数据结构]图krusakl算法实现

目录 Kruskal算法 Kruskal算法 我们要在连通图中去找生成树 连通图&#xff1a;在无向图中&#xff0c;若从顶点v1到顶点v2有路径&#xff0c;则称顶点v1与顶点v2是连通的。如果图中任意一对顶点都是连通的&#xff0c;则称此图为连通图。 生成树&#xff1a;一个连通图的最小…

QEMU学习之路(5)— 从0到1构建Linux系统镜像

QEMU学习之路&#xff08;5&#xff09;— 从0到1构建Linux系统镜像 一、前言 参考&#xff1a;从内核到可启动镜像&#xff1a;0到1构建你的极简Linux系统 二、linux源码获取 安装编译依赖 sudo apt install -y build-essential libncurses-dev flex bison libssl-dev li…

node ---- 解决错误【Error: error:0308010C:digital envelope routines::unsupported】

1. 报错 在 Node.js 18.18.0 的版本中&#xff0c;遇到以下错误&#xff1a; this[kHandle] new _Hash(algorithm, xofLen);^ Error: error:0308010C:digital envelope routines::unsupported这个错误通常发生在运行项目或构建时&#xff0c;尤其是在使用 Webpack、Vite 或其他…

蓝桥杯——走迷宫问题(BFS)

这是一个经典的BFS算法 1. BFS算法保证最短路径 核心机制&#xff1a;广度优先搜索按层遍历所有可能的路径&#xff0c;首次到达终点的路径长度即为最短步数。这是BFS的核心优势。队列的作用&#xff1a;通过队列按先进先出的顺序处理节点&#xff0c;确保每一步探索的都是当…

详解 Redis repl_backlog_buffer(如何判断增量同步)

一、repl_backlog_buffer 复制积压缓冲区&#xff08;Replication Backlog Buffer&#xff09; 是一个环形内存区域&#xff08;Ring Buffer&#xff09;&#xff0c;用于临时保存主节点最近写入的写命令&#xff0c;以支持从节点断线重连后的增量同步。 1.1 三个复制偏移量 …

使用PyTorch实现ResNet:从残差块到完整模型训练

ResNet&#xff08;残差网络&#xff09;是深度学习中的经典模型&#xff0c;通过引入残差连接解决了深层网络训练中的梯度消失问题。本文将从残差块的定义开始&#xff0c;逐步实现一个ResNet模型&#xff0c;并在Fashion MNIST数据集上进行训练和测试。 1. 残差块&#xff08…

Scala相关知识学习总结5

1、多维数组 定义&#xff1a; val arr Array.ofDim[Double](3,4) 表示二维数组中有三个一维数组&#xff0c;每个一维数组有四个元素。 2、列表 List 不可变 List&#xff1a;默认不可变&#xff0c;可创建有序且可重复的列表&#xff0c;可使用:从右向左增加数据&#xf…

Day1:前端项目uni-app壁纸实战

uni-app官网下载HBuilder。 uni-app快速上手 | uni-app官网 点击HBuilder 安装 新建项目 工具——插件安装 安装uni-app&#xff08;vue3&#xff09; 我们先来准备一下&#xff1a; 先在wallpaper下新建目录 我已经建过了 同样&#xff0c;再在common下建images和style目录&…

光谱相机的光谱数据采集原理

光谱相机的光谱数据采集原理基于‌分光技术‌和‌光电信号转换‌&#xff0c;通过将入射光按波长分解并记录各波段的强度信息&#xff0c;最终生成包含空间和光谱维度的数据立方体。以下是详细原理分解&#xff1a; ‌1. 分光技术&#xff1a;将复合光分解为单色光‌ 光谱相机…

宏碁笔记本电脑擎7PRO搭载的 NVIDIA RTX 5080 显卡安装pytorch

宏碁笔记本电脑擎7PRO搭载的 NVIDIA RTX 5080 显卡是一款高性能移动 GPU&#xff0c;基于 NVIDIA 最新的 Blackwell 架构设计&#xff0c;通过修正架构&#xff08;Blackwell&#xff09;、显存类型与带宽&#xff08;GDDR7、960GB/s&#xff09;、Tensor Core 与 RT Core 全面…

html+css+js 实现一个贪吃蛇小游戏

目录 游戏简介 游戏功能与特点 如何玩转贪吃蛇 游戏设计与实现 HTML结构 JavaScript核心实现 代码结构&#xff1a; 效果 关于“其他游戏” 游戏简介 贪吃蛇是一款经典的单人小游戏&#xff0c;玩家通过控制蛇的移动&#xff0c;吃掉食物来增加长度&#xff0c;避免撞…

Python爬虫生成CSV文件的完整流程

引言 在当今数据驱动的时代&#xff0c;网络爬虫已成为获取互联网数据的重要工具。Python凭借其丰富的库生态系统和简洁的语法&#xff0c;成为了爬虫开发的首选语言。本文将详细介绍使用Python爬虫从网页抓取数据并生成CSV文件的完整流程&#xff0c;包括环境准备、网页请求、…

21.OpenCV获取图像轮廓信息

OpenCV获取图像轮廓信息 在计算机视觉领域&#xff0c;识别和分析图像中的对象形状是一项基本任务。OpenCV 库提供了一个强大的工具——轮廓检测&#xff08;Contour Detection&#xff09;&#xff0c;它能够帮助我们精确地定位对象的边界。这篇博文将带你入门 OpenCV 的轮廓…

医学图像分割效率大幅提升!U-Net架构升级,助力精度提升5%!

在医学图像分割领域&#xff0c;U-Net模型及其变体的创新应用正在带来显著的性能提升和效率优化。最新研究显示&#xff0c;通过引入结构化状态空间模型&#xff08;SSM&#xff09;和轻量级LSTM&#xff08;xLSTM&#xff09;等技术&#xff0c;VMAXL-UNet模型在多个医学图像数…

智能设备运行监控系统

在工业 4.0 与智能制造浪潮下&#xff0c;设备运行效率与稳定性成为企业竞争力的核心要素。然而&#xff0c;传统设备管理模式面临数据采集分散、状态分析滞后、维护成本高昂等痛点。为破解这些难题&#xff0c;设备运行监控系统应运而生&#xff0c;通过融合智能传感、5G 通信…

服务器报错:xxx/libc.so.6: version `GLIBC_2.32‘ not found

/lib/x86_64-linux-gnu/libc.so.6: version GLIBC_2.32 not found (required by ./aima-sim-app-main) 解决思路 根据错误信息&#xff0c;您的应用程序 aima-sim-app-main 和 libmujoco.so.3.1.6 库依赖于较新的 GNU C Library (glibc) 版本&#xff08;如 GLIBC_2.32, GLIBC…

Flutter之页面布局一

目录&#xff1a; 1、页面布局一2、无状态组件StatelessWidget和有状态组件StatefulWidget2.1、无状态组件示例2.2、有状态组件示例2.3、在 widget 之间共享状态1、使用 widget 构造函数2、使用 InheritedWidget3、使用回调 3、布局小组件3.1、布置单个 Widget3.2、容器3.3、垂…

架构思维: 数据一致性的两种场景深度解读

文章目录 Pre案例数据一致性问题的两种场景第一种场景&#xff1a;实时数据不一致不要紧&#xff0c;保证数据最终一致性就行第二种场景&#xff1a;必须保证实时一致性 最终一致性方案实时一致性方案TCC 模式Seata 中 AT 模式的自动回滚一阶段二阶段-回滚二阶段-提交 Pre 架构…