在WinForm开发中,你是否遇到过这些令人头疼的问题:窗体间数据传递混乱、控件事件处理逻辑复杂、异步操作后UI更新困难?这些看似复杂的问题,其实都可以通过正确使用**委托(Delegate)和事件(Event)**来优雅解决。
作为C#开发的核心概念,委托和事件在WinForm应用中扮演着至关重要的角色。它们不仅能帮你实现松耦合的代码架构,还能让复杂的UI交互变得简单直观。本文将通过5个实战场景,带你深入掌握这两个强大工具的使用技巧。
在传统的WinForm开发中,我们经常面临以下挑战:
委托(Delegate):可以理解为"方法的指针",允许我们将方法作为参数传递。
事件(Event):基于委托的特殊封装,提供了更安全的发布-订阅模式。
主窗体打开一个设置窗体,用户修改设置后需要通知主窗体更新界面。
C#using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace AppWinformDelAndEvent
{
public partial class FrmSetting : Form
{
public delegate void SettingsChangedHandler(string theme, bool showToolbar); //正常我会用action
public event SettingsChangedHandler OnSettingsChanged;
public FrmSetting()
{
InitializeComponent();
}
private void btnSave_Click(object sender, EventArgs e)
{
string selectedTheme = cmbTheme.SelectedItem?.ToString();
bool showToolbar = chkShowToolbar.Checked;
// 触发事件,通知订阅者
OnSettingsChanged?.Invoke(selectedTheme, showToolbar);
this.DialogResult = DialogResult.OK;
this.Close();
}
}
}
C#using System.Windows.Forms;
namespace AppWinformDelAndEvent
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void btnSettings_Click(object sender, EventArgs e)
{
var frmSetting = new FrmSetting();
// 订阅事件
frmSetting.OnSettingsChanged += HandleSettingsChanged;
frmSetting.ShowDialog();
}
private void HandleSettingsChanged(string theme, bool showToolbar)
{
// 应用新设置
ApplyTheme(theme);
toolStrip.Visible = showToolbar;
var property = new
{
Theme = theme,
ShowToolbar = showToolbar
};
propertyGrid1.SelectedObject = property;
MessageBox.Show($"设置已更新:主题={theme}, 显示工具栏={showToolbar}");
}
private void ApplyTheme(string theme)
{
switch (theme)
{
case "暗黑":
this.BackColor = Color.DarkGray;
this.ForeColor = Color.White;
break;
case "高亮":
this.BackColor = Color.White;
this.ForeColor = Color.Black;
break;
}
}
}
}


