电の岁月---点滴记忆
交流,分享,进步

名称:基于silverlight的宿舍管理系统

开发时间:2010.9.7~2010.9.13

开发环境:Silverlight+Blend4

数据库:MS SQL Server 2005

 

 

一.功能分析

来访人员:为了安全起见,需要在验证页面验证想要寻找的学生和学生宿舍是否对应

在校学生:一方面,在校学生携带贵重物品时需要登记;另一方面,也需要方便的使用一些   校园信息.因此系统区分了【学生主页】和【登记】两个页面。

宿舍管理员:宿舍管理员有查看,删除来访人员的登记信息,学生携带贵重物品外出登记信 息的权限,同时,可以根据学号或者宿舍等信息查看宿舍成员;具有发布校园 动态和宿舍资讯的权限.

系统管理员:系统管理员具有查看和删除反馈信息的权限.

 

二.数据库列表

1. stucard.mdf 在校学生和宿舍管理员的身份信息

 

2. visit.mdf 访客的身份信息和来访登记信息

 

3. outreg.mdf 在校学生携带贵重物品出门登记信息

 

4. news.mdf 记录校内新闻好人通知公告的信息

 

5. job.mdf 记录招聘会的信息

 

6. pick.mdf 记录拾获物品的信息

 

7. lost.mdf 记录遗失物品的信息

 

8. feedback.mdf 记录反馈信息

 

三.功能实现及效果演示

从学生登记页面到学生主页,宿舍管理员页面等逐步介绍开发过程,以及之间遇到的问题,解决办法.

1. 系统主页登录界面

(1)silverlight通过asp.net web service与数据库连接

由于silverlight的安全机制,不允许前台客户端和数据库直接连通,所以只能通过代码作用于服务器上来操作数据库,web service即是其中之一,也是比较常用的一种。

参考资料:http://www.cnblogs.com/lyj/archive/2008/04/09/1145828.html

步骤说明:

 

 

使用方法:

上图中的WebService1.asmx.cs打开以后,就可以插入自己的方法了,在[WebMethod]

public string update(string sqlsentence)

{

    string tip="";

    SqlConnection updatecon = new SqlConnection();

    try

    {

        updatecon.ConnectionString = @"Initial Catalog=stuinfo;Data Source=(local);Integrated Security=SSPI;User Instance=false";

        updatecon.Open();

        SqlCommand updatecom = new SqlCommand("", updatecon);

        updatecom.CommandText = sqlsentence;

        if (updatecom.ExecuteNonQuery() > 0)

            tip = "更新成功|";

        else

            tip = "没有找到合适的记录";

 

    }

    catch (Exception g)

    {

        tip = g.Message;

    }

    updatecon.Close();

    return tip;

}

这时需要下载配置才能在前台使用服务器上的代码..

 

这样就可以使用后台自定义的方法了

以管理员界面发布通知公告为例:

前台代码:(用来提交新闻内容)

//此方法通过委托完成,注意第2个参数

void wssc0_updateCompleted(object sender, ServiceReference2.updateCompletedEventArgs e)

{

    label9.Content = e.Result;

}

private void button1_Click(object sender, RoutedEventArgs e)

{

    if (comboBox1.SelectedIndex == 1)

    {

        if (textBox3.Text == "")

            label9.Content = @"新闻内容必填";

        ServiceReference2.WebService1SoapClient wssc0 = new ServiceReference2.WebService1SoapClient();              wssc0.updateAsync(

"insert  into  news values ('"+textBox3.Text+"','NULL','NULL','"+MainPage.strid+"','"+datePicker1.Text+"','宿舍资哩讯')" );       

   wssc0.updateCompleted+=new EventHandler<ServiceReference2.updateCompletedEventArgs>(wssc0_updateCompleted);               

    }

    else if(comboBox1.SelectedIndex==0)

    {

        if (textBox1.Text == "")

            label9.Content = @"新闻主题不能为空";

        if (textBox2.Text == "")

            label9.Content = @"新闻地址不能为空";

        ServiceReference2.WebService1SoapClient wssc0 = new ServiceReference2.WebService1SoapClient();

        wssc0.updateAsync("insert into news values('" + textBox1.Text +"','"+ textBox2.Text+"','NULL','NULL','" + System.DateTime.Now.ToString() + "','校园动态')"); 

  wssc0.updateCompleted+=new EventHandler<ServiceReference2.updateCompletedEventArgs>(wssc0_updateCompleted);

    }

}

