0%

Head first C# 第一章

前言

最近突然想写一个一直以来想要的 App , 用于记录每日做的事情,做一个统计,最好能一眼看到一共学了什么多少个小时(可能是之前的 10000 小时情结)之前在安卓端有看到一个类似的 App , 但是主打功能是番茄钟,另外有 VIP。于是打算自力更生写一款。但是这回想试试 Windows PC 端,而且希望 App 能有比较便于使用的图形化界面,所以这回不打算用之前的语言,用 Windows 家的 C# 试试,毕竟自家原生支持肯定会非常好。

但是这就面临了学习一个新语言的问题。回想自己的各个语言都是怎么学习的:

  • C : 啊哈C语言,洛谷,程序设计专业课
  • Python : 菜鸟教程,Codecademy
  • Java : Head First Java, Head First Android.
  • Javascript : MDN , 廖雪峰的教程

最后想了想还是用 Head First C# 来学习 C# .

正文

阅读体验

Head First 系列阅读起来都是比较爽快的,几乎全程手把手实验,还有很多有趣的配图和文字,效果非常好。但是这一本 Head First C# 读起来却遇到了很多问题。

计算机学科的书籍基本都面临数年内更新换代,API 变动的问题。找了很久,发现 Head First C# 的最新版是第三版。但是也已经是 2013 年的书籍了,书中使用的操作系统还是 Windows 8, 使用的平台也不是现在的 VS 2019,问题重重。所幸 VS 的界面变化几乎不大,让大部分的阅读过程还是不算太艰难。

但是很多 API 都发生了改变,包括现在使用 .NET 时甚至还区分 Framework 和 Core , 这样刚刚上手 C# 的程序员很容易束手无策。下面介绍一些我在阅读过程中发现的问题(很多问题百度、谷歌无果)

问题

新建项目

首先在新建项目时就遇到了问题。在配置 VS 2019 时就面临了选择 .NET 平台开发 还是 Windows 通用平台开发的问题。稍作百度后发现,后者是用于 Windows 的跨平台应用的,如用于平板等,由于存储空间吃紧便只安装了 .NET 平台开发 。 注意这里也许安装了 Windows 通用平台开发 可能就不会遇到后面我叙述的一些问题,所以仅供参考。

新建项目时,要选择什么模板呢?

使用 VS 2019 新建项目时的选项

