C#开发的告白小程序完整源码与可运行项目包
简介:“告白小程序&源码&运行文件.rar”是一个基于C#语言开发的简易情感表达桌面应用,专为编程初学者及对C#感兴趣的单身用户设计。项目在Windows 10环境下完成编译与测试,包含完整源代码和已编译的可执行文件,支持直接运行。通过该程序,用户可学习如何使用C#结合.NET Framework构建图形化应用程序,适用于情感表白场景,兼具实用性与教学价值。
C#与.NET桌面开发实战:从零构建一个“告白小程序”
在某个深夜,你突然想给那个人说一句藏了很久的话——但又怕当面说不出口。于是你打开电脑,决定写个小程序,把心意悄悄传递出去。这不仅仅是一个浪漫的念头,更是一次绝佳的C#入门实践机会。
我们今天要做的,不是简单地敲几行代码弹出个对话框,而是完整走一遍 现代Windows桌面应用的开发全流程 :从最基础的语法结构,到GUI界面设计、文件资源管理、编译调试,再到最终打包发布。整个过程将以“告白小程序”为载体,带你沉浸式体验真实项目开发的每一个细节。
准备好了吗?让我们开始吧!
一段代码背后的整个世界
先看这样一段简单的C#程序:
using System;
namespace LoveApp
{
class Program
{
static void Main()
{
string message = "我喜欢你!";
Console.WriteLine(message);
}
}
}
看起来很简单对吧?但你知道这短短几行代码背后藏着多少系统级协作吗?
using System;—— 这是在告诉编译器:“我要用.NET基类库里的东西”Main()方法 —— 操作系统加载你的程序后,第一眼就找它作为入口Console.WriteLine()—— 看似只是打印文字,实则调用了Windows API中的控制台输出函数- 整个执行流程 —— 需要CLR(公共语言运行时)先把IL中间代码JIT编译成本地指令
你以为你在写C#?不,你其实在指挥一场精密的交响乐 🎻。
而这一切的基础,就是 .NET Framework 。
.NET Framework:不只是个“框架”,是个生态系统
很多人误以为.NET Framework就是一个DLL集合,其实不然。它是一个完整的运行时环境,由两大核心构成:
🔧 公共语言运行时(CLR)
想象一下,如果你写的C#代码能被VB.NET直接继承,Python也能调用你的方法……这不是魔法,而是CLR的日常操作。
它的秘密在于:所有.NET语言都会先编译成一种叫 IL(Intermediate Language) 的中间代码。然后由CLR在运行时通过JIT(Just-In-Time)编译器动态转成CPU能理解的机器码。
这就实现了:
✅ 跨语言互操作
✅ 内存自动管理(GC垃圾回收)
✅ 安全沙箱机制
✅ 异常统一处理
来看一张图,感受下CLR的工作流:
graph TD
A[C# Source Code] --> B[C# Compiler]
B --> C[Assembly with IL + Metadata]
C --> D[CLR Loader]
D --> E[JIT Compiler]
E --> F[Native Machine Code]
F --> G[Execution Engine]
G --> H[Memory Management & GC]
H --> I[Thread & Security Services]
I --> J[Operating System]
是不是有点像“翻译官+保安队长+调度员”的组合体?😎
特别值得一提的是它的 分代垃圾回收机制 (Generation 0/1/2)。新创建的对象优先放在Gen0,如果多次回收都没被释放,就会升级到Gen1甚至Gen2。这种设计极大提升了内存清理效率,避免了全堆扫描带来的性能卡顿。
💡 小贴士:频繁短命对象适合Gen0;长期存活的大对象建议用
ArrayPool<T>复用,减少GC压力。
📚 基类库(BCL):程序员的瑞士军刀
如果说CLR是引擎,那BCL就是整车的各种功能模块。它提供了几乎所有你能想到的标准API:
| 命名空间 | 功能 |
|---|---|
System |
核心类型如 String , Int32 , DateTime |
System.IO |
文件读写、目录操作 |
System.Net |
HTTP请求、Socket通信 |
System.Collections |
List、Dictionary等集合类 |
System.Threading |
多线程与异步编程 |
System.Windows.Forms |
GUI控件支持 |
比如你想保存用户的告白内容到本地文件,一行代码搞定:
File.WriteAllText(@"C:\temp\love.txt", "今天我想告诉你…");
不用手动开流、关句柄、处理异常缓冲区,BCL已经帮你封装好了最佳实践。这才是真正的“站在巨人肩膀上”。
而且这些类库都是强命名程序集,存储在GAC(全局程序集缓存)中,保证版本一致性和高效加载。
开发环境怎么配?别再靠运气了!
你说你写了程序别人打不开?八成是环境没整明白 😅。
对于“告白小程序”这类轻量级应用,推荐使用 .NET Framework 4.8 ,因为它:
- 支持Win7 SP1及以上系统(覆盖绝大多数用户)
- 向后兼容性好
- API丰富,UI渲染稳定
安装方式有三种:
1. Windows Update自动推送
2. 在线安装包(Web Installer)
3. 离线安装包(Offline Installer)
强烈建议选第3种!因为前两种需要联网下载组件,在批量部署或网络差的环境下容易失败。
👉 下载地址: https://dotnet.microsoft.com/download/dotnet-framework
安装完之后,怎么确认成功了呢?这里有三个办法👇
✅ 方法一:注册表检测(PowerShell脚本)
$regPath = 'HKLM:\SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full'
$version = Get-ItemProperty -Path $regPath -Name Version -ErrorAction SilentlyContinue
if ($version -and $version.Version -ge '4.8.0') {
Write-Host "🎉 检测到 .NET Framework 4.8 或更高版本"
} else {
Write-Warning "⚠️ 未检测到所需版本,请安装 .NET Framework 4.8"
}
这个脚本可以集成进你的发布流程,自动扫描客户机环境。
✅ 方法二:CMD命令行查询
reg query "HKLM\SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full" /v Version
输出类似:
Version REG_SZ 4.8.9032.0
✅ 方法三:C#代码内建检测
using Microsoft.Win32;
using System;
public static class DotNetVersionChecker
{
public static void CheckFrameworkVersion()
{
const string subkey = @"SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full";
using (var key = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry32)
.OpenSubKey(subkey))
{
if (key != null && key.GetValue("Install")?.ToString() == "1")
{
var version = key.GetValue("Version")?.ToString();
Console.WriteLine($".NET Framework 已安装,版本:{version}");
}
else
{
Console.WriteLine("❌ 未检测到 .NET Framework 4.x");
}
}
}
}
把这个检查逻辑放进程序启动页,用户一运行就知道缺不缺依赖,体验瞬间拉满 ❤️。
程序集和GAC:DLL也能玩出花来
任何非玩具级的应用都会引用外部库。怎么管好这些DLL,是一门学问。
🧩 程序集(Assembly)是什么?
你可以把它理解为.NET世界的“模块单元”。每个 .exe 或 .dll 文件都是一个程序集,里面不仅包含IL代码,还有元数据、资源文件、签名信息。
添加引用的方式有两种:
- 项目引用 :同一解决方案内的其他项目
- 程序集引用 :已编译的DLL或GAC中的组件
🗃️ GAC(Global Assembly Cache)的作用
路径在 %WINDIR%\Microsoft.NET\assembly ,相当于系统的共享库仓库。
优点:
- 多个应用共用一份DLL,省磁盘空间
- 支持Side-by-Side多版本共存(比如A程序用Newtonsoft.Json v12,B用v13)
缺点:
- 必须强名称签名(sn.exe生成密钥)
- 更新麻烦,得重新注册
常用命令:
gacutil -l # 查看所有GAC程序集
gacutil -i MyLibrary.dll # 注册到GAC
不过现在更多人倾向于用NuGet包管理器,自动处理依赖关系,比手动搞GAC舒服多了~
🔄 版本冲突怎么办?bindingRedirect来救场!
当你同时引用了多个不同版本的同一个库时,可能会遇到“找不到方法”错误。这时候就需要在 app.config 里加个重定向:
<configuration>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed"/>
<bindingRedirect oldVersion="0.0.0.0-13.0.0.0" newVersion="13.0.0.0"/>
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>
一句话:不管旧版本是多少,统统指向v13.0!👏
app.config:让配置不再硬编码
还记得那些写死在代码里的URL、超时时间、颜色主题吗?改一次就得重新编译?太原始啦~
现在流行的做法是用 app.config 文件做配置中心:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<appSettings>
<add key="ServerUrl" value="https://api.lovecoder.com"/>
<add key="MaxRetries" value="3"/>
<add key="FontSize" value="14"/>
</appSettings>
<connectionStrings>
<add name="DefaultDB"
connectionString="Data Source=.\SQLEXPRESS;Initial Catalog=LoveApp;"
providerName="System.Data.SqlClient"/>
</connectionStrings>
</configuration>
读取也很方便:
string url = ConfigurationManager.AppSettings["ServerUrl"];
int retries = int.Parse(ConfigurationManager.AppSettings["MaxRetries"]);
别忘了引用 System.Configuration 程序集哦!
还可以通过 <startup> 指定运行时版本,防止因CLR不匹配导致崩溃:
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.8"/>
</startup>
Windows Forms:老派但依然香
虽然现在都在卷WPF、MAUI,但对于快速开发小型工具类应用, Windows Forms依然是王者 。
为什么?因为它够简单、够直观、够快!
⚡ 事件驱动模型:一切交互都靠“消息泵”
传统的线性程序没法应对鼠标点击、键盘输入这种异步行为。所以WinForms用了 事件驱动编程范式 。
原理大概是这样的:
graph TD
A[用户点击按钮] --> B{操作系统接收WM_LBUTTONDOWN}
B --> C[消息循环捕获]
C --> D[Control.WndProc处理]
D --> E[转化为Click事件]
E --> F[触发OnButtonClick]
F --> G[执行业务逻辑]
G --> H[更新UI]
整个过程由CLR的消息泵驱动,开发者只需关注“发生了什么”和“我要做什么”,完全不用操心底层通信。
举个例子:
private void InitializeEvents()
{
btnSubmit.Click += OnButtonClick;
}
private void OnButtonClick(object sender, EventArgs e)
{
MessageBox.Show("你已成功提交告白信息!");
}
看到那个 += 了吗?这就是委托订阅事件的经典写法。支持多播(多个处理器)、动态解绑( -= ),灵活得很!
🧱 控件体系:万物皆Control
WinForms的所有UI元素都继承自 System.Windows.Forms.Control 类,包括Form本身!
主要分类如下:
| 类型 | 常见控件 | 用途 |
|---|---|---|
| 输入控件 | TextBox, ComboBox, CheckBox | 获取用户输入 |
| 显示控件 | Label, PictureBox, ListBox | 展示信息 |
| 按钮控件 | Button, LinkLabel | 触发操作 |
| 容器控件 | Panel, GroupBox, TabControl | 组织布局 |
以我们的“告白小程序”为例,可以用这些控件搭建主界面:
Label:标题“给最爱的人说一句心里话”TextBox:让用户输入告白语PictureBox:放个跳动的心形动画Button:点击后播放音乐并显示成功提示Timer:控制闪烁效果的时间节奏
设置文本框属性也很简单:
txtConfession.MaxLength = 50; // 最多输入50字
txtConfession.Multiline = true; // 支持换行
txtConfession.ScrollBars = ScrollBars.Vertical; // 自动加滚动条
还能用 Anchor 和 Dock 实现自适应布局:
Anchor = AnchorStyles.Left | Right | Top→ 宽度随窗体拉伸Dock = DockStyle.Fill→ 填满父容器
再也不用担心窗口缩放时UI错乱啦!
文件读写:数据持久化的必修课
你的告白内容难道只存在内存里?当然不行!得存下来啊!
C#提供了强大的 System.IO 命名空间,让你轻松搞定文件操作。
🛠️ 核心类一览
| 类名 | 是否静态 | 适用场景 |
|---|---|---|
File |
是 | 快速一次性操作 |
FileInfo |
否 | 多次操作同一文件 |
Directory |
是 | 批量管理文件夹 |
DirectoryInfo |
否 | 遍历目录树 |
Path |
是 | 构建跨平台路径 |
FileStream |
否 | 大文件流式处理 |
比如你要创建一个私密消息文件夹:
string folderPath = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments),
"ConfessionsApp", "PrivateMessages"
);
if (!Directory.Exists(folderPath))
{
Directory.CreateDirectory(folderPath);
Console.WriteLine($"📁 已创建私密文件夹:{folderPath}");
}
注意这里用了 Path.Combine() 而不是拼字符串,自动适配 \ 还是 / ,跨平台无忧。
📝 文本读写策略选择
根据文件大小选择合适的方法:
| 方法 | 适用场景 | 推荐指数 |
|---|---|---|
File.ReadAllText/WriteAllText |
小文件(<100KB) | ⭐⭐⭐⭐☆ |
StreamReader/Writer |
日志、配置文件 | ⭐⭐⭐⭐⭐ |
FileStream + Buffer |
大文件或二进制流 | ⭐⭐⭐⭐☆ |
例如保存用户留言:
using (StreamWriter writer = new StreamWriter("diary.txt", append: true, Encoding.UTF8))
{
writer.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm}] {userInput}");
}
加上 using 语句,确保即使出错也能正确释放资源,防止文件被锁定。
决策流程图送你一张:
graph TD
A[开始文件操作] --> B{文件大小 < 100KB?}
B -- 是 --> C[使用File.ReadAllText/WriteAllText]
B -- 否 --> D{是否需要逐行处理?}
D -- 是 --> E[使用StreamReader/Writer]
D -- 否 --> F[使用FileStream流式处理]
C --> G[完成操作]
E --> G
F --> G
内嵌资源:打造真正“绿色”的单文件应用
你有没有遇到过这种情况:程序发给别人,结果图片音频找不到了?
解决办法只有一个: 把资源嵌入程序集 !
步骤很简单:
- 把
heart.png、romantic.mp3拖进项目的“Resources”文件夹 - 在属性中设置“生成操作”为 Embedded Resource
- 运行时通过流提取:
Assembly assembly = Assembly.GetExecutingAssembly();
Stream imageStream = assembly.GetManifestResourceStream("LoveApp.Assets.heart.png");
Bitmap image = new Bitmap(imageStream);
pictureBox.Image = image;
⚠️ 注意资源名格式: [默认命名空间].[文件夹].[文件名]
可以通过下面代码查看所有可用资源:
string[] resources = assembly.GetManifestResourceNames();
foreach (string name in resources)
{
Console.WriteLine(name); // 调试用
}
播放音频也一样:
private void PlayMusic()
{
var stream = GetType().Assembly.GetManifestResourceStream("LoveApp.Sounds.romantic.mp3");
using (var temp = new MemoryStream())
{
stream.CopyTo(temp);
temp.Position = 0;
SoundPlayer player = new SoundPlayer(temp);
player.Play(); // 异步播放
}
}
❗ 但是注意:
SoundPlayer只支持WAV!MP3需要用NAudio等第三方库解码。
图片、字体、图标都可以这么处理。从此你的程序就是一个独立的 .exe ,走到哪都能跑!
异常处理:别让程序莫名其妙闪退
文件操作最容易翻车的地方就是权限不足、路径非法、磁盘满了……
所以必须做好异常防护:
public bool SaveMessageSafely(string filePath, string message)
{
try
{
File.WriteAllText(filePath, message, Encoding.UTF8);
return true;
}
catch (UnauthorizedAccessException)
{
MessageBox.Show("没有权限写入该路径,请选择其他位置。");
return false;
}
catch (DirectoryNotFoundException)
{
MessageBox.Show("指定的文件夹不存在,请确认路径正确。");
return false;
}
catch (IOException ioEx)
{
MessageBox.Show($"文件写入失败:{ioEx.Message}");
return false;
}
finally
{
LogOperation("SaveAttempt", filePath); // 记录日志
}
}
记住几个关键异常类型:
- UnauthorizedAccessException :权限不够
- DirectoryNotFoundException :路径中的目录不存在
- IOException :泛指I/O问题(如文件被占用)
更重要的是,记得用 using 语句确保资源释放:
using (var reader = new StreamReader("data.txt"))
{
string content = reader.ReadToEnd();
ProcessContent(content);
} // 自动Dispose()
等价于:
try { ... } finally { reader?.Dispose(); }
但前者简洁安全,不易遗漏。
数据持久化:让用户设置不丢失
用户的偏好设置总不能每次重启都重置吧?得存起来!
方案一:Properties.Settings(最快上手)
在Visual Studio的“项目属性 → 设置”中定义键值:
// 保存
Properties.Settings.Default.LastColor = Color.Red.ToArgb();
Properties.Settings.Default.Save();
// 读取
int colorArgb = Properties.Settings.Default.LastColor;
Color restoredColor = Color.FromArgb(colorArgb);
会自动序列化到 %AppData%\... 目录下的config文件,无需关心路径。
方案二:XML/JSON序列化(更灵活)
保存自定义对象:
public class ConfessionRecord
{
public string Message { get; set; }
public DateTime Timestamp { get; set; }
}
void SaveToXml(ConfessionRecord record, string path)
{
var serializer = new XmlSerializer(typeof(ConfessionRecord));
using (var writer = new StreamWriter(path))
{
serializer.Serialize(writer, record);
}
}
输出XML:
<ConfessionRecord>
<Message>我喜欢你</Message>
<Timestamp>2025-04-05T10:00:00</Timestamp>
</ConfessionRecord>
JSON更轻量,配合Newtonsoft.Json:
string json = JsonConvert.SerializeObject(record, Formatting.Indented);
File.WriteAllText("record.json", json);
对比一下:
| 格式 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| XML | 结构清晰,原生支持 | 冗长 | 配置文件 |
| JSON | 紧凑,Web友好 | 需引入库 | 移动同步 |
编译调试:高手和菜鸟的区别在这里
写代码容易,调试才见真功夫。
🧩 .sln vs .csproj:谁说了算?
.sln是解决方案文件,管理多个项目之间的关系.csproj是MSBuild项目文件,控制具体编译行为
典型的 .csproj 内容:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net48</TargetFramework>
<AssemblyName>LoveApp</AssemblyName>
</PropertyGroup>
<ItemGroup>
<Compile Include="Form1.cs" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
</ItemGroup>
</Project>
你可以脱离IDE,直接命令行构建:
msbuild LoveApp.sln
🔍 Debug vs Release:差别可不止断点
| 配置项 | Debug | Release |
|---|---|---|
| 优化代码 | ❌ 否 | ✅ 是 |
| 调试信息 | ✅ 全量PDB | ⚠️ 可选 |
| 断言生效 | ✅ Debug.Assert触发 | ❌ 不触发 |
| 输出体积 | 较大 | 较小 |
开发阶段用Debug,发布前切Release,性能提升明显。
还能用预处理器指令做差异化逻辑:
#if DEBUG
Console.WriteLine("启用详细日志");
#endif
这段代码在Release模式下会被完全剔除,零开销!
🐞 常见编译错误急救手册
| 错误码 | 含义 | 解决方案 |
|---|---|---|
| CS0246 | 类型未找到 | 加 using 或引用 |
| CS1061 | 对象无此成员 | 检查类型转换 |
| CS0161 | 不是所有路径都返回值 | 补 return 或抛异常 |
| CS0006 | 找不到元数据文件 | 构建依赖项目 |
善用“错误列表”窗口筛选级别,效率翻倍。
调试技巧:掌控程序的灵魂
设个断点就叫调试?太初级了!
高级断点玩法:
- 条件断点 :满足表达式才中断
- 命中次数断点 :第N次执行时停
- 操作断点 :不中断,只输出日志
快捷键:
- F10 :逐过程(Step Over)
- F11 :逐语句(Step Into)
- Shift+F11 :跳出当前函数
配合这些窗口:
- Locals :看局部变量
- Watch :监控表达式
- Immediate Window :运行时执行代码(如 ? message.Length )
异常发生时,打开 Call Stack 看调用链,一键定位源头。
开启 Just My Code ,隐藏系统库代码,专注业务逻辑。
性能监控与日志:生产环境的眼睛
别等到用户投诉才知道有问题!
内置日志:Debug.WriteLine
Debug.WriteLine("【调试】开始发送消息...");
Trace.TraceInformation("用户点击了发送按钮");
#if DEBUG
Debug.Assert(txtMessage.Text.Length > 0, "消息不能为空");
#endif
输出到“输出”窗口,配合监听器写入文件:
<system.diagnostics>
<trace autoflush="true">
<listeners>
<add name="fileListener"
type="System.Diagnostics.TextWriterTraceListener"
initializeData="log.txt" />
</listeners>
</trace>
</system.diagnostics>
第三方日志框架:Serilog(推荐)
Install-Package Serilog.Sinks.File
配置:
Log.Logger = new LoggerConfiguration()
.WriteTo.File("logs/app-.log", rollingInterval: RollingInterval.Day)
.CreateLogger();
Log.Information("应用启动,用户:{UserName}", Environment.UserName);
结构化日志,便于搜索分析:
{
"Timestamp": "2025-04-05T10:23:45Z",
"Level": "Information",
"MessageTemplate": "应用启动,用户:{UserName}",
"Properties": { "UserName": "Alice" }
}
打包部署:最后一公里不能掉链子
发布前 checklist:
- [ ] 删除调试代码
- [ ] 切换至Release模式
- [ ] 检查资源嵌入设置
- [ ] 验证目标框架兼容性
Visual Studio发布向导
右键项目 → Publish → 创建发布配置
可设置:
- 发布路径(本地/网络/FTP)
- 依赖项包含方式
- 是否自动更新
- 前置条件(.NET版本检测)
生成结构:
/LoveApp_Publish/
├── setup.exe
├── LoveApp.application
└── Application Files/
└── LoveApp_1_0_0_0/
├── LoveApp.exe.deploy
└── *.dll.deploy
.deploy 文件需重命名还原:
ren *.deploy *
ClickOnce vs MSI 安装包
| 特性 | ClickOnce | MSI |
|---|---|---|
| 开发难度 | 极低 | 中等 |
| 权限要求 | 用户级 | 通常需管理员 |
| 自动更新 | 内建支持 | 需额外集成 |
| 注册表操作 | 有限 | 完全支持 |
| 卸载便捷性 | 高 | 高 |
| 自定义界面 | 否 | 是 |
企业级推荐用 Microsoft Visual Studio Installer Projects 扩展生成MSI包,支持组策略推送。
分发验证:别忘了最后一步
一定要在纯净虚拟机里测试!
常见问题及对策:
| 问题 | 原因 | 解决 |
|---|---|---|
| 缺少msvcr120.dll | VC++运行库缺失 | 安装vcredist |
| 程序闪退 | 未捕获异常 | 添加全局处理器 |
| 图片不显示 | 资源未嵌入 | 检查.resx设置 |
| DPI错位 | 未启用高DPI感知 | 修改manifest |
| 配置写入失败 | 写入Program Files | 改用AppData |
加个全局异常捕获,优雅兜底:
AppDomain.CurrentDomain.UnhandledException += (sender, e) =>
{
LogErrorToServer(e.ExceptionObject.ToString());
MessageBox.Show("程序出错了,请联系开发者~");
};
写在最后:技术也可以很温柔
你看,一个“告白小程序”背后,竟然牵扯出这么多知识体系:
语法 → 运行时 → GUI → 文件 → 调试 → 部署……
但这不正是编程的魅力所在吗?每一行代码都在连接现实与数字世界,每一次编译都在把想法变成可触摸的产品。
下次当你按下F5运行那个写着“我喜欢你”的程序时,希望你能感受到:
不仅是情感的传递,更是技术力量的温柔绽放 💖。
毕竟,最好的代码,永远是带着温度写的。
简介:“告白小程序&源码&运行文件.rar”是一个基于C#语言开发的简易情感表达桌面应用,专为编程初学者及对C#感兴趣的单身用户设计。项目在Windows 10环境下完成编译与测试,包含完整源代码和已编译的可执行文件,支持直接运行。通过该程序,用户可学习如何使用C#结合.NET Framework构建图形化应用程序,适用于情感表白场景,兼具实用性与教学价值。
更多推荐




所有评论(0)