很多的手机软件,在底部或者顶部都有两个菜单,左边一个是功能菜单,右边一个是“返回”或者“退出”的按钮,这个软件也不列外,为了方便开发,我做了一个集成。
要实现的最终样式大概是这样的:
顶部的左边是功能菜单;顶部的右边是“返回”或者“退出”;顶部的中间显示相应的功能描述;中间是一个Panel容器,用来加载不同的用户控件。下面,我们就来一步步实现这些需求。
主窗体
打开VS2005,新建一个智能设备应用程序项目,将窗体的ControlBox属性设置成false,FormBorderStyle属性设置成none.然后拖放两个Panel,一个据顶,一个居中,然后再拖放两个按钮到顶部的Panel中,一个居左,一个居右,中间放一个Label用来显示功能描述。如下图:
(为了简单起见,在这里,我直接在左右两边放了个button,显得十分的简陋,在实际的项目中,大家可以考虑封装成一个控件,做出更加美观的效果)
要实现“返回”的功能,我们必须在主窗体里面放置一个容器,来保存历史操作,在这里,我用Stack来保存。容器有了,我们还缺少两个方法,一个是把历史操作放进去(即前进)的方法,一个是取出历史操作(即返回)的方法。同时,我们也添加一个父用户控件ucBaseControl,将返回的操作与返回按钮关联起来,代码如下:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
namespace PDA
{
public partial class MainForm : Form
{
private Stack<ucBaseControl> StackList = new Stack<ucBaseControl>();
public MainForm()
{
InitializeComponent();
}
/// <summary>
/// 控件跳转
/// </summary>
/// <param name="ctl">用户控件</param>
public void ShowForm(ucBaseControl ctl)
{
if (StackList == null)
{
StackList = new Stack<ucBaseControl>();
}
ucBaseControl uc = null;
if (this.pnlMain.Controls.Count == 0)
{
return;
}
else
{
uc = this.pnlMain.Controls[0] as ucBaseControl;
}
if (uc != null)
{
if (!StackList.Contains(uc))
{
StackList.Push(uc);
this.btnReturn.Text = "返回";
}
this.pnlMain.Controls.Clear();
ctl.Dock = DockStyle.Fill;
this.pnlMain.Controls.Add(ctl);
}
}
/// <summary>
/// 返回
/// </summary>
public void Return()
{
//返回按钮的事件
if (StackList != null && StackList.Count > 0)
{
ucBaseControl ctl = StackList.Pop();
if (StackList != null && StackList.Count > 0)
{
this.btnReturn.Text = "返回";
}
else
{
this.btnReturn.Text = "退出";
}
this.ShowForm(ctl);
}
}
private void btnReturn_Click(object sender, EventArgs e)
{
Return();
}
}
}
至此,“返回”功能仿佛就已经完成了。下面我们再来看“功能”按钮和模块功能描述该怎么实现。
ucBaseControl
我知道,每一个功能模块都功能菜单和模块名称,但却都各不相同的功能,所以我们在ucBaseControl里面添加一个两个抽象方法,让各功能模块去实现自己的菜单和给出自己的功能描述,然后再赋值给ucBaseControl控件的ContextMenu和Text属性,所以,我们想到在ucBaseControl中添加如下代码:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Text;
using System.Windows.Forms;
namespace PDA
{
public abstract partial class ucBaseControl : UserControl
{
public ucBaseControl()
{
InitializeComponent();
ContextMenu menu = null;
string titel = string.Empty;
OnFunction(ref menu);
OnTitel(ref titel);
this.ContextMenu = menu;
this.Text = titel;
}
/// <summary>
/// 功能菜单的方法
/// </summary>
/// <param name="contextMenu"></param>
protected abstract void OnFunction(ref ContextMenu contextMenu);
/// <summary>
/// 模块标题
/// </summary>
/// <param name="titel"></param>
protected abstract void OnTitel(ref string titel);
}
}
我们在子控件中有类似如下的代码来对这两个方法进行实现:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
namespace PDA
{
public partial class UserControl1 : PDA.ucBaseControl
{
public UserControl1():base()
{
InitializeComponent();
}
protected override void OnFunction(ref ContextMenu contextMenu)
{
if (contextMenu == null)
{
contextMenu = new ContextMenu();
}
else
{
contextMenu.MenuItems.Clear();
}
MenuItem item = new MenuItem();
item.Text = "跳转到Test2";
item.Click += new EventHandler(item_Click);
contextMenu.MenuItems.Add(item);
}
protected override void OnTitel(ref string titel)
{
titel = "Test1";
}
void item_Click(object sender, EventArgs e)
{
}
}
}
请注意,子控件的构造函数必须继承基类构造函数,原因大家都知道,不继承的话,我们写在基类构造函数中的两个抽象方法就不会执行。现在新的问题产生了,我们该如何在item_Click事件中做功能模块的跳转呢?在这里,我们首先需要获得当前用户控件所在的窗体,这里面可不想PC上的winform开发,用户控件直接有ParentForm属性,在这里面,我们需要自己去写,所以,我们在ucBaseControl里面添加如下代码,已获得用户控件所在的窗体:
/// <summary>
/// 获取用户控件所在的窗体
/// </summary>
protected MainForm ParentForm
{
get
{
Control frm = this.Parent;
while (!(frm is MainForm))
{
frm = frm.Parent;
}
return frm as MainForm;
}
}
至此,我们所有的准备工作就完成了,我们来测试我们的框架吧!
测试后,大家会发现几个问题:
1.功能按钮不是一般的难用。
2.我们的模块菜单都是在够好函数里面加载的,也就是说,在返回的时候,菜单不会重新加载,然而在很多时候,比方说,一个表单,在菜单里面提交后,再返回时,菜单中的“提交”按钮应该是灰色的。
3.在控件跳转的时候,如果跳转的用户控件中的内容比较的多,窗体会卡好一会,要是能像AJAX一样,在加载的过程中,放一个loading图片就好了。
这些答案将在下篇中一一解答。
测试代码下载:测试代码