我们先以“风险测试”这一模块入手吧,这个模块功能相对比较简单,业务逻辑很清楚,做题,得分,出结果
效果如下
结果页
看似简单,但这里牵涉到的知识点可不少,比如ItemControls控件的使用,Chart控件的使用,程序集的动态加载,资源的应用程序级(是程序级别,不是程序集)共享等,可能在一篇里说不完,不怕,那就一步一步来吧。
在开始这一模块之前,先来整理一下我思路。我们的“项目”计划是使用全站SL,这样的话,如果全部功能都在一个项目中,那最终可能会使我们的XAP灰常巨大,如果访问一个页面是要等上几分钟的话,你认为我们的用户会有那么多的耐心吗?好的做法应该是各模块分开,按需加载。
既然如此,不用多想,再在我们的解决方案中新建一个项目,如下
在接下来的对话框中会询问在哪个网站上承载新建的sl应用程序
我们选择默认,并去掉生成测试页的选项,这样VS2010自动找到了我们解决方案中的网站项目,并添加了进去。按F6重新编译后可以看到,现在我们的项目结构如下
接下来我们要做的,就是把我们这几个项目连通,界面流程为:启动,显示登录界面,加载主界面,显示风险测试模块。
现在回到我们的主SL应用程序,也就是TFT-WebFirst-SL中,现在我们有了login页与mainpage页,之前为了显示login效果,我们在mainpage页里简单的把login页里show了出来,但现在需要改改了,很明显,我们现在缺少一个“容器”页,即用来在login与mainpage之间切换的父容器,我们新建一个Index页。(这可以在vs中操作也可以在blend中操作,但他们之间往往不同即时同步,比如我们新加了一个页面,如果马上打开blend就会看不到,如果在blend中新加了一个元素或命名了一个元素,同样无法在vs中马上反映出来,这时只要用vs编译一下即可)
相关代码类似如下
App.xaml.cs
private void Application_Startup(object sender, StartupEventArgs e)
{
//this.RootVisual = new MainPage();
this.RootVisual = new Index();
}
Index.xaml.cs
void Index_Loaded(object sender, RoutedEventArgs e)
{
Login l = new Login();
l.ParentPage = this;
this.Content = l;
}
public void GoToMainPage()
{
this.Content = new MainPage();
}
Login.xaml.cs
public Index ParentPage { get; set; }
void btnLogin_Click(object sender, RoutedEventArgs e)
{
ParentPage.GoToMainPage();
}
注:该项目主要以学习sl为目的,因此没有使用任何开发模式,代码实现上以也以简单实现功能为主
接下来,我们动态加载风险测试模块
主要代码如下
/// <summary>
/// 从XAP包中返回程序集信息(该方法来源网上)
/// </summary>
/// <param name="packageStream"></param>
/// <param name="assemblyName"></param>
/// <returns></returns>
public Assembly GetAssemblyFromXap(Stream packageStream, String assemblyName)
{
Stream stream = Application.GetResourceStream(new StreamResourceInfo(packageStream, null), new Uri("AppManifest.xaml", UriKind.Relative)).Stream;
Assembly asm = null;
XmlReader xmlReader = XmlReader.Create(stream);
xmlReader.MoveToContent();
if (xmlReader.ReadToFollowing("Deployment.Parts"))
{
string str = xmlReader.ReadInnerXml();
Regex reg = new Regex("x:Name=\"(.+?)\"");
Match match = reg.Match(str);
string sName = "";
if (match.Groups.Count == 2)
{
sName = match.Groups[1].Value;
}
reg = new Regex("Source=\"(.+?)\"");
match = reg.Match(str);
string sSource = "";
if (match.Groups.Count == 2)
{
sSource = match.Groups[1].Value;
}
AssemblyPart assemblyPart = new AssemblyPart();
StreamResourceInfo streamInfo = Application.GetResourceStream(new StreamResourceInfo(packageStream, "application/binary"), new Uri(sSource, UriKind.Relative));
if (sSource == assemblyName)
{
asm = assemblyPart.Load(streamInfo.Stream);
}
}
return asm;
}
}
void LoadRiskTest()
{//加载风险测试模块
WebClient rtwc = new WebClient();
rtwc.OpenReadCompleted += new OpenReadCompletedEventHandler(
(s, ex) =>
{
Assembly assembly = da.GetAssemblyFromXap(ex.Result, "RiskTest.dll");
UIElement element = assembly.CreateInstance("RiskTest.MainPage") as UIElement;
this.BorderMainPanel.Child = element; //这里使用Border做为容器,是因为动态加载控件时,使用Canvas不能自适应大小
});
Uri xapUri = new Uri(HtmlPage.Document.DocumentUri, "ClientBin/RiskTest.xap");
rtwc.OpenReadAsync(xapUri);
}
具体思路是通过WebClient到网站的ClientBin目录中下载RiskTest.xap包,然后通过读取其中的清单文件(AppManifest.xaml),加载该模块的主程序集(RiskTest.dll),然后通过反射创建其中的MainPage页面并加载到主应用程序中的指定位置。这个实现在网上资源好多,详细过程可以去搜索一下。
这样,我们的流程就算是完成了,现在运行应该就可以看到动态加载后的效果了,只不过现在我们的风险测试模块没有内容
现在我们来看看风险测试模块的界面布局,我们注意到在每个模块下都有一个左边的导航功能,如
但这里面又分上下两部分,显然,下半部分为各模块内部功能的导航,上半部分在每个模块中是一样的,因此,上半部分不应放到模块中,应该在主程序中实现。既然如此,那我们就需要在各模块的这一部分保留一个位置,在主程序加载各模块后再把主程序中的“用户信息”部分加进去,简单实现为在各模块的主界面中提供一个方法,并在主程序集中调用,实现代码如
模块mainpage.xaml.cs
public void FillUserInfoCtr(UserControl uc)
{
this.BorderUserInfo.Child = uc;
}
主程序调用时
void LoadRiskTest()
{//加载风险测试模块
WebClient rtwc = new WebClient();
rtwc.OpenReadCompleted += new OpenReadCompletedEventHandler(
(s, ex) =>
{
Assembly assembly = da.GetAssemblyFromXap(ex.Result, "RiskTest.dll");
UIElement element = assembly.CreateInstance("RiskTest.MainPage") as UIElement;
this.BorderMainPanel.Child = element;
LoadUserInfo(element);
});
Uri xapUri = new Uri(HtmlPage.Document.DocumentUri, "ClientBin/RiskTest.xap");
rtwc.OpenReadAsync(xapUri);
}
void LoadUserInfo(UIElement element)
{//为各模块加载用户信息
UserInfo ui = new UserInfo();
Type t = element.GetType();
MethodInfo mi = t.GetMethod("FillUserInfoCtr");
mi.Invoke(element, new object[] { ui });
}
风险测试模块的主界面布局类似
BorderUserInfo就是用来加载主程序中的用户信息部分的容器
SVMain是该模块中各功能页面的展示区