2018-04-11

Final Project Plan__Class Schedule

一、应用功能介绍

       我们的应用是一个Windows 10 端的课程表,能实现课程表的基本功能——查看课表,在此基础上添加了课程信息介绍、课程作业的提醒,并且还添加了代课模块,可以理解为把课程表和日程表相结合,还能增添一些因老师的课程调整以及时间灵活的实验课。

二、应用特色(具体的在功能模块剖析中)

I.用户友好
       界面简单,菜单导航清晰,功能全面;课程不仅仅可以通过教务处导入,还可以通过自己编辑添加一些课程,比如,**实验课。
II.蹭课功能;
       考虑到学生课程的局限性,我们增加蹭课功能,一方面让学生拓宽知识面,同时也学到了额自己感兴趣的东西。
III.代课功能很实用;
       代课功能有点坏坏的,嘻嘻;但是,人有三急嘛,万一有个紧急事件可是可以谅解的嘛。不过代课可是要收费的哦,公平交易。
IV.日程与提醒;
       我们把课程表和日程表结合起来实现的一个有意思的功能;作业忘记了,考试临近了,都可以在最初添加事件用来提醒自己,每天电脑右下角弹出,“你要学习了,你要学习了”。

三、功能模块剖析

①·功能说明_课程介绍:

       该功能是在课程表显示之后点击相应的课程磁贴,能进入一个关于该课程的讨论界面,里面有我们内置的课程介绍、前置课程,以及上过的同学对该课程的评价、留言界面,帮助同学们在学期初了解这个课程。

·功能预计实现图:(上面就是课程和百度百科的介绍,下面是评论区)

image.png

·预计实现方法:

       这个功能涉及一开始点进去的界面转换,以及默认课程介绍的TextBlock以及评论区功能。对于评论区如何实现,现在还是有点迷茫。通过查找资料得知,评论回复的功能涉及ASP和Sql数据库,以下有一个留言板系统的功能实现可供参考: https://wenku.baidu.com/view/24c704d158f5f61fb7366646.html

https://wenku.baidu.com/view/4072d2ad33d4b14e8424687c.html

代码:

 protected void Page_Load(object sender, EventArgs e)
    {       
        try
        {
            this.Label1.Text = Session["Uname"].ToString();            
             SqlConnection con = db.CreateConnection();
            con.Open();
            string strsql = "select count(*) from login";
            SqlCommand cmd = new SqlCommand(strsql, con);
            SqlDataReader rd = cmd.ExecuteReader();
             while (rd.Read())
             {
                 this.Label2.Text = rd[0].ToString();              
            }
            rd.Close();
            con.Close();
        }
        catch 
        {
            Response.Write("<script>alert('用户未登录成功!')</script>"); 
       Response.Redirect("login.aspx");
        }
    }
    protected void Button1_Click(object sender, EventArgs e)
    {
        SqlConnection con = db.CreateConnection();
        con.Open();
        string strsql="insert into messages values('"+Label1 .Text +"','"+TextBox2.Text +"','"+TextBox3 .Text +"','"+DateTime .Now .ToString ()+"')";
        SqlCommand cmd =new SqlCommand (strsql ,con );
        cmd .ExecuteNonQuery();
        con.Close ();
        Response.Write("<script>alert('留言成功!')</script>"); 
        Response .Redirect ("messageout.aspx");
}
"取消"事件代码:
 protected void Button2_Click(object sender, EventArgs e)
    {
        TextBox2.Text = "";
        TextBox3.Text = "";
    }

②·功能说明_代课:

       该功能是帮助同学们更便捷、可靠的完成校园里有偿帮助上课这一活动。有的时候遇到紧急事件又不想因为缺课而被老师抓从而影响心情和成绩,这个时候花一点小钱找空闲的同学帮忙上课就能解燃眉之急,很实用的功能。

·功能预计实现图:

       模式基本是:任务发布-任务接受,双方联系并完成交易。

image.png

       这个功能我觉得类似留言评论功能,也就是可以理解为一个学生留下了时间地点和联系方式(手机号),然后另一个同学看到了这条“评论”,联系之后完成此次交易,之后发表评论的人选择删除自己的留言,那么这一过程就结束了,也就是赋予权限,可以删除自己的留言,别人不能删除自己的留言,就可以实现这一功能了,这个功能只是提供一个平台,所以就避免了私聊功能的介入。

