.NET Compact Framework应用程序因为运行于移动设备上, 由于移动设备性能和PC有很大差距,因此在设计程序时候必须更多的考虑程序的性能,通常的场景是在多窗体切换时往往加载窗体的速度都很不理想,项目中窗体窗体过多会导致程序过大,我们通常在一个窗体中设置很多的UserControl,通过更换Control来实现多窗体的效果。
过多的Control切换是一件很烦人的事情,在实际操作中控件的切换总是有规律的,它们的显示顺序很符合Stack结构最后显示的Control就是栈顶元素。为此我考虑了通过Stack结构实现一个控件缓存。大致思想是把一个窗体中的控件通过一个统一的栈实现,我们在调入一个控件时候相当于向栈中Push一个元素,反之当要关闭(其实是隐藏)正在显示的控件时候相当于Pop栈顶元素。我们用一个ArrayList实现窗体的控件缓存,用另一个ArrayList实现Stack。
首先定义Stack类,因为我们的Stack并不一定用于Form.Controls集合,也有可能它是位于窗体中一个Panel的Controls集合,因此对于通用性的考虑是必须的,我们把存放元素用的ArrayList变成IList接口,让Stack可以和IList接口组装,从而让它的应用更广泛,同时为了在更多的地方能够使用这个Stack,我们可以把Stack的元素设为Object,但是装相操作必然损失性能,好在.NET 2.0已经支持泛型了,我们把Stack设计为泛型既满足了通用性又不损失性能。
Stack类包含一些Stack的基本属性和方法:例如Stack大小,栈顶元素,Push方法,Pop方法等。先看Stack类构造器:
public class CacheStack<T>
{
private ArrayList m_arrStack = new ArrayList();
private IList m_iList = null;
public CacheStack(IList list)
{
this.m_iList = list;
}
public CacheStack()
: this(new System.Collections.ArrayList())
{
}
}
首先看Push操作,Push一个Control时,当缓存中存在该控件时我们把该控件置于顶层,并向Stack中Push这个控件的标识,最后显示控件,如果是第一次Push我们先把它加入缓存,然后在Stack中Push这个控件的标识,最后显示控件。因为Control对象在通常应用场景中必然存在数据更新或界面更新的问题,为此我们定义一个Pushing事件,该时间在Push操作前被触发,为更新操作预留了方法插入点,同时处于通用性的考虑,我们定义了Pushed事件,可以在Stack压入元素后定义自己的行为,比如前面说的显示控件。
public delegate void DoingPush(T ElementToPush);
public event DoingPush Pushing;
public delegate void Pushed(T ElementToPush);
public event Pushed AfterPush;
其中的参数为被压入栈的对象元素。
下面看Push方法的实现:
public void Push(T element)
{
// 同一时间只允许单线程操作,确保线程安全
Monitor.Enter(this);
//触发压栈前事件
this.Pushing(element);
foreach (T ele in this.m_iList)
{
if (element.GetType().Name.Equals(ele.GetType().Name))
{
//添加对象到缓存
m_arrStack.Add(element.GetType().Name);
//更新和栈顶元素
this.m_entireObj = element;
// 触发事件
this.AfterPush(element);
return;
}
}
// 添加元素
this.m_iList.Add(element);
m_arrStack.Add(element.GetType().Name);
this.m_entireObj = element;
//触发压栈后事件
this.AfterPush(element);
Monitor.Exit(this);
}
当然你可以用单独的线程Pushing执行事件以获得更好的性能,这里仅仅体现实现思想就不讨论了。
下面看Pop方法, Pop方法很简单,Pop一个元素时从Stack的ArrayList中删除最后Add的元素,为了通用性我们同样定义一个Poping事件,这个事件在Pop元素过程中触发,这个事件可以定义我们希望进行的Pop操作,例如上面隐藏控件,或者你真的希望从元素集合中真实的删除这个元素都可以把方法插入到该事件中,同时在最后我们定义一个Poped事件预留插入完成Pop后的调用方法。
public delegate void DoingPop(T ElementToPop);
public event DoingPop Poping;
public delegate void Poped();
public event Poped AfterPop;
ElementToPop参数为被Pop的元素
我们来看Pop方法:
public void Pop(uint EleToPop)
{
if (m_arrStack.Count <= EleToPop)
{
throw new Exception("Entire element cannot pop!");
}
else
{
// 从stack中删除(缓存中元素存在)
for (int i = 0; i < EleToPop; i++)
{
m_arrStack.RemoveAt(m_arrStack.Count - 1);
foreach (T element in this.m_iList)
{
// 逐个取得栈顶元素并触发事件
if (element.GetType().Name.Equals(m_arrStack[m_arrStack.Count - 1]))
{
this.Poping(this.m_entireObj);
//更新栈顶元素
this.m_entireObj = element;
}
}
}
}
this.AfterPop();
}
注意这个语句,这里由于我们的应用场景是Control集合,我们的窗体在只剩最后一个Control时是不可以再隐藏这个Control的,而因该去关闭它,这个后面会讨论。为了通用,你也可以设一个属性决定是否可以Pop最后一个元素。
if (m_arrStack.Count <= EleToPop)
{
throw new Exception("Entire element cannot pop!");
}
其他属性:
public IList Elements
{
get { return this.m_iList; }
}
public T EntireElement
{
get
{
if (this.m_arrStack.Count == 0)
{
throw new NullReferenceException("the stack is empty!");
}
return this.m_entireObj;
}
}
public int Size
{
get { return this.m_arrStack.Count; }
}
到此,一个Stack缓存栈就实现了。我们来看看应用,还是以我们的应用场景窗体控件缓存来看,CacheStack使用很简单,首先和一个IList完成组装,然后定义好事件就OK.
我们在窗体类中加入下面的代码:
this.m_stack = new CacheStack<Control>(this.pnlMiddle.Controls);
this.m_stack.Pushing += new CacheStack<Control>.DoingPush(m_stack_Pushing);
this.m_stack.AfterPush += new CacheStack<Control>.Pushed(m_stack_AfterPush);
this.m_stack.Poping += new CacheStack<Control>.DoingPop(m_stack_Poping);
private void m_stack_Pushing(Control ElementToPush)
{
IMainControl ct = ElementToPush as IMainControl;
if (ct != null)
{
ct.ResetStatus();
}
}
private void m_stack_Poping(Control ElementToPop)
{
ElementToPop.Visible = false;
}
private void m_stack_AfterPush(Control ElementToPush)
{
if (ElementToPush.Dock != DockStyle.Fill)
{
ElementToPush.Dock = DockStyle.Fill;
}
ElementToPush.BringToFront();
ElementToPush.Visible = true;
}
最后处理一个问题,当Stack中只用一个元素时我们因该关闭窗体而不是Pop控件, 我们在窗体Closing事件中处理这个问题。
private void frm_Closing(object sender, CancelEventArgs e)
{
try
{
this.m_stack.Pop(1);
e.Cancel = true;
}
catch
{
}
}
到这里我们通过多控件实现了多窗体的效果,使得编译后得程序大小得到缩小,同时通过Stack提高了程序得性能,三八一下,实际的应用场景中我们其实是把Control都提前加载到了一个全集的Cache中,Stack所Push的Control都是从这个全集缓存中得到,这里的Stack仅仅是"二级缓存"。虽然文章是针对Compact Framework写的,其实这个设计在.NET Framework中同样实用。好了,文章完,睡大头觉去了,周末的心情总是如此的好!