?.操作符安全调用事件多个按钮需要执行相似的操作,但参数不同。
C#using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace AppWinformDelAndEvent
{
public partial class FrmCalculator : Form
{
// 定义通用的按钮点击委托
public delegate void NumberButtonClickHandler(int number);
public delegate void OperatorButtonClickHandler(string operatorSymbol);
private StringBuilder currentInput = new StringBuilder();
private double result = 0;
private string currentOperator = "";
private bool isNewNumber = true;
public FrmCalculator()
{
InitializeComponent();
InitializeNumberButtons();
InitializeOperatorButtons();
}
private void InitializeNumberButtons()
{
// 将数字按钮统一绑定到委托
NumberButtonClickHandler numberHandler = HandleNumberClick;
btn0.Click += (s, e) => numberHandler(0);
btn1.Click += (s, e) => numberHandler(1);
btn2.Click += (s, e) => numberHandler(2);
btn3.Click += (s, e) => numberHandler(3);
btn4.Click += (s, e) => numberHandler(4);
btn5.Click += (s, e) => numberHandler(5);
btn6.Click += (s, e) => numberHandler(6);
btn7.Click += (s, e) => numberHandler(7);
btn8.Click += (s, e) => numberHandler(8);
btn9.Click += (s, e) => numberHandler(9);
}
private void InitializeOperatorButtons()
{
// 操作符按钮统一处理
OperatorButtonClickHandler operatorHandler = HandleOperatorClick;
btnAdd.Click += (s, e) => operatorHandler("+");
btnSubtract.Click += (s, e) => operatorHandler("-");
btnMultiply.Click += (s, e) => operatorHandler("×");
btnDivide.Click += (s, e) => operatorHandler("÷");
}
private void HandleNumberClick(int number)
{
if (isNewNumber)
{
currentInput.Clear();
isNewNumber = false;
}
currentInput.Append(number);
txtDisplay.Text = currentInput.ToString();
}
private void HandleOperatorClick(string operatorSymbol)
{
if (!isNewNumber)
{
if (!string.IsNullOrEmpty(currentOperator))
{
PerformCalculation();
}
else
{
result = double.Parse(currentInput.ToString());
}
}
currentOperator = operatorSymbol;
isNewNumber = true;
// 在状态栏显示当前操作
lblStatus.Text = $"{result} {operatorSymbol} ";
}
private void PerformCalculation()
{
double currentValue = double.Parse(currentInput.ToString());
switch (currentOperator)
{
case "+":
result += currentValue;
break;
case "-":
result -= currentValue;
break;
case "×":
result *= currentValue;
break;
case "÷":
if (currentValue != 0)
result /= currentValue;
else
MessageBox.Show("除数不能为零!", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
break;
}
txtDisplay.Text = result.ToString();
currentInput.Clear();
currentInput.Append(result);
}
private void btnEquals_Click(object sender, EventArgs e)
{
}
private void btnClear_Click(object sender, EventArgs e)
{
txtDisplay.Clear();
}
}
}

使用泛型委托可以让代码更简洁:
C#// 使用Action和Func简化委托声明
private void InitializeButtons()
{
// Action<T> 相当于 delegate void Handler(T parameter)
Action<int> numberHandler = HandleNumberClick;
Action<string> operatorHandler = HandleOperatorClick;
// 甚至可以直接使用Lambda表达式
var buttons = new Dictionary<Button, int>
{
{ btn0, 0 }, { btn1, 1 }, { btn2, 2 }, { btn3, 3 }, { btn4, 4 },
{ btn5, 5 }, { btn6, 6 }, { btn7, 7 }, { btn8, 8 }, { btn9, 9 }
};
foreach (var kvp in buttons)
{
int number = kvp.Value; // 捕获局部变量
kvp.Key.Click += (s, e) => numberHandler(number);
}
}
创建一个自定义的图片查看器控件,需要向外发布图片切换、缩放等事件。
C#using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace AppWinformDelAndEvent
{
public partial class ImageViewerControl : UserControl
{
// 定义自定义事件参数
public class ImageEventArgs : EventArgs
{
public string ImagePath { get; }
public int CurrentIndex { get; }
public int TotalCount { get; }
public float ZoomLevel { get; }
public ImageEventArgs(string imagePath, int currentIndex, int totalCount, float zoomLevel)
{
ImagePath = imagePath;
CurrentIndex = currentIndex;
TotalCount = totalCount;
ZoomLevel = zoomLevel;
}
}
// 定义事件委托
public delegate void ImageChangedHandler(object sender, ImageEventArgs e);
public delegate void ZoomChangedHandler(object sender, ImageEventArgs e);
public delegate void ImageLoadErrorHandler(object sender, string errorMessage);
// 声明公共事件
public event ImageChangedHandler ImageChanged;
public event ZoomChangedHandler ZoomChanged;
public event ImageLoadErrorHandler ImageLoadError;
private List<string> imageFiles = new List<string>();
private int currentIndex = -1;
private float zoomLevel = 1.0f;
private Image currentImage;
public ImageViewerControl()
{
InitializeComponent();
this.DoubleBuffered = true; // 启用双缓冲减少闪烁
this.SetStyle(ControlStyles.ResizeRedraw, true);
}
// 加载图片文件夹
public void LoadImages(string folderPath)
{
try
{
var supportedExtensions = new[] { ".jpg", ".jpeg", ".png", ".bmp", ".gif" };
imageFiles = Directory.GetFiles(folderPath)
.Where(f => supportedExtensions.Contains(Path.GetExtension(f).ToLower()))
.OrderBy(f => f)
.ToList();
if (imageFiles.Count > 0)
{
currentIndex = 0;
LoadCurrentImage();
}
else
{
OnImageLoadError("文件夹中没有找到支持的图片文件");
}
}
catch (Exception ex)
{
OnImageLoadError($"加载文件夹失败:{ex.Message}");
}
}
// 显示下一张图片
public void NextImage()
{
if (imageFiles.Count == 0) return;
currentIndex = (currentIndex + 1) % imageFiles.Count;
LoadCurrentImage();
}
// 显示上一张图片
public void PreviousImage()
{
if (imageFiles.Count == 0) return;
currentIndex = currentIndex > 0 ? currentIndex - 1 : imageFiles.Count - 1;
LoadCurrentImage();
}
// 缩放图片
public void ZoomIn()
{
SetZoom(zoomLevel * 1.2f);
}
public void ZoomOut()
{
SetZoom(zoomLevel / 1.2f);
}
public void SetZoom(float newZoomLevel)
{
zoomLevel = Math.Max(0.1f, Math.Min(5.0f, newZoomLevel));
this.Invalidate(); // 触发重绘
// 触发缩放事件
OnZoomChanged();
}
private void LoadCurrentImage()
{
if (currentIndex < 0 || currentIndex >= imageFiles.Count) return;
try
{
// 释放之前的图片资源
currentImage?.Dispose();
// 加载新图片
string imagePath = imageFiles[currentIndex];
currentImage = Image.FromFile(imagePath);
// 重置缩放级别
zoomLevel = 1.0f;
// 重绘控件
this.Invalidate();
// 触发图片改变事件
OnImageChanged();
}
catch (Exception ex)
{
OnImageLoadError($"加载图片失败:{ex.Message}");
}
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
if (currentImage == null) return;
Graphics g = e.Graphics;
g.SmoothingMode = SmoothingMode.HighQuality;
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
// 计算显示尺寸和位置
int displayWidth = (int)(currentImage.Width * zoomLevel);
int displayHeight = (int)(currentImage.Height * zoomLevel);
int x = (this.Width - displayWidth) / 2;
int y = (this.Height - displayHeight) / 2;
// 绘制图片
g.DrawImage(currentImage, x, y, displayWidth, displayHeight);
// 绘制信息文本
string info = $"{currentIndex + 1}/{imageFiles.Count} - 缩放: {zoomLevel:P0}";
using (var font = new Font("微软雅黑", 12))
using (var brush = new SolidBrush(Color.White))
using (var shadowBrush = new SolidBrush(Color.Black))
{
// 绘制阴影效果
g.DrawString(info, font, shadowBrush, 11, 11);
g.DrawString(info, font, brush, 10, 10);
}
}
protected override void OnMouseWheel(MouseEventArgs e)
{
base.OnMouseWheel(e);
// 鼠标滚轮缩放
if (e.Delta > 0)
ZoomIn();
else
ZoomOut();
}
protected override void OnKeyDown(KeyEventArgs e)
{
base.OnKeyDown(e);
switch (e.KeyCode)
{
case Keys.Left:
PreviousImage();
break;
case Keys.Right:
NextImage();
break;
case Keys.Add:
case Keys.Oemplus:
ZoomIn();
break;
case Keys.Subtract:
case Keys.OemMinus:
ZoomOut();
break;
case Keys.D0:
case Keys.NumPad0:
SetZoom(1.0f);
break;
}
}
// 事件触发方法
protected virtual void OnImageChanged()
{
if (currentIndex >= 0 && currentIndex < imageFiles.Count)
{
var args = new ImageEventArgs(
imageFiles[currentIndex],
currentIndex,
imageFiles.Count,
zoomLevel);
ImageChanged?.Invoke(this, args);
}
}
protected virtual void OnZoomChanged()
{
if (currentIndex >= 0 && currentIndex < imageFiles.Count)
{
var args = new ImageEventArgs(
imageFiles[currentIndex],
currentIndex,
imageFiles.Count,
zoomLevel);
ZoomChanged?.Invoke(this, args);
}
}
protected virtual void OnImageLoadError(string errorMessage)
{
ImageLoadError?.Invoke(this, errorMessage);
}
}
}
C#using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace AppWinformDelAndEvent
{
public partial class FrmImageViewer : Form
{
private ImageViewerControl imageViewer;
public FrmImageViewer()
{
InitializeComponent();
InitializeImageViewer();
}
private void InitializeImageViewer()
{
imageViewer = new ImageViewerControl
{
Dock = DockStyle.Fill,
TabIndex = 0
};
// 订阅自定义事件
imageViewer.ImageChanged += OnImageChanged;
imageViewer.ZoomChanged += OnZoomChanged;
imageViewer.ImageLoadError += OnImageLoadError;
this.Controls.Add(imageViewer);
imageViewer.Focus(); // 确保能接收键盘事件
}
private void OnImageChanged(object sender, ImageViewerControl.ImageEventArgs e)
{
// 更新窗体标题
this.Text = $"图片查看器 - {Path.GetFileName(e.ImagePath)} ({e.CurrentIndex + 1}/{e.TotalCount})";
// 更新状态栏
stsMain_lblTitle.Text = $"文件:{e.ImagePath}";
}
private void OnZoomChanged(object sender, ImageViewerControl.ImageEventArgs e)
{
// 更新缩放显示
stsMain_lblZoom.Text = $"缩放:{e.ZoomLevel:P0}";
}
private void OnImageLoadError(object sender, string errorMessage)
{
MessageBox.Show(errorMessage, "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
stsMain_lblTitle.Text = "加载失败";
}
private void btnOpenFolder_Click(object sender, EventArgs e)
{
using (var dialog = new FolderBrowserDialog())
{
dialog.Description = "选择包含图片的文件夹";
if (dialog.ShowDialog() == DialogResult.OK)
{
imageViewer.LoadImages(dialog.SelectedPath);
}
}
}
private void btnNext_Click(object sender, EventArgs e)
{
imageViewer.NextImage();
}
private void btnPre_Click(object sender, EventArgs e)
{
imageViewer.PreviousImage();
}
}
}

通过以上3个实战场景,我们深入探索了WinForm中委托和事件的强大应用:
C#// 万能事件定义模板
public class CustomEventArgs<T> : EventArgs
{
public T Data { get; }
public DateTime Timestamp { get; }
public CustomEventArgs(T data)
{
Data = data;
Timestamp = DateTime.Now;
}
}
// 线程安全的事件触发模板
private void SafeInvokeEvent<T>(Action<T> eventHandler, T parameter)
{
if (InvokeRequired)
BeginInvoke(eventHandler, parameter);
else
eventHandler?.Invoke(parameter);
}
你在WinForm开发中最常遇到的事件处理难题是什么?是跨线程UI更新,还是复杂的窗体间通信?欢迎在评论区分享你的经验和困惑!
如果这篇文章帮你解决了实际问题,请转发给更多需要的同行,让我们一起提升C#开发水平!
关注我,获取更多实用的C#开发技巧和最佳实践!
📝 本文所有代码均经过实际测试,可直接用于生产项目。如需完整示例源码,请私信获取。
本文作者:技术老小子
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!