ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

198个C# WinForm实例源码:从基础控件到多线程与数据库的实战指南

198个C# WinForm实例源码:从基础控件到多线程与数据库的实战指南 简介这是一套面向C# Windows桌面开发学习者与工程师的实战型源码合集聚焦WinForm框架核心技能训练覆盖窗体设计、控件交互、图像处理、报表生成、系统调用、文件操作、网络通信、数据库访问、加解密及硬件信息读取等十大典型场景有效解决初学者缺乏项目实感、中高级开发者急需模块化参考代码的痛点。压缩包含5058个文件主体为1353个C#源文件.cs、201个工程文件.csproj、199个解决方案.sln及449个资源文件.resx辅以可执行程序.exe、调试符号.pdb、图标.ico、位图.bmp/.png等完整呈现从编码、编译到运行的全链路结构总大小40.66MB。已有10272人下载学习每个实例均为独立可运行的小型项目含启动界面、背景图、动画光标等细节实现便于逐模块调试、对比分析与二次开发是夯实WinForm编程基础、提升工程化能力的高价值参考资料。1. 项目概述一份沉淀了开发智慧的宝藏如果你是一名C# WinForm开发者无论是刚入门的新手还是想提升项目开发效率、寻找灵感的熟手那么“198个经典C# WinForm实例源码”这个项目对你而言无疑是一座值得深挖的宝藏。这不仅仅是一个简单的代码合集它更像是一位经验丰富的老兵在无数个项目实战后为你精心整理的一份“避坑指南”和“灵感源泉”。我最初接触到这类资源时正处于从控制台应用转向图形界面开发的迷茫期官方文档虽然详尽但总感觉缺少了那种“如何把多个知识点串起来解决实际问题”的临场感。而这198个实例恰好填补了这个空白。这些源码覆盖了WinForm开发的方方面面从最基础的窗体控件操作、事件处理到进阶的数据绑定、多线程界面更新、自定义控件绘制再到实际业务中常见的文件操作、数据库访问、图表报表生成等。每一个实例都聚焦于一个具体的功能点或技术难点相当于把一本厚厚的教科书拆解成了198个可独立运行、可即插即用的“乐高积木”。对于新手你可以通过模仿和运行这些代码快速建立起对WinForm编程的直观感受理解Button的Click事件、TextBox的数据验证、DataGridView的绑定机制是如何在真实场景中运作的。对于有经验的开发者你可以直接从中借鉴成熟的解决方案比如优雅地实现一个仿QQ的抽屉式侧边栏、一个支持拖拽排序的列表或者一个高性能的日志显示控件从而避免重复造轮子把精力集中在业务逻辑的创新上。2. 源码库的核心价值与内容架构解析2.1 为何是“经典”而非“简单”很多人可能会把“实例”等同于“入门示例”但这里的“经典”二字大有深意。它意味着这些代码片段是经过筛选的它们所解决的问题具有普遍性和代表性其实现方式也往往体现了良好的编程实践或巧妙的思路。例如一个“经典”的文件搜索实例它不仅会教你如何使用Directory.GetFiles方法更可能会展示如何利用后台线程BackgroundWorker或Task来保持UI响应如何实现实时进度反馈以及如何处理搜索过程中的取消操作。这种将基础API与用户体验、程序健壮性结合起来的代码才是其价值的核心。从网络热词中我们可以看到开发者们的常见痛点winform timer的精确控制、propertygrid的只读属性定制、show和showdialog的区别、多线程下控件的安全访问InvalidOperationException、以及c# 无法加载一个或多个请求的类型这类棘手的运行时错误。一个高质量的实例库应当能对这些问题提供清晰的示范。例如针对winform timer好的实例会对比System.Windows.Forms.TimerUI线程、System.Timers.Timer和System.Threading.Timer的区别并给出在WinForm中更新UI的正确姿势。2.2 内容分类与学习路径建议面对198个实例如何高效学习而不至于迷失我建议可以按照由浅入深、由点到面的方式进行分类学习基础控件与界面交互约40-50个实例这是WinForm的基石。包括窗体Form的生命周期、常用属性如StartPosition,WindowState设置基本控件Label,TextBox,Button,ComboBox,CheckBox,RadioButton的属性和事件深度使用布局技巧Anchor,Dock,TableLayoutPanel,FlowLayoutPanel。例如一个“登录窗口”实例就应涵盖输入验证、密码掩码、回车键触发登录事件、以及登录失败后的友好提示等完整流程。高级控件与数据展示约30-40个实例这部分涉及更复杂的数据处理和展示。核心是DataGridView的全面应用数据绑定DataSource、自定义列类型、单元格格式、编辑验证、排序过滤。此外TreeView和ListView控件的动态加载、节点操作、图标管理也是重点。PropertyGrid控件的使用以及如何通过自定义类型描述符来实现动态的只读或分类显示这正是热词中“只能查看不能修改”问题的解决方案也属于这个范畴。图形绘制与自定义控件约20-30个实例当标准控件无法满足独特的设计需求时就需要自己动手绘制。这部分实例会深入讲解GDI的基础Graphics对象、Pen、Brush、Font的使用以及如何重写控件的OnPaint方法。从绘制简单的几何图形、曲线图表到创建具有复杂外观的按钮、进度条最终封装成可复用的自定义控件UserControl。这对于实现winform界面美化和独特的交互效果至关重要。文件、IO与系统交互约20-25个实例涵盖本地数据持久化和与操作系统交互。包括文件/文件夹的遍历、创建、读写StreamReader/StreamWriter,FileStreamXML和JSON配置文件的解析与生成注册表的简单访问调用系统API完成特殊功能如设置壁纸、获取系统信息。一个完整的“文本编辑器”实例会串联起文件打开、保存、另存为等多个IO操作。多线程与异步编程约15-20个实例这是保证WinForm程序流畅性的关键。实例会展示如何正确使用BackgroundWorker组件以及更现代的async/await模式配合Task类。重点在于解决“跨线程操作控件”的问题通过Control.Invoke或Control.BeginInvoke方法安全地在工作线程中更新UI元素。例如一个网络下载器实例必须包含进度条更新、速度显示和取消下载的功能。数据库访问与ORM约25-35个实例从最基础的ADO.NETSqlConnection,SqlCommand,SqlDataAdapter操作SQL Server开始到使用参数化查询防止SQL注入再到通过DataSet和DataTable进行离线数据操作。进阶部分会引入轻量级ORM框架如Dapper演示如何更高效、更安全地进行数据库增删改查并将结果直接绑定到DataGridView。图表、报表与打印约15-20个实例数据可视化是很多业务系统的刚需。这部分实例会介绍如何使用Chart控件即热词中的chart surface winform创建折线图、柱状图、饼图等并动态绑定数据。同时也会涵盖如何使用PrintDocument类实现文档和报表的打印、打印预览功能。综合项目与小工具约10-15个实例将前面所学融会贯通形成小型完整应用。例如简易计算器、通讯录管理系统、图片浏览器、音乐播放器、屏幕截图工具等。这些实例最能锻炼模块划分、代码组织和事件驱动的编程思维。3. 深度剖析从源码中提炼关键技术与避坑指南3.1 线程安全UI更新的永恒课题在多线程实例中最常见的错误就是直接从工作线程访问或修改UI控件这会导致InvalidOperationException: Cross-thread operation not valid异常。经典的解决方案是使用Control.Invoke方法。// 错误示例在工作线程中直接更新UI private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e) { for (int i 0; i 100; i) { // 直接更新进度条会导致异常 progressBar1.Value i; Thread.Sleep(50); } } // 正确示例使用Invoke进行跨线程调用 private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e) { for (int i 0; i 100; i) { // 通过Invoke将更新操作派发到UI线程执行 progressBar1.Invoke(new Action(() { progressBar1.Value i; })); Thread.Sleep(50); } }注意在.NET Framework 2.0以后可以通过设置Control.CheckForIllegalCrossThreadCalls false来禁用跨线程调用检查但这是一种非常危险的做法会掩盖潜在的线程竞争问题导致程序状态不可预测绝对不推荐在生产代码中使用。更现代的做法是使用async/await模式配合Task.Run和IProgressT接口来报告进度代码更清晰private async void btnStartAsync_Click(object sender, EventArgs e) { btnStartAsync.Enabled false; var progress new Progressint(percent { progressBar1.Value percent; lblStatus.Text ${percent}%; }); await Task.Run(() DoHeavyWork(progress)); btnStartAsync.Enabled true; MessageBox.Show(处理完成); } private void DoHeavyWork(IProgressint progress) { for (int i 0; i 100; i) { // 模拟耗时操作 Thread.Sleep(50); // 报告进度Progressint会自动在UI线程中调用回调 progress?.Report(i); } }3.2 数据绑定告别手动赋值与事件监听WinForm的数据绑定机制能极大减少样板代码但理解其双向绑定的原理是关键。一个常见的绑定场景是将一个对象的属性绑定到文本框。public class Person { public string Name { get; set; } public int Age { get; set; } } private Person _currentPerson new Person { Name 张三, Age 25 }; private void Form1_Load(object sender, EventArgs e) { // 简单绑定 txtName.DataBindings.Add(Text, _currentPerson, Name, false, DataSourceUpdateMode.OnPropertyChanged); numAge.DataBindings.Add(Value, _currentPerson, Age, false, DataSourceUpdateMode.OnPropertyChanged); // 复杂绑定如DataGridView ListPerson peopleList new ListPerson { _currentPerson, new Person { Name 李四, Age 30 } }; dataGridView1.DataSource peopleList; }这里DataSourceUpdateMode.OnPropertyChanged意味着当文本框内容改变时会立即更新数据源_currentPerson对象的属性。但需要注意的是要使绑定生效数据源对象最好实现INotifyPropertyChanged接口这样当对象属性在代码中被改变时UI也能自动更新。public class Person : INotifyPropertyChanged { private string _name; public string Name { get { return _name; } set { if (_name ! value) { _name value; OnPropertyChanged(nameof(Name)); } } } // ... Age属性类似 public event PropertyChangedEventHandler PropertyChanged; protected virtual void OnPropertyChanged(string propertyName) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } }实操心得对于简单的模型手动实现INotifyPropertyChanged有些繁琐。在实际项目中我通常会使用社区库如Fody/PropertyChanged通过编译时织入的方式自动生成属性变更通知代码或者直接使用ObservableCollectionT作为列表数据源它已经实现了集合变化的通知。3.3 自定义控件与GDI绘图打造独特UI当标准控件的外观无法满足需求时自定义控件是唯一选择。一个简单的自定义圆形按钮示例如下public class CircleButton : Control { private Color _borderColor Color.Gray; private Color _hoverColor Color.LightBlue; public CircleButton() { this.Size new Size(60, 60); this.DoubleBuffered true; // 启用双缓冲减少绘制闪烁 } protected override void OnPaint(PaintEventArgs e) { base.OnPaint(e); Graphics g e.Graphics; g.SmoothingMode SmoothingMode.AntiAlias; // 抗锯齿 Rectangle rect new Rectangle(0, 0, this.Width - 1, this.Height - 1); // 绘制背景 using (Brush brush new SolidBrush(this.BackColor)) { g.FillEllipse(brush, rect); } // 绘制边框 using (Pen pen new Pen(_borderColor, 2)) { g.DrawEllipse(pen, rect); } // 绘制文字居中 if (!string.IsNullOrEmpty(this.Text)) { StringFormat sf new StringFormat(); sf.Alignment StringAlignment.Center; sf.LineAlignment StringAlignment.Center; using (Brush textBrush new SolidBrush(this.ForeColor)) { g.DrawString(this.Text, this.Font, textBrush, rect, sf); } } } protected override void OnMouseEnter(EventArgs e) { base.OnMouseEnter(e); this.BackColor _hoverColor; this.Invalidate(); // 触发重绘 } protected override void OnMouseLeave(EventArgs e) { base.OnMouseLeave(e); this.BackColor SystemColors.Control; this.Invalidate(); } }注意事项双缓冲DoubleBuffering在绘制复杂图形或频繁刷新时务必设置DoubleBuffered true或在构造函数中调用SetStyle(ControlStyles.OptimizedDoubleBuffer, true)这能有效消除画面闪烁。资源释放在OnPaint方法中创建的Pen、Brush、Font、GraphicsPath等GDI对象必须在使用完毕后及时释放Dispose或者使用using语句块确保释放否则会导致内存泄漏GDI对象泄漏。上例中的using语句是正确做法。Invalidate与RefreshInvalidate()标记控件区域为需要重绘由系统在合适的时机调用OnPaintRefresh()会立即强制重绘。在大多数情况下使用Invalidate()性能更好。3.4 配置文件与序列化管理应用程序设置对于PropertyGrid只能查看不能修改的问题其核心在于所绑定对象的属性是否具有Browsable和ReadOnly特性或者是否提供了set访问器。例如我们希望一个类的某个属性在PropertyGrid中可见但不可编辑public class AppSettings { [Category(基本信息)] [DisplayName(应用程序名称)] [Description(当前应用的名称仅供显示。)] [Browsable(true)] // 可见 [ReadOnly(true)] // 只读 public string AppName { get; } 我的WinForm应用; // 只有getter [Category(基本信息)] [DisplayName(版本号)] [Description(软件的版本信息。)] [Browsable(true)] public string Version { get; set; } 1.0.0; // 可读可写 [Category(用户设置)] [DisplayName(窗口位置X)] public int WindowX { get; set; } [Category(用户设置)] [DisplayName(窗口位置Y)] public int WindowY { get; set; } } // 在Form中使用 private void LoadSettings() { propertyGrid1.SelectedObject new AppSettings(); }这样AppName属性在PropertyGrid中就会显示为灰色无法编辑。而保存和加载这些设置通常使用XML或JSON序列化。以System.Text.Json为例.NET Core/.NET 5using System.Text.Json; public class SettingsManager { private static readonly string SettingsPath Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), MyApp, settings.json); public static AppSettings LoadSettings() { if (File.Exists(SettingsPath)) { try { string json File.ReadAllText(SettingsPath); return JsonSerializer.DeserializeAppSettings(json); } catch { // 如果文件损坏返回默认设置 return new AppSettings(); } } return new AppSettings(); } public static void SaveSettings(AppSettings settings) { string dir Path.GetDirectoryName(SettingsPath); if (!Directory.Exists(dir)) { Directory.CreateDirectory(dir); } var options new JsonSerializerOptions { WriteIndented true }; string json JsonSerializer.Serialize(settings, options); File.WriteAllText(SettingsPath, json); } }4. 实战演练构建一个简易的图片浏览器让我们综合运用多个知识点快速构建一个具备基本功能的图片浏览器。这个实例将涵盖文件对话框、图片加载、缩放显示、简单图像处理旋转以及状态栏更新。4.1 界面设计与控件布局首先在Visual Studio中创建一个新的WinForm项目。在默认的Form1上拖放以下控件一个MenuStrip添加“文件(F)”菜单其下包含“打开(O)”、“退出(X)”子项。一个ToolStrip添加“放大”、“缩小”、“顺时针旋转”、“逆时针旋转”按钮和分隔符再添加一个显示缩放比例的ToolStripLabel。一个PictureBox设置Dock属性为FillSizeMode属性为Zoom保持比例缩放。一个StatusStrip添加一个ToolStripStatusLabel用于显示文件信息。一个OpenFileDialog控件。4.2 核心代码实现using System.Drawing; using System.Drawing.Drawing2D; using System.IO; namespace SimpleImageBrowser { public partial class MainForm : Form { private Image _currentImage null; private float _zoomFactor 1.0f; private const float ZOOM_STEP 0.2f; private const float MIN_ZOOM 0.1f; private const float MAX_ZOOM 5.0f; public MainForm() { InitializeComponent(); UpdateUIState(); } // 1. 打开图片文件 private void openToolStripMenuItem_Click(object sender, EventArgs e) { openFileDialog1.Filter 图片文件|*.jpg;*.jpeg;*.png;*.bmp;*.gif|所有文件|*.*; if (openFileDialog1.ShowDialog() DialogResult.OK) { try { // 释放之前的图片资源 _currentImage?.Dispose(); // 加载新图片 _currentImage Image.FromFile(openFileDialog1.FileName); pictureBox1.Image _currentImage; _zoomFactor 1.0f; UpdateStatusBar(openFileDialog1.FileName); UpdateZoomLabel(); } catch (Exception ex) { MessageBox.Show($加载图片失败{ex.Message}, 错误, MessageBoxButtons.OK, MessageBoxIcon.Error); } } } // 2. 更新状态栏信息 private void UpdateStatusBar(string filePath) { if (_currentImage ! null File.Exists(filePath)) { FileInfo fi new FileInfo(filePath); toolStripStatusLabel1.Text ${Path.GetFileName(filePath)} | ${_currentImage.Width} x {_currentImage.Height} | ${fi.Length / 1024} KB; } else { toolStripStatusLabel1.Text 就绪; } } // 3. 图片缩放功能 private void zoomInToolStripButton_Click(object sender, EventArgs e) { ZoomImage(ZOOM_STEP); } private void zoomOutToolStripButton_Click(object sender, EventArgs e) { ZoomImage(-ZOOM_STEP); } private void ZoomImage(float delta) { if (_currentImage null) return; float newZoom _zoomFactor delta; if (newZoom MIN_ZOOM || newZoom MAX_ZOOM) return; _zoomFactor newZoom; ApplyZoom(); UpdateZoomLabel(); } private void ApplyZoom() { if (_currentImage null) return; int newWidth (int)(_currentImage.Width * _zoomFactor); int newHeight (int)(_currentImage.Height * _zoomFactor); // 创建一个新的缩放后的位图 Bitmap zoomedBitmap new Bitmap(newWidth, newHeight); using (Graphics g Graphics.FromImage(zoomedBitmap)) { g.InterpolationMode InterpolationMode.HighQualityBicubic; g.DrawImage(_currentImage, 0, 0, newWidth, newHeight); } // 更新PictureBox注意释放旧图 Image oldDisplayImage pictureBox1.Image; pictureBox1.Image zoomedBitmap; oldDisplayImage?.Dispose(); } private void UpdateZoomLabel() { toolStripZoomLabel.Text ${_zoomFactor * 100:F0}%; } // 4. 图片旋转功能 private void rotateCWToolStripButton_Click(object sender, EventArgs e) { RotateImage(RotateFlipType.Rotate90FlipNone); } private void rotateCCWToolStripButton_Click(object sender, EventArgs e) { RotateImage(RotateFlipType.Rotate270FlipNone); } private void RotateImage(RotateFlipType rotateType) { if (_currentImage null) return; // 直接旋转原图 _currentImage.RotateFlip(rotateType); // 重新应用当前缩放比例以更新显示 ApplyZoom(); UpdateStatusBar(openFileDialog1.FileName); // 更新尺寸信息 } // 5. 更新按钮状态无图片时禁用操作按钮 private void UpdateUIState() { bool hasImage (_currentImage ! null); zoomInToolStripButton.Enabled hasImage; zoomOutToolStripButton.Enabled hasImage; rotateCWToolStripButton.Enabled hasImage; rotateCCWToolStripButton.Enabled hasImage; } // 6. 窗体关闭时释放资源 private void MainForm_FormClosing(object sender, FormClosingEventArgs e) { _currentImage?.Dispose(); pictureBox1.Image?.Dispose(); } } }4.3 功能要点与技巧解析资源管理Image对象是非托管资源封装了GDI对象必须及时释放。代码中在加载新图前释放旧图_currentImage?.Dispose()在缩放时释放临时生成的显示图oldDisplayImage?.Dispose()在窗体关闭时释放所有资源。这是避免内存泄漏的关键。缩放实现我们没有直接修改PictureBox的SizeMode而是通过生成一个缩放后的新Bitmap来显示。这样做可以更灵活地控制缩放算法通过Graphics.InterpolationModeHighQualityBicubic能提供较好的缩放质量。同时_zoomFactor变量记录了逻辑缩放比例便于实现“重置缩放”等功能。旋转实现使用Image.RotateFlip方法直接修改原图数据简单高效。旋转后需要重新调用ApplyZoom()来更新显示。UI状态同步通过UpdateUIState方法根据是否有图片加载来启用或禁用工具栏按钮提升用户体验。异步加载的考虑对于超大图片同步加载会阻塞UI线程。在实际项目中应考虑使用Task.Run在后台线程加载图片然后通过Invoke回UI线程显示并添加一个加载动画。5. 进阶思考从实例到架构的跨越学习这198个实例的最终目的不是成为“代码搬运工”而是理解其背后的设计思想和最佳实践并能在自己的项目中灵活运用甚至进行改进。5.1 设计模式的应用许多经典实例背后都隐藏着设计模式。例如观察者模式事件event机制本身就是观察者模式的实现。自定义控件时定义自己的事件如ValueChanged让窗体或其他控件订阅实现解耦。单例模式用于管理全局配置SettingsManager或日志记录器。工厂方法模式根据文件扩展名创建不同的图片处理策略IImageProcessor。MVP/MVVM模式在复杂的WinForm应用中可以引入这些模式来分离界面逻辑和业务逻辑。虽然WinForm没有原生绑定命令但可以通过第三方框架如MvvmLight或自己实现简单的数据绑定和命令模式来提升代码的可测试性和可维护性。5.2 性能优化与用户体验虚拟模式Virtual Mode当DataGridView需要显示海量数据数万行时启用虚拟模式VirtualMode true并处理CellValueNeeded等事件可以极大减少内存占用和初始化时间。延迟加载Lazy Loading对于TreeView加载深层级、大数据量的目录结构或分类数据不要一次性加载所有节点。可以只加载顶层节点当用户展开某个节点时再动态加载其子节点。后台处理与进度反馈任何可能耗时的操作如文件批量处理、复杂计算、网络请求都必须放在后台线程执行并通过进度条、状态文本或Marquee样式的进度条给用户明确的反馈。异常处理与日志记录不要用空的catch块。所有可能出错的地方文件IO、网络、数据库访问都应进行try-catch并向用户提供友好的错误提示同时将详细的异常信息记录到日志文件或系统中便于排查。5.3 模块化与复用当你从实例中学到某个优秀的功能比如一个漂亮的圆角面板控件、一个支持语法高亮的文本框、一个通用的数据库帮助类不要仅仅复制粘贴代码。应该思考如何将其封装成一个独立的类库Class Library项目或打成NuGet包。这样在你的所有项目中都可以方便地引用和升级真正做到代码复用提升开发效率。6. 常见问题排查与调试技巧即使有了丰富的实例参考在实际开发中仍会遇到各种问题。以下是一些常见WinForm问题的排查思路界面卡顿或无响应首要检查点是否在UI线程执行了耗时操作使用DebugWindowsThreads窗口查看线程状态。解决方案将耗时操作移至Task.Run或BackgroundWorker中。工具使用性能分析器Profiler查找热点代码。控件布局在运行时错乱检查点窗体或用户控件的AutoScaleMode属性。不同DPI的显示器可能导致缩放问题。建议设置为AutoScaleMode.Dpi或AutoScaleMode.Font并进行高DPI测试。检查点Anchor和Dock属性设置是否冲突。复杂的嵌套布局建议使用TableLayoutPanel。内存泄漏进程内存持续增长常见原因未释放GDI对象Pen,Brush,Image,Graphics、事件订阅未取消、静态集合持有对象引用。排查工具使用.NET Memory Profiler或ANTS Memory Profiler等工具分析内存快照查看对象存活路径。预防严格遵守IDisposable模式在using语句中使用相关对象为自定义控件或窗体清理事件订阅-。部署后运行报错如“无法加载DLL”或“找不到类型”对应热词c# 无法加载一个或多个请求的类型。有关更多信息请检索 loaderexceptions 属性。排查步骤检查目标机器是否安装了正确版本的.NET Framework/.NET运行时。检查引用的第三方原生DLLx86/x64是否与目标平台匹配并已复制到输出目录。在AppDomain.CurrentDomain.AssemblyResolve事件中处理程序集加载失败或使用Assembly.LoadFrom动态加载。查看异常内部的LoaderExceptions属性里面通常有更详细的失败信息。调试数据绑定问题在输出窗口Output中查看绑定错误信息需在项目属性中启用“绑定失败时在输出窗口显示详细信息”。检查数据源对象是否实现了INotifyPropertyChanged并且属性名拼写是否正确。对于复杂绑定使用BindingSource组件作为中间层可以更方便地进行调试和错误处理。198个经典实例是一座桥梁连接着书本上的理论知识与真实世界中的软件开发需求。我的建议是不要满足于运行通这些代码。打开每一个实例用调试器一步步跟踪执行流程观察变量如何变化尝试修改其中的参数看看会发生什么思考如果需求变了代码应该如何调整最后尝试将多个实例的功能组合起来创造出属于自己的小工具。这个过程才是从“看懂”到“学会”最终到“精通”的必经之路。当你能够游刃有余地借鉴、修改和创造这些代码时你就已经超越了这198个实例本身成为了一个能够独立解决问题的WinForm开发者。本文还有配套的精品资源点击获取
返回列表