特别提醒:当对后台WebService1.asmx.cs中的WebMethod进行添加或者删除的时候,需要更新服务引用

 

(2) 页面跳转和页面传值

普通页面跳转比较简单的方法是通过生成目标页面类的对象实现,同时可以通过构造函数将一个页面的参数传递给目标页面...当然对于各个页面几乎都会使用的数据成员,可以设置为静态数据成员,然后通过页面类名去访问.如MainPage中的strid

依MainPage中的页面跳转为例:

先看看stureg类的构造函数

由于学生登录以后,登记页面就要显示通过验证的学生姓名和学号.所以需要MainPage传递参数给Stureg.

private string str;

private string strid;

public stureg(string name,string id)

{

    InitializeComponent();

    str = name;

    strid = id;

    label3.Content = name;

    textBox1.Text = id;

    Storyboard s = (Storyboard)Resources["opchange"];

    s.Begin();

}

 

 

点击Button1进入stureg.xaml界面,点击Button2进入stuindex.xmal界面

if ((bool)radioButton2.IsChecked)

{

    isstu = true;

    if (bt1)

        this.Content = new stureg(e.Result[1], textBox1.Text);

    else if (bt2)

        this.Content = new stuindex();

}

2. 学生主页界面

(1)子页面跳转

由于学生主页有很多工作要做,而且页面的页头部分和导航栏是相同的,所以为了避免重复的工作,采用部分页面跳转的策略.

做好主框架图后(其中DockPanel容器空缺),点击导航栏相关的标签即可将其对应的页面填充到DockPanel中. 这个想法可以通过“反射”来完成. 

 

下面来看一下如何通过“反射”来实现选择哪个标签,用哪个页面填充容器....

treeView的前台xaml代码:

<sdk:TreeView Height="420" HorizontalAlignment="Left" Margin="51,157,0,0" Name="treeView1" VerticalAlignment="Top" Width="127">

<sdk:TreeViewItem Header="通知公告" Selected="OnSelected" Tag="notice" IsSelected="False" />

<sdk:TreeViewItem Header="个人信息" Selected="OnSelected" Tag="perinfo"/>

<sdk:TreeViewItem Header="水电网费" IsExpanded="True">

       <sdk:TreeViewItem Header="水费" Selected="OnSelected" Tag="waterfee"/>

       <sdk:TreeViewItem Header="电费" Selected="OnSelected" Tag="electricfee"/>

        <sdk:TreeViewItem Header="网费" Selected="OnSelected" Tag="netfee"/>

 </sdk:TreeViewItem>

<sdk:TreeViewItem Header="生活资讯" IsExpanded="True">

        <sdk:TreeViewItem Header="寻找宿舍" Selected="OnSelected" Tag="findmate"/>

        <sdk:TreeViewItem Header="失物登记" Selected="OnSelected" Tag="lostreg"/>

        <sdk:TreeViewItem Header="失物招领" Selected="OnSelected" Tag="pickreg"/>

        <sdk:TreeViewItem Header="缴费报修" Selected="OnSelected" Tag="dormrepair"/>

</sdk:TreeViewItem>

<sdk:TreeViewItem Header="工作就业" Selected="OnSelected" IsExpanded="True">

        <sdk:TreeViewItem Header="发布信息" Selected="OnSelected" Tag="submitjob"/>

        <sdk:TreeViewItem Header="信息一览" Selected="OnSelected" Tag="browsejob"/>

 </sdk:TreeViewItem>

<sdk:TreeViewItem Header="网络电视" Selected="OnSelected" Tag="iptv"/>

<sdk:TreeViewItem Header="反馈意见" Selected="OnSelected" Tag="feedback"/>

<sdk:TreeViewItem Header="联系我们" Selected="OnSelected" Tag="contact"/>

</sdk:TreeView>

所以的subitem点击后都会选择Onselected方法,因此反射的实现自然是在这个方法中完成,同时使用Tag来唯一化目标类.

后台代码:(添加using System.Reflection;命名空间)

void DockPanelAddChildren(DockPanel dp, string formname)

        {

            Assembly ass = Assembly.GetExecutingAssembly();

            UIElement value = (UIElement)ass.CreateInstance("dms." + formname);

            dp.Children.Clear();

            dp.Children.Add(value);

        }

 

 

        private void OnSelected(object sender, RoutedEventArgs e)

        {

            TreeViewItem tvi = (TreeViewItem)sender;

            if (tvi.Tag != null)

            {

                DockPanelAddChildren(dockPanel1, (tvi.Tag.ToString()));

            }

        }

