65.9K
CodeProject 正在变化。 阅读更多。
Home

在 Silverlight 中实现主从页面

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.79/5 (12投票s)

2009年8月19日

CPOL

7分钟阅读

viewsIcon

65255

downloadIcon

3378

本文将演示如何在 Silverlight 中构建传统的母版页式应用程序。

引言

有几篇关于如何在 Silverlight 中实现母版页功能的文章。问题是我们真的需要 Silverlight 中的母版页功能吗?如果 ASP.NET 的母版页功能有优势,那么我看不出 Silverlight 不能利用这一点的理由。本文将演示如何在 Silverlight 中构建传统的母版页式应用程序。

系统要求

设计要求

与传统的网页一样,登录页面是启动页面,如图 1 所示。登录控件包括两个文本框、一个组合框和两个按钮控件。文本框用于收集用户的用户名和密码信息,组合框用于确定登录哪个环境。取消按钮将清除文本框中的数据,登录按钮用于提交信息进行身份验证。

Login Page

图 1. 登录页面

点击登录按钮后,将显示 MainPage,如图 2 所示。

Main Page

图 2. MainPage

MainPage 分为两个主要部分:主页部分和子页面部分,如图 3 所示。主页部分顶部有一个命令按钮栏,左侧有一个树状视图菜单。子页面部分右侧有一个内容区域。

Master page section and Sub page section

图 3. 主页部分和子页面部分

主页包括

  • 窗体标题标签:显示子窗体 ID
  • 用户 ID 标签:显示当前用户
  • 系统标签:显示系统环境名称
  • 日期标签:显示当前日期
  • 计数标签:显示数据记录数
  • 状态标签:显示当前状态
  • 树状视图:动态更改内容区域中的内容
  • 11 个命令按钮:在子页面中执行操作

图 4. 主页和子页面的详细信息

这 11 个命令按钮包括以下内容:

  • 搜索:触发搜索状态
  • 执行:将服务器数据提取回客户端,并触发修改状态
  • 编辑:启用可编辑字段控件
  • 删除:删除当前记录
  • 保存:更新更改
  • 第一条记录:转到第一条记录
  • 上一条记录:转到上一条记录
  • 下一条记录:转到下一条记录
  • 最后一条记录:转到最后一条记录
  • Excel:将数据导出到 Excel
  • 退出:退出并关闭浏览器

Button Description

图 5. 11 个命令按钮说明

有四种状态:

初始:启用搜索按钮,如图 6 所示。

Initial State

图 6. 初始状态

搜索:启用搜索和执行按钮,如图 7 所示。

Search State

图 7. 搜索状态

修改:除执行按钮外,所有按钮都启用,如图 8 所示。

Modify State

图 8. 修改状态

自定义:您可以决定启用/禁用哪些按钮,例如,可以启用所有按钮,如图 9 所示。

Custom State

图 9. 自定义状态

树状视图可以展开或折叠,如图 10 所示。

Treeview Expand

图 10. 展开树状菜单

项目简介

有四个项目:

  • DataObjectCollection:服务用于与客户端通信的数据结构。
  • CommandInMasterDaoWcf:用于将数据从服务器端传递到客户端的服务。
  • CommandInMasterDemo:Silverlight 应用程序项目。
  • 有三个主要的 Silverlight 控件需要处理:

    • LeftTreeViewMain - 树状视图菜单
    • Login 控件 – 登录页面
    • TopToolBar - 包含 11 个命令按钮的控件
  • CommandInMasterDemo.Web:创建 Silverlight 应用程序时会自动创建。它将托管 Silverlight 控件在浏览器中运行。

Projects

图 11. 项目

Using the Code

在开始查看示例之前,有几个方法我需要先介绍一下。

App.xaml.cs

将四种状态分类:InitialSearchModifyCustom

public enum WorkState
{
    INT,//Initial State
    SEA,//Search State
    MOD,//Modify State 
    CUS,//Custom State
}

获取或设置树状视图菜单控件

public static System.Collections.ObjectModel.Collection<MenuDataContext> 
                                                     MenuList { get; set; } 

