这个日志系统不算大,但也有一些含金量,初学者可以通过这个项目快速的掌握C#的一些用法,以及winform编程里面用到的思想。实现两大类功能,一类是实时日志,另一类是历史日志,下面是实时日志的界面。
实时日志效果
以上就是效果图,那么我们分析一下思路。日志系统中的实时日志和消息并没有多大区别,我们在VS2013下新建类库,名字就叫Loglib。建完之后,思考一个问题,我们描述一个消息和日志,最基本的就是要描述消息或日志的格式。那么常见的日志的格式都是分了很多等级的。那么代码就来了,我们先定义一个最基本的LogBase类,根据单一性原则,该类只负责描述消息的格式,并不决定日志的动作,代码如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace logLib
{
public class LogBase
{
public string _Msg;
public LogLevel _level;
public LogBase(string Msg,LogLevel level )
{
_Msg = Msg;
_level = level;
}
}
public enum LogLevel
{
Information =0,
warrning,
error
}
}
这里强调一个规范,类内的局部变量要用下划线+变量名。这个logBase实际上定义了消息的协议,就是说你想用这个库或者模块,那么你想传递的消息必然要遵循这个协议。可以看出除了消息原本的内容之外,还加了日志等级,等级用枚举描述。再想下一个问题,我们最终想把这个消息传递到某个窗口上,根据不同的需求,可能你有很多窗口想输出日志信息,可能你自己专门定制了一个专门用于日志打印的附加应用程序,那么我们现在要写的这个类要兼容这几种情况,怎么做?既然和窗口有关系,我们定义一个LogView类,代码如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace logLib
{
public class LogView
{
private static LogView _instance = null;
private static readonly object InitLock = new object();
private static List<TextBoxBase> _logList = null;
private LogView()
{
_logList = new List<TextBoxBase>();
}
private static LogView Instance
{
get
{
if(_instance == null)
{
lock(InitLock)
{
if(_instance == null)
{
_instance = new LogView();
}
}
}
return _instance;
}
}
private bool AddLogView(TextBoxBase logBox)
{
if(_logList.Contains(logBox))
{
return false;
}
_logList.Add(logBox);
return true;
}
private bool RemovelogView(TextBoxBase logBox)
{
if(_logList.Count == 0|| !_logList.Contains(logBox))
{
return false;
}
_logList.Remove(logBox);
return true;
}
private bool Toast(LogBase msg)
{
if(_logList != null)
{
_logList.ForEach(logControl => logControl.Invoke(new Action(() =>
{
if (logControl.Lines.Length > 100)
{
logControl.Text = @"";
}
logControl.Text += msg._Msg;
logControl.Select(logControl.Text.Length, 0);
logControl.ScrollToCaret();
})));
}
return true;
}
public static bool ToastLog(LogBase log)
{
return Instance.Toast(log);
}
public static bool AddLogBox(TextBoxBase logBox)
{
return Instance.AddLogView(logBox);
}
public static bool RemoveLogBox(TextBoxBase logBox)
{
return Instance.RemovelogView(logBox);
}
}
}
在这里先不讲代码,先说说我自己的理解,我一直很惊叹于图形界面的底层实现,因为之前学的linux,linux的编程亦或者unix环境高级编程是不涉及图形界面的,这与windows不同,windows有三大模块,user,内核,GDI,也就是说windows操作系统原生的就为你提供了图形界面的接口。而linux不行。那么在面向对象思想的中,一切皆对象,正如在linux操作系统中一切皆文件一样,我们的每一个窗体,每一个控件都是类。都可以实例化成对象来进行传递。当懂得了这个道理的时候,一切都变的灵活了。现在我们来分析代码。当这个类可能被很多地方引用的时候,我们往往考虑单例设计模式。单例设计模式的实现首先是要把构造函数私有化,只有这样,外部的程序调用你的类就实例不了对象,那么为什么上锁呢,是为了防止多线程的访问造成并没有实现单例的后果。
这里我们怎么实现窗体对象的传递呢,首先上述说明已经说过,任何窗体和控件都是对象。那么在这里我在logView中定义了AddlogView这个方法。这个方法可以理解为窗体的注册类,在外部调用这个方法,可以把窗体对象传递进来存在了一个列表里。这样的话就兼容了以上两种情况。
操作这一段重点要说的是c#跨线程访问控件。解决这个问题有两种方式,第一种,直接写入一个语句,具体的百度吧,这种方式不推荐。可以让c#忽略安全问题。第二种 用委托的方式。Toast方法就是实现委托的方式,看官们 自己体会吧。
当上述功能实现之后,我想定义一个接口,直接调用这个方法就可以实现实时打印日志,怎么办,首先我们互相提供接口的时候都是用静态类里的静态方法,好的,那么代码如下:
using System.Collections.Generic;
using System.Reflection;
using System.Linq;
using System.Windows.Forms;
using System.Text;
namespace logLib
{
public static class log
{
public static void Pop(string Msg, LogLevel level)
{
LogBase msgEntry = new LogBase(Msg + Environment.NewLine, (LogLevel)(int)level);
string log = String.Format(@"{0:mm:ss:} {1}: {2}", DateTime.Now,
msgEntry._level,
msgEntry._Msg);
LogView.ToastLog(msgEntry);
}
}
}
Pop这个方法是最终调用方法,也就是说,在把窗口注册进去之后,我们就可以通过这个Pop方法实现实时打印,界面以及历史日志查询,下篇讲。