③·功能说明_登录、导入与编辑:

       我们软件面向东大学生,所以我们的登录设置为学号和教务处密码进行登录;所以,我们可以根据教务处导入我们的课程;也可以通过自行编辑课表,添加课程;

image.png

       登录界面的实现可以参考:

https://docs.microsoft.com/zh-cn/windows/uwp/security/microsoft-passport-login-auth-service
代码:

<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
  <StackPanel Orientation="Vertical">
    <TextBlock Text="Login" FontSize="36" Margin="4" TextAlignment="Center"/>

    <TextBlock x:Name="ErrorMessage" Text="" FontSize="20" Margin="4" Foreground="Red" TextAlignment="Center"/>

    <TextBlock Text="Enter your credentials below" Margin="0,0,0,20"
               TextWrapping="Wrap" Width="300"
               TextAlignment="Center" VerticalAlignment="Center" FontSize="16"/>

    <StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
      <!-- Username Input -->
      <TextBlock x:Name="UserNameTextBlock" Text="Username: "
             FontSize="20" Margin="4" Width="100"/>
      <TextBox x:Name="UsernameTextBox" PlaceholderText="sampleUsername" Width="200" Margin="4"/>
    </StackPanel>

    <StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
      <!-- Password Input -->
      <TextBlock x:Name="PasswordTextBlock" Text="Password: "
             FontSize="20" Margin="4" Width="100"/>
      <PasswordBox x:Name="PasswordBox" PlaceholderText="samplePassword" Width="200" Margin="4"/>
    </StackPanel>

    <Button x:Name="PassportSignInButton" Content="Login" Background="DodgerBlue" Foreground="White"
        Click="PassportSignInButton_Click" Width="80" HorizontalAlignment="Center" Margin="0,20"/>

    <TextBlock Text="Don't have an account?"
                TextAlignment="Center" VerticalAlignment="Center" FontSize="16"/>
    <TextBlock x:Name="RegisterButtonTextBlock" Text="Register now"
               PointerPressed="RegisterButtonTextBlock_OnPointerPressed"
               Foreground="DodgerBlue"
               TextAlignment="Center" VerticalAlignment="Center" FontSize="16"/>

    <Border x:Name="PassportStatus" Background="#22B14C"
               Margin="0,20" Height="100">
      <TextBlock x:Name="PassportStatusText" Text="Windows Hello is ready to use!"
             Margin="4" TextAlignment="Center" VerticalAlignment="Center" FontSize="20"/>
    </Border>

    <TextBlock x:Name="LoginExplaination" FontSize="24" TextAlignment="Center" TextWrapping="Wrap" 
        Text="Please Note: To demonstrate a login, validation will only occur using the default username 
        'sampleUsername' and default password 'samplePassword'"/>

  </StackPanel>
</Grid>

       在 Login 类代码隐藏中,需要将该类顶部的 Account 私有变量更改为 UserAccount。 更改 OnNavigateTo 事件,以便将该类型强制转换为 UserAccount。代码如下:

using PassportLogin.AuthService;
namespace PassportLogin.Views
{
    public sealed partial class Login : Page
    {
        private UserAccount _account;
        private bool _isExistingAccount;

        public Login()
        {
            this.InitializeComponent();
        }

        protected override async void OnNavigatedTo(NavigationEventArgs e)
        {
            //Check Windows Hello is setup and available on this machine
            if (await MicrosoftPassportHelper.MicrosoftPassportAvailableCheckAsync())
            {
                if (e.Parameter != null)
                {
                    _isExistingAccount = true;
                    //Set the account to the existing account being passed in
                    _account = (UserAccount)e.Parameter;
                    UsernameTextBox.Text = _account.Username;
                    SignInPassport();
                }
            }
        }
    }
}

       大致界面用Excel实现如下图。可以通过点击右上角加号通过教务处导入课表或者手动。

其中的菜单栏设计可以用NavigationView控件实现。

<u>https://docs.microsoft.com/zh-cn/windows/uwp/design/controls-and-patterns/navigationview</u>

代码:

private void NavView_Loaded(object sender, RoutedEventArgs e)
{
    // you can also add items in code behind
    NavView.MenuItems.Add(new NavigationViewItemSeparator());
    NavView.MenuItems.Add(new NavigationViewItem()
    { Content = "My content", Icon = new SymbolIcon(Symbol.Folder), Tag = "content" });

    // set the initial SelectedItem 
    foreach (NavigationViewItemBase item in NavView.MenuItems)
    {
        if (item is NavigationViewItem && item.Tag.ToString() == "home")
        {
            NavView.SelectedItem = item;
            break;
        }
    }
            
    ContentFrame.Navigated += On_Navigated;

    // add keyboard accelerators for backwards navigation
    KeyboardAccelerator GoBack = new KeyboardAccelerator();
    GoBack.Key = VirtualKey.GoBack;
    GoBack.Invoked += BackInvoked;
    KeyboardAccelerator AltLeft = new KeyboardAccelerator();
    AltLeft.Key = VirtualKey.Left;
    AltLeft.Invoked += BackInvoked;
    this.KeyboardAccelerators.Add(GoBack);
    this.KeyboardAccelerators.Add(AltLeft);
    // ALT routes here
    AltLeft.Modifiers = VirtualKeyModifiers.Menu;
    
}

private void NavView_ItemInvoked(NavigationView sender, NavigationViewItemInvokedEventArgs args)
{  
    if (args.IsSettingsInvoked)
    {
        ContentFrame.Navigate(typeof(SettingsPage));
    }
    else
    {
        // find NavigationViewItem with Content that equals InvokedItem
        var item = sender.MenuItems.OfType<NavigationViewItem>().First(x => (string)x.Content == (string)args.InvokedItem);
        NavView_Navigate(item as NavigationViewItem);
    }
}

private void NavView_Navigate(NavigationViewItem item)
{
    switch (item.Tag)
    {
        case "home":
            ContentFrame.Navigate(typeof(HomePage));
            break;

        case "apps":
            ContentFrame.Navigate(typeof(AppsPage));
            break;

        case "games":
            ContentFrame.Navigate(typeof(GamesPage));
            break;

        case "music":
            ContentFrame.Navigate(typeof(MusicPage));
            break;

        case "content":
            ContentFrame.Navigate(typeof(MyContentPage));
            break;
    }           
}

private void NavView_BackRequested(NavigationView sender, NavigationViewBackRequestedEventArgs args)
{
    On_BackRequested();
}

private void BackInvoked(KeyboardAccelerator sender, KeyboardAcceleratorInvokedEventArgs args)
{
    On_BackRequested();
    args.Handled = true;
}

private bool On_BackRequested()
{
    bool navigated = false;

    // don't go back if the nav pane is overlayed
    if (NavView.IsPaneOpen && (NavView.DisplayMode == NavigationViewDisplayMode.Compact || NavView.DisplayMode == NavigationViewDisplayMode.Minimal))
    {
        return false;
    }
    else
    {
        if (ContentFrame.CanGoBack)
        {
            ContentFrame.GoBack();
            navigated = true;
        }
    }
    return navigated;
}

private void On_Navigated(object sender, NavigationEventArgs e)
{
    NavView.IsBackEnabled = ContentFrame.CanGoBack;

    if (ContentFrame.SourcePageType == typeof(SettingsPage))
    {
        NavView.SelectedItem = NavView.SettingsItem as NavigationViewItem;
    }
    else 
    {
        Dictionary<Type, string> lookup = new Dictionary<Type, string>()
        {
            {typeof(HomePage), "home"},
            {typeof(AppsPage), "apps"},
            {typeof(GamesPage), "games"},
            {typeof(MusicPage), "music"},
            {typeof(MyContentPage), "content"}    
        };

        String stringTag = lookup[ContentFrame.SourcePageType];

        // set the new SelectedItem  
        foreach (NavigationViewItemBase item in NavView.MenuItems)
        {
            if (item is NavigationViewItem && item.Tag.Equals(stringTag))
            {
                item.IsSelected = true;
                break;
            }
        }        
    }
}

       这个界面我还想增加一点界面交互的实现;比如,突出显示。突出显示是当用户在交互式元素(如命令栏)附近移动指针时突出显示这些元素的灯光效果。我们可以对每个课程的Button自定义,实现突出显示的效果。

<u>https://docs.microsoft.com/zh-cn/windows/uwp/design/style/reveal</u>

代码:

<Grid RequestedTheme="Dark">
    <Button Content="Button" Click="Button_Click" Style="{ThemeResource ButtonRevealStyle}"/>