获取或设置当前窗体 ID

public static string CurrentFormID 
{
    get;
    set;
}

Application_Startup 中,将登录控件添加到 RootVisual。这将把登录控件放在启动位置。

private void Application_Startup(object sender, StartupEventArgs e)
{
    this.RootVisual = rootVisual; 
    rootVisual.Children.Add(lg); 
}

我使用反射来创建用户控件的实例。例如,MainPage 的命名空间是 CommandInMasterDemo,类名是 MainPage,因此我可以使用反射来创建 CommandInMasterDemo.MainPage 对象,然后将其转换为 UserControl 类型。如果用户控件不为空,则将 CurrentFormID 设置为 strName,然后将用户控件添加到当前的 Application.RootVisual

public static void Navigate(string strName)
{
    App CurrentApp = (App)Application.Current; 
    Type type = CurrentApp.GetType(); 
    Assembly assembly = type.Assembly; 
    UserControl uc = (UserControl)assembly.CreateInstance(
                        type.Namespace + "." + strName); 
    if (uc != null) 
    {
        CurrentFormID = strName; 
        CurrentApp.rootVisual.Children.Clear(); 
        CurrentApp.rootVisual.Children.Add((UserControl)assembly.CreateInstance(
                   type.Namespace + "." + strName));
    }
}

GetUserControl 也使用反射来创建用户控件实例。NavigateGetUserControl 之间的唯一区别是准备命名空间。所有子页面都有自己的子组文件夹,例如 CHM、FCM。因此,我们需要在命名空间中添加子组名称。例如,FCM201 用户控件的命名空间是 CommandInMasterDemo.FCM,类名是 FCM201,因此我们使用 type.Namespace + "." + strName.Substring(0, 3) + "." + strName 来创建其实例。

public static UserControl GetUserControl(string strName) 
{
    CurrentFormID = strName; 
    App CurrentApp = (App)Application.Current; 
    Type type = CurrentApp.GetType(); 
    Assembly assembly = type.Assembly; 
    return (UserControl)assembly.CreateInstance(type.Namespace + "." + 
                        strName.Substring(0, 3) + "." + strName); 
}

LoginControl.xaml.cs

登录按钮将触发 proxy_GetUserInfoCompleted,然后 proxy_GetUserInfoCompleted 将触发 proxy_GetFunctionMenuCompleted

private void btnLogin_Click(object sender, RoutedEventArgs e)
{
    if (string.IsNullOrEmpty(txtAccount.Text) || 
              string.IsNullOrEmpty(txtPassword.Password)) 
    {
        txtErrorInformation.Text = "Account and Password must enter"; 
        return;
    }
    else
    {
        this.Cursor = Cursors.Wait; 
        try
        {
            DaoWcfClient daoWcf = new DaoWcfClient(); 
            daoWcf.GetUserInfoCompleted += 
              new EventHandler<GetUserInfoCompletedEventArgs>(proxy_GetUserInfoCompleted); 
            daoWcf.GetUserInfoAsync(txtAccount.Text, txtPassword.Password); 
        }
        catch (Exception ex) 
        {
            MessageBox.Show("btnLogin_Click Exception: " + ex.Message); 
        }
        finally
        {
            this.Cursor = Cursors.Arrow; 
        }
    }
} 