通过tvi.Tag.ToString()达到控制目标类的目的,例如,“通知公告”subitem的Tag=“notice”,添加silverlight子页notice.xaml,这样的话,单击“通知公告”的时候,就会自动执行notice.xmal.cs类,即实现了页面的“跳转”.

(2)listBox列表项的处理

如下图所示,校园动态栏,点击新闻标题进入对应的链接页面,其中链接地址为数据库的字段,所以主体和链接是一一对应的,因此考虑用Dictionary键值对来实现。

宿舍资讯一栏的添加比较简单,但是添加的内容是数据库中好几个字段的内容连接在一块的,所以不能通过点击项的内容来进行删除操作。最后想到一个方法:搜索出符合条件的记录,然后在结果记录集中找到点击项的索引来操作.

SQL语句为:

string sqlcte = @"with newslist as(select row_number() over(order by 序号) as rowid,* from news where 新闻类型='宿舍资讯') delete newslist  where rowid='" + (listBox2.SelectedIndex + 1).ToString() + "'";

平常使用with..as..语句的次数不多,但是确实能解决比较棘手的问题.

 

(3)comboBox的bug修正

不知道是不是combox的bug,但是当你想实现下图的时候,确实需要打大费周折..

采用下面的代码会出现Null Reference的错误提示

if(comboBox1.SelectionItems=="电子信息工程学院")

{comboBox2.Items.Add("电子信息工程");}

 

于是在网上找到了勉强可以解决的办法,按道理微软不会为此绕弯的.而且这种解决办法会出现某些时候comboBox内容为空的情况.

string[] ei = new string[3] { "电子信息工程", "通信工程", "微电子学" };

if (comboBox1.SelectedItem.ToString() == "电子信息工程学院")

{

    comboBox2.ItemsSource = ei;

    comboBox2.UpdateLayout();

    comboBox2.SelectedItem = "电子信息工程";

}

(4)读写数据源

数据库的连接出错的概率比较大,而且出现错误的类型五花八门,所以异常处理时必须的,同时我们也希望从前台得知出现异常的原因。一下代码是一个使用的小技巧,也是多次调试的结果。

[WebMethod]

public List<string[]> selection(string sqlsentence)

{

    SqlConnection selectioncon = new SqlConnection();

    List<string[]> lists = new List<string[]>();

    try

    {

        selectioncon.ConnectionString = @"Initial Catalog=stuinfo;Data Source=(local);Integrated Security=SSPI;User Instance=false";

        selectioncon.Open();

        SqlCommand selectioncom = new SqlCommand("", selectioncon);

        selectioncom.CommandText = sqlsentence;

        SqlDataReader selectiondr = selectioncom.ExecuteReader();

        if (selectiondr.HasRows)

        {

            while (selectiondr.Read())

            {

                string[] tablelists = new string[15];

                tablelists[0] = @"null";

                for (int i = 1; i < selectiondr.FieldCount; i++)

                {

                    if (!selectiondr.IsDBNull(i))

                        tablelists[i] = selectiondr.GetString(i);

                    else

                        tablelists[i] = @"null";

                }

                lists.Add(tablelists);

            }

        }

        else

        {

            string[] s = new string[1];

            s[0] = @"信息不对应,没有查到记录";

            lists.Add(s);

        }

    }

    catch (Exception g)

    {

        string[] s=new string[1];

        s[0] = g.Message;

        lists.Add(s);

    }

    selectioncon.Close();

    return lists;

}

由于数据库返回查询的记录,而且返回的是一个列表,字符串数组的长度不等,所以不能在查询的记录之后添加“是否更新成功”或者“异常”的信息。但是可以在最前面的一个索引处添加。tablelists[0] = @"null";起到了很好的作用,在有查询结果且没有异常出现的时候,0索引处为null字符串,所以之后想知道是否成功查询时,就可以检测0索引处是否为null字符串.当然有异常的时候,也可以在前台显示出来异常的结果,不会是系统崩溃.

 

另外一点特别值得注意,而且估计很多人都犯过这样的错误:那就是引用和值的使用.

