【乱写代码坑人系列】ZJUT数据库大型实验 - 学生管理系统(五):数据绑定

学生管理系统(五)数据绑定

学生管理系统(一)建立项目

学生管理系统(二)项目规划

学生管理系统(三)建立数据库和登录

学生管理系统(四)建立主界面

 

  虽然才过了一天,但是我已经不记得之前做了什么了(误。

  在完成主界面之后,就是开始对各个页面进行编辑了。主界面我选择的是用TabControl 来实现,所以可以将每个界面的元素都写在不同的TabItem 中。

  还是先写数据绑定,再来说UI 的部分吧。绑定就先用最简单的表Building 来做例子。

  首先,打开SSMS,新建几个存储过程。

USE EducationManager
GO

CREATE PROCEDURE GetBuildings
AS
BEGIN
    SELECT * FROM Building
    ORDER BY 2, 1;
END
GO

CREATE PROCEDURE AddBuilding(@bldgName nvarchar(6), @roomNum int)
AS
BEGIN
    INSERT INTO Building
    VALUES
    ( @bldgName, @roomNum);
END
GO

CREATE PROCEDURE DeleteBuilding(@bldgName nvarchar(6), @roomNum int)
AS
BEGIN
    DELETE FROM Building
    WHERE BldgName = @bldgName AND
            RoomNum = @roomNum;
END
GO

  分别是获取所有行,增加以及删除行。

  回到VS,在EducationManagerClass 项目中,新建一个文件夹命名为Models,然后在里面添加一个类Building。

 1     public class Building
 2     {
 3         public string BldgName { get; set; }
 4 
 5         public int RoomNum { get; set; }
 6 
 7         public Building(string bldgName, int roomNum)
 8         {
 9             BldgName = bldgName;
10             RoomNum = roomNum;
11         }
12 
13         public override string ToString()
14         {
15             return BldgName + " " + RoomNum;
16         }
17     }

  注意要重写ToString() 方法,不然是显示不出来数据的。这个类就是表Building 在整改项目中的模型类。

  然后打开AppDbConnection 类,由于这些存储过程在每次使用时,都要将类中的所有属性设为参数,所以我又顺便写了个帮助方法SetSqlParamters,来将模型类中所有的属性添加到SqlCommand 的参数中。这个方法的具体解释可以看【乱写代码坑人系列】小插曲(一)

 1         // 通过反射自动设置参数值
 2         private void SetSqlParameters<T>(SqlCommand cmd, T model)
 3             where T : class
 4         {
 5             foreach (PropertyInfo prop in
 6                 model.GetType().GetProperties
 7                 (BindingFlags.Instance | BindingFlags.Public))
 8             {
 9                 cmd.Parameters.AddWithValue(
10                     "@" + prop.Name, prop.GetValue(model, null));
11             }
12         }

  然后添加对应这三个存储过程的方法,都是使用了异步的方法来执行Sql 的操作,不过在这里遇到了一个问题,就是在使用OpenAsync 方法的时候,会突然就不知道跳到哪里去了,UI也出不来,但也没报异常……有知道的还麻烦告诉一下,感激不尽。Add 和Delete 两个方法都会返回有多少行受到了影响,虽然没什么用但是我依旧返回了,之后若是遇到有返回值的也会进行返回。

 1         public async Task<ICollection<Building>> GetBuildings()
 2         {
 3             using (SqlConnection con = new SqlConnection(AppConnectionString))
 4             {
 5                 SqlCommand cmd = new SqlCommand("GetBuildings", con);
 6                 cmd.CommandType = CommandType.StoredProcedure;
 7                 ObservableCollection<Building> buildings = new ObservableCollection<Building>();
 8 
 9                 // 由于是在using 语句块中,所以就算抛了异常,con 也会自然阵亡,不用手动Close() 或Dispose()
10                 con.Open();
11                 SqlDataReader reader = await cmd.ExecuteReaderAsync();
12                 while (reader.Read())
13                 {
14                     Building building = new Building
15                         (
16                             (string)reader["BldgName"],
17                             (int)reader["RoomNum"]
18                         );
19                     buildings.Add(building);
20                 }
21                 reader.Close();
22 
23                 return buildings;
24             }
25         }
26 
27         public async Task<int> AddBuilding(Building building)
28         {
29             using (SqlConnection con = new SqlConnection(AppConnectionString))
30             {
31                 SqlCommand cmd = new SqlCommand("AddBuilding", con);
32                 cmd.CommandType = CommandType.StoredProcedure;
33                 SetSqlParameters(cmd, building);
34 
35                 con.Open();
36                 var lines = await cmd.ExecuteNonQueryAsync();
37                 return lines;
38             }
39         }
40 
41         public async Task<int> DeleteBuilding(Building building)
42         {
43             using (SqlConnection con = new SqlConnection(AppConnectionString))
44             {
45                 SqlCommand cmd = new SqlCommand("DeleteBuilding", con);
46                 cmd.CommandType = CommandType.StoredProcedure;
47                 SetSqlParameters(cmd, building);
48 
49                 con.Open();
50                 var lines = await cmd.ExecuteNonQueryAsync();
51                 return lines;
52             }
53         }

   到这里,对数据库进行操作的方法都写完了,也十分简单,似乎也没什么需要解释的。

 

  然后到UI 部分。

  首先,我们需要能够查看所有的数据,同时,如果只是简单的用ListView 来将所有的教室列出来的话是很不友好的,所以需要使用的是TreeView;我们还需要一个操作区来放一些按钮和输入框,来对数据进行操作。

  选中教室管理的标签页,在上面添加一个Grid 并分成左右两半部分,左边放TreeView,右边就是操作区。

  【乱写代码坑人系列】ZJUT数据库大型实验 - 学生管理系统(五):数据绑定_第1张图片

  把新建和详情分开,确保它们之间不会相互干扰。同时要在xaml 代码中加入TreeView 的模板,来保证能正确的分组显示数据。

 1     <Window.Resources>
 2         <HierarchicalDataTemplate x:Key="tvit" ItemsSource="{Binding}">
 3             <TextBlock Text="{Binding Key}"/>
 4             <HierarchicalDataTemplate.ItemTemplate>
 5                 <DataTemplate>
 6                     <TextBlock Text="{Binding}"/>
 7                 </DataTemplate>
 8             </HierarchicalDataTemplate.ItemTemplate>
 9         </HierarchicalDataTemplate>
10     </Window.Resources>
11 
12     <!--Other code-->
13 
14     <TreeView Name="buildingTree" Grid.Column="0"
15             ItemTemplate="{StaticResource ResourceKey=tvit}" >
16     </TreeView>  

  还有,详情框中的两个TextBox 需要进行数据绑定,将Binding 的Path 设为Building 类中对应的属性名,才能自动的显示对应信息。

     <Label Content="建筑名" HorizontalAlignment="Left" Width="70" Height="25" VerticalAlignment="Top" Margin="0,38,0,0"/>
     <TextBox Name="bldgNameTextBox" Margin="75,38,10,0" Text="{Binding Path=BldgName}" Height="25" VerticalAlignment="Top"></TextBox>
    <Label Content="教室编号" Margin="0,68,0,0" HorizontalAlignment="Left" Width="70" Height="28" VerticalAlignment="Top"/>
    <TextBox Name="roomNumTextBox" Margin="75,68,10,0" Text="{Binding Path=RoomNum}" Height="28" VerticalAlignment="Top"></TextBox>

 

  进入后台代码。

  写之前,如果使用的是VS 2015,可以在最开始加上一句using static EductionManagerUI.App; ,这样就能直接使用App 类中的AppDbConnection 对象,而不用在前面加App. 了。

  在程序启动时需要加载数据,而加载数据的方法都是异步的,在构造函数前也无法添加async 关键字,所以需要单独写一个初始化数据的方法。这个方法的返回值需要为async void 而不是Task,否则会在调用的地方(构造函数中)出现警告。由于返回值是void,所以也必须在这个方法中对异常进行捕获。同时,还需要一块地盘来保存从数据库获取的信息,以方便之后进行操作和处理。

 1     // 存放Building 的属性
 2     private ICollection<Building> Buildings { get; set; }
 3 
 4         /// <summary>
 5         /// 初始化数据,使用的是async void,所以必须使用catch
 6         /// </summary>
 7         private async void InitData()
 8         {
 9             try
10             {
11                 Buildings = await AppDbconnection.GetBuildings();
12                 // Other initialization ...
13             }
14             catch
15             {
16                 MessageBox.Show("数据初始化异常");
17                 Application.Current.Shutdown();
18             }
19             buildingTree.ItemsSource = Buildings.GroupBy(b => b.BldgName);
20         }

  注意,TreeView 的ItemsSource 的值应该是对Buildings 进行分组之后的结果,这样才能正确的分层显示。

 

  然后开始写UI 元素的事件。

  事件主要有这么几个:选择TreeView 的SelectedItemChanged事件,用来在选中的行改变时显示对应的信息;添加、删除按钮的Click 事件,这两个就是分别调用AppDbConnection 中对应的方法,并且对UI 进行更新;还有一个reset 按钮的Click 事件,就是在点击后清除详情框中的值。

  另外,在事件中使用的也是async void ,而不是Task,这是没得选的,我也想做个好人,但是法官不让(逃。

  先看看TreeView 的事件,在更新值时必须先判断选中的是不是子元素,如果是的话才进行显示,不然就假装四处看风景。

 1         private void buildingTree_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<Object> e)
 2         {
 3             if (buildingTree.SelectedValue.GetType() == typeof(Building))
 4             {
 5                 var building = (Building)buildingTree.SelectedItem;
 6                 try
 7                 {
 8                     gridBuildingDetails.DataContext = building;
 9                 }
10                 catch
11                 {
12                     MessageBox.Show("出现了奇怪的错误耶");
13                 }
14             }
15         }

  异常的catch 部分可以改成输出log,我就是懒得写了所以直接用MessageBox了(……

  添加和删除按钮的事件比较类似,只是数据的来源不同;在修改之后,可以用两个方法返回的受影响行数来判断是否成功,不过这里也没太大必要所以我就偷懒了啊哈哈,同时最好重新获取一遍数据库中的数据,并且必须重新设置TreeView 的ItemsSource 来刷新界面。这里应该是有办法实现无刷新更新的,不过感觉有点麻烦就没做,思路大概就是将Buildings 的类型改成分组后的类型,不过这样的话更新删除什么的都有点麻烦。而reset 按钮的事件只需要将绑定的信息设为null,就会自动清除了。

 1         private async void bldgAddButton_Click(object sender, RoutedEventArgs e)
 2         {
 3             int roomNum;
 4             if (int.TryParse(newRoomNumTextBox.Text, out roomNum))
 5             {
 6                 var building = new Building(newBldgNameTextBox.Text, roomNum);
 7                 try
 8                 {
 9                     await AppDbconnection.AddBuilding(building);
10                     Buildings = await AppDbconnection.GetBuildings();
11                     buildingTree.ItemsSource = Buildings.GroupBy(b => b.BldgName);
12                 }
13                 catch
14                 {
15                     MessageBox.Show("添加失败");
16                 }
17             }
18             else
19             {
20                 MessageBox.Show("教室编号有误");
21             }
22         }
23 
24         private async void bldgDelButton_Click(object sender, RoutedEventArgs e)
25         {
26             var building = (Building)gridBuildingDetails.DataContext;
27             if (building == null)
28                 return;
29 
30             try
31             {
32                 await AppDbconnection.DeleteBuilding(building);
33                 Buildings.Remove(building);
34                 buildingTree.ItemsSource = Buildings.GroupBy(b => b.BldgName);
35                 gridBuildingDetails.DataContext = null;
36             }
37             catch
38             {
39                 MessageBox.Show("删除失败");
40             }
41         }
42 
43         private void bldgResetButton_Click(object sender, RoutedEventArgs e)
44         {
45             gridBuildingDetails.DataContext = null;
46         }

  这里面的catch 也是可以加log 输出的,个人推荐这么做,而不是像这样只是一个消息框(严肃脸。

  嗯,可以用了耶!

  说实话晚上敲键盘这么响好怕被舍友砍。

  嗯最后上Git:Github\LzxHahaha\EducationManager

  转载请注明出处:LzxHahaha-博客园

你可能感兴趣的:(数据绑定)