</Grid>
image.png

       点击课程,弹出课程信息界面。主要包括一下几个功能:1.简要介绍本课程信息;2.本本课程所在教室;3.教学时长;4.课程日程。课程日程,主要是可以添加课程日程,比如预习、复习、作业;并添加时限,以便及时提醒完成。

       我们也可以点击右上角的编辑,编辑课程名称,教室,时间。也可以点击右上角的添加日程,添加新的日程(日程若是过了结束时间会自动删除);同时每个课程都对应一个吐槽区,点击可在其中发表自己对此课程的看法或感受;

image.png

学号、密码可以通过Textblock、Textbox控件实现

       主界面可由NavigationView控件,课表结构可以下通过Grid实现;具体可见可以用Button或Textblock实现;

课程信息中的日程开始、结束时间,用CalenderDatepicker控件实现;

④·功能说明_蹭课

       蹭课功能跟课程导入差不多。通过点击右上角的加号,选择蹭课,选择学院,选择专业,选择蹭课课程;

image.png

⑤·功能说明_导出课程表

       这里我们会实现将当前的gird整体输出,以导出一张课程表。因此需要使用一下技术

  1. RenderTargetBitmap类(选择UI位图)

       RenderTargetBitmap类可以实现将UI元素转化为位图 ,即将UI元素截图,生成一张图片。与截图不同的是,它可以定义UIElement,这里,我只需要截图的是图片和文字部分,即上面的字Grid里面的图像信息。

  1. StorageFolder类(选择文件,并保存)
image.png

要实现截图并保存到应用内存储,就先要定义文件名


image.png

再定义文件存储的位置,并创建文件

image.png

实例化对象成果:

image.png

⑥功能说明_消息提醒

I:Popup 类

       不需要我们自己额外去写一个弹窗类,微软自己有一个Popup 弹窗类。当弹窗打开时,会自动放在当前应用页面的最顶层。

image.png

Popup类里有一个Child属性,用来存弹窗中的内容。

child的类型是UIElement。

UIElement 是具有可视外观并可以处理基本输入的大多数对象的基类。

因此child属性可以存grid stackpannel 这些......

image.png

       Popup类还有一个IsOpen属性,当会true的时候,弹窗是打开的。false则相反。

II:PS

       当创建一个popup的对象,并且将它的IsOpen属性设置为true的时候,代表将会有一个弹窗 显示在当前应用的最顶层。
像上面图中的做法,看上去只有一小块是弹窗,其实我的做法是,最顶层的popup的child属性里放的是一个grid,在grid里才是我显示出来的那一小块提示框,因为grid如果没有背景颜色的话,底下一层是会显示的,所以没有什么问题。不会因为盖了一层grid,底下的内容会被盖住。

可参考:http://www.cnblogs.com/MzwCat/p/7748033.html

四、市场调研&采访结果

       因为Markdown的原因,这里不能直接插入视频,所以以下是采访视频的传送门:(密码是uvp)
http://v.youku.com/v_show/id_XMzUzMTEwNTA3Ng==.html?spm=a2h3j.8428770.3416059.1
http://v.youku.com/v_show/id_XMzUzMTA4NzUxNg==.html?spm=a2h3j.8428770.3416059.1
http://v.youku.com/v_show/id_XMzUzMTEzOTkyMA==.html?spm=a2hzp.8244740.0.0

       采访回馈:通过本次采访,我们发现,课程表这类应用,在windows端的生长环境并不好,也就是说,如果是手机端的话实用性更强一点,而我们推出的代课功能其实在网上也有过类似的软件传出,并且带来了较大的负面影响,但是从我们学生的角度来说,如果不是恶意无故跷课,有的时候真的是必要的,或许我们应该设置找代课限制,不能让同学们无法无天……抛去平台的差异性,我们只从应用的功能性看,我们的应用功能比较精干,不像某课程表那样庞杂,又是小纸条,又是景点买票,又是火车票什么的,试想有多少同学会使用那些功能?而且会有很多垃圾消息的灌入,比如看dskjd%¥&%……&加Vsakjdhashd_219873,好了不说这个了,我们的应用能体现课程表的主干功能,好用,精简!
鸣谢:所有被采访者和工作人员

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,463评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,868评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,213评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,666评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,759评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,725评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,716评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,484评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,928评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,233评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,393评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,073评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,718评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,308评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,538评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,338评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,260评论 2 352

推荐阅读更多精彩内容