真绕啊。
话说,我有个窗体程序,运行着一个线程,线程里各种操作。操作过程中,各种提示、
相关信息需要记录到日志,同时也要输出到窗体上,好让人一目了然。
这部分的信息处理工作,我放在一个专门的类里完成。姑且叫做日志类吧。这个日志类的正职,是将信息记录到日志文件。现在,还要输出到窗体,怎么办呢?如果直接在这个类里操作窗体对象,就破坏了这个类的封闭,又增加了耦合。如果是对象注入吧,可能也是个办法,但我总觉得不是很好。今天输出到窗体,你新增一个对象;明天要记录到数据库呢,是不是又要增加个啥?
用委托啊。
日志类里声明一个委托,窗体注册这个委托。委托委托,谁委托谁?各方委托给执行类去执行。将自己的私货委托给执行类去执行。这有点像装饰模式。执行类本身有自己的功能,然后大家委托它,给它刷了一层又一层的漆。在我这个例子中,日志类就是执行类,窗体是委托方。
上代码:
//线程中调用日志类
Logger.Instance.Info($@"第 {iCycle} 轮开始 ……");
//日志类
public class Logger
{
//声明委托
public delegate void DlgShowInfo(string msg);
public event DlgShowInfo ShowMsg;
public void Info(string msg)
{
//执行正职工作,写入日志文件
applog.Info(msg);
//执行委托。委托委托,受人之托
//本来可以这样写:if (ShowMsg != null) ShowMsg(msg);
//但下面的写法更简洁
ShowMsg?.Invoke($@"{DateTime.Now.ToString("HH:mm")}-->{msg}");
}
static Logger instance = null;
public static Logger Instance
{
get
{
if (instance == null)
{//单例模式
instance = new Logger();
}
return instance;
}
}
}
//窗体
public partial class Form1 : Form
{
ShowInfo si;
public Form1()
{
InitializeComponent();
si = new ShowInfo((Form)this,rtb1, 20);//rtb1是一个富文本控件
Logger.Instance.ShowMsg += si.ShowText;//注册委托
}
}
//窗体输出信息类
public class ShowInfo
{
Form form;
RichTextBox _rtb;//富文本控件,用于在窗体上展示信息
int _rowsLimit;
public ShowInfo(Form form,RichTextBox rtb,int rowsLimit = 20)
{
this.form = form;
this._rtb = rtb;
this._rtb.BackColor = System.Drawing.SystemColors.ControlText;//背景色为黑色
this._rtb.ForeColor = System.Drawing.Color.Lime;//前景色即文字颜色为蓝绿色
this._rowsLimit = rowsLimit;
}
public void ShowText(string mess)
{//本方法支持多线程
if (_rtb.InvokeRequired)
{// 当一个控件的InvokeRequired属性值为真时,说明有一个创建它以外的线程想访问它
Action<string> dlg = (x) => {
showText((object)mess);
};
_rtb.Invoke(dlg, mess);
}
else
{
showText((object)mess);
}
}
long _rows = 0;
void showText(object objmess)
{
string mess = objmess.ToString();
if (_rows < _rowsLimit)
{
_rows++;
_rtb.Text += mess + "\r\n";
}
else
{
_rows = 0;
_rtb.Text = mess + "\r\n";
}
_rtb.SelectionStart = _rtb.Text.Length;
_rtb.ScrollToCaret();
}
}
运行结果
日志文件:
窗体:
上面这个委托,现在说起来,思路非常清晰,看上去应该是很自然就能想到的,但当时却想了好一阵子才理清头绪。现在对委托的理解又深了一些。