【AI插件开发】Notepad++ AI插件开发实践:支持配置界面
一、引用
此前的系列文章已基本完成了Notepad++的AI插件的功能开发,但是此前使用的配置为JSON配置文件,不支持界面配置。
本章在此基础上集成支持配置界面,这样不需要手工修改配置文件,直接在界面上操作,方便快捷。
注:项目已开源、镜像,欢迎Start、Fork使用和指正。
二、配置界面设计
在插件的菜单栏中支持参数配置,用户点击该菜单时弹出配置对话框,在该配置界面对配置进行增删改查。
{"platform": "infini","timeout": 90,"platforms": {"infini": {"enable_ssl": true,"base_url": "cloud.infini-ai.com","authorization": {"type": "Bearer","data": "sk-xxx"},"model_name": "deepseek-r1-distill-qwen-32b","models": [ "deepseek-r1-distill-qwen-32b", "deepseek-r1", "deepseek-v3" ],"generate_endpoint": {"method": "post","api": "/maas/v1/completions","prompt": ""},"chat_endpoint": {"method": "post","api": "/maas/v1/chat/completions","prompt": ""},"models_endpoint": {}}}
}
根据现有的配置文件格式,对配置界面分为两块区域,区域一配置插件相关的参数,区域二设计一个AI平台的下拉列表框,支持AI平台相关的参数。
考虑到AI平台有多个接口(虽然现在只用了对话接口),因此接口部分使用表单,但表单不方便修改,因此需要新增一个字段编辑对话框,双击列表行时支持编辑列表行。
这样,界面设计差不多这样了。因为使用原生的Windows编程,因此需要花费较多的时间处理界面、事件,对Windows接口也是半生不熟,一边做一边查,所以还是比较费时间的,整体功能流程如下:
先看下效果图:

三、参数配置界面
1. 创建对话框资源
- 先在插件的资源文件中新建一个ID为
IDD_DIALOG_PLUG_CONFIG的对话框,设计菜单界面,如下:

2. 新建一个类,关联该对话框资源
PluginConfigDlg::PluginConfigDlg(HINSTANCE hInstance, Scintilla::PluginConfig& plugConf) : m_hInstance(hInstance), m_plugConfig(plugConf)
{// 创建无模式对话框m_hDlg = CreateDialogParam(m_hInstance,MAKEINTRESOURCE(IDD_DIALOG_PLUG_CONFIG),nullptr,DlgProc,reinterpret_cast<LPARAM>(this));if (m_hDlg) {InitControls();LoadConfig();}
}
在构造函数中创建对话框,关联对话框资源ID,并指定消息处理函数为DlgProc:
INT_PTR CALLBACK PluginConfigDlg::DlgProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {if (uMsg == WM_INITDIALOG) {// 关联类实例指针到窗口SetWindowLongPtr(hDlg, GWLP_USERDATA, lParam);}// 获取类实例指针PluginConfigDlg* pThis = reinterpret_cast<PluginConfigDlg*>(GetWindowLongPtr(hDlg, GWLP_USERDATA));if (pThis) {return pThis->RealDlgProc(hDlg, uMsg, wParam, lParam);}return FALSE;
}
在DlgProc中处理WM_INITDIALOG消息,关联类实例指针到窗口,并将事件透传到类的实际消息处理函数RealDlgProc,RealDlgProc可以便捷地访问操作类对象成员变量及函数。
3. 初始化界面
因为界面部分控件是下拉组合框和列表,因此需先初始化该部分,主要是初始化下拉列表数据、表单头,方便后续直接使用:
void PluginConfigDlg::InitControls()
{// 授权类型HWND hWnd = GetDlgItem(m_hDlg, IDC_COMBO_AUTH_TYPE);SendMessage(hWnd, CB_RESETCONTENT, 0, 0);SendMessageW(hWnd, CB_ADDSTRING, 0, (LPARAM)L"无");SendMessageW(hWnd, CB_ADDSTRING, 0, (LPARAM)L"Basic");SendMessageW(hWnd, CB_ADDSTRING, 0, (LPARAM)L"Bearer");SendMessageW(hWnd, CB_ADDSTRING, 0, (LPARAM)L"ApiKey");// 接口列表HWND hList = GetDlgItem(m_hDlg, IDC_LIST_ENDPOINT);// 1. 设置基础样式(必须包含 LVS_REPORT)SetWindowLongW(hList, GWL_STYLE, GetWindowLongW(hList, GWL_STYLE) | LVS_REPORT | // 报表视图LVS_SINGLESEL // 禁止多选);// 2. 配置扩展样式ListView_SetExtendedListViewStyle(hList, LVS_EX_GRIDLINES | // 显示网格线LVS_EX_FULLROWSELECT // 整行选中);// 3. 初始化列头LVCOLUMNW lvc = {0};lvc.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT;// 批量添加列const struct {int width;const wchar_t* title;} columns[] = {{50, L"名称"},{50, L"方法"},{300, L"接口"},{150, L"参数"}};for (size_t i = 0; i < _countof(columns); ++i) {lvc.fmt = LVCFMT_CENTER;lvc.cx = columns[i].width;lvc.pszText = const_cast<LPWSTR>(columns[i].title);ListView_InsertColumn(hList, i, &lvc);}
}
然后是将配置文件中的内容在界面上显示,设置超时、设置当前平台:
bool PluginConfigDlg::LoadConfig()
{SetDlgItemTextA(m_hDlg, IDC_EDIT_TIMEOUT, std::to_string(m_plugConfig.timeout).c_str());int nSel = 0;HWND hWnd = GetDlgItem(m_hDlg, IDC_COMBO_PLATFORM);SendMessage(hWnd, CB_RESETCONTENT, 0, 0);int nIdx = 0;for (auto& p : m_plugConfig.platforms){SendMessageA(hWnd, CB_ADDSTRING, 0, (LPARAM)p.first.c_str());if (!Scintilla::String::icasecompare(p.first, m_plugConfig.platform)){nSel = nIdx;}nIdx++;}SendMessage(hWnd, CB_SETCURSEL, (WPARAM)nSel, 0);auto& platform = m_plugConfig.Platform();Load(platform);return true;
}
其中平台配置信息只显示单个,即当前选中的平台,启动时显示当前配置的平台信息,然后调用Load函数加载该平台信息配置。考虑到配置需要支持增删改查,因此通过平台组合框下拉列表可以切换到不同平台:
void PluginConfigDlg::Load(const Scintilla::PlatformConfig& platform)
{// SSLCheckDlgButton(m_hDlg, IDC_CHECK_SSL, platform.enable_ssl ? BST_CHECKED : BST_UNCHECKED);// 授权类型HWND hWnd = GetDlgItem(m_hDlg, IDC_COMBO_AUTH_TYPE);SendMessage(hWnd, CB_SETCURSEL, (WPARAM)(int)platform.authorization.eAuthType, 0);// 授权数据SetDlgItemTextA(m_hDlg, IDC_EDIT_AUTH_DATA, platform.authorization.auth_data.c_str());// 根地址SetDlgItemTextA(m_hDlg, IDC_EDIT_ROOT_URL, platform.base_url.c_str());// 模型名称int nSel = 0;int nIdx = 0;hWnd = GetDlgItem(m_hDlg, IDC_COMBO_MODEL_NAME);SendMessage(hWnd, CB_RESETCONTENT, 0, 0);for (auto& m : platform.models){SendMessageA(hWnd, CB_ADDSTRING, 0, (LPARAM)m.c_str());if (m == platform.model_name){nSel = nIdx;}nIdx++;}SendMessage(hWnd, CB_SETCURSEL, (WPARAM)nSel, 0);hWnd = GetDlgItem(m_hDlg, IDC_LIST_ENDPOINT);ListView_DeleteAllItems(hWnd);if (!platform.base_url.empty()){auto pE = &platform.chat_endpoint;ListViewAddRow(hWnd, { "对话", pE->method, pE->api, pE->prompt});pE = &platform.generate_endpoint;ListViewAddRow(hWnd, { "生成", pE->method, pE->api, pE->prompt});pE = &platform.models_endpoint;ListViewAddRow(hWnd, { "模型", pE->method, pE->api, pE->prompt});}
}
4. 切换平台
处理平台切换事件,并显示切换后平台信息
#define OnDlgItemEvent(nItemId, nEventId, fnCall) if(LOWORD(wParam) == nItemId && HIWORD(wParam) == nEventId) { fnCall(); return TRUE; }
INT_PTR PluginConfigDlg::RealDlgProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{switch (uMsg) {case WM_CLOSE:DestroyWindow(m_hDlg);return TRUE;case WM_INITDIALOG:return TRUE;case WM_COMMAND:{OnDlgItemEvent(IDC_COMBO_PLATFORM, CBN_SELCHANGE, OnPlatformChange);OnDlgItemEvent(IDC_COMBO_MODEL_NAME, CBN_SELCHANGE, OnModelChange);OnDlgItemEvent(IDC_BUTTON_MODEL_SAVE, BN_CLICKED, OnSaveMode);OnDlgItemEvent(IDC_BUTTON_MODEL_DEL, BN_CLICKED, OnRemoveModel);OnDlgItemEvent(IDC_BUTTON_PLATFORM_SAVE, BN_CLICKED, OnSavePlatform);OnDlgItemEvent(IDC_BUTTON_PLATFORM_DEL, BN_CLICKED, OnRemovePlatform);OnDlgItemEvent(IDCANCEL, BN_CLICKED, LoadConfig);OnDlgItemEvent(IDOK, BN_CLICKED, SaveConfig);}break;case WM_NOTIFY:LPNMITEMACTIVATE pNmItem = (LPNMITEMACTIVATE)lParam;if (pNmItem->hdr.idFrom == IDC_LIST_ENDPOINT && pNmItem->hdr.code == NM_DBLCLK) {OnEndpointListViewDBClick(pNmItem);return TRUE;}break;}return FALSE;
}
在窗口事件处理函数中,处理WM_COMMAND消息,根据消息参数识别出控件对象和消息类型,处理平台切换是OnDlgItemEvent(IDC_COMBO_PLATFORM, CBN_SELCHANGE, OnPlatformChange),实现内容在函数OnPlatformChange中:
void PluginConfigDlg::OnPlatformChange()
{std::string name;if (!GetComboSelectedText(GetDlgItem(m_hDlg, IDC_COMBO_PLATFORM), name) || name.empty()){return;}auto e = m_plugConfig.platforms.find(name);if (e == m_plugConfig.platforms.end()){return;}Load(e->second);
}
获取ComboBox的当前选中项,注意:不能取当前控件的文本即GetWindowText,否则取到的是选中前的内容。然后调用Load函数加载切换后的AI平台配置信息。
5. 模型删除
void PluginConfigDlg::OnRemoveModel()
{HWND hCombo = GetDlgItem(m_hDlg, IDC_COMBO_MODEL_NAME);if (hCombo == nullptr){return;}auto name = String::Trim(GetComboSelectedText(IDC_COMBO_MODEL_NAME));if (name.empty()){ShowConfigError("请输入或选择当前模型名称");return;}auto nRet = ShowMsgBox(String::Format("是否要删除模型【%s】配置?", name.c_str()), "删除确认", MB_YESNO | MB_ICONQUESTION);if (nRet != IDYES){return;}SendMessageA(hCombo, CB_DELETESTRING, 0, (LPARAM)name.c_str());
}
删除当前选中项的模型名称,仅是从列表中删除,不是从对象的内存中删除,后续通过保存平台配置时更新删除后的列表。
6. 模型添加
void PluginConfigDlg::OnSaveMode()
{HWND hCombo = GetDlgItem(m_hDlg, IDC_COMBO_MODEL_NAME);if (hCombo == nullptr){return;}auto name = String::Trim(GetComboSelectedText(IDC_COMBO_MODEL_NAME));if (name.empty()){ShowConfigError("请输入或选择当前模型名称");return;}SendMessageA(hCombo, CB_ADDSTRING, 0, (LPARAM)name.c_str());
}
删除当前选中项的模型名称,和删除类似,仅是从列表中删除。
7.删除平台配置
void PluginConfigDlg::OnRemovePlatform()
{HWND hPlatform = GetDlgItem(m_hDlg, IDC_COMBO_PLATFORM);if (hPlatform == nullptr){return;}std::string name;auto pPlat = GetCurSelPlatform(name);if (!pPlat){ShowConfigError("请选择当前平台名称");return;}auto nRet = ShowMsgBox(String::Format("是否要删除平台【%s】配置?", name.c_str()), "删除确认", MB_YESNO | MB_ICONQUESTION);if (nRet != IDYES){return;}SendMessageA(hPlatform, CB_DELETESTRING, 0, (LPARAM)name.c_str());m_plugConfig.platforms.erase(name);// 清空数据Load(PlatformConfig());
}
获取当前选中的平台配置,然后从列表和内存中删除该平台配置,删除后清空平台配置信息(通过加载一个空对象实现),此外为防止误删,删除前做了弹框确认。
8.修改或新增平台配置
bool PluginConfigDlg::OnSavePlatform()
{HWND hPlatform = GetDlgItem(m_hDlg, IDC_COMBO_PLATFORM);if (hPlatform == nullptr){return false;}std::string name;auto pPlat = GetCurSelPlatform(name);if (name.empty()){ShowConfigError("请输入或选择当前平台名称");return false;}Scintilla::PlatformConfig plat;if (!Save(plat)){return false;}if (pPlat == nullptr){// 新增SendMessageA(hPlatform, CB_ADDSTRING, 0, (LPARAM)name.c_str());m_plugConfig.platforms[name] = plat;}else{*pPlat = plat;}m_plugConfig.Save("");return true;
}
先把界面上平台信息配置保存到一个临时变量中(防止保存了部分就返回),然后根据是否已存在该名称的平台配置决定是新增还是更新信息。
9. 响应接口列表行双击事件
INT_PTR PluginConfigDlg::RealDlgProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{switch (uMsg) {case WM_NOTIFY:LPNMITEMACTIVATE pNmItem = (LPNMITEMACTIVATE)lParam;if (pNmItem->hdr.idFrom == IDC_LIST_ENDPOINT && pNmItem->hdr.code == NM_DBLCLK) {OnEndpointListViewDBClick(pNmItem);return TRUE;}break;}return FALSE;
}
处理窗口的WM_NOTIFY消息,然后根据控件ID和事件类型识别出是列表双击事件,然后列表双击编辑函数OnEndpointListViewDBClick。
四、字段编辑界面
字段编辑框设计为一个通用的编辑窗口,提供一个字段组合,然后界面显示并支持编辑字段信息。
步骤也是和创建配置对话框差不多,不过这里创建的是一个模态对话框,不使用Show显示,而是使用DoModal模态对话框显示。
1. 调用编辑窗口更新字段
void PluginConfigDlg::OnEndpointListViewDBClick(LPNMITEMACTIVATE& pNmItem)
{if (pNmItem == nullptr){return;}int nRow = pNmItem->iItem;if (nRow < 0){return;}std::map<std::string, std::string> fields;if (ListViewGetRow(GetDlgItem(m_hDlg, IDC_LIST_ENDPOINT), nRow, fields) <= 0){return;}FieldEditDlg dlg(m_hInstance, m_hDlg);for (auto& [k, v] : fields){dlg.m_mapField[k] = { v };}dlg.m_mapField["方法"].options = { "post", "get" };dlg.m_mapField["方法"].type = FieldEditDlg::FieldType::Combo;dlg.m_strTitle = "接口参数设置";dlg.m_nLabelWidth = 40;if (dlg.DoModal() != IDOK){return;}std::vector<std::string> vs;std::vector<std::string> names = { "名称", "方法", "接口", "参数" };for (auto& name : names){auto e = dlg.m_mapField.find(name);if (e == dlg.m_mapField.end()){return;}vs.push_back(e->second.val);}ListViewSetRow(GetDlgItem(m_hDlg, IDC_LIST_ENDPOINT), nRow, vs);
}
这里使用map存储字段,感觉使用vector更合适,有序且字段存取方便,后续改一下。
2. 创建模态编辑窗口
#pragma once
#include <windows.h>
#include "PluginConf.h"
#include <commctrl.h>namespace Ui
{class Util{public:// 局长显示窗口static void Show(HWND hWnd, bool bShow, HWND hParent = nullptr);static std::string GetText(HWND hWnd);};
}class FieldEditDlg
{
public:enum class FieldType{Edit,Combo,};struct Field{std::string val;std::vector<std::string> options;FieldType type = FieldType::Edit;bool readonly = false;};FieldEditDlg(HINSTANCE hInstance, HWND hParent);~FieldEditDlg();static INT_PTR CALLBACK DlgProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);INT_PTR DoModal();private:INT_PTR RealDlgProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);void CreateDynamicControls();void OnInitDialog();void OnSave();public:std::map<std::string, Field> m_mapField;int m_nLabelWidth = 100;int m_nBoxWidth = 300;std::string m_strTitle = "字段设置";private:HINSTANCE m_hInstance = nullptr;HWND m_hDlg = nullptr;HWND m_hParent = nullptr;HFONT m_hFont = nullptr;std::map<HWND, std::string> m_hwndMap;
};
注意,这里不在构造函数中初始化创建窗口,而是在DoModal中创建,并等待窗口结束:
INT_PTR FieldEditDlg::DoModal()
{// 创建模态对话框(需提前定义对话框模板ID,假设为IDD_FIELD_EDIT_DLG)return DialogBoxParam(m_hInstance, MAKEINTRESOURCE(IDD_DIALOG_EDIT_FIELD),m_hParent,FieldEditDlg::DlgProc,reinterpret_cast<LPARAM>(this));
}// 对话框消息处理
INT_PTR CALLBACK FieldEditDlg::DlgProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{if (uMsg == WM_INITDIALOG) {// 关联类实例指针到窗口SetWindowLongPtr(hDlg, GWLP_USERDATA, lParam);auto* pThis = reinterpret_cast<FieldEditDlg*>(lParam);pThis->m_hDlg = hDlg;pThis->OnInitDialog();Ui::Util::Show(hDlg, true);return TRUE;}// 获取类实例指针auto pThis = reinterpret_cast<FieldEditDlg*>(GetWindowLongPtr(hDlg, GWLP_USERDATA));if (pThis) {return pThis->RealDlgProc(hDlg, uMsg, wParam, lParam);}return FALSE;
}INT_PTR FieldEditDlg::RealDlgProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{switch (uMsg) {case WM_INITDIALOG: {SetWindowLongPtr(hDlg, GWLP_USERDATA, (LONG_PTR)this);OnInitDialog();return TRUE;}case WM_COMMAND: {int wmId = LOWORD(wParam);int wmEvent = HIWORD(wParam);if (wmId == IDOK && wmEvent == BN_CLICKED) {OnSave();EndDialog(m_hDlg, IDOK);return TRUE;} else if (wmId == IDCANCEL && wmEvent == BN_CLICKED) {EndDialog(m_hDlg, IDCANCEL);return TRUE;}break;}case WM_CLOSE:EndDialog(m_hDlg, IDCLOSE);return TRUE;}return FALSE;
}void FieldEditDlg::OnInitDialog()
{if(!m_mapField.empty()) CreateDynamicControls();SetWindowTextA(m_hDlg, m_strTitle.c_str());
}
3. 更新保存数据
void FieldEditDlg::OnSave()
{for (auto& [k, v] : m_hwndMap){m_mapField[v].val = Ui::Util::GetText(k);}
}
5. 总结说明
这一篇文章主要介绍了关于配置的两个对话框的实现,完成了手工编辑JSON配置文件到界面快捷配置的革命转换,本文主要涉及的技术要点如下:
相关文章:
【AI插件开发】Notepad++ AI插件开发实践:支持配置界面
一、引用 此前的系列文章已基本完成了Notepad的AI插件的功能开发,但是此前使用的配置为JSON配置文件,不支持界面配置。 本章在此基础上集成支持配置界面,这样不需要手工修改配置文件,直接在界面上操作,方便快捷。 注…...
polkitd服务无法启动导致docker无法启动问题解决
问题docker服务无法启动,溯源发现是polkit服务没有正确运行 systemctl status polkit可以看到类似提示 Sep 18 02:58:24 server1 dbus[897]: [system] Failed to activate service org.freedesktop.PolicyKit1: timed out Sep 18 02:59:29 server1 systemd[1]: po…...
软件工程中数据一致性的探讨
软件工程中数据一致性的探讨 引言数据一致性:软件工程中的业务正确性与性能的权衡数据一致性为何重要业务正确性:事务的原子性与一致性ACID原则的基石分布式事务的挑战一致性级别:从强一致到最终一致 实践中的一致性权衡金融系统:…...
数据库原理及应用mysql版陈业斌实验四
🏝️专栏:Mysql_猫咪-9527的博客-CSDN博客 🌅主页:猫咪-9527-CSDN博客 “欲穷千里目,更上一层楼。会当凌绝顶,一览众山小。” 目录 实验四索引与视图 1.实验数据如下 student 表(学生表&…...
华为OD机试真题——最长的顺子(2025A卷:100分)Java/python/JavaScript/C++/C语言/GO六种最佳实现
2025 A卷 100分 题型 本文涵盖详细的问题分析、解题思路、代码实现、代码详解、测试用例以及综合分析; 并提供Java、python、JavaScript、C、C语言、GO六种语言的最佳实现方式! 本文收录于专栏:《2025华为OD真题目录全流程解析/备考攻略/经验…...
【HTML】html文件
HTML文件全解析:搭建网页的基石 在互联网的广袤世界里,每一个绚丽多彩、功能各异的网页背后,都离不开HTML文件的默默支撑。HTML,即超文本标记语言(HyperText Markup Language),作为网页创建的基…...
使用 XWPFDocument 生成表格时固定列宽度
一、XWPFDocument XWPFTable个性化属性 1.初始默认写法 XWPFTable table document.createTable(n, m); //在文档中创建一个n行m列的表格 table.setWidth("100%"); // 表格占页面100%宽度// 通过getRow获取行进行自定义设置 XWPFTableRow row table.getRow(0); XW…...
足球AI模型:一款用数据分析赛事的模型
2023 年欧冠决赛前,某体育数据平台的 AI 模型以 78% 的概率预测曼城夺冠 —— 最终瓜迪奥拉的球队首次捧起大耳朵杯。当足球遇上 AI,那些看似玄学的 "足球是圆的",正在被数据与算法拆解成可计算的概率命题。今天我们就来聊聊&#…...
【ESP32|音频】一文读懂WAV音频文件格式【详解】
简介 最近在学习I2S音频相关内容,无可避免会涉及到关于音频格式的内容,所以刚开始接触的时候有点一头雾水,后面了解了下WAV相关内容,大致能够看懂wav音频格式是怎么样的了。本文主要为后面ESP32 I2S音频系列文章做铺垫࿰…...
万向死锁的发生
我是标题 1.欧拉角2.万向死锁 参考:小豆8593 1.欧拉角 欧拉角在Unity中描述的是一种变换(Transform)共有3个轴体,默认顺序为x->y->z. 2.万向死锁 可以把万向死锁的情况理解成:由于轴体旋转的顺序是固定的&am…...
JavaScript学习教程,从入门到精通,JavaScript BOM (Browser Object Model) 详解(18)
JavaScript BOM (Browser Object Model) 详解 1. BOM 介绍 BOM (Browser Object Model) 是浏览器对象模型,它提供了独立于内容而与浏览器窗口进行交互的对象。BOM的核心对象是window,它表示浏览器的一个实例。 BOM包含的主要对象: window…...
人工智能与云计算:技术融合与实践
1. 引言 人工智能(AI)和云计算是当今科技领域最具变革性的两项技术。AI通过模拟人类智能解决问题,而云计算则提供了弹性可扩展的计算资源。两者的结合创造了前所未有的可能性,使企业能够以更低的成本部署复杂的AI解决方案。 本文将探讨AI与云计算的技术融合,包括核心概念、…...
42.[前端开发-JavaScript高级]Day07-手写apply-call-bind-块级作用域
手写apply-call-bind <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"viewport" content"widthdevi…...
ObjectInputStream 终极解析与记忆指南
ObjectInputStream 终极解析与记忆指南 一、核心本质 ObjectInputStream 是 Java 提供的对象反序列化流,继承自 InputStream,用于读取由ObjectOutputStream序列化的Java对象。 核心特性速查表 特性说明继承链InputStream → ObjectInputStream核心功能实现Java对象反序列化…...
数据结构有哪些类型(对于数据结构的简述)
在学习计算机时,数据结构是不可忽视的一点,从考研时的408课程,再到工作中编写软件,网站,要想在计算机领域站住脚跟,数据结构是必备的 在这里,我对于数据结构进行了汇总,并简要描述&…...
Vscode 插件开发
文章目录 1、使用vscode官方插件生成框架,下载脚手架2、使用脚手架初始化项目,这里我选择的是js3、生成的文件结构如下,重要的就是以下两个文件4、代码5、打包使用6、发布官网地址7、publisher ID undefined provided in the extension manif…...
C# string和其他引用类型的区别
在C#中,字符串(String)和其他引用类型(Reference Types)之间有几个关键的区别,这些区别主要体现在它们的内存管理、赋值行为以及使用方式上。 1. 内存管理 字符串(String)࿱…...
RTT添加一个RTC时钟驱动,以DS1307为例
添加一个外部时钟芯片 这里多了一个选项 复制drv_rtc.c,重命名为drv_rtc_ds1307.c 添加到工程中 /*** @file drv_rtc_ds1307.c* @brief * @author jiache (wanghuan3037@fiberhome.com)* @version 1.0* @date 2025-01-08* * @copyright Copyright (c) 2025 58* */ #...
常见的低代码策略整理
低代码策略通过简化开发流程、降低技术门槛、提升效率,帮助用户快速构建灵活可靠的应用。这些策略的核心优势体现在以下方面: 快速交付与降本增效 减少编码需求:通过可视化配置(如变量替换、表达式函数)替代传统编码…...
从彩色打印单行标准九九表学习〖代码情书〗的书写范式(Python/DeepSeek)
写给python终端的情书,学习代码设计/书写秘笈。 笔记模板由python脚本于2025-04-17 12:49:08创建,本篇笔记适合有python编程基础的coder翻阅。 【学习的细节是欢悦的历程】 博客的核心价值:在于输出思考与经验,而不仅仅是知识的简…...
QML与C++:基于ListView调用外部模型进行增删改查(附自定义组件)
目录 引言相关阅读项目结构文件组织 核心技术实现1. 数据模型设计联系人项目类 (datamodel.h)数据模型类 (datamodel.h)数据模型实现 (datamodel.cpp) 2. 主程序入口点 (main.cpp)3. 主界面设计 (Main.qml)4. 联系人对话框 (ContactDialog.qml)5. 自定义组件CustomTextField.qm…...
postman莫名奇妙报错,可能是注释引起的。postman 过滤请求体中的注释。
postman莫名奇妙报错,可能是注释引起的。postman 过滤请求体中的注释。 1、问题描述2、问题分析3、解决方法 1、问题描述 postman http请求测试时,如果在请求体中添加了注释,那么这个注释会被带到服务端执行,导致服务端接口返回报…...
扩增子分析|基于R语言microeco包进行微生物群落网络分析(network网络、Zi-Pi关键物种和subnet子网络图)
一、引言 microeco包是福建农林大学姚敏杰教授团队开发的扩增子测序集成分析。该包综合了扩增子测序下游分析的多种功能包括群落组成、多样性、网络分析、零模型等等。通过简单的几行代码可实现复杂的分析。因此,microeco包发表以来被学界广泛关注,截止2…...
中间件--ClickHouse-4--向量化执行(什么是向量?为什么向量化执行的更快?)
1、向量(Vector)的概念 (1)、向量的定义 向量:在计算机科学中,向量是一组同类型数据的有序集合,例如一个包含多个数值的数组。在数据库中,向量通常指批量数据(如一列数…...
TDengine 存储引擎剖析:数据文件与索引设计(一)
TDengine 存储引擎简介 在物联网、工业互联网等快速发展的今天,时间序列数据呈爆发式增长。这些数据具有产生频率高、依赖采集时间、测点多信息量大等特点,对数据存储和处理提出了极高要求。TDengine 作为一款高性能、分布式、支持 SQL 的时序数据库&am…...
【kubernetes】pod.spec.containers.ports的介绍
目录 1. 说明2. 基本结构3. 字段说明4. 使用场景5. 示例6. 注意事项 1. 说明 1.在 Kubernetes 中,pod.spec.containers.ports 是 Pod 定义中用于配置容器端口映射的字段,其作用是声明容器需要监听的端口以及如何将这些端口暴露给 Pod 的外部访问。 2. …...
【SpringBoot+Vue自学笔记】001
跟着这位老师学习的:https://www.bilibili.com/video/BV1nV4y1s7ZN?vd_sourceaf46ae3e8740f44ad87ced5536fc1a45 前后端开发技术的全栈课程: Java EE企业级框架:SpringBootMyBatisPlus Web前端核心框架:VueElement UI 公共云…...
第十节:性能优化-如何排查组件不必要的重复渲染?
工具:React DevTools Profiler 方法:memo、shouldComponentUpdate深度对比 React 组件性能优化:排查与解决重复渲染问题指南 一、定位性能问题:React DevTools 高级用法 使用 React Developer Tools Profiler 精准定位问题组件&…...
MATLAB项目实战(一)
题目: 某公司有6个建筑工地要开工,每个工地的位置(用平面坐标系a,b表示,距离单位:km)及水泥日用量d(t)由下表给出.目前有两个临时料场位于A(5,1),B(2,7),日储…...
spring boot 文件下载
1.添加文件下载工具依赖 Commons IO is a library of utilities to assist with developing IO functionality. <dependency><groupId>commons-io</groupId><artifactId>commons-io</artifactId><version>2.6</version> </depe…...
