1. 从零搭一个 WinForms 文本编辑器:字体、自动换行、保存对话框到底怎么写
如果你正在用 C# 做 WinForms 桌面工具,多半绕不开一个经典需求:做一个能改字体、能自动换行、能弹保存对话框的文本编辑器。听起来简单,但真动手时问题一堆——FontDialog弹出来改了字体,结果只改了当前选中文字;WordWrap一开,横向滚动条还在那杵着;SaveFileDialog保存完中文变乱码。这些坑我基本都踩过一遍。
这篇内容聚焦 C# WinForms 文本编辑器开发场景,围绕字体设置、自动换行、保存文件对话框三大核心功能,给出可直接复制的FontDialog、WordWrap、SaveFileDialog配置代码和运行验证步骤。同时我会把 TaoToken 统一 Key 接入 AI 辅助生成与校验代码的流程串进来,让你在写这类样板代码时能少查文档、少试错。
适合谁看:刚接触 WinForms 的 C# 新手、需要快速交付桌面小工具的后端同学、以及想用 AI 辅助写 UI 事件代码但不知道怎么接通道的开发者。核心检索词就是「C# 字体设置 自动换行 保存文件对话框代码」,全文围绕它展开,不跑题。
先说清楚一个前提:WinForms 里做文本编辑,控件选RichTextBox还是TextBox差别很大。TextBox的WordWrap是只读属性,只能在设计器或构造时设,运行时改不了;RichTextBox的WordWrap可以运行时切换,还支持字体、颜色、保存为 RTF。所以下面统一用RichTextBox,这也是大多数编辑器 demo 的选择。
2. TaoToken 前置准备:统一 Key 怎么拿、怎么配到 AI 辅助流程里
在写代码之前,先把 AI 辅助这条链路搭好。TaoToken 的作用是提供一个统一的 API 通道,你拿一个 Key 就能调用多种模型,用来生成 WinForms 事件处理代码、校验SaveFileDialog的 Filter 写法、或者让模型帮你 review 一段FontDialog逻辑,都走同一个入口。
第一步,打开官网 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 注册账号。注册完进控制台,地址是 https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_content=console&utm_campaign=rewrite ,在左侧找到 API Keys 菜单,点进去创建一个新 Key。Key 一般以sk-开头,创建后只显示一次,复制下来存好,别截图发群里。
第二步,如果你用的是 Claude Code 这类命令行编码工具,可以走 Coding Plan 通道,地址 https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding-plan&utm_campaign=rewrite ,按页面说明把 Base URL 和 Key 填进去。如果你只是想快速对话验证一段代码,用模型对话入口就行:https://taotoken.net/model-chat?utm_source=taotoken_aicg_blog_end&utm_content=model-chat&utm_campaign=rewrite 。
第三步,API 的基础地址是 https://taotoken.net/api ,注意这个地址不带任何查询参数,配置时直接填这个。文档在 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite ,里面有各语言的调用示例。
这里要强调一个概念:TaoToken 是统一 Key 接入通道,不是让你绕过什么,它就是把多个模型的调用收敛到一个 Key 和一套 Base URL 上,方便你在项目里切换模型做代码生成和校验。你可以在一个.env或者配置文件里只维护一份 Key,不用每个模型记一套。
拿 Key 的流程本身不复杂,但有几个细节容易翻车:Key 创建后如果没复制,只能删了重建;控制台里能看到调用量和余额,调试阶段建议先充一点点,避免请求到一半额度没了报 401。另外,把 Key 写进代码时千万别硬编码提交到 Git,用环境变量或者用户机密(User Secrets)更稳妥。
配好之后,你可以让 AI 帮你生成下面这些代码片段,比如「用 C# 写一个 RichTextBox 的字体设置菜单事件,要求保留原字体作为默认值」,模型返回的代码你再对照本文的版本校验一遍,效率会高不少。接下来进入正题,开始写可复制的配置代码。
3. 可复制配置:FontDialog、WordWrap、SaveFileDialog 三件套代码
这一节是全文的核心,给出三大功能的完整代码。你可以新建一个 WinForms 项目,拖一个RichTextBox命名richTextBox1,一个MenuStrip,然后按下面的写法挂事件。
先看字体设置。关键点是:打开对话框前,把当前控件的字体赋给FontDialog,这样用户看到的是当前字体而不是默认字体;确认后再把选中的字体应用回去。如果你希望字体作用于全部文本而不是选中部分,要先SelectAll()。
private void menuItemFont_Click(object sender, EventArgs e) { // 用当前字体作为对话框初始值,避免每次都是默认字体 fontDialog1.Font = richTextBox1.Font; // 允许选择颜色(可选) fontDialog1.ShowColor = false; if (fontDialog1.ShowDialog() == DialogResult.OK) { // 方案A:只改选中文本的字体 // richTextBox1.SelectionFont = fontDialog1.Font; // 方案B:改全部文本字体(推荐给整篇编辑器) richTextBox1.SelectAll(); richTextBox1.SelectionFont = fontDialog1.Font; richTextBox1.SelectionStart = richTextBox1.TextLength; richTextBox1.SelectionLength = 0; } }注意方案 B 里最后两行是把光标移到末尾并取消选中,否则用户改完字体会发现全文被选中,体验很差。这个细节很多教程不讲,但实际用起来很影响手感。
再看自动换行。RichTextBox的WordWrap是bool属性,运行时可以切换。菜单项用Checked状态记录当前是否开启,切换时同步更新控件属性和横向滚动条。
private void menuItemWordWrap_Click(object sender, EventArgs e) { if (menuItemWordWrap.Checked) { menuItemWordWrap.Checked = false; richTextBox1.WordWrap = false; // 关闭换行时显示横向滚动条,方便看长行 richTextBox1.ScrollBars = RichTextBoxScrollBars.Both; } else { menuItemWordWrap.Checked = true; richTextBox1.WordWrap = true; // 开启换行后横向滚动条没意义,只留纵向 richTextBox1.ScrollBars = RichTextBoxScrollBars.Vertical; } }然后是保存文件对话框。原始写法里有个小问题:saveFileDialog1.FileName += "*.txt"这种拼接容易出怪结果,正确做法是设置Filter和DefaultExt,让对话框自己处理扩展名。
private void menuItemSave_Click(object sender, EventArgs e) { saveFileDialog1.Filter = "文本文件(*.txt)|*.txt|RTF 文件(*.rtf)|*.rtf|所有文件(*.*)|*.*"; saveFileDialog1.FilterIndex = 1; saveFileDialog1.DefaultExt = "txt"; saveFileDialog1.AddExtension = true; saveFileDialog1.RestoreDirectory = true; if (saveFileDialog1.ShowDialog() == DialogResult.OK) { string name = saveFileDialog1.FileName; try { if (name.EndsWith(".rtf")) { richTextBox1.SaveFile(name, RichTextBoxStreamType.RichText); } else { richTextBox1.SaveFile(name, RichTextBoxStreamType.PlainText); } } catch (Exception ex) { MessageBox.Show(ex.Message, "保存失败", MessageBoxButtons.OK, MessageBoxIcon.Error); } } }如果你用 AI 辅助生成,可以把上面三段贴给模型,让它帮你补一个「另存为」和「编码选择」的逻辑。这里给一个可复制的配置片段,方便你在项目里统一管理这些对话框参数。假设你用appsettings.json存默认配置:
{ "Editor": { "DefaultFontFamily": "Consolas", "DefaultFontSize": 12, "DefaultWordWrap": true, "SaveFilter": "文本文件(*.txt)|*.txt|RTF 文件(*.rtf)|*.rtf|所有文件(*.*)|*.*", "DefaultExt": "txt" } }读取时用System.Text.Json反序列化,把DefaultWordWrap应用到richTextBox1.WordWrap,把字体应用到richTextBox1.Font。这样你的编辑器启动状态就是可控的,而不是每次靠设计器里手点。
如果你用 Claude Code 或 Cline 这类工具做辅助,配置里要写全三件套:Base URL 填https://taotoken.net/api,Key 填你创建的那串,Model ID 按文档里列出的填。三者缺一,请求就会失败。Cline 的 MCP 配置里也是同样的三件套,别只填 Key 忘了 Base URL。
4. 验证请求与成功结果:跑起来看到什么才算对
代码写完,得验证。分两层:一层是 WinForms 功能本身跑通,一层是 AI 辅助通道能正常返回。
先验证功能。按 F5 启动程序,依次做这几个动作:
打开字体菜单,弹出的FontDialog里初始字体应该和你RichTextBox当前字体一致。选一个「微软雅黑 14 号」,确定后全文文字应该立刻变化,光标停在末尾,没有全选高亮。如果你发现只有部分文字变了,说明你用了SelectionFont但没SelectAll,回到上一节检查。
点自动换行菜单,输入一行很长的文字(比如复制一段 500 字的段落),开启换行时文字应该在控件宽度处折行,横向滚动条消失;关闭换行时文字不折行,横向滚动条出现。菜单项的勾选状态要和实际状态一致,切换两次应该回到初始状态。
点保存,SaveFileDialog弹出,文件名输入框默认带.txt扩展名,文件类型下拉里有 txt、rtf、所有文件三项。保存成 txt 后用记事本打开,中文不乱码;保存成 rtf 后用写字板打开,字体格式保留。如果中文乱码,检查你是不是用了PlainText但源文本编码不对,RichTextBox.SaveFile的PlainText默认按系统编码走,跨机器可能出问题,必要时改用UTF8相关重载或先转码。
再验证 AI 通道。用 curl 发一个最小请求,确认 Key 和 Base URL 通了:
curl https://taotoken.net/api/v1/chat/completions \ -H "Content-Type: application/json" \ -H "Authorization: Bearer sk-你的Key" \ -d '{ "model": "按文档填对应Model ID", "messages": [ {"role": "user", "content": "用C#写一个RichTextBox设置字体的方法"} ] }'成功的话你会看到返回 JSON 里有choices数组,里面是模型生成的代码文本。如果返回 401,说明 Key 不对或没带Bearer前缀;如果返回 404,多半是 Base URL 写错了,注意是https://taotoken.net/api后面接/v1/chat/completions,别多加斜杠。
实测下来,把生成的代码贴回项目里,再让模型帮你检查SaveFileDialog的 Filter 字符串有没有转义问题,能省不少调试时间。比如 Filter 里的竖线|在 C# 字符串里不需要转义,但如果你从 JSON 配置读进来,就要注意 JSON 里反斜杠的转义规则,这个坑我见过好几次。
验证通过后,你的编辑器三大功能就算立住了。接下来是排错环节,把常见报错一次说清。
5. 本篇常见错排查:401、local proxy failed、reading choices、OAuth 逐个拆
这一节按真实报错来。你在接 AI 辅助通道时,最可能撞上下面几类。
第一类,401 Unauthorized。返回体里通常写invalid api key或authentication failed。原因就三个:Key 复制时漏了字符、Key 前面没加Bearer、或者 Key 已经被删。排查方法:重新去控制台 API Keys 页面生成一个,用 curl 单独测,别在代码里测,排除代码拼接问题。如果 curl 也 401,那就是 Key 本身的问题。
第二类,local proxy failed。这个报错一般出现在你本地配了某个代理工具,请求先走本地端口再转发,结果本地端口没起来或者规则不对。处理方式:检查你的环境变量里有没有HTTP_PROXY、HTTPS_PROXY指向一个不存在的本地端口,有就临时清掉再试。命令行里可以unset HTTPS_PROXY后重跑 curl。注意,这里说的是排查本地代理配置错误,不是让你去搭什么通道,纯粹是环境变量层面的清理。
第三类,reading choices 相关报错,比如cannot read property 'choices' of undefined或者reading 'choices' failed。这通常不是网络问题,而是你解析返回 JSON 时,返回体结构和你预期的不一样。比如请求失败时返回的是{"error": {...}},没有choices字段,你的代码却直接取response.choices[0],就炸了。修法:先判断response.error是否存在,再取choices。用 C# 的话,反序列化后先判空:
var result = JsonSerializer.Deserialize<ApiResponse>(json); if (result?.Error != null) { MessageBox.Show("接口返回错误:" + result.Error.Message); return; } var content = result?.Choices?[0]?.Message?.Content;第四类,OAuth 相关报错。如果你用的是 Claude Code 这类工具,它可能默认走 OAuth 登录流程,而你想用 Key 方式接入,就会冲突。表现是提示OAuth token expired或者一直跳登录。处理方式:在工具的配置里显式指定用 API Key 模式,把 Base URL 和 Key 填到对应字段,别让它走 OAuth。Claude Code 的配置里通常有一个auth.json或类似文件,里面区分oauth和apiKey两种模式,选后者。
再补一个 WinForms 侧的常见错:SaveFileDialog保存后文件是空的。原因多半是你保存的是richTextBox1.Text但控件里其实没内容,或者你在保存前清空了。还有一种情况是FilterIndex和实际扩展名不匹配,用户选了 rtf 但代码按 txt 存,打开就乱。对照上一节的保存代码,确保按扩展名分支处理。
最后一个容易忽略的:FontDialog改了字体但界面没刷新。RichTextBox一般会立即重绘,如果你发现没变,检查是不是在ShowDialog返回OK之前就应用了,或者你改的是SelectionFont但当前没有选中文本。改成SelectAll再设SelectionFont基本能解决。
把这些排错点过一遍,你的编辑器稳定性会好很多。遇到新报错,把完整错误信息贴给 AI 通道,让它帮你定位,比盲搜快。
6. 继续往下做:把 AI 辅助接进你的日常编码流
三大功能跑通之后,你可以把 TaoToken 的接入固化到日常流程里。比如每次写新的 WinForms 事件,先让模型生成一版,再对照本文的写法校验;遇到SaveFileDialog的 Filter 或编码问题,直接问模型要正确写法。
需要长期做编码和 Agent 类任务的,走 Coding Plan 通道更合适:https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding-plan&utm_campaign=rewrite 。只是临时验证一段代码,用模型对话就行:https://taotoken.net/model-chat?utm_source=taotoken_aicg_blog_end&utm_content=model-chat&utm_campaign=rewrite 。Key 管理和创建在控制台:https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_content=console&utm_campaign=rewrite ,接入细节看文档:https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite 。
最后留一个实用技巧:把本文的字体、换行、保存三段代码存成一个EditorHelper.cs静态类,菜单事件里只调一行方法。这样下次做新项目,直接拷这个文件,改改控件名就能用。AI 辅助生成的部分,重点放在边界处理上,比如空文本保存、超长文本换行性能、RTF 和纯文本互转,这些才是真正花时间的地方。