void proxy_GetUserInfoCompleted(object sender, GetUserInfoCompletedEventArgs e) 
{
    try 
    { 
        strCurrentUser = e.Result; 
        if (string.IsNullOrEmpty(strCurrentUser)) 
        { 
            txtErrorInformation.Text = "Account or Password is incorrect"; 
        }
        else
        {
            Resources.Remove("CurrentUser"); 
                  Resources.Add("CurrentUser", txtAccount.Text); 
                       Resources.Remove("CurrentDatabase"); 
            Resources.Add("CurrentDatabase", cbDb.SelectionBoxItem.ToString()); 
            DaoWcfClient daoWcf = new DaoWcfClient(); 
            daoWcf.GetFunctionMenuCompleted += new 
              EventHandler<GetFunctionMenuCompletedEventArgs>(
              proxy_GetFunctionMenuCompleted); 
            daoWcf.GetFunctionMenuAsync(txtAccount.Text); 
        }
    }
    catch (Exception ex) 
    {
        MessageBox.Show("proxy_FindUserInfoCompleted Exception: " + ex.Message); 
    }
}
void proxy_GetFunctionMenuCompleted(object sender, GetFunctionMenuCompletedEventArgs e) 
{
    System.Collections.ObjectModel.Collection<MenuDataContext> list = e.Result; 
    if (list.Count > 0)
    {
        App CurrentApp = (App)Application.Current;
        App.MenuList = list; 
        App.Navigate("MainPage"); 
    } 
}

我们来看看 proxy_GetUserInfoCompleted;我使用 Resources 来存储用户 ID 和系统名称。

Resources.Remove("CurrentUser");
Resources.Add("CurrentUser", txtAccount.Text);
Resources.Remove("CurrentDatabase");
Resources.Add("CurrentDatabase", cbDb.SelectionBoxItem.ToString());

proxy_GetFunctionMenuCompleted 中,在我获取菜单列表后,我将菜单列表存储到 AppMenuList 属性中,然后使用 App.Navigate 转到 MainPage

System.Collections.ObjectModel.Collection<MenuDataContext> list = e.Result;
if (list.Count > 0) 
{
    App CurrentApp = (App)Application.Current; 
    App.MenuList = list; 
    App.Navigate("MainPage"); 
}

我为所有命令按钮创建了一个委托 MenuEventHandler(object sender, RouteEventArgs e)

public delegate void MenuEventHandler(object sender, RoutedEventArgs e); 

每个命令按钮都有自己的事件。

public event MenuEventHandler SearchClick; 
public event MenuEventHandler ExecuteClick; 
public event MenuEventHandler EditClick; 
public event MenuEventHandler DeleteClick; 
public event MenuEventHandler SaveClick; 
public event MenuEventHandler LastClick; 
public event MenuEventHandler FirstClick; 
public event MenuEventHandler PreviousClick; 
public event MenuEventHandler NextClick; 
public event MenuEventHandler ExcelClick; 

有三个属性:

  • CurrentState:获取/设置当前状态。
  • public WorkState CurrentState
    {
        get
        {
            return curretState;
        }
        set
        {
            curretState = value;
            SetButtonState();
        }
    }
  • BindGrid:获取/设置 DataGrid 控件,以便它可以与第一条、上一条、下一条和最后一条按钮进行交互。
  • public DataGrid BindGrid { get; set; }
  • TotalRowCount:获取/设置总数据记录数。
  • public int TotalRowCount { get; set; }

SetButtonState 用于在不同状态下隐藏/显示命令按钮。

private void SetButtonState()
{
    switch (CurrentState)
    {
        case WorkState.INT:
        txtStatus.Text = "Initial";
        //Search
        btnSearch.IsEnabled = true;
        imgbtnSearchOn.Visibility = Visibility.Visible;
        ...............
        break;
        case WorkState.SEA:
        txtStatus.Text = "Search";
        //Search
        btnSearch.IsEnabled = true;
        imgbtnSearchOn.Visibility = Visibility.Visible;
        ...............
        break;
        case WorkState.MOD:
        txtStatus.Text = "Modify";
        //Search
        btnSearch.IsEnabled = true;
        imgbtnSearchOn.Visibility = Visibility.Visible;
        ...............
        break;
        case WorkState.CUS:
        txtStatus.Text = "Custom";
        break;
        default:
        txtStatus.Text = "Search";
        //Search
        btnSearch.IsEnabled = true;
        imgbtnSearchOn.Visibility = Visibility.Visible;
        ...............
        break;
    }
}

有两种方式可以触发四个记录移动按钮(第一条、上一条、下一条和最后一条)。一种是与 DataGrid 控件交互,另一种是在子页面中触发它。

