主要用于解决:在一个窗体中修改了数据,而另一个使用此数据的窗体实现自动的数据更新。
它有以下特点:
1) 对象之间的松藕合性。两个窗体或多个窗体之间,相互之间不需要知道对方的存在。
2) 可以实现同一应用程序中,多个子窗体之间的联动;
3) 也可以实现分布式的联动。即A计算机修改了数据,依赖此数据的B计算机中的窗体进行自动刷新。
假如此DataTracker进行了封装可以直接调用,首先看用法:
接口定义:
/// <summary>
/// 定义可以刷新的窗体或控件的接口
/// </summary>
public interface ICanReLoad
{
/// <summary>
/// 刷新的窗体或控件
/// </summary>
void ReLoad();
}
/// <summary>
/// 可以跟踪别的对象的修改并触发默认行为
/// </summary>
public interface ITracker : ICanReLoad
{
/// <summary>
/// 被跟踪对象的字符串名称表,以逗号分隔
/// </summary>
string TrackTypes { get; }
/// <summary>
/// 指示是否是在被跟踪对象改变时是否立即重新获取数据
/// 如果为否则等控件在被激活时才刷新。
/// </summary>
bool FastReload { get; set; }
}
/// <summary>
/// 可以跟踪别的对象的修改并触发指定名称的行为
/// </summary>
public interface INamedTracker : ITracker
{
/// <summary>
/// 刷新窗体或控件
/// </summary>
/// <param name="typeName">指定的名称</param>
void ReLoad(string typeName);
}
也就是说,要实现窗体之间的联动,各联动窗体要实现以上接口。如果是整个窗体的刷新,实现ITracker接口,实现局部刷新,实现INamedTracker接口。
实现以上接口以上,整个应用程序的联动管理类是DataTracker工具类。它实现总控。它的方法有:
/// <summary>
/// 源对象在修改时通知对应的跟踪它的对象
/// </summary>
/// <param name="sourceTypes">用逗号隔开的数据类型名称列表</param>
public static void Change(params string[] sourceTypes)
/// <summary>
/// 通知本地和远程同时更新界面数据
/// </summary>
/// <param name="sourceTypes">要更新的数据名称列表</param>
public static void ChangeWithRemote(params string[] sourceTypes)
/// <summary>
/// 判断并刷新指定的跟踪对象
/// </summary>
/// <param name="tracker"></param>
public static void ReLoad(ITracker tracker)
/// <summary>
/// 判断并刷新指定名称的跟踪对象
/// </summary>
/// <param name="tracker"></param>
/// <param name="typeName"></param>
public static void ReLoad(INamedTracker tracker, string typeName)
/// <summary>
/// 注册跟踪对象
/// </summary>
/// <param name="tracker"></param>
public static void Register(ITracker tracker)
/// <summary>
/// 反注册跟踪对象
/// </summary>
/// <param name="tracker"></param>
public static void UnRegister(ITracker tracker)
以上限于篇幅,只列出了DataTracker中的方法签名并没有列出实现。它的实现也很简单,大体是维护一个跟踪列表,当收到用Change()方法接收的联动信号以后,
遍历跟踪表,依次调用窗体的ReLoad()方法实现联动。
总之,使用它的方法是:
1) 窗体实现ITracker接口或INamedTracker接口, 其中,TrackTypes属性中列出本窗体有关的数据项名称。这个数据项名称可以任意,但是必须和发送方的名称相同。
2) 用DataTracker.Register()方法注册本窗体到跟踪列表中。
3 ) 什么都不管了,坐等其他窗体发信息。
示例:
//窗体A, 展示数据的窗体
public class FormA:Form,ITracker
{
public string TrackTypes { get {return "数据A";} }
public FormA()
{
InitializeComponent();
DataTracker.Register(this);
}
public void ReLoad()
{
//刷新数据A
}
protected void FormA_Closed(object sender, EventArgs e)
{//对于窗体或控件而言,如果不在关闭时注销跟踪项,后果会很严重
DataTracker.Unregister(this);
}
}
//窗体B, 修改了数据的窗体
public class FormB:Form
{
public void SaveDataA()
{
//修改数据A
// ...
//发出信号
DataTracker.Change("数据A");
}
}
以上是实现ITracker接口的窗体联动的例子,最为简单。
如果实现INamedTracker接口,则稍稍复杂一点,可能要用一系列case语句来路由判断要刷新哪一部分数据。这里就先不赘述了。
以上FormA中的Register和UnRegister等方法,可以在基类窗体中作进一步封装,以进一步简化业务窗体的使用。
毕竟,要实现一个复杂的应用程序,很少有窗体只用到Form这个基类的。
下面还有最激动人心的,分布式的联动,我可以把这部分代码整个晒出来。因为很简单:
/// <summary>
/// 远程跟踪类:用于远程通知并接收数据的更新信号
/// </summary>
public class RemoteTracker
{
/// <summary>
/// 最后一次更新的时间
/// </summary>
DateTime lastUpdateTime = ServerHelper.ServerTime;
/// <summary>
/// 从服务端获取数据的更新信号
/// </summary>
/// <returns></returns>
string GetRemoteChangedTypes()
{
string r = WebHelper.GetWebResponseText(
String.Format("{0}/DataTrackerService.ashx?dt={1}&{2}",
XpoFactory.Instance.ServiceUrl,
HttpUtility.UrlEncode(lastUpdateTime.ToString("yyyy-MM-dd HH:mm:ss")),
LoginMgr.Instance.VerifyQuery));
return r;
}
/// <summary>
/// 将本地数据的更新信号发到服务端
/// 再由服务端通知其他客户端更新
/// </summary>
/// <param name="trackTypes">发生变更的数据名称列表 </param>
public string TellRemoteChanged(params string[] trackTypes)
{
string r = WebHelper.GetWebResponseText(
String.Format("{0}/DataTrackerService.ashx?tt={1}&dt={2}&{3}",
XpoFactory.Instance.ServiceUrl,
HttpUtility.UrlEncode(String.Join(",", trackTypes)),
HttpUtility.UrlEncode(ServerHelper.ServerTime.ToString("yyyy-MM-dd HH:mm:ss")),
LoginMgr.Instance.VerifyQuery));
return r;
}
System.Timers.Timer timer = new System.Timers.Timer(10000); //默认10秒一次
/// <summary>
/// 远程跟踪对象单例
/// 用于远程通知并接收数据的更新信号
/// </summary>
public static RemoteTracker Instance = new RemoteTracker();
private RemoteTracker()
{
}
/// <summary>
/// 开始执行跟踪
/// </summary>
public void Start()
{
timer.Elapsed += new System.Timers.ElapsedEventHandler(timer_Elapsed);
timer.Start();
}
void timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
string types = GetRemoteChangedTypes();
if (!string.IsNullOrEmpty(types))
{
DataTracker.Change(types.Split(','));
}
lastUpdateTime = ServerHelper.ServerTime;
}
}
事实上,分布式联动其实就是一个轮询机制,每隔一段时间请求一次服务器(这里用的asp.net的ashx服务),获取最近修改过的数据列表。
在联动的发起方,要做的事情是在修改数据后,调用TellRemoteChanged()方法来通知服务器。
另外,在DataTracker.ChangeWithRemote()方法中,已经综合调用了本地通知和远程通知,因此,通过这个方法可以同时发起本地的联动和远程其他客户机的联动。
虽然此工具类处理窗体是最典型的应用,它也可以处理其他任何object对象,从而实现一些非常巧妙的组合应用。