比对后发现已经找不到书上的模板,我们只好推测。因为是一个图形化界面窗口而非控制台界面,也并非服务、库、测试项目等,排除后就剩下了 WPF 应用 和 Windows 窗体应用。我先试了 Windows 窗体应用,但是发现这个模板不会生成 XAML 文件,而按照书中说明会有 XAML 文件。于是又试了 WPF App,我目前的使用来看, Core 和 Framework 之间没有找到显著区别。(百度后的结果说明 Core 应该是一个较新的版本,具体区别还是待 C# 略微入门后在做研究)

最终使用的是 WPF 应用(.NET Framework)

Page 与 Window

新建项目后最明显的与书中不同便是自动生成的是 MainWindow 而非书中所述的 MainPage。百度后得知 page 有点像我们浏览器里的那个 page ,有前进后退等操作,而 Window 是一个更大的对象,可能内部的内部会包含 page。所以书中所有的 Page 换做 Window 应该是没有问题的。

替换 MainPage?

书中会要求删去自动生成的 Page , 导入模板。我在尝试后发现并没有相关的模板。但是这一步我改为了修改 Window 大小。因为书中的 Page 的大小时自动的,但是我自动生成的窗口大小显得很古怪,尤其项目中将grid 的两行都确定了高度,使得中间的游戏区域变得很窄,最后我调整了窗口的大小为 1366 * 768. 相关代码如下。

1
2
3
4
5
6
7
8
<Window x:Class="SaveTheHumans.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:SaveTheHumans"
mc:Ignorable="d"
Title="MainWindow" Height="768" Width="1366">

失踪的文件大纲图标

Document_outline_logo

书中提到点击这个图标可以打开文件大纲的窗口,但是找了很久没有找到。事实上并不需要找他,默认情况下他就在左侧。如果还是找不到的话可以在视图中寻找。

找不到 Windows.UI.Xaml.Media.Animation

这个库应该改名了,其实在代码中如果直接使用 Storyboard 会自动提示补全新的库的名称。

改为 using System.Windows.Media.Animation; 就可以了。

找不到 PointerPressed

新版的 VS 和 C# 中似乎没有这个了。在我的代码中全部替换为了 Mouse 相关的事件,并且测试在笔记本平台可以使用。

最终代码

我对代码做了一些修改,原版代码打开后就显示 Game Over 实在太不友好,我改为了中文的介绍以便于扔给亲朋好友测试。同时在屏幕上方加了分数机制。

下面两份代码分别是 MainWindow.xaml 与 MainWindow.xaml.cs。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
<Window x:Class="SaveTheHumans.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:SaveTheHumans"
mc:Ignorable="d"
Title="MainWindow" Width="1280" Height="720" >
<Window.Resources>
<ControlTemplate x:Key="EnemyTemplate" TargetType="{x:Type ContentControl}">
<Grid>
<Ellipse Height="100" Stroke="Black" Width="75" Fill="Gray"/>
<Ellipse Fill="Black" Height="35" Width="25" Margin="40,20,70,0" Stroke="Black" VerticalAlignment="Top" HorizontalAlignment="Center" RenderTransformOrigin="0.5,0.5">
<Ellipse.RenderTransform>
<TransformGroup>
<ScaleTransform/>
<SkewTransform AngleX="10"/>
<RotateTransform/>
<TranslateTransform/>
</TransformGroup>
</Ellipse.RenderTransform>
</Ellipse>
<Ellipse Fill="Black" Height="35" Width="25" Margin="70,20,40,0" Stroke="Black" VerticalAlignment="Top" HorizontalAlignment="Center" RenderTransformOrigin="0.5,0.5">
<Ellipse.RenderTransform>
<TransformGroup>
<ScaleTransform/>
<SkewTransform AngleX="-10"/>
<RotateTransform/>
<TranslateTransform/>
</TransformGroup>
</Ellipse.RenderTransform>
</Ellipse>

</Grid>
</ControlTemplate>
</Window.Resources>
<Grid x:Name="grid" >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="160"/>
<ColumnDefinition/>
<ColumnDefinition Width="160"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="140"/>
<RowDefinition />
<RowDefinition Height="160"/>
</Grid.RowDefinitions>
<Button x:Name="startButton" Content="Start!" HorizontalAlignment="Center" Grid.Row="2" VerticalAlignment="Center" Click="startButton_Click" FontSize="25"/>
<StackPanel Grid.Column="2" Orientation="Vertical" Grid.Row="2">
<TextBlock Text="Avoid These" TextWrapping="Wrap" HorizontalAlignment="Center" VerticalAlignment="Center"/>
<ContentControl Template="{DynamicResource EnemyTemplate}" Content="ContentControl" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</StackPanel>
<ProgressBar x:Name="progressBar" Grid.Column="1" Grid.Row="2" Height="20"/>
<Canvas x:Name="playArea" Grid.Column="0" Grid.Row="1" Grid.ColumnSpan="3" MouseMove="playArea_MouseMove" MouseLeave="playArea_MouseLeave">
<Canvas.Background>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="#FF4AB4FF"/>
<GradientStop Color="#FFE0E0E0" Offset="1"/>
</LinearGradientBrush>
</Canvas.Background>
<StackPanel x:Name="human" Orientation="Vertical" MouseDown="human_MouseDown" TouchDown="human_TouchDown">
<Ellipse Fill="White" Height="10" Width="10"/>
<Rectangle Fill="White" Height="25" Width="10"/>
</StackPanel>
<TextBlock x:Name="gameOverText" Text="Game Over" TextWrapping="Wrap" FontFamily="Arial" FontSize="100" FontWeight="Bold" FontStyle="Italic" Canvas.Left="416" Canvas.Top="177" HorizontalAlignment="Center" VerticalAlignment="Center"/>
<Rectangle x:Name="target" Height="50" Width="50" RenderTransformOrigin="0.5,0.5" MouseEnter="target_MouseEnter">
<Rectangle.RenderTransform>
<TransformGroup>
<ScaleTransform/>
<SkewTransform/>
<RotateTransform Angle="45"/>
<TranslateTransform/>
</TransformGroup>
</Rectangle.RenderTransform>
<Rectangle.Fill>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="Black"/>
<GradientStop Color="White" Offset="1"/>
</LinearGradientBrush>
</Rectangle.Fill>
</Rectangle>
</Canvas>
<StackPanel Grid.Column="1">
<TextBlock Grid.Column="1" HorizontalAlignment="Center" Margin="0" TextWrapping="Wrap" Text="Your score " VerticalAlignment="Center" FontSize="40"/>
<TextBlock x:Name="scoreNumber" Grid.Column="1" HorizontalAlignment="Center" Margin="0" TextWrapping="Wrap" Text="100" VerticalAlignment="Center" FontSize="50"/>

</StackPanel>
</Grid>
</Window>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Threading;

namespace SaveTheHumans
{
/// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
public partial class MainWindow : Window
{
Random random = new Random();
DispatcherTimer enemyTimer = new DispatcherTimer();
DispatcherTimer targetTimer = new DispatcherTimer();
bool humanCaptured = false;
int score = 0; // Score added by Haulyn5

public MainWindow()
{
InitializeComponent();
enemyTimer.Tick += enemyTimer_Tick;
enemyTimer.Interval = TimeSpan.FromSeconds(2);

targetTimer.Tick += targetTimer_Tick;
targetTimer.Interval = TimeSpan.FromSeconds(.1);

// Added by Haulyn5, Welcome , Intro and Score
//gameOverText.Text = "Welcome, Press Start Button in the Left Bottom to Start, \nand try to drag your man to the portal. Every time you \nsave someone, you gain a score.";
gameOverText.Text = "欢迎! 点击左下角的 start 按钮开始游戏,单击白色小人指引其移动。\n当其成功移动至菱形传送门后即可获得一分。注意当你不控制小人时,\n外星人就无法抓住小人。另外,注意不要让下方的时间条耗尽。";
gameOverText.FontSize = 20;

scoreNumber.Text = "0";

}

private void enemyTimer_Tick(object sender, EventArgs e)
{
AddEnemy();
}
private void targetTimer_Tick(object sender, EventArgs e)
{
progressBar.Value += 1;
if (progressBar.Value >= progressBar.Maximum)
EndTheGame();
// score added by Haulyn5
scoreNumber.Text = score.ToString();
}

private void EndTheGame()
{
if (!playArea.Children.Contains(gameOverText))
{
// Added by Haulyn5, Welcome and Intro
gameOverText.Text = "Game over";
gameOverText.FontSize = 100;

enemyTimer.Stop();
targetTimer.Stop();
humanCaptured = false;
startButton.Visibility = Visibility.Visible;
playArea.Children.Add(gameOverText);



}
}

private void startButton_Click(object sender, RoutedEventArgs e)
{
StartGame();
}

private void StartGame()
{
human.IsHitTestVisible = true;
humanCaptured = false;
progressBar.Value = 0;
startButton.Visibility = Visibility.Collapsed;
playArea.Children.Clear();
playArea.Children.Add(target);
playArea.Children.Add(human);
enemyTimer.Start();
targetTimer.Start();
score = 0;

// Randomize the place of human and target, added by Haulyn5
Canvas.SetLeft(target, random.Next(100, (int)playArea.ActualWidth - 100));
Canvas.SetTop(target, random.Next(100, (int)playArea.ActualHeight - 100));
Canvas.SetLeft(human, random.Next(100, (int)playArea.ActualWidth - 100));
Canvas.SetTop(human, random.Next(100, (int)playArea.ActualHeight - 100));

}

private void AddEnemy()
{
ContentControl enemy = new ContentControl();
enemy.Template = Resources["EnemyTemplate"] as ControlTemplate;
AnimateEnemy(enemy, 0, playArea.ActualWidth - 100, "(Canvas.Left)");
AnimateEnemy(enemy, random.Next((int)playArea.ActualHeight - 100), random.Next((int)playArea.ActualHeight - 100), "(Canvas.Top)");
playArea.Children.Add(enemy);

enemy.MouseEnter += Enemy_MouseEnter;
}

private void Enemy_MouseEnter(object sender, MouseEventArgs e)
{
if (humanCaptured)
{
EndTheGame();
}
}

private void AnimateEnemy(ContentControl enemy, double from, double to, string propertyToAnimate)
{
Storyboard storyboard = new Storyboard() { AutoReverse = true, RepeatBehavior = RepeatBehavior.Forever };
DoubleAnimation animation = new DoubleAnimation()
{
From = from,
To = to,
Duration = new Duration(TimeSpan.FromSeconds(random.Next(4, 6)))
};
Storyboard.SetTarget(animation, enemy);
Storyboard.SetTargetProperty(animation, new PropertyPath(propertyToAnimate));
storyboard.Children.Add(animation);
storyboard.Begin();
}

private void human_MouseDown(object sender, MouseButtonEventArgs e)
{
if(enemyTimer.IsEnabled)
{
humanCaptured = true;
human.IsHitTestVisible = false;

}
}

private void human_TouchDown(object sender, TouchEventArgs e)
{
if (enemyTimer.IsEnabled)
{
humanCaptured = true;
human.IsHitTestVisible = false;

}
}

private void target_MouseEnter(object sender, MouseEventArgs e)
{
if(targetTimer.IsEnabled && humanCaptured)
{
progressBar.Value = 0;
Canvas.SetLeft(target, random.Next(100, (int)playArea.ActualWidth - 100));
Canvas.SetTop(target, random.Next(100, (int)playArea.ActualHeight - 100));
Canvas.SetLeft(human, random.Next(100, (int)playArea.ActualWidth - 100));
Canvas.SetTop(human, random.Next(100, (int)playArea.ActualHeight - 100));
humanCaptured = false;
human.IsHitTestVisible = true;
score++;
}
}

private void playArea_MouseMove(object sender, MouseEventArgs e)
{
if(humanCaptured)
{
Point mousePosition = e.GetPosition(null);
Point relativePosition = grid.TransformToVisual(playArea).Transform(mousePosition);
if((Math.Abs(relativePosition.X - Canvas.GetLeft(human))>human.ActualWidth * 3)
|| (Math.Abs(relativePosition.Y - Canvas.GetTop(human)) > human.ActualHeight * 3))
{
humanCaptured = false;
human.IsHitTestVisible = true;
}
else
{
Canvas.SetLeft(human, relativePosition.X - human.ActualWidth / 2);
Canvas.SetTop(human, relativePosition.Y - human.ActualHeight / 2);
}
}
}

private void playArea_MouseLeave(object sender, MouseEventArgs e)
{
if(humanCaptured)
{
EndTheGame();
}
}
}
}