private void btnLast_Click(object sender, RoutedEventArgs e)
{
    if (BindGrid != null)
    {
        BindGrid.SelectedIndex = TotalRowCount - 1;
    }
    else
    {
        LastClick(this, e);
    }
}
private void btnNext_Click(object sender, RoutedEventArgs e)
{
    if (BindGrid != null)
    {
        if (BindGrid.SelectedIndex != TotalRowCount - 1)
        {
            BindGrid.SelectedIndex = BindGrid.SelectedIndex + 1;
        }
    }
    else
    {
        NextClick(this, e);
    }
}
private void btnPrevious_Click(object sender, RoutedEventArgs e)
{
    if (BindGrid != null)
    {
        if (BindGrid.SelectedIndex != 0)
        {
            BindGrid.SelectedIndex = BindGrid.SelectedIndex - 1;
        }
    }
    else
    {
        PreviousClick(this, e);
    }
}
private void btnFirst_Click(object sender, RoutedEventArgs e)
{
    if (BindGrid != null)
    {
        BindGrid.SelectedIndex = 0;
    }
    else
    {
        FirstClick(this, e);
    }
}

CommonUtility.cs

为了获取 TopToolBar 控件,我首先需要找到 MainPage 控件。这是因为 MainPage 包含 TopToolBar

public MainPage GetMainPage(UserControl currentPage, bool blSub)
{
    MainPage mainPage = blSub ? (MainPage)((Grid)((Grid)((Grid)
      currentPage.Parent).Parent).Parent).Parent : (MainPage)(
      (Grid)((Grid)currentPage.Parent).Parent).Parent;
    return mainPage;
}

在我找到 MainPage 控件后,就可以使用 FindName 方法获取 TopToolBar 控件。

public  GetTopToolBar(UserControl currentPage, bool blSub)
{
     ttb = GetMainPage(currentPage, blSub).FindName("topToolBar") as ;
    return ttb;
}

ExportExcel.ashx.cs

Silverlight 不提供在本地磁盘上保存文件的能力,因此我使用一个处理程序来创建 CSV/Excel 文件。

public void ProcessRequest(HttpContext context)
{
    string strContext = context.Request.QueryString["Context"] != null ? 
    HttpUtility.UrlDecode(context.Request.QueryString["Context"]) : 
                          DateTime.Now.ToString("yyyyMMdd_HHmmss");
    string[] strSplit = 
      strContext.Replace("[", "").Replace("]", "").Split(char.Parse(";"));
    string strFileName = strSplit[0];
    string strQueryCase = strSplit[1];
    DataGrid dg = new DataGrid();
    switch (strQueryCase)
    {
        case "FindMTAccntScopeByYear":
            List<AccountDataContext> list = new ().GetAccountByYear(strSplit[2]);
            dg.DataSource = list;
            break;
        case "FindAllyCompAccountByOwnerId":
            List<AllyCompAcctDataContext> listAlly = 
                      new DaoWcf().GetAllyCompAccountByOwnerId(strSplit[2]);
            dg.DataSource = listAlly;
            break;
    }
    dg.DataBind();
    context.Response.Buffer = true;
    context.Response.ClearContent();
    context.Response.ClearHeaders();
    context.Response.ContentType = "application/vnd.ms-excel";
    context.Response.AddHeader("content-disposition", 
      "attachment;filename=" + strFileName + ".xls");
    dg.HeaderStyle.ForeColor = Color.Blue;
    dg.HeaderStyle.BackColor = Color.White;
    dg.ItemStyle.BackColor = Color.White;
    System.IO.StringWriter tw = new StringWriter();
    System.Web.UI.HtmlTextWriter hw = new HtmlTextWriter(tw);
    dg.RenderControl(hw);
    context.Response.Write(tw.ToString());
    context.Response.Flush();
    context.Response.Close();
    context.Response.End();
}

演示

我将介绍三个示例:

FCM201 硬件

此示例演示如何使用按钮触发不同的状态。