上面背景色加亮的语句,假如放到while外面,会出现添加到list列表中的记录是完全一样,后来琢磨了很长时间,才想起来string[] 为引用类型,每次并不生成新的实例,而是将原对象进行copy,但是指向的都是同一个对象,所以当一个对象的属性或者数据成员等发生变化的时候,所有指向该引用的对象的值都会发生变化,所以就产生了上述现象.所以要特别强调这一点,虽然我们都明白引用和值之间的区别,什么堆栈和托管堆之类的,但是细节的注意容易调试程序!!慎重!!

 

上图为招聘会信息提交界面.下图为招聘会搜索和查看页面.

 

3. 管理员页面

(1)数据源的绑定

 

上图为查询宿舍成员的界面

数据源绑定容器(通常为datagrid或者listbox等容器),本系统采用listbox

刚开始以为数据源绑定listbox,就是简单的将listbox的itemsource设置为一个记录集这样简单,后来实践的过程中,发现可能是通过绑定属性值来实现的(这部分理论知识薄弱,只是想当然的认为)。所以每次绑定都为数据源单提供一个类,里面提供数据源数据的属性.以下面的访客离开登记为例说明数据源绑定listbox。

 

在前台xaml中:

 

<ListBox Grid.Row="1" Height="197" HorizontalAlignment="Left" Margin="20,68,0,0" Name="listBox1" VerticalAlignment="Top" Width="533" >

     <ListBox.ItemTemplate>

        <DataTemplate>

          <StackPanel Orientation="Horizontal">

            <TextBlock Text="{Binding ID}" Width="150" HorizontalAlignment="Center" />

            <TextBlock Text="{Binding Gender}" Width="40" HorizontalAlignment="Center" />

            <TextBlock Text="{Binding Dorm}" Width="100" HorizontalAlignment="Center" />

            <TextBlock Text="{Binding Stu}" Width="60" HorizontalAlignment="Center" />

            <TextBlock Text="{Binding Intime}" Width="80" HorizontalAlignment="Center" />

             <CheckBox Content="是否离开" Checked="CheckBox_Checked" Width="100" IsChecked="{Binding Leavestate}"/>

           </StackPanel>

          </DataTemplate>

       </ListBox.ItemTemplate>

  </ListBox>

在后台cs文件中:

首先为要绑定的数据创建一个类,为其提供属性

public class visitors

{

    public string ID { get; set; }

    public string Gender { get; set; }

    public string Dorm { get; set; }

    public string Stu { get; set; }

    public string Intime { get; set; }

    public bool Leavestate { get; set; }

}

然后在处理函数中添加以下代码(加亮部分即为绑定属性值..)

List<visitors> l = new List<visitors>();

if (e.Result[0][0] == @"null")

{

    for (int i = 0; i < e.Result.Count; i++)

    {

        if (e.Result[i][7] == "N")

            isleave = false;

        else

            isleave = true;

        visitors v = new visitors { ID = e.Result[i][1], Gender = e.Result[i][2], Dorm = e.Result[i][5], Stu = e.Result[i][6], Intime = e.Result[i][4], Leavestate = isleave };

        l.Add(v);

    }

listBox1.ItemsSource = l;

}

(2)动画设计

下图中红色方框是一种透明度渐变效果(持续循环从1到0),在xaml中实现相当的方便

再者xaml使渐变色彩实现起来也异常方便

 

下图中红色框内可以实现上下左右移动,伴随着色彩的变化。这个实现是在Blend4中进行。Blend4已经和VS2010进行无缝结合.但是Blend4好像对连接数据库有些问题.....

 

另外每个页面之间的跳转均采用渐变的方式(有模糊到逐渐清晰).不会出现突变的现象.

四.缺陷

在设计的开始没有进行紧凑的规划,前期的工作有些仓促.特别是在WebService中的WebMethod部分,前面的几个函数均可以从后面的selection和update实现,前期欠考虑其通用性.感觉update的通用性就比较好. 后期会优化代码。

设计的过程中发现很多概念虽然了解,但是还没有到熟练应用的部分,之前也没有了解太多关于WPF和Silverlight之间控件的区别...很多WPF控件的属性在Silverlight中都没有,主要是出于silverlight安全性的考虑吧.

设计的过程中还是无法驾驭已经学习过的设计模式,代码仍然是繁琐的,而且没有添加注释的习惯,希望以后可以改过!!

posted on 2010-09-07 20:14  doucontorl  阅读(2939)  评论(9编辑  收藏  举报