private void Button_Click(object sender, RoutedEventArgs e) 
{
    Button b = (Button)sender; 
    switch (b.Tag.ToString()) 
    { 
        case "INT": 
            WorkState.INT; 
            break; 
        case "SEA":
            WorkState.SEA; 
            break; 
        case "MOD": 
            WorkState.MOD; 
            break; 
        case "CUS": 
            WorkState.CUS; 
            true; 
            topToolBar.ExecuteEnable = true; 
            topToolBar.EditEnable = true; 
            topToolBar.DeleteEnable = true; 
            topToolBar.SaveEnable = true; 
            topToolBar.RecordMoveEnable = true;
            topToolBar.ExcelEnable = true; 
            break; 
    } 
}

初始按钮:触发初始状态,如图 12 所示。

图 12. 初始状态

搜索按钮:触发搜索状态。

图 13. 搜索状态

修改按钮:触发修改状态。

图 14. 修改状态

自定义按钮触发自定义状态。

图 15. 自定义状态

FCM202 软件

这是通用情况下的默认流程。流程为:初始状态 --> 搜索状态 --> 修改状态。

在初始状态下,只有搜索按钮可用。

Initial State

图 16. 初始状态

点击搜索按钮后,状态将变为搜索,执行按钮将显示出来。

Search State

图 17. 搜索状态

点击执行按钮后,除执行按钮外,所有命令按钮都将显示出来。现在您应该在内容页面中看到显示的数据。

Modify State

图 18. 修改状态

点击删除按钮时,会弹出警告消息。删除在此示例中不起作用。

Delete Button

图 19. 删除按钮

点击编辑按钮时,可编辑字段将变为可修改。日期类型字段将显示一个日历控件。多选字段将显示一个组合框。

Edit Button

图 20. 编辑按钮

修改数据后,您可以点击保存按钮来更新服务器数据。

Save Button

图 21. 保存按钮

在 FCM202 中,我在子页面控件中触发了记录导航按钮。

voidobject sender, RoutedEventArgs e)
{
    iCurrent = 0;
    SetCountStatus(iCurrent);
}
voidobject sender, RoutedEventArgs e)
{
    iCurrent = list.Count - 1;
    SetCountStatus(iCurrent);
}
voidobject sender, RoutedEventArgs e)
{
    if (iCurrent != list.Count - 1)
    {
        iCurrent = iCurrent + 1;
        SetCountStatus(iCurrent);
    }
}
voidobject sender, RoutedEventArgs e)
{
    if (iCurrent != 0)
    {
        iCurrent = iCurrent - 1;
        SetCountStatus(iCurrent);
    }
}

Record Navigation Button

图 22. 记录导航按钮

我们将信息传递给处理程序来生成 Excel 文件。

voidobject sender, RoutedEventArgs e)
{
    string strOwnerId = txtOwnerId.Text;
    string strEncodeUrl = System.Windows.Browser.HttpUtility.UrlEncode(
       "[AllyCompAcct;FindAllyCompAccountByOwnerId;" + strOwnerId + "]");
    string strUri = "https:///CommandInMasterDaoWcf/ExportExcel.ashx?Context=" + 
                    strEncodeUrl;
    HtmlPage.Window.Navigate(new Uri(strUri, UriKind.Absolute));
}

Excel Button

图 23. 将数据导出到 Excel

FCM203 本地

这是一个自定义流程。流程为:初始状态 --> 搜索状态 --> 自定义状态。要激活自定义状态,您需要设置 topToolBar.CurrentState = WorkState.CUS

图 24. 自定义状态

在 FCM203 中,我通过将 DataGrid 设置为 TopToolBarBindGrid 属性来触发 TopToolBar 中的记录导航按钮。

topToolBar.BindGrid = this.dgAccountYear;
topToolBar.TotalRowCount = list.Count;

Record Navigation Button

图 25. 记录导航按钮

展望未来

我将此代码放入公共领域,不设任何限制。它没有最好的设计模式或编码风格。您可以将其用于任何目的,包括商业产品。如果您可以改进代码,甚至使其更清晰,请告诉我。我将更新代码以使其更有用。谢谢大家。

© . All rights reserved.