-
Notifications
You must be signed in to change notification settings - Fork 11
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
完整的C#大师课程 Complete C# Masterclass[进行中] #216
Comments
1 - 你的第一个C程序与Visual Studio概述1 - 引言完整C#大师班介绍欢迎来到完整C#大师班。在本课程中,您将学习关于C#的所有知识,因为您是本课程存在的原因。我创建这个课程是为了为您提供尽可能最好的C#编程学习体验。令人兴奋的是,您将在这个课程中获得的知识不仅适用于C#,还可以应用于其他编程语言。如果您已经了解编程,那么这对您来说将是小菜一碟,对吧?但如果您不熟悉编程,没关系,您依然会很好地掌握这些内容。 学习内容您将经历不同的示例,观看大量演示,这些将以简单易懂的方式将知识传递给您。此外,您还将进行一系列练习,我强烈建议您完成这些练习,因为这是您成长的关键所在。实际上,任何编程学习体验中最重要的部分就是应用。在这个过程中,您将亲自做一些事情,仅仅观看视频是不够的。虽然这是一个很好的开始,能够帮助您理解课程内容,但真正的学习体验来自于实际的应用。 练习与项目我准备了许多练习和项目供您跟随,同时也鼓励您独立尝试。过程中可能会遇到一些困难,但没有关系,您可以在互联网上搜索解决方案。如果找不到答案,您当然可以随时在问答部分给我们留言,我们会帮助您解决问题。 感谢您参加本课程,祝您拥有最佳的学习体验,期待在下一个视频中见到您! 2 - 你想达成什么课程目标与学习心态好吧,您已经购买了课程,非常感谢您参与这个课程。现在,您必须问自己最重要的问题是:您想要实现什么?您的总体目标是什么?您想创建视频游戏吗?您想编写PC程序?找到一份工作,开始作为C#程序员的自由职业者,还是仅仅学习一项新技能?无论如何,这门课程都是适合您的,您只需跟随课程进行学习。相信这个系统,按照步骤进行,您就能成为一名真正的开发者,实现所有这些目标。 学习时间与心态当然,根据您的目标,您对课程和学习体验的心态会有所不同。我强烈建议您每周至少花费3到4小时专注于学习课程。虽然完成整个课程需要时间,但您会在过程中获得许多宝贵的学习体验,这将激励您投入更多的时间。因此,我建议您进行练习、参加课程,并尝试将自己的想法融入到您所学到的知识中。不要仅仅把视频当作Netflix系列剧一样观看,那不是我们的目标。您的目标应该是真正理解所学内容。一旦对某个主题有了清晰的理解,您再去学习下一个主题,因此一定要尽量进行练习,这对您将大有帮助。 可视化目标希望您能为自己找到答案,也许您会在脑海中形成一个成功开发者的形象,可能是一个游戏开发者,或是作为开发者找到一份工作的样子。您可以把这个形象放在屏幕下方或上方,这种视觉提醒可以在困难时刻帮助您,推动您向前。现在,带着这种能量,让我们进入下一个视频! 3 - 安装Visual Studio社区版安装 Visual Studio欢迎回来。在本视频中,我们将安装Visual Studio,这是我们编写程序所需的软件。首先,让我们搜索下载Visual Studio。您也可以直接在搜索框中输入其名称。具体来说,2022年是新版本发布的一年,之前的版本是Visual Studio 2019,因此并不是每年都会有新版本。 下载 Visual Studio搜索后,您将找到 Visual Studio Microsoft.com 的链接。让我们点击进入,该页面可能会因您打开的时间而有所不同,但基本思路是下载Visual Studio 2022的社区版,这是免费的,您无需支付任何费用。您当然可以了解Visual Studio能为您提供什么,它帮助您提高生产力,具有现代的理念和创新的功能,特别是强大的代码补全功能,这非常酷。您将看到它是如何帮助您编写代码的,很多时候您甚至不需要输入太多即可得到结果。 安装程序现在,让我们下载Visual Studio安装程序的exe文件,下载完成后运行它。这将不会直接安装Visual Studio本身,而是安装Visual Studio安装程序,这是一个帮助您安装Visual Studio的软件。Visual Studio是“集成开发环境”(Integrated Development Environment,IDE)的缩写。 一旦Visual Studio安装程序安装完成,您将自动看到一个界面,如果没有弹出,您会看到一个“已安装”的屏幕,显示没有安装任何内容,您可以从可用选项中进行选择。我要使用的是Visual Studio社区版,点击“安装”。 工作负载现在,您将回到默认的屏幕,这里有多个选项卡,我们将逐步了解它们。首先,我们来看看工作负载选项。在这里,您会看到网络和云相关的选项,比如ASP.NET和Web开发、Node.js、Python、Azure等。如果您需要其中的某个,可以进行安装,但这些都是高级主题,在开始时您并不需要。 对于我们来说,桌面和移动应用程序将是相关的,因为我们将专门开发WPF应用程序,因此我建议您直接安装这个工作负载。您当然也可以直接安装Unity,但我们会在课程的后期进行,所以此时无需担心。 组件选择现在,您可以切换到“单独组件”选项卡,您会找到可以选择的所有组件。例如,您甚至可以安装旧版的.NET Core运行时,如2.1或3.1,这些都已停止支持或为长期支持版(LTS)。许多解决方案是基于.NET Core 3.1构建的,但最新版本是.NET 6.0。随着时间的推移,命名约定已经改变,从.NET 5开始,微软将所有内容整合在一起,包括.NET Core和其他框架。 我希望您选择.NET 5运行时,因为在某些视频中我们将使用它。您可以从这里选择任何组件,但我建议您不要仅仅保留默认设置,除了我刚才提到的添加.NET 5运行时。 如果您想安装其他语言包,也可以随意选择。在我的情况下,默认选择了英语,但您可以使用提供的任何语言包进行安装。 您还可以更改安装位置以及下载缓存,并查看共享组件将放置的位置。这是我们创建新应用程序时默认的解决方案位置。好了,现在让我们点击“安装”,这将安装Visual Studio Community 2022。 多个实例您可以在Visual Studio安装程序中拥有多个Visual Studio实例。如果您想使用其他版本,可以在可用选项中安装企业版或专业版,但它们是收费版本。安装将需要一些时间,您会看到默认勾选“安装完成后启动”。 登录与主题设置一旦Visual Studio安装完成,您会被要求登录。这将允许您跨设备同步设置,实时协作以及与Azure服务无缝集成。您可以创建一个新账户或登录已有的Microsoft账户,也可以选择稍后登录。接下来,您可以选择使用的主题。我主要使用深色主题,因为我觉得它比其他主题更好,但如果您喜欢默认的蓝色主题或亮色主题,也可以选择这些。在长时间使用时,我觉得深色主题更护眼。 启动 Visual Studio现在启动Visual Studio。您可以创建一个新项目,或者选择“继续没有代码”来快速打开Visual Studio。当前没有打开的项目,我想快速展示一下,如果您决定更改颜色主题,比如觉得深色主题不合适,可以进入“工具” -> “选项”,在“环境”下的“常规”中找到颜色主题。您可以将其更改为蓝色,或者设置为亮色主题。 下一步如我所说,我更喜欢深色主题,所以我将其设置回去。在下一个视频中,我们将学习如何设置我们的第一个项目。期待在下个视频中见到您! 5 - 第一个程序:你好,世界设置第一个项目欢迎回来。在本视频中,我们将使用Visual Studio设置我们的第一个项目。您将看到Visual Studio如何自动为我们生成代码并构建整个项目结构,而我们无需做太多工作。 打开 Visual Studio打开Visual Studio后,您将看到一个窗口,您可以在此创建新项目、打开本地文件夹、项目解决方案或甚至克隆代码库。您也可以选择“继续没有代码”。打开Visual Studio后,这个窗口会弹出,同时Visual Studio会在后台打开。现在,我们可以登录到我们的Microsoft账户,或者如果还没有账户,可以创建一个。您也可以选择关闭它,这样Visual Studio仍然会打开,您可以继续使用而无需注册。但注册后有一些优势,比如您的设置可以被保存。 查看新特性打开Visual Studio后,您会看到一个名为“最新消息”的选项卡,在这里可以找到关于您安装或更新的Visual Studio版本的新功能详细信息。好的,让我关闭这个窗口,直接从这里创建一个新项目。 创建新项目点击“项目”,您会看到一个窗口,允许您选择不同类型的项目模板。例如,有控制台应用程序、类库等,很多可供选择。这些项目模板取决于您在Visual Studio安装程序中选择的包。我们感兴趣的是控制台应用程序,而这些项目模板之间的区别主要在于Visual Studio自动为我们生成的文件和文件中的代码。我们将选择控制台应用程序,这是一个创建命令行应用程序的项目,可以在.NET、Windows、Linux和macOS上运行。请确保选择C#版本而不是Visual Basic版本。 命名项目接下来,我们可以点击“下一步”,为控制台应用程序命名,我将命名为“Hello World”。在我的情况下,它显示“该目录不是空的”,因为我在名为“Repos”的位置中已经有一个“Hello World”项目。因此,解决方案名称是根据我的项目名称自动生成的,然后我可以继续点击“下一步”。 选择目标框架在这里,我需要选择要使用的框架,即目标框架。在此上下文中,框架基本上是一组大量代码和文件,具体说明我们编写的代码将如何被我们的机器执行。您可以选择.NET 6(长期支持版本)或.NET 7(标准支持版本)。根据您在Visual Studio安装程序中的设置,您可能会看到更多选项。对于我们初学者而言,选择长期支持的.NET 6版本就可以了,因为这些是底层的变化,只有在课程结束时才会对我们有影响。 创建项目让我们创建这个项目。一旦项目创建完成,您将看到程序“Hello World”已经为我们创建,默认情况下会显示这两行代码。可以使用控制键和滚轮放大视图,您也可以在Visual Studio应用程序左下角更改缩放级别,以便更好地查看代码。 代码解释在这里,我们有新的控制台模板。您可以按住Ctrl并单击查看详细信息。基本上,这里有一个注释(绿色文本)和一个链接(蓝色文本),我们通过在行首添加两个斜杠来表示注释。第二行是我们的Console.WriteLine,输出“Hello world”文本。这行代码将在控制台上打印出“Hello, world”。如果运行此应用程序,只需点击上方的按钮,它将开始运行,并弹出一个小窗口显示“Hello world”。您可以按任意键关闭此应用程序。 任务挑战这是我们构建的第一个应用程序,我们甚至没有自己写一行代码。这是自动生成的代码。接下来,我有一个小挑战,希望您将程序修改为输出“Hello”,然后是您的名字。在我的例子中是Dennis。将“world”替换为您的名字,然后运行应用程序。 希望您暂停视频并快速尝试一下。因此,我将输出“Hello Dennis”,因为这是我的名字,然后再次运行应用程序。结果是“Hello Dennis”。这就是我们构建的第一个小应用程序,通常在学习新编程语言时会编写“Hello World”应用程序,以确保程序能够正确执行。 下一步在下一个视频中,我们将查看项目的结构。期待与您再次见面! 6 - 你好,世界项目结构项目结构欢迎回来。在本视频中,我们将查看Visual Studio构建的项目结构。右侧有一个名为“解决方案资源管理器”的小选项卡,您可以点击它,在那里找到您的项目。我的项目叫做“Hello World one”。在这个项目内部,有一个C#文件夹,里面有许多依赖项,还有一个名为“program.cs”的文件,这就是我们的C#文件。 打开文件夹您可以通过右键单击此文件夹在文件资源管理器中打开它。您会发现,在“Repos”文件夹中有一个名为“Hello World one”的文件夹。此文件夹中存储了我创建的所有不同项目,而“Hello World one”正是其中之一。您可以看到有一个SLN文件,这是一个Visual Studio解决方案文件。双击该文件将打开项目,但由于我们已经打开了项目,所以不需要这样做。 项目文件夹进入该文件夹后,您会发现有一个“bin”文件夹,里面有一个“debug”文件夹,包含我们选择的.NET 6框架。您可以看到我们的“Hello World”可执行文件。这就是我们创建的小程序。如果您点击“Hello World one”,将打开该程序。遗憾的是,程序在关闭时没有什么限制,只有当我们点击上方的“Hello World”按钮时,Visual Studio才会保持程序处于活动状态。 保持程序运行为了让项目保持打开状态,我们可以使用 查看项目输出完成后,您现在可以打开您的“Hello World”程序,您会看到项目保持活动状态,输出“Hello Dennis”,并且由于等待输入而保持活跃。之前没有做到这一点,所以它才会自动关闭。 自动生成的文件在文件夹中,您会看到一些JSON文件和其他文件,这些都是自动生成的,位于“bin/debug”文件夹中。然后是“obj”文件夹,其中也包含一些JSON文件、debug文件夹、targets、props缓存以及各种为我们创建的文件。还有我们的C#项目文件,它同样是我们的项目“Hello World one”。如果我们点击该文件,它也会在Visual Studio中打开。 程序源文件我们再次看到“program.cs”文件,它与之前的文件是相同的。您可以使用不同的编辑器打开它,比如默认编辑器,您会发现代码也在其中。当然,您也可以不使用Visual Studio,直接编写程序,然后使用命令行进行编译,但这将非常繁琐,这就是使用Visual Studio这种集成开发环境(IDE)的巨大优势。此外,它还提供了美观的语法高亮,使得软件开发更为轻松。 总结这就是我们项目的结构。当我们在Visual Studio中构建控制台应用程序时,它会自动生成这些文件。在下一个视频中,我将快速向您展示为控制台应用程序生成的不同模板,因为在本课程中我们将构建许多控制台应用程序,这将非常方便我们展示C#的特性和基础知识。期待在下个视频中见到您! 7 - 理解新旧格式,以及如何在控制台中发出声音老式模板介绍欢迎回来。在本视频中,您将了解旧模板。这是.NET 6之前的默认模板,在.NET 7中仍然保持不变。我想向您展示这个模板,因为它可以帮助您更好地理解后台发生的事情,同时确保您了解我们在本课程中将构建的其他项目的结构,这样当您突然看到一些我们之前没有讨论过的代码时,就不会感到困惑。 创建新项目您看到,当我们创建这个项目时,自动生成了如下代码,同时给我们提供了一个链接,指向“aka.ms/new-console-template”。这个页面包含了很多信息,虽然我认为一开始深入探讨所有内容有些复杂,但我想向您展示的是,当我们在Visual Studio中创建新项目时,如果勾选一个额外的选项,我们将获得不同的代码,但其功能完全相同。 让我们一起创建一个新项目。点击“文件”>“新建项目”,也可以使用显示的快捷键。在我的例子中是 自动生成的代码创建新项目后,我自动得到了如下代码。我们可以看到,唯一相同的是 理解方法和类所有这些信息对于初学者来说可能有些复杂,所以我只是大致向您解释一下。您不需要记住所有这些内容,因为我们会逐步建立这些知识。我们在这个文件中使用了许多概念,这些概念将在本课程的前七章中进行讲解。
内部类和命名空间
所有这些代码都位于名为 您可以折叠代码,右侧有一个小减号,可以折叠各级代码。这在您有很多代码时非常有用,可以让您隐藏不相关的部分。请注意,每个打开的括号必须有相应的闭合括号。如果多了一个括号,代码将无法执行,IDE Visual Studio会报错。 语句和分号我们在语句后使用分号。缺少分号时,会报错提示“期望分号”。例如, 示例演示让我们快速演示一下。我将添加一个 接下来,我想挑战您在“Beep”之后写一段代码并再运行一次“Beep”。希望您尝试过了!例如,我将写 您会看到“Hello World”,然后输入内容,听到第一次“哔”声后,会输出“I beeped”和第二次“哔”声。这只是控制台可以做的一小部分,后续我们会逐步构建更多功能。 总结好的,以上就是本视频的内容。期待在下个视频中见到您! 8 - Mac上的你好,世界在Mac上使用C#启动Visual Studio项目欢迎回来。在本视频中,我将向您展示如何在Mac上使用Visual Studio启动一个C#项目。请按照以下步骤操作: 创建新解决方案
命名项目
运行项目
保持控制台打开在Windows上,您需要手动添加 Console.ReadKey(); 这行代码会使程序等待用户按下任意键,因此控制台不会立即关闭。 运行示例运行项目后,您将看到控制台等待输入。当您按下任意键后,控制台将输出“Press any key to continue”。如果在Windows上运行,该提示不会出现,程序将会直接关闭。 用户界面除了这些细节外,其他功能应该是相同的,当然,用户界面会有所不同。 期待在下个视频中与您见面! 9 - Visual Studio界面Visual Studio 用户界面介绍欢迎回来。在本视频中,我想和您聊聊 Visual Studio 的用户界面。在上一个视频中,我直接开始了编码,但这次我们将稍微深入了解界面。 用户界面概述Visual Studio 是一个逐步发展的程序,功能强大,但并非所有功能都需要您了解。因此,如果您不理解界面上的每个按钮,也不必担心。以下是我们需要关注的几个重要部分:
解决方案资源管理器在解决方案资源管理器中,您可以打开整个项目,项目名称为
输出窗口在底部的输出窗口中,您可以查看文件创建的位置,以及运行程序时可能出现的错误信息。这对调试非常有帮助。 界面自定义您可以根据需要自定义界面。例如,可以将解决方案资源管理器拖到窗口的右侧,使其更方便访问。您可以通过“视图”菜单找到更多窗口,未来的课程中我们会用到其中的一些。
窗口布局保存如果您改变了窗口布局并希望保存,可以选择“窗口布局”>“保存窗口布局”,并为其命名。这样您可以随时切换到该布局。 其他功能在顶部菜单中,还有很多功能,例如:
开发者新闻在启动页面上,您还可以找到与 C# 及 Visual Studio 相关的开发者新闻,这对保持最新信息非常有用。 工具栏和快捷键界面上还有一个按钮栏,其中“保存”和“启动”按钮是我们最常用的。通常,我更喜欢使用快捷键来操作。
总结这只是对 Visual Studio 用户界面的简要介绍。了解这些基本功能将帮助您更好地使用它。随着课程的深入,您将逐步熟悉这些功能。 期待在下个视频中与您见面! 13 - 第一章总结第一章总结恭喜您完成了第一章!在这一章中,您已经明确了自己的学习动机,并成功设置了开发环境,这是您在整个课程和软件开发生涯中不可或缺的工具。此外,您还创建了您的第一个软件——“Hello World”示例。虽然这是一个非常基础的示例,但它是您学习的第一步,而第一步往往是最艰难的。 鼓励继续学习现在,没有借口了!只需坚持学习课程,您将逐步掌握编程技能。希望您能积极参与课程讨论,并随时访问我们的博客。在博客中,我们将发布关于 C# 及其他编程主题的精彩文章。这些内容不仅可以帮助您提升技能,有时也会激励您保持学习动力。 下一章预告接下来,我们将进入第一章的实际编程部分,即第二章。在这一章中,您将学习如何使用变量以及数据类型的相关知识。期待在下一章与您相见! |
2 - 数据类型与变量19 - 变量和数据类型的高级概述变量与数据类型欢迎回来!在本视频中,我将教您关于变量的知识,因为变量是任何编程语言中非常重要的一个概念。变量是一个容器,可以存储一个值。随着时间的推移,我们可能会决定在同一个变量中存储另一个值,意味着在同一个容器中更改内容。 变量的类比想象一下,您正在参加自助餐,想要拿一杯咖啡和一块美味的蛋糕。您需要一个杯子来装咖啡和一个盘子来放蛋糕,因为咖啡杯是用来盛液体的,而不是用来放蛋糕的。把蛋糕放进咖啡杯里是行不通的,既不合适,也可能会引起他人的侧目。变量的工作原理就像这个例子一样。 变量的类型如果我们将变量比作咖啡的例子,那么每个变量必须有一个类型,就像杯子或盘子的类型。变量的类型告诉我们它可以存储什么类型的数据,而数据则相当于我们的食物或饮料。由于我们通常在程序中处理许多变量,因此需要通过给每个变量命名来区分它们。 在 C# 中定义变量在 C# 中,我们如何定义一个变量呢?假设我们想要存储一个整数,这里的整数是像 1、2、3 这样的完整数字。那么 其他数据类型在 C# 中,还有其他数据类型,以下是一些我们目前需要了解的主要数据类型:
变量与内存我们声明的变量越多,应用程序所需的内存就会越大。可以将其视作使用一个非常大的咖啡杯来装一小杯浓缩咖啡。 小结以上是关于 C# 中变量和数据类型的简单介绍。接下来,我们将通过实际示例来使用这些概念,以便更好地理解。 深入了解数据类型在接下来的视频中,我们将深入探讨更多数据类型,并学习它们的限制。尽管整数可以存储数字,但它不能存储像一万亿这样的数字,这就是 20 - 更多数据类型及其限制数据类型与变量欢迎回来!在本视频中,我们将探讨数据类型和变量,具体来说就是将数据存储在变量中的过程。让我们直接开始。 变量的声明首先,变量可以在方法外部声明,也可以在方法内部声明。在这个例子中,我们有一个整数变量,它的类型是 int age = 15;
Console.WriteLine(age); // 输出 15 更新变量的值接下来,我们可以看到变量的值被更改为 20。最开始是 15,但我们后来赋了一个新值 20。如果我们再次使用 默认值如果我们声明一个没有赋值的变量, 方法内的变量作用域如果我们在方法内部声明一个变量,比如 数据类型的分类我们已经了解了变量的基本概念,接下来让我们快速介绍几种不同的数据类型:
选择合适的数据类型在使用这些数据类型时,请记住使用最小的数据类型以适应您的值。换句话说,存储的小数字用 浮点值数据类型如果您需要使用浮点值,可以选择以下几种数据类型:
其他数据类型
代码示例与课程反馈现在,让我们开始编码,深入实践吧!如果您喜欢这门课程,请别忘了评分! 22 - 数据类型:整型、浮点型与双精度型实践中的变量与数据类型在了解了变量和数据类型的理论之后,接下来我们将实际应用这些知识。我们将从整数、双精度浮点数(doubles)和浮点数(floats)开始。 声明变量首先,声明变量的方法是使用数据类型,接着给变量命名,并以分号结尾。比如,下面是一个声明变量的示例: int num1; // 声明一个整数变量 num1 尽管没有赋值,我们仍然可以先声明它。之后,可以在下一行中给变量赋值: num1 = 13; // 将值 13 赋给 num1 输出变量要在控制台输出变量,可以使用 Console.WriteLine(num1); 运行后,控制台会显示 计算变量之和接下来,让我们展示两个变量的和。首先声明并初始化另一个变量 int num2 = 23; // 声明并初始化 num2 现在我们可以创建一个新的变量 int sum = num1 + num2; // 计算 num1 和 num2 的和 输出和我们可以输出计算结果: Console.WriteLine("num1 + num2 = " + sum); 运行程序后,结果会是 字符串连接为了使输出更具可读性,我们可以使用字符串连接(concatenation)。例如: Console.WriteLine("num1 is " + num1 + ", num2 is " + num2 + ", sum is " + sum); 运行后输出为 一次性声明多个变量我们还可以一次性声明多个变量,例如: int num1, num2, num3; // 声明多个整数变量 用逗号分隔变量名,并以分号结束。这种方式通常在方法或类的开始处使用,以便于管理变量。 变量重赋值变量的值可以在程序运行时被重赋值,例如: num2 = 100; // 重赋值 num2 注意,重新计算和输出时需要确保代码顺序正确,以避免错误的结果。 使用双精度浮点数使用 double d1 = 3.14; // 声明并初始化双精度浮点数
double d2 = 5.1;
double div = d1 / d2; // 进行除法运算
Console.WriteLine("d1 / d2 = " + div); 使用浮点数声明浮点数时,必须在数字后加 float f1 = 3.14F; // 声明浮点数 f1
float f2 = 5.1F;
float fDiv = f1 / f2; // 除法运算
Console.WriteLine("f1 / f2 = " + fDiv); 数据类型的选择在编写高效软件时,选择合适的数据类型非常重要。若只需整数,使用 类型转换在进行不同数据类型之间的运算时,可能会遇到类型转换问题。对于 从外部数据源获取数据当数据来自外部源(如数据库)时,你不能总是确定接收到的数据格式,因此在处理这些数据时需格外小心。 结语现在,你应该对如何使用变量和数据类型有了更好的理解,特别是 23 - 字符串数据类型及其方法欢迎回来。在本视频中,我们将讨论字符串(string),它用于表示文本。我在之前的视频中展示了如何使用字符串,字符串的首字母大写以表示系统字符串类。我们定义一个类型为字符串的变量,我将其命名为 现在,让我们看看字符串能做什么。我们可以将字符串打印到控制台。例如,使用 我们可以在字符串之间进行连接,例如,不仅仅写 "Dennis",而是可以说 "My name is " 加上字符串,这就是字符串的连接,称为 连接(concatenation)。也就是说,可以将多个字符串合并成一个字符串。因此,你可以创建一个新字符串 需要注意的是,虽然在此处使用的是小写的 同样,我们还可以创建一个新的字符串变量,将 string lowerCaseMessage = message.ToLower();
Console.WriteLine(lowerCaseMessage); 输出将是 "my name is dennis"。现在你已经看到了如何创建和使用字符串,以及可用的多种字符串方法,当然还有更多方法我们会在后续学习中使用。 在下一个视频中,我们将讨论命名约定。期待与你在那见面! 24 - 值类型与引用类型欢迎回来。在本视频中,你将学习值类型和引用类型。数据类型可以根据它们在内存中的占用方式分为两类:值类型和值类型。我们将逐一介绍这两类。 值类型值类型通常存储在栈中,这意味着它们的分配和释放是自动管理的,随着程序的增长和缩小,数据会直接存储。常见的值类型包括基本数据类型,如 可空值类型可空值类型(nullable value types)是每种值类型都有一个对应的可空版本,可以持有该类型的任意值或 null。这些类型用问号(?)表示,例如 值类型示例以下是一个值类型的示意图:
引用类型引用类型是另一种变量类型,它不是直接在内存中存储值,而是存储实际数据的内存位置。它只指明数据的位置。因此,变量存储的是数据的内存引用,而不是数据本身。引用数据类型包括字符串(strings)、类(classes)、数组(arrays)和其他对象。 复制引用当我们复制一个引用类型时,只会复制数据的内存地址,这样就会有两个变量指向同一数据。 引用类型示例以下是引用类型的示意图:
总结到此为止,让我们回到编码中。希望你对值类型和引用类型有了更清晰的理解。 25 - 编码标准欢迎回来。在本视频中,我想讨论编码标准。编码标准是一组指导方针、最佳实践和编程风格,开发人员在为项目编写源代码时遵循这些标准。所有大型软件公司都会遵循这些标准,而且公司通常会有自己特定的标准。以下是一些编码标准的示例。当然,当你在公司工作时,他们会告诉你以某种方式编写代码,因为这是他们的做法。 编码标准的组成部分变量命名你应该始终给变量一个合理的名称。在声明变量时,开发人员必须为变量提供一个适当的名称。变量的名称应基于其用途。想一想:我用这个变量做什么?然后给它一个描述该变量真正用途的名称。例如,如果你想存储用户的年龄,那么该变量的好名称可以是 函数命名你也应该给函数一个适当的名称。函数的名称应基于其在代码或程序中所执行的功能。函数通常执行某种操作。例如,如果你想要一个检查互联网连接的函数,可以将其命名为 注释在代码中留下注释是良好的实践,在大型科技公司中这更是必不可少。函数应该有注释,说明其用途和功能。这有助于其他开发人员理解该函数的作用。并且这不仅是为了其他开发人员,也是为了你自己。当你在几年后或几个月后回到代码时,如果没有适当的注释,你会发现很难理解自己的代码,可能需要从头开始理清思路,这会花费大量时间。 注释的类型单行注释单行注释用于描述变量或 // 这是一个单行注释 多行注释多行注释用于注释多于一行的内容。它以 /* 这是一个
多行注释 */ XML 文档注释XML 文档注释用于创建函数或类的文档。在 C# 中,以三个斜杠开始,接着是标签 例如, 总结以上就是标准实践的介绍。当然,还有更多的实践,你在大型公司工作时会遇到,例如他们如何决定命名变量等。好的,我们下个视频再见。 26 - 控制台类及其方法欢迎回来。在本视频中,我想帮助你更好地理解我们正在使用的所有控制台方法,以及未来我们将频繁使用这些方法的原因。因此,我想提供一些背景信息。如果你只是想使用这些方法,大部分内容可能与您无关;但如果你真的想了解发生了什么,那么这个视频是适合你的。让我们开始吧。 控制台类概述控制台类中有多种方法可供使用,我们将重点关注与输入和输出相关的方法。首先,我们有 Console.Write("text here"); 这段代码会将 "text here" 打印到控制台上,非常适合显示某些值,以验证代码是否正常工作。尤其是在编程初学阶段,这一点非常重要。
|
3 - 函数、方法及如何节省时间43 - 方法简介欢迎来到方法章节!在这一章中,你将学习如何使用方法、什么是方法,以及它们如何为你节省大量时间。你将了解方法的重要性,以及如何使用它们来组织你的代码。 本章内容
接下来,我们将讨论如何使用用户输入,使你的程序能够接收并处理数据。这非常酷!此外,我们还会介绍 期待在下一个视频中见到你! 44 - 函数与方法介绍欢迎来到方法章节。在这一部分,我们将深入了解方法,了解它们的作用以及如何使用它们。方法在任何面向对象编程语言中都非常重要。我们将从定义开始,这个定义来源于微软的官方文档。 方法定义方法是一个代码块,包含一系列语句。程序通过调用方法并指定所需的参数来执行这些语句。在 C# 中,每条执行的指令都是在某个方法的上下文中执行的。 方法语法方法的语法如下:
各部分解释
示例让我们看一个简单的示例: public int Add(int num1, int num2)
{
return num1 + num2;
} 在这个例子中:
方法的简化我们可以简化代码,如下所示: public int Add(int num1, int num2) => num1 + num2; 这样可以更简洁地表达相同的功能。 接下来的内容在下一个视频中,我们将创建一个没有返回值的 45 - 无返回值的方法现在我们了解了方法的理论,接下来让我们看看方法的实际应用。我们将创建一个方法,首先我们定义一个访问修饰符,这里我们使用 创建方法我们开始定义方法,如下所示: public void WriteSomething()
{
Console.WriteLine("I am called from a method");
Console.Read();
} 这个方法非常简单,它将在控制台输出“I am called from a method”。为了调用这个方法并执行它,我们需要在 static void Main(string[] args)
{
WriteSomething();
} 注意事项这里我们会遇到一个错误,因为 public static void WriteSomething() 方法的结构在这里,方法的结构是:
当你想要在一个静态方法中调用另一个方法时,必须确保被调用的方法也是静态的。现在我们已经解决了错误,调用方法后,控制台将显示:
创建特定输出的方法接下来,我们创建一个新的方法,它不仅仅输出固定的内容,而是根据调用时提供的文本输出。这个方法如下: public static void WriteSomethingSpecific(string text)
{
Console.WriteLine(text);
} 在这里,我们定义了一个接受一个字符串参数 WriteSomethingSpecific("I am an argument and I am called from a method."); 参数与参数值当你创建方法时,
小结在这部分中,我们了解了如何创建和调用方法,使用 46 - 有返回值和参数的方法现在我们已经了解了如何创建不带返回值的方法,接下来我们将讨论带有返回值的方法,例如整数类型的返回值。让我们开始创建一个新的方法。 创建返回整数值的方法我们定义一个方法,返回类型为 public static int Add(int num1, int num2)
{
return num1 + num2;
} 在这里,我们需要确保每条路径都返回一个值。如果没有返回值,编译器将会报错,提示“并非所有代码路径都返回一个值”。在这个简单的情况下,我们只需要返回 调用返回值的方法要调用这个 int result = Add(15, 31);
Console.WriteLine(result); 当我们运行这个代码时,控制台将显示结果 Console.WriteLine(Add(15, 31)); 这样可以简化代码,不需要额外的变量。 创建乘法方法现在,请尝试创建一个新的方法,用于计算两个值的乘积并返回结果。方法如下: public static int Multiply(int num1, int num2)
{
return num1 * num2;
} 我们可以通过以下方式调用这个方法,例如: Console.WriteLine(Multiply(25, 25)); 这将返回 创建除法方法接下来,让我们创建一个用于除法的静态方法,返回值类型为 public static double Divide(int num1, int num2)
{
return (double)num1 / num2;
} 在调用这个方法时,可以如下操作: Console.WriteLine(Divide(25, 13)); 注意事项在整数相除时,结果也将是整数。如果 总结通过这些示例,我们了解了如何创建具有返回值的方法,如何传递参数,以及如何调用这些方法。在接下来的内容中,你可以尝试自己创建一些方法,这将有助于巩固你的理解。期待在下一个视频中见到你! 48 - 方法挑战的解决方案欢迎回来!希望你已经成功编写了代码所需的方法和变量,并完成了挑战。接下来,让我们创建表示朋友的变量。 创建朋友的变量我们将创建三个朋友的字符串变量,代码如下: string friend1 = "Frank";
string friend2 = "Michael";
string friend3 = "Vlad"; 创建问候方法接下来,我们需要一个方法来问候这些朋友。这个方法将接受一个参数 public static void GreetFriend(string friendName)
{
Console.WriteLine("Hi, " + friendName + ", my friend!");
} 调用问候方法现在,我们可以多次调用这个方法来问候每个朋友: GreetFriend(friend1);
GreetFriend(friend2);
GreetFriend(friend3); 完整代码示例如下: public class Program
{
public static void Main()
{
string friend1 = "Frank";
string friend2 = "Michael";
string friend3 = "Vlad";
GreetFriend(friend1);
GreetFriend(friend2);
GreetFriend(friend3);
}
public static void GreetFriend(string friendName)
{
Console.WriteLine("Hi, " + friendName + ", my friend!");
}
} 其他实现方式当然,你也可以选择使用一个方法来问候所有朋友,通过修改方法以接受多个参数。例如: public static void GreetFriends(string friend1, string friend2, string friend3)
{
Console.WriteLine("Hi, " + friend1 + ", my friend!");
Console.WriteLine("Hi, " + friend2 + ", my friend!");
Console.WriteLine("Hi, " + friend3 + ", my friend!");
} 然后,你只需一次调用这个方法: GreetFriends(friend1, friend2, friend3); 结论这两种方法都能达到相同的效果,具体使用哪种取决于你的编码风格。希望你能自由地尝试其他方法,创建一些新的方法并进行实验,因为在接下来的内容中,我们将频繁使用这些方法。期待在下一个视频中见到你! 49 - 用户输入欢迎回来!现在你已经掌握了如何创建方法,我们将探讨如何使用用户输入来运行这些方法。接下来,我们将学习如何获取用户输入,并利用这些输入来进行简单的计算。 获取用户输入首先,我们需要创建一个字符串变量来存储用户输入,代码如下: string input = Console.ReadLine(); 这个方法会等待用户输入,并将输入内容存储在 创建简单的加法计算器接下来,让我们创建一个简单的加法计算器,允许用户输入两个数字并计算它们的和。我们将定义一个方法 public static int Calculate()
{
Console.WriteLine("Please enter the first number:");
string number1Input = Console.ReadLine();
Console.WriteLine("Please enter the second number:");
string number2Input = Console.ReadLine();
// 将字符串转换为整数
int num1 = int.Parse(number1Input);
int num2 = int.Parse(number2Input);
// 计算结果
int result = num1 + num2;
return result;
} 调用计算方法在 public static void Main()
{
int sum = Calculate();
Console.WriteLine("The result is: " + sum);
} 完整代码示例以下是完整的代码示例,展示如何将上述部分结合在一起: using System;
public class Program
{
public static void Main()
{
int sum = Calculate();
Console.WriteLine("The result is: " + sum);
}
public static int Calculate()
{
Console.WriteLine("Please enter the first number:");
string number1Input = Console.ReadLine();
Console.WriteLine("Please enter the second number:");
string number2Input = Console.ReadLine();
// 将字符串转换为整数
int num1 = int.Parse(number1Input);
int num2 = int.Parse(number2Input);
// 计算结果
int result = num1 + num2;
return result;
}
} 错误处理在此代码中,我们没有处理输入格式错误。如果用户输入非数字字符,程序会崩溃。为了避免这种情况,我们可以稍后学习使用 结论通过以上步骤,你可以创建一个简单的用户输入加法器。练习这些步骤,确保你熟悉如何获取用户输入并将其用于方法调用。期待在下一个视频中见到你! 50 - 尝试-捕获与最终欢迎回来。在本视频中,您将学习如何捕捉错误,以及在发生错误时如何处理,而不是让程序崩溃。 理解 Try、Catch 和 Finally
示例:捕获异常让我们从一个简单的示例开始,我们提示用户输入一个数字,解析它,并处理任何错误。 Console.WriteLine("请输入一个数字:");
string userInput = Console.ReadLine();
try {
int number = int.Parse(userInput);
Console.WriteLine("您输入的数字是:" + number);
}
catch (FormatException) {
Console.WriteLine("那不是有效的数字。");
}
catch (OverflowException) {
Console.WriteLine("数字太大或太小。");
}
finally {
Console.WriteLine("感谢您使用数字输入程序。");
} 除法示例现在,让我们处理除以零的挑战。下面是您可以实现此功能的代码: int num1 = 5;
int num2 = 0;
try {
int result = num1 / num2;
Console.WriteLine("结果是:" + result);
}
catch (DivideByZeroException) {
Console.WriteLine("您不能除以零!");
} 实践中的错误处理要了解它的工作原理,您可以先运行没有 挑战任务您的任务是:
这个练习将帮助您巩固对 C# 中错误处理的理解。祝您好运,期待在下一个视频中见到您! 51 - 运算符欢迎回来。在本视频中,我们将讨论运算符,这些小东西对我们的程序有很大的影响,所以我们赶紧开始吧。我们将创建两个有值的变量和一个目前为空的变量。为了简单起见,我将用数字一、二和三。第一个变量的值是5,第二个是3,第三个暂时为空。 一元运算符首先,我们来看一元运算符。我将把 number3 = -number1;
Console.WriteLine($"num3 is {number3}"); 这样做后, 逻辑运算符接下来,我们使用逻辑运算符。假设我们有一个布尔值 bool isSunny = true;
Console.WriteLine($"Is it sunny? {isSunny}");
Console.WriteLine($"Is it not sunny? {!isSunny}"); 这将显示 增量运算符接下来,我们来看看增量运算符。我将创建一个新的变量,初始化为0,然后使用 int num = 0;
num++;
Console.WriteLine($"NUM is {num}"); // 结果是1 如果我们在同一行中使用它并打印,递增操作将在下一行生效。 减量运算符减量运算符与增量运算符类似,只需使用 num--;
Console.WriteLine($"NUM is {num}"); // 结果是0 加法与乘法运算符现在让我们看一下加法和乘法运算符: int result = number1 + number2; // 5 + 3 = 8
Console.WriteLine($"Result of num1 + num2 is {result}");
result = number1 - number2; // 5 - 3 = 2
Console.WriteLine($"Result of num1 - num2 is {result}");
result = number1 * number2; // 5 * 3 = 15
Console.WriteLine($"Result of num1 * num2 is {result}");
result = number1 / number2; // 5 / 3 = 1(整数除法)
Console.WriteLine($"Result of num1 / num2 is {result}"); 整数除法只返回商,而舍弃小数部分。如果使用 取余运算符取余运算符 int remainder = number1 % number2; // 5 % 3 = 2
Console.WriteLine($"Remainder of num1 % num2 is {remainder}"); 关系运算符我们创建一个布尔变量 bool isLower = number1 < number2; // false
Console.WriteLine($"Result of number1 < number2 is {isLower}"); 同样,可以使用等于运算符 bool isEqual = number1 == number2; // false
Console.WriteLine($"Result of number1 == number2 is {isEqual}"); 逻辑运算符与条件运算符最后,我们来看逻辑运算符 bool isSunny = true;
bool isLowerAndSunny = isLower && isSunny; // 检查两个条件是否都为true
Console.WriteLine($"Result of isLower and isSunny is {isLowerAndSunny}"); 如果我们使用 bool isLowerOrSunny = isLower || isSunny; // 只需其中一个为真
Console.WriteLine($"Result of isLower or isSunny is {isLowerOrSunny}"); 总结这段视频涵盖了各种运算符,包括一元运算符、增量和减量运算符、算术运算符、取余运算符以及关系和逻辑运算符。虽然没有具体的练习,但理解这些基础知识对今后的编程非常重要。如果您对某个运算符不太确定,可以随时回头查看本视频并尝试理解每一步的代码。 希望您喜欢本视频,下一个视频我们将进行更短的内容,期待与您再次见面! 52 - 方法总结方法章节总结在这一章节中,你学习了方法的工作原理以及不同类型的方法。以下是你所掌握的内容: 方法类型
异常处理你还学习了如何使用 try 和 catch 来捕捉可能出现的错误,例如在处理用户输入时。 操作符你也了解了操作符的用法,包括如何进行数学计算和逻辑判断。 练习的重要性希望你完成了这一章节的所有练习,因为这对学习至关重要。通过练习,你会发现问题并寻找解决方案,而这些解决方案会帮助你在未来更好地记忆和理解所学内容。每次成功解决问题,都是你作为开发者成长的一部分。 感谢你继续与我学习!希望你对下一章节充满期待,期待在下一个章节见到你! |
4 - 决策53 - 决策简介决策的重要性在现实生活中,做决策非常重要。在编程中也是如此。你需要编写一些程序,这些程序必须根据条件做出决策。你决定程序将做出哪些决策,以及这些决策的最终结果。 本章内容概览在本章中,我们将学习如何使用 if 语句 来实现这一点。学习完之后,你将能够编写一个根据条件执行代码的程序。 主要内容包括:
练习在本章结束时,你将进行相关练习,以巩固所学的内容。 让我们直接开始吧!希望你能享受这一章的学习过程。 54 - C语言中的决策制作介绍决策与编程有些人说,生活中一切都与决策有关。对于我来说,编程也是如此。我们将要学习 if 语句,它们帮助我们根据决策来执行代码。 if 语句的基本结构一个基本的 if 语句包括一个条件,通常在大括号中定义要执行的代码。比如,我们可以检查天气是否好。如果天气好,我们就出去;如果天气不好,我们就待在里面。条件可以是“天气好吗?”或“阳光明媚吗?”这样简单的比较。 复杂的条件判断我们还可以使用 else 语句 来处理未满足的条件:
此外,还可以使用 else if 语句:
实际示例接下来,我们将通过一个示例来展示这些概念。在 C# 中,我们可以创建一个整数变量来表示温度: int temperature = 10; // 设定温度为10摄氏度
if (temperature < 10) {
Console.WriteLine("穿上外套");
} else if (temperature == 10) {
Console.WriteLine("气温为10度C");
} else {
Console.WriteLine("温暖舒适");
} 代码演示我们会使用不同的温度值来查看输出。比如,温度设置为6度,将输出“穿上外套”;设置为25度,将输出“温暖舒适”。 现在,挑战一下自己,修改代码以考虑天气,并让用户手动输入温度。你可以提示用户输入温度,并根据输入输出相应的建议,比如:
效率提升在本章的最后,我们将学习如何提高代码的效率。当前的方法是顺序执行每个条件,但我们可以优化这一流程。比如,如果第一个条件为真,那么其他条件就不需要检查了。 使用 else if 结构可以使代码更加简洁和高效,因为它确保了只有一个条件会被满足并执行对应的代码。 if (temperature < 20) {
Console.WriteLine("穿上外套");
} else if (temperature == 20) {
Console.WriteLine("穿长裤和毛衣");
} else {
Console.WriteLine("今天只需要短裤");
} 小结通过这些示例和结构,你将更好地理解如何在编程中做出决策。这些基本的控制流语句是构建复杂程序的重要组成部分。接下来,我们将继续深入学习和练习这些内容,帮助你成为一名更有效的开发者。 55 - 尝试解析简介欢迎回来在这一视频中,我们将探讨 TryParse 方法,它允许我们将字符串转换为数字数据类型。这在多个场合非常有用,我们将举一个例子。 TryParse 方法的基本用法TryParse 方法可以将字符串(例如 "128")转换为整数。在这里,重要的是字符串必须用引号括起来,因为 "128" 是文本而非数字。数据类型在 C# 中非常重要。 用户输入与数据类型通常,TryParse 方法用于处理用户输入。例如,用户输入一个名称,但输入的数据是字符串,所以我们需要将其转换为数字,以便进行进一步处理。 以下是一个示例,我们在主方法中定义一个字符串变量 numberAsString,其值为 "128"。我们需要将其解析为整数: string numberAsString = "128"; // 定义字符串
int parsedValue; // 创建变量来存储解析值
bool success = int.TryParse(numberAsString, out parsedValue); // 尝试解析字符串 在上面的代码中, 解析结果的处理我们可以使用解析的结果进行输出。例如: if (success) {
Console.WriteLine($"Parsing successful! Number is: {parsedValue}");
} else {
Console.WriteLine("Parsing failed.");
} 在这里,如果解析成功,将打印解析的值;否则,打印解析失败的信息。 解析其他数据类型你还可以使用 TryParse 方法解析其他数字数据类型,例如 float 或 double。示例如下: string floatAsString = "128.75";
float floatParsedValue;
bool floatSuccess = float.TryParse(floatAsString, out floatParsedValue); 与 int.TryParse 类似, 解析失败的示例让我们来看一些解析失败的示例:
实践演示接下来,我们将进入一个实际示例,以查看 TryParse 方法的使用效果。感谢观看,我们马上开始! 56 - IF与ELSE IF尝试解析确保代码的安全运行现在我们已经了解了如何使用 if 语句,我们还可以确保代码运行顺利。当用户输入不正确的值(例如字母)时,代码可能会崩溃。为了解决这个问题,我们可以使用 TryParse 方法替代 Parse。 使用 TryParse 处理输入我将使用 int number;
bool success = int.TryParse(temperatureInput, out number); 如果解析成功,我将 numTemp 设置为 number。这样,如果解析失败,numTemp 仍然会有一个值。为了处理未输入正确值的情况,如果解析失败,我会将 numTemp 设置为 0。 处理用户输入在代码中,我还会告知用户未输入正确值。示例如下: if (!success) {
numTemp = 0; // 设置为 0
Console.WriteLine("Value entered was no number. Zero set as temperature.");
} 这样,如果用户输入了无效值,例如非数字字符,程序将不会崩溃,而是给出提示并将温度设置为 0。 总结到目前为止,我们使用了 Parse,它简单明了,但若用户输入错误,程序会崩溃。通过使用 TryParse,我们可以安全地处理用户输入,避免程序崩溃。 下一步在下一个视频中,我们将学习更复杂和嵌套的 if 语句。期待与大家再见! 57 - 嵌套IF语句创建一个简单的登录系统示例欢迎回来。在这个视频中,我们将创建一个简单的登录系统示例,主要使用 if 语句和逻辑判断。我将为您展示一个类似于登录系统的示例。 定义变量首先,我们定义一些变量:
检查用户状态接下来,我们构建一个程序,检查用户是否已注册。如果用户已注册,我们将输出一条消息: if (isRegistered) {
Console.WriteLine("Hi there, registered user.");
if (!string.IsNullOrEmpty(username)) {
Console.WriteLine("Hi there, " + username);
if (username.Equals("admin", StringComparison.OrdinalIgnoreCase)) {
Console.WriteLine("Hi there, admin.");
}
}
} 获取用户名用户输入用户名: Console.WriteLine("Please enter your username:");
username = Console.ReadLine(); 处理输入和输出在获取用户输入后,我们使用 if 语句来检查用户名是否为空,并根据条件输出不同的消息: if (isRegistered) {
if (string.IsNullOrEmpty(username)) {
Console.WriteLine("Hi there, registered user.");
} else {
Console.WriteLine("Hi there, " + username);
if (username.Equals("admin", StringComparison.OrdinalIgnoreCase)) {
Console.WriteLine("Hi there, admin.");
}
}
} 代码运行结果
简化条件判断我们可以将条件判断进行简化,避免多层嵌套的 if 语句。可以将多个条件合并到一个 if 语句中: if (isRegistered && !string.IsNullOrEmpty(username)) {
Console.WriteLine("Hi there, " + username);
if (username.Equals("admin", StringComparison.OrdinalIgnoreCase)) {
Console.WriteLine("Hi there, admin.");
}
} else {
Console.WriteLine("Hi there, registered user.");
} 使用逻辑运算符我们还可以使用逻辑运算符 ||(或)来检查多个条件。例如,如果用户是管理员或已注册: if (isAdmin || isRegistered) {
Console.WriteLine("You are logged in.");
} 结束语在本视频中,我们学习了如何使用 if 语句和嵌套的条件判断来构建一个简单的登录系统示例。下一个视频中,我们将进行一个小挑战,请尝试解决它。期待与您再见! 59 - IF语句挑战的解决方案创建登录和注册系统的挑战解决方案欢迎回来。在本视频中,我们将解决上节课的挑战,希望您已经尝试过并找到了解决方案。现在,我们来创建一个登录系统和注册系统。 创建注册方法首先,我们需要创建一个注册方法。我们将其定义为静态方法,以便可以在主方法中调用它。 public static void Register() {
Console.WriteLine("Please enter your username:");
static string username = Console.ReadLine();
Console.WriteLine("Please enter your password:");
static string password = Console.ReadLine();
Console.WriteLine("Registration completed.");
Console.WriteLine("--------------------");
} 在此代码中,我们首先要求用户输入用户名,然后输入密码。注册完成后,我们向用户确认注册已成功。 创建登录方法接下来,我们创建一个登录方法,结构与注册方法相似: public static void Login() {
Console.WriteLine("Please enter your username:");
string enteredUsername = Console.ReadLine();
if (enteredUsername == username) {
Console.WriteLine("Please enter your password:");
string enteredPassword = Console.ReadLine();
if (enteredPassword == password) {
Console.WriteLine("Login successful.");
} else {
Console.WriteLine("Login failed. Wrong password. Restart the program.");
}
} else {
Console.WriteLine("Login failed. Wrong username. Restart the program.");
}
} 测试登录和注册流程现在,我们可以在主方法中调用这两个方法: static void Main(string[] args) {
Register();
Login();
} 完整代码示例整合以上代码,完整示例如下: using System;
class Program {
static string username;
static string password;
public static void Register() {
Console.WriteLine("Please enter your username:");
username = Console.ReadLine();
Console.WriteLine("Please enter your password:");
password = Console.ReadLine();
Console.WriteLine("Registration completed.");
Console.WriteLine("--------------------");
}
public static void Login() {
Console.WriteLine("Please enter your username:");
string enteredUsername = Console.ReadLine();
if (enteredUsername == username) {
Console.WriteLine("Please enter your password:");
string enteredPassword = Console.ReadLine();
if (enteredPassword == password) {
Console.WriteLine("Login successful.");
} else {
Console.WriteLine("Login failed. Wrong password. Restart the program.");
}
} else {
Console.WriteLine("Login failed. Wrong username. Restart the program.");
}
}
static void Main(string[] args) {
Register();
Login();
}
} 总结通过这个简单的代码示例,我们创建了一个基本的注册和登录系统。虽然当前实现只在运行时存储数据,但未来我们将学习如何持久化存储数据。 希望您能成功构建一个类似的系统。如果您没有成功,不要担心,给自己一些时间,明天再尝试。期待在下一个视频中见到您! 60 - 开关语句Switch 和 Case 语句介绍欢迎回来。在本视频中,我们将讨论 创建 Switch Case 示例首先,我们定义一个用于比较的变量。例如,我们可以使用一个整型变量来表示年龄。 int age = 25; // 假设我们设定年龄为25 接下来,我们使用 switch (age) {
case 15:
Console.WriteLine("Too young to party in the club.");
break;
case 25:
Console.WriteLine("Good to go.");
break;
default:
Console.WriteLine("How old are you then?");
break;
} 在这个例子中,我们检查 默认情况
运行代码假设我们将
如果我们将
使用 If 语句的挑战现在,作为一个小挑战,请尝试将上面的 if (age == 15) {
Console.WriteLine("Too young to party in the club.");
} else if (age == 25) {
Console.WriteLine("Good to go.");
} else {
Console.WriteLine("How old are you then?");
} 如您所见, 使用字符串的 Switch 语句接下来,我们快速看一下如何使用字符串进行 string username = "Dennis"; 我们可以使用 switch (username) {
case "Dennis":
Console.WriteLine("Username is Dennis.");
break;
case "root":
Console.WriteLine("Username is root.");
break;
default:
Console.WriteLine("Username is unknown.");
break;
} 运行字符串 Switch 语句如果我们将
这表明 总结在本视频中,我们介绍了 62 - IF语句挑战2的解决方案高分挑战简介欢迎回来,今天我们将讨论第二个 创建变量首先,我们需要定义一些变量: static int highScore = 300; // 旧的高分是300
static string highScorePlayer = "我"; // 记录高分的玩家 检查高分的方法接下来,我们创建一个方法来检查玩家的新得分是否高于当前高分: public static void CheckHighScore(int score, string playerName) {
if (score > highScore) {
highScore = score; // 更新高分
highScorePlayer = playerName; // 更新高分玩家
// 通知控制台
Console.WriteLine($"New High Score is {score}.");
Console.WriteLine($"It is now held by {playerName}.");
} else {
// 旧高分未被打破
Console.WriteLine($"The old high score could not be broken. It is still {highScore}, held by {highScorePlayer}.");
}
} 使用方法现在,我们可以调用这个方法来检查玩家的得分是否打破了高分。以下是一些示例: CheckHighScore(250, "Maria"); // 250分由Maria获得
CheckHighScore(315, "Michael"); // 315分由Michael获得
CheckHighScore(350, "Dennis"); // Dennis打破了自己的记录 控制台输出为了在控制台上看到输出,我们需要确保在代码的末尾加上 示例代码以下是完整的代码示例: using System;
class Program {
static int highScore = 300;
static string highScorePlayer = "我";
public static void CheckHighScore(int score, string playerName) {
if (score > highScore) {
highScore = score;
highScorePlayer = playerName;
Console.WriteLine($"New High Score is {score}.");
Console.WriteLine($"It is now held by {playerName}.");
} else {
Console.WriteLine($"The old high score could not be broken. It is still {highScore}, held by {highScorePlayer}.");
}
}
static void Main(string[] args) {
CheckHighScore(250, "Maria");
CheckHighScore(315, "Michael");
CheckHighScore(350, "Dennis");
Console.ReadLine(); // 等待输入以查看输出
}
} 总结在这个视频中,我们展示了如何创建一个功能来检查高分是否被打破。通过使用 63 - 增强IF语句:三元运算符简介欢迎回来!在本视频中,我将向你展示一种简化 创建变量首先,我们需要定义一些变量: int temperature = -5; // 温度设定为-5度
string stateOfMatter = ""; // 状态变量初始化为空 使用传统的
|
5 - 循环67 - 循环简介欢迎来到循环章节在这一章节中,你将学习如何使用不同类型的循环。实际上,有四种主要的循环: 循环的优势这些循环在许多场景中是可以互换的,但它们的使用场景各有不同。你将学习如何使用这些循环以及它们的具体用途。这使你能够编写能够多次迭代的程序,从而重复执行某段代码。 深入理解就像你之前学习的其他关键字一样,越多地在不同的上下文中看到这些循环,它们的含义和用法就会变得越清晰。接下来,我们将直接进入具体内容。 期待在下一个视频中见到你! 68 - 循环基础欢迎来到循环的介绍在本章中,我们将讨论不同类型的循环,并学习如何在实际中使用它们。首先,我们来看看理论部分,了解各种循环的优势。 循环的优势
循环类型我们将学习几种主要的循环类型:
示例与演示现在,我们将进入示例演示部分,开始一些实际的代码示例。期待在接下来的部分见到你! 69 - For循环欢迎回来在本视频中,我们将详细了解
|
6 - 面向对象编程(OOP)76 - 对象简介面向对象编程章节简介欢迎进入**面向对象编程(OOP)**章节!这是整个学习过程中的重要章节之一,因为你即将学习C#中的核心概念之一。面向对象编程是一种编程范式,虽然还有其他编程方法,但OOP目前是使用最广泛且效率较高的方法,尤其是在开发复杂项目时表现尤为出色。 什么是面向对象编程?OOP的核心思想是通过对象来组织代码。这种方法非常适合于涉及现实生活中的对象的项目。例如,当你开发一个包含用户信息的数据库时,"用户"可以被视为一个对象,而你可以围绕这个用户创建一个类,用来包含特定的用户信息。 OOP的优势OOP能够将现实中的概念直接映射到代码中,使代码与现实更接近,也更容易理解。面向对象编程的优势在于可以帮助你构建更具可维护性、更易扩展和更灵活的代码。 学习内容概览在本章节中,你将学习以下内容:
具体内容和练习
在接下来的学习中,你会逐步掌握如何创建和使用对象、类以及属性,并将这些知识应用到你的项目中。准备好了吗?让我们一起进入面向对象编程的世界,学习如何创建自己定义的类和对象。 让我们开始吧! 77 - 类与对象介绍面向对象编程简介欢迎回来!在本章节中,我们将深入探讨C#中的面向对象编程(OOP)。到目前为止,我们仅在一个类中进行编程,即包含 类(Class)是什么?一个类(Class)是一个对象的蓝图或模板。OOP(面向对象编程)中的“对象”源自类,可以通过类创建多个特定的对象。例如,我们可以定义一个“Car”(汽车)类,然后通过该类创建多个不同的汽车对象。 事实上,我们已经使用过一些类,例如
什么是对象?一个对象(Object)是类的实例,或者说是类的具体实现。例如,我们可以将一个
面向对象编程的基本概念
接下来的内容在下一步,我们将创建属于自己的类,亲自体验如何定义类的属性和方法,构建更复杂的对象。通过实践,你会更深入地理解类的作用和用途。 让我们继续进入下一节,创建第一个属于自己的类吧! 78 - 我们的第一个自定义类创建第一个类欢迎回来!在本视频中,我们将创建自己的第一个类,它是可以用来创建对象的蓝图。类在 C# 中基本上相当于一种自定义的数据类型。通过定义类,我们可以设定对象的属性和方法,然后基于这个类生成多个对象实例。 什么是类(Class)?在 C# 以及其他面向对象编程语言中,类是创建对象的模板。类定义了一组属性(数据属性)和方法(功能函数),这些是该类的对象会拥有的特征和行为。接下来我们将创建一个类 创建
|
快捷键 | 功能 |
---|---|
F12 |
跳转到定义 |
Ctrl + - |
返回到前一个位置 |
Ctrl + Shift + - |
前进到下一个位置 |
Ctrl + Space |
打开 IntelliSense |
Ctrl + K + C |
注释选定代码 |
Ctrl + K + U |
取消注释选定代码 |
Ctrl + M + M |
折叠/展开代码块 |
F5 |
启动调试器运行代码 |
Ctrl + F5 |
无调试器运行代码 |
Ctrl + R + R |
重命名方法或变量 |
Ctrl + Tab |
切换到另一个文件 |
在下一个视频中,我们将探讨如何使用代码片段来加速编程。
84 - Visual Studio中的代码片段
代码片段简介
欢迎回来!在本视频中,您将学习如何使用代码片段 (code snippets) 来加速编码。代码片段是预定义的代码模板,可以快速插入常用代码结构,从而减少手动输入的重复性。我们之前已经用到过代码片段,比如输入 CW
并按下 Tab
键可以直接插入 Console.WriteLine
,非常方便。
以下是如何高效使用代码片段的一些技巧。
常用代码片段
-
Console.WriteLine
- 输入
cw
然后按Tab
键,自动生成Console.WriteLine()
,并且光标会自动跳入括号中,便于您立即输入内容。 - 示例:
cw + Tab → Console.WriteLine();
- 输入
-
构造函数(Constructor)
- 输入
ctor
然后按Tab
,会自动生成一个默认构造函数。创建新类时需要构造函数,这个片段非常方便。 - 示例:
ctor + Tab → public ClassName() { }
- 输入
-
if 语句
- 输入
if
然后按Tab
,会生成一个基本的if
语句框架,光标会自动跳到条件括号中。 - 示例:
if + Tab → if (condition) { }
- 输入
-
while 循环
- 输入
while
然后按Tab
,会生成一个while
循环的框架。可以快速设置循环条件。 - 示例:
while + Tab → while (condition) { }
- 输入
-
try-catch 语句
- 输入
try
然后按Tab
,会自动生成一个try-catch
代码块,便于异常处理。 - 示例:
try + Tab → try { } catch (Exception ex) { }
- 输入
-
属性 (Property)
-
输入
prop
并按Tab
会生成一个自动实现的属性框架。 -
示例:
prop + Tab → public int MyProperty { get; set; }
-
输入
propg
并按Tab
生成一个带get
和private set
的属性。
-
如何查看代码片段库
您可以通过以下步骤查看更多可用的代码片段:
- 进入 工具 (Tools) 菜单,选择 代码片段管理器 (Code Snippets Manager)。
- 在代码片段管理器中选择 C#,然后查看不同的代码片段,例如
exception
、for
循环片段等。
自定义代码片段
Visual Studio 还允许您创建自定义代码片段,这样您可以设计适合自己需求的片段。在 Visual Studio 的代码片段管理器中,您可以进一步探索创建代码片段的方法。在 Visual Studio Productivity Masterclass 课程中,我提供了如何创建自定义代码片段的详细教程。
总结
代码片段可以帮助您减少重复输入,提高效率。以下是一些快捷片段的总结:
代码片段 | 用途 |
---|---|
cw |
Console.WriteLine |
ctor |
默认构造函数 |
if |
if 语句 |
while |
while 循环 |
try |
try-catch 语句 |
prop |
自动属性 |
propg |
get 和 private set 属性 |
通过熟练使用这些代码片段,您可以更高效地编写代码。在接下来的课程中,您将看到代码片段在不同场景下的具体应用。
85 - 理解方法与变量的私有与公共
私有与公共访问修饰符
欢迎回来!在本视频中,我们将讨论 私有 (private) 和 公共 (public) 访问修饰符,它们是控制类成员访问权限的关键。我们将理解如何在 C# 中利用这些修饰符进行数据封装,确保数据安全,同时保持类的易用性。
私有 (private) 访问修饰符
在对象的成员变量(字段)前使用 private
关键字,就表示这个变量只能在该类内部访问,无法从外部直接修改或访问。例如,我们在类 Car
中定义了一个私有变量 _name
:
private string _name;
在类的外部,例如在主程序文件中,我们不能直接访问 _name
。如果尝试修改 myCar._name
,编译器会报错:“此变量由于保护级别不可访问”。
使用私有成员变量的原因
- 数据完整性:通过限制直接访问,我们可以确保数据不会被随意修改。通过在 getter 和 setter 中添加数据校验,我们能确保数据始终保持有效状态。
- 抽象性:私有变量隐藏了类的内部实现,外部代码无需关心类的内部状态,只需要关注类公开的功能。
- 复杂性降低:只公开必要的成员方法或变量,可以减少类的复杂度,使得类的使用变得简单。
示例:如何让变量在类外部可访问
一种方法是将 _name
设为公共 (public),但这样会违背数据封装的原则。另一种更优雅的方法是使用 属性 (property),这将在下一节中介绍。
公共 (public) 访问修饰符
与 private
相对,public
关键字允许变量或方法在类外部访问。例如,若我们将 _name
设为 public
:
public string Name;
此时,主程序文件可以直接访问和修改 Name
,这也是我们一般为成员方法设置 public
的原因,使其可以被类外部调用。
方法的访问修饰符
访问修饰符不仅适用于变量,也适用于方法。比如,Drive
方法用于启动汽车,我们可以将它设为私有,这样外部类无法直接调用。
private void Drive()
{
Console.WriteLine($"{_name} is driving.");
}
现在,Drive
方法只能在 Car
类内部被调用。我们可以在构造函数中调用它,使得汽车在创建时自动启动:
public Car()
{
_name = "Car";
Drive();
}
如何使用私有方法
当我们调用 new Car()
创建汽车时,构造函数会自动调用 Drive
方法。即使在主程序中无法直接调用 Drive
,汽车仍会启动。
公共方法与私有方法的使用场景
- 公共方法:一般用于对外提供类的功能,例如操作数据、获取状态等。
- 私有方法:一般用于在类内部完成某些辅助任务或内部逻辑,不希望外部干预这些操作。
总结
- 私有 (private):限制成员变量或方法的访问权限,仅在类内部使用,确保数据封装与安全。
- 公共 (public):开放成员变量或方法的访问权限,允许外部访问。
在下一节中,我们将探讨 getter 和 setter 的概念,这将帮助我们在保持成员变量私有的同时,安全地提供访问权限。
86 - C#中的设置器
私有成员变量的Setter方法
欢迎回来!在本视频中,我们将讨论Setter方法的使用,这是一种通过方法来改变私有变量的值的方式。通过使用Setter方法,我们可以在保持变量私有的情况下,安全地从类外部设置变量值,从而确保数据的完整性和封装性。
什么是Setter方法?
Setter方法是一种公共方法,允许我们从类外部更改私有成员变量的值。例如,假设我们有一个private
修饰的成员变量_name
,我们可以通过一个public
的SetName
方法来更改它。这个方法不会直接返回变量值,而是用于设置值。
代码示例
假设我们有以下的私有变量:
private string _name;
我们可以通过以下方式创建一个Setter方法:
public void SetName(string name)
{
_name = name;
}
这个SetName
方法允许我们在不直接访问_name
的情况下更改它的值。为了测试该方法,我们可以在Program.cs
文件中调用它:
Car myCar = new Car();
myCar.SetName("My Special Car");
运行代码后,myCar
的名字将被设置为"My Special Car"。
使用Setter方法的优势
Setter方法不仅让我们能够从外部更改私有变量的值,而且还能在设置值时添加条件或限制。例如,我们可以在设置_name
时,确保它不为空字符串:
public void SetName(string name)
{
if (string.IsNullOrEmpty(name))
{
_name = "Default Name"; // 如果为空,则使用默认名称
}
else
{
_name = name; // 否则使用传入的值
}
}
在上面的代码中,如果用户传入的name
为空字符串,_name
将被设置为"Default Name"。这种方法可以确保我们不会为_name
分配无效的数据。
真实场景中的应用
在实际开发中,Setter方法广泛应用于确保数据的完整性。例如,当从数据库中读取用户信息时,可能会遇到某些字段为空的情况。在这种情况下,可以通过Setter方法为这些空字段分配默认值,以确保系统正常运行。
总结
- Setter方法是一种控制私有变量更改的方式,允许我们在类外部更改私有变量的值。
- 使用Setter方法时,我们可以加入条件或限制,确保变量值的有效性。
- 这种方法可以防止不合规数据进入系统,保持数据的一致性和完整性。
在下一节视频中,我们将讨论Getter方法,帮助我们在保持变量私有的情况下安全地读取它的值。
87 - C#中的获取器
理解Getter方法
欢迎回来!在上一个视频中,我们了解了Setter方法,现在我们将探讨Getter方法,或称“获取方法”。Getter方法允许我们在类外部访问私有变量的值,而不直接暴露该变量。通过这种方式,我们可以在保持数据封装性的同时,从类外部安全地获取变量的值。
什么是Getter方法?
Getter方法是一种公共方法,用于返回私有变量的值。例如,我们有一个私有成员变量_name
,通过创建一个GetName
方法,我们可以从类外部获取该变量的值。与Setter方法不同的是,Getter方法通常有一个返回类型,因为它返回一个值。
代码示例
假设我们有以下的私有变量:
private string _name;
我们可以创建一个Getter方法GetName
,来获取该变量的值:
public string GetName()
{
return _name;
}
在这里,GetName
方法返回_name
的值。我们可以在Program.cs
文件中调用它来获取汽车的名字:
Car myCar = new Car();
myCar.SetName("My Special Car"); // 先设置名字
Console.WriteLine("My car's name is " + myCar.GetName()); // 然后获取名字
运行这段代码后,输出将显示"My car's name is My Special Car"。
使用Getter方法的优势
Getter方法不仅允许我们安全地从类外部获取私有变量的值,而且还能让我们在返回值之前执行特定的逻辑。例如,我们可以添加一个后缀到汽车名称中:
public string GetName()
{
return _name + " Car"; // 在名称后添加“ Car”
}
现在,当调用GetName
方法时,返回的将是带有“ Car”后缀的名字。
Getter的实际应用
Getter方法的灵活性使我们能够控制返回的值格式,这在需要对外展示数据时非常有用。例如,在一个应用程序中,当获取用户信息时,我们可能希望格式化返回的数据以适应界面要求。
创建一个用于获取马力的Getter方法
一个简单的练习:创建一个返回汽车马力的Getter方法。
public int GetHP()
{
return _hp;
}
在Program.cs
中调用该方法:
Console.WriteLine("My car's horsepower is " + myCar.GetHP());
这将显示汽车的马力值。
总结
- Getter方法用于从类外部安全地获取私有变量的值。
- Getter方法让我们能够添加规则或格式化返回的数据。
- 在许多情况下,Getter方法返回的是变量的原始值,但我们也可以自定义返回的格式。
在下一节视频中,我们将进一步探索属性,它结合了Getter和Setter的概念,并简化了访问和设置私有变量的方法。
88 - C#中的属性
探索属性(Properties)的用法
欢迎回来!在本视频中,我们将深入了解属性(Properties),以及它们如何简化了私有成员变量的访问和控制。属性可以看作是结合了Getter和Setter方法的简便方式,它让我们能够更灵活地控制数据的访问和设置方式。
什么是属性?
属性是类(Class)的成员,可以被看作是一种特殊的方法,用于读写或计算私有字段(Private Field)的值。属性在外部代码看来就像是公开的数据成员,但实际上,它们是通过特殊的方法(称为访问器 Accessor)来访问的。
通过属性,我们可以:
- 读取数据 - 使用
get
访问器。 - 写入数据 - 使用
set
访问器。
使用属性代替Getter和Setter
在上个视频中,我们手动创建了Getter和Setter方法来访问和更改私有变量。使用属性,我们可以简化这个过程。来看具体步骤:
1. 定义属性
我们可以使用快捷代码 prop
并按下Tab键,Visual Studio将自动生成属性结构。例如:
public string Name
{
get { return _name; }
set { _name = value; }
}
这里的Name
属性包含了:
- Get访问器:用于获取私有变量
_name
的值。 - Set访问器:用于设置
_name
的值。value
是默认关键词,代表传入的新值。
示例:实现Name属性
假设我们有一个私有成员变量 _name
,并希望通过属性Name
来访问和修改它:
private string _name;
public string Name
{
get { return _name; }
set { _name = value; }
}
现在,我们可以通过 Name
属性来设置和获取 _name
的值。例如,在 Program.cs
文件中:
Car myCar = new Car();
myCar.Name = "My Audi A3"; // 设置名称
Console.WriteLine(myCar.Name); // 获取名称并输出
这样,我们可以通过属性直接访问,而不必显式调用Getter或Setter方法。
属性的好处
属性的主要作用在于控制数据访问,同时确保对象的内部状态有效。与直接公开字段相比,属性提供了更高的灵活性和安全性。例如,通过添加条件语句,我们可以控制变量的设置方式:
set
{
if (string.IsNullOrWhiteSpace(value))
{
_name = "Default Name"; // 如果值为空,则使用默认名称
}
else
{
_name = value;
}
}
通过这种方式,我们可以在设置值时进行验证,确保不接受空字符串。
总结
- 属性是用于读写私有字段的特殊方法,通过
get
和set
访问器实现。 - 保护数据:属性控制数据的访问方式,确保对象的内部状态符合预期。
- 便捷性:属性提供了更简洁的代码,不需要手动创建Getter和Setter方法。
属性在C#开发中非常常用,它们既能增强代码的可读性,又能保护数据的完整性。在接下来的章节中,我们将继续深入探讨更多面向对象编程的概念。
89 - 自动实现属性
自动实现属性(Auto-Implemented Properties)
欢迎回来!在本视频中,我们将探讨自动实现属性(Auto-Implemented Properties)。这是C#中的一个简洁特性,可以大大简化属性的创建。
什么是自动实现属性?
当我们不需要在 get
或 set
方法中添加任何额外逻辑时,可以使用自动实现属性来简化代码。自动实现属性在内部会创建一个匿名的私有字段,我们不需要手动定义它。这个私有字段只能通过属性的 get
和 set
访问器进行访问。
例如,我们之前创建的 Name
属性是一个自动实现属性:
public string Name { get; set; }
这个 Name
属性实际上创建了一个隐藏的私有字段来存储数据,但我们不需要显式地编写这个字段。接下来我们将添加一个新属性来展示这一功能。
创建自动实现属性
假设我们想为汽车添加一个 MaxSpeed
属性,表示最大速度。我们可以通过自动实现属性来实现:
public int MaxSpeed { get; set; }
这里的 MaxSpeed
属性是自动实现的,它会在内部自动创建一个私有的存储字段,我们可以通过 get
和 set
访问器来读取和设置 MaxSpeed
的值,而无需手动定义字段。
使用自动实现属性
在 Program.cs
中,我们可以通过以下代码来设置和获取 MaxSpeed
属性:
Car myCar = new Car();
myCar.MaxSpeed = 180; // 设置最大速度为180
Console.WriteLine("Max speed is " + myCar.MaxSpeed); // 输出最大速度
运行这段代码,我们会得到输出:
Max speed is 180
如你所见,属性 MaxSpeed
的值被成功设置为180,并通过 Console.WriteLine
打印出来。这一切都通过自动实现属性完成,无需额外的字段定义。
自动实现属性的优点
- 简洁:自动实现属性省去了显式定义私有字段的步骤。
- 安全性:它仍然保留了封装的优势,私有字段仅能通过
get
和set
访问器访问。 - 高效:在不需要复杂逻辑的情况下,简化了代码结构。
总结
自动实现属性非常适合不需要额外逻辑的简单属性。它既提供了对私有数据的封装,同时让代码更为简洁、清晰。只需一行代码,即可拥有 get
和 set
访问器,自动创建的私有字段在内部处理数据存储。
接下来,我们将进一步探讨如何使用只读和只写属性,来满足不同的数据访问需求。
90 - 只读与只写属性
只读和只写属性
欢迎回来!在本视频中,我们将探讨 只读 和 只写 属性。这些属性的用途在于控制如何访问类中的数据。通过只读或只写属性,我们可以限制属性的访问方式,从而增强代码的安全性和封装性。
什么是只读属性?
只读属性具有 get
访问器,但没有 set
访问器。这意味着您可以读取该属性的值,但无法修改它的值。
示例
假设我们不希望最大速度 (MaxSpeed
) 能在外部被设置。我们可以去掉 set
访问器,使其成为只读属性:
public int MaxSpeed { get; } = 150;
在这种情况下,MaxSpeed
的值在类内部定义,外部只能读取但不能修改它。
Car myCar = new Car();
Console.WriteLine("Max speed is " + myCar.MaxSpeed); // 只能读取,不能设置
运行代码将显示:Max speed is 150
。由于 MaxSpeed
是只读的,我们无法在程序中设置它的值。
什么是只写属性?
只写属性具有 set
访问器,但没有 get
访问器。这意味着您可以在外部设置该属性的值,但不能读取它的值。
示例
假设我们只想设置最大速度,但不希望从外部读取它。在这种情况下,我们可以为 MaxSpeed
创建一个只写属性:
private int _maxSpeed;
public int MaxSpeed
{
set { _maxSpeed = value; }
}
在 Program.cs
文件中,我们可以设置 MaxSpeed
,但无法读取它的值:
Car myCar = new Car();
myCar.MaxSpeed = 180; // 可以设置值
Console.WriteLine("Max speed is " + myCar.MaxSpeed); // 无法读取,编译错误
这样,MaxSpeed
成为只写属性,不能在外部获取该属性的值。
只读和只写属性的应用场景
只读属性的应用场景
-
不可变数据:例如出生日期。出生日期在设置后不应再更改,适合用只读属性。
-
计算属性:当属性值是从其他数据计算而来时,可用只读属性。例如,矩形的
Area
(面积)属性可以是只读的,通过宽度和高度计算得出,而不允许手动设置。
只写属性的应用场景
-
敏感数据:例如密码。在用户凭据类中,可能需要将密码设置为只写,以防止应用程序读取密码。
-
触发操作:只写属性还可以用于触发某些操作。例如在日志类中,可以用一个只写属性
Message
来写入日志信息,但不允许直接读取它的值。
总结
只读和只写属性在数据保护和封装方面非常有用。它们确保了数据只能在符合要求的情况下被访问或修改。虽然只写属性在实际应用中较少见,但在某些特殊场景下,仍然可以发挥重要作用。
希望这些示例帮助您理解如何有效使用属性的不同访问权限!我们下节课再见。
91 - 成员与终结器(析构函数)
成员(Members)
欢迎回来!在本视频中,我们将介绍 成员(Members) 的概念,并通过创建一个新的 Members
类来展示所有与面向对象编程相关的成员类型。成员是类的一部分,可以包括字段、属性、方法、构造函数和析构函数等内容。了解这些不同的成员类型将帮助您更好地设计和使用类。
创建类和字段
首先,我们创建一个名为 Members
的类。该类包含多个不同类型的成员。
私有字段(Private Fields)
我们可以定义一些私有字段,用于存储类的内部数据。私有字段只能在类的内部访问,不能从类的外部直接访问。例如:
private string memberName = "Lucy";
private string jobTitle = "Developer";
private int age = 30;
private int salary = 60000;
这些字段表示成员的名字、职位、年龄和薪水。
公有字段(Public Fields)
在某些情况下,您可能需要将字段设为公有,以便可以从类的外部直接访问它。例如:
public int experienceYears = 5;
公有字段可以直接从类的外部访问,但通常不推荐将字段直接设为公有,因为这样会降低封装性。
属性(Properties)
属性是用于访问私有字段的成员。我们可以使用自动实现的属性,或者手动编写 getter 和 setter 方法。
自动实现的属性
public string JobTitle { get; set; } // 自动实现的属性
手动实现的属性
public string JobTitle
{
get { return jobTitle; }
set { jobTitle = value; }
}
手动实现的属性允许我们在获取和设置属性值时添加自定义逻辑。属性通常用于安全地暴露类中的私有字段。
方法(Methods)
方法是类的行为,通常为公有,以便从类外部调用。例如:
public void Introduce(bool isFriend)
{
if (isFriend)
{
SharePrivateInfo();
}
else
{
Console.WriteLine($"Hi, my name is {memberName}, my job title is {jobTitle}, and I am {age} years old.");
}
}
这个 Introduce
方法允许成员进行自我介绍。如果调用者是朋友(即 isFriend
为 true
),则调用私有方法 SharePrivateInfo()
。
私有方法(Private Methods)
私有方法只能在类的内部使用,通常用于内部逻辑或辅助计算。例如:
private void SharePrivateInfo()
{
Console.WriteLine($"My salary is {salary}.");
}
私有方法 SharePrivateInfo
会输出成员的薪水,但不会对外部公开。
构造函数(Constructors)
构造函数用于初始化对象,是类的一个特殊成员。构造函数在创建对象时自动调用。它们可以设置初始值或执行其他初始化任务。
public Members()
{
Console.WriteLine("Object created");
age = 30;
memberName = "Lucy";
jobTitle = "Developer";
salary = 60000;
}
在这个构造函数中,我们设置了 age
、memberName
、jobTitle
和 salary
的初始值,并输出 "Object created" 以指示对象的创建。
析构函数(Destructors)
析构函数(又称“终结器”)是在对象被垃圾回收时调用的特殊成员,用于清理资源。析构函数通常用于释放非托管资源。
~Members()
{
Console.WriteLine("Destruction of members object");
}
在 C# 中,析构函数的定义以 ~
开头。请注意,析构函数不应包含实际业务逻辑,而仅限于清理操作。如果析构函数为空,最好不要定义它,以免降低性能。
测试代码
在 Program.cs
文件中,我们可以创建一个 Members
对象并调用方法来查看效果:
Members member1 = new Members();
member1.Introduce(true); // 调用带朋友标记的介绍方法,输出薪水信息
通过调用 Introduce
方法,我们可以让成员自我介绍并分享其私密信息(如薪水),这是因为我们设置了 isFriend
参数为 true
。
总结
在本节中,我们讨论了以下成员类型:
- 字段:用于存储数据,字段可以是私有或公有。
- 属性:用于安全地访问私有字段,支持自动和手动实现。
- 方法:定义类的行为,通常为公有方法,但也可以有私有方法。
- 构造函数:用于初始化对象,自动调用。
- 析构函数:用于清理资源,通常在对象超出范围时调用。
这些成员类型是 C# 中面向对象编程的核心内容。理解它们并合理使用它们,将帮助您编写更加清晰、可维护的代码。
现在您已经了解了成员的不同类型及其用途。我们将在后续课程中继续深入探讨面向对象编程的更多内容。下节课见!
92 - 对象总结
对象和面向对象编程总结
现在,您已经完成了面向对象编程(OOP)章节,并学习了如何创建类,这些类可以拥有属性和方法,还包含了不同类型的成员(如字段、构造函数等)。您还了解了如何使用构造函数,析构函数等重要的编程概念。
为什么要使用对象和OOP?
到目前为止,您可能会觉得 OOP 有些复杂或不必要,因为您可能在编写的小程序中没有真正感受到它的优势。确实,当我们编写一个简单的小程序时,确实不需要使用复杂的对象结构和类设计。然而,当我们进入更复杂的项目时,对象的力量将会显现出来。
通过 OOP,您可以更好地管理代码结构,实现代码复用,简化维护和修改,并使程序更易于扩展。当应用规模扩大并引入更多复杂的功能和逻辑时,OOP 的设计思想和原则可以显著提升程序的组织性和可读性。
接下来要学习的内容
接下来,我们将进入 数组 的学习章节。数组是一种将多个对象或多个变量存储在一起的数据结构。与对象类似,数组也可以帮助您更好地组织和处理数据,特别是在需要存储和操作大量数据时。
在下一章中,您将学到如何使用数组和列表。这些工具将为您提供有效管理数据的强大手段,特别是当您需要对一组数据进行操作时,数组和列表将变得非常有用。
下一步
请继续保持耐心,不要担心现在是否完全理解了 OOP。随着编程经验的积累,这些概念将会逐渐变得清晰,您也会更灵活地运用它们。接下来,让我们继续学习数组,这将为您的编程技能带来更多的提升!
7 - C中的集合93 - 数组简介数组章节介绍在本章中,您将学习数组(Arrays)和列表(Lists)的工作原理。具体来说,我们将探索:
数组和列表的实际应用在编程中,我们经常需要管理和操作较大数量的数据。假设在一个数据库中有 100 个用户,并且您需要为每位用户更改某些信息。这时,将这些用户放入数组或列表中将是非常高效的做法。不同于单独处理每个用户,通过数组或列表,您可以对整个数据集进行批量操作。 数组与列表的区别在学习中,您会发现数组和列表在某些方面有所不同,每种都有自己的优势:
在接下来的章节中,您将更深入地理解何时应该使用数组,何时适合使用列表,以及如何使用它们来高效地处理数据。 接下来在本章,我们将提供相关练习和测验,帮助您巩固对数组和列表的理解。准备好了吗?让我们深入探讨这些重要的数据结构,揭开它们的强大之处! 94 - 数组基础数组理论概述在本视频中,我们将介绍 C# 中数组的理论部分。在下一个视频中,我们会通过实际操作和示例更深入地理解数组的使用。首先,让我们来了解数组的基本概念和作用。 数组的定义与特点数组是一种固定大小的顺序集合,用于存储相同类型的元素。需要特别注意,数组只能存储相同类型的数据元素,这意味着在同一个数组中,不能既有字符串(string)又有整数(int)等不同的数据类型。
数组的类型和灵活性数组可以是任意类型的数据集合。例如,您可以创建一个仅包含整数的数组、一个仅包含字符串的数组,甚至可以存储对象的数组。几乎任何类型都可以作为数组的元素类型,只需保证数组中的所有元素都为同一类型。 数组的结构和索引可以将数组想象成一个具有多个存储单元的存储结构,这些存储单元用于存放相同类型的数据。例如,以下图所示是一个长度为6的整数数组:
在这个数组中,每个数据都有其索引,用于定位特定的数据元素。数组的索引从0开始,并逐步增加。因此,在上图中:
这意味着在一个长度为 声明和初始化数组在 C# 中声明数组时,需要指定数据类型和数组名称,例如: int[] grades; 上面的代码表示声明了一个 初始化数组需要以下步骤:
例如,下面的代码定义了一个包含 int[] grades = new int[5]; 这表示数组 给数组赋值赋值时,通过数组名称加方括号中的索引来指定要赋值的位置。如下所示: grades[0] = 15;
grades[1] = 12; 这段代码为 总结在本视频中,我们了解了数组的基础理论:
数组是编程中用于存储和管理一组数据的重要工具。在接下来的视频中,我们会进行实操,探索数组的实际应用及其在编程中的重要性。 95 - 声明与初始化数组及长度属性数组的创建和使用在本视频中,我们将学习如何在 C# 中创建和使用数组,以及几种初始化数组的不同方法。让我们从在 创建一个数组首先,我们创建一个整数类型的数组,命名为 int[] grades = new int[5]; 这表示我们创建了一个 为数组元素赋值通过指定数组的索引来为数组中的元素赋值。请注意,数组的索引从 grades[0] = 20; // 第一个学生的成绩是 20
grades[1] = 15;
grades[2] = 18;
grades[3] = 10;
grades[4] = 17; 在上面的代码中,我们为数组 访问数组元素可以通过索引来访问数组中的某个元素。例如,要访问 Console.WriteLine("Grade at index 0 is: {0}", grades[0]); 输出结果将是:
修改数组元素的值在运行时,可以通过索引修改数组中的元素。下面我们实现一个例子,通过用户输入来更改数组中的第一个元素: Console.WriteLine("Enter a new grade for the first student:");
string input = Console.ReadLine();
grades[0] = int.Parse(input);
Console.WriteLine("Updated grade at index 0 is: {0}", grades[0]); 在这里,我们通过 不同的数组初始化方法除了使用索引分配值之外,还可以通过其他方式初始化数组。
获取数组长度可以使用数组的 Console.WriteLine("Length of gradesOfMathStudentsA: {0}", gradesOfMathStudentsA.Length); 输出结果将会显示 总结在本视频中,我们学习了如何创建和初始化数组,如何为数组中的元素赋值、访问和修改它们,以及如何使用 96 - Foreach循环使用
|
8 - 调试122 - 调试简介Debugging 入门欢迎进入调试章节。在这一章节中,您将学习如何使用集成开发环境(IDE)进行调试。这是一项至关重要的技能,因为无论代码经验多么丰富,程序员的工作中总免不了要面对各种 bug 和错误。有时错误只是一个简单的空指针异常,但您找不到问题的根源;有时错误更复杂,代码看似正确,但却在运行时出现了意外的异常。这正是调试过程的意义所在,调试可以帮助您理清思路,找到问题,并最终修复代码中的 bug。 调试过程不仅仅是编程工作的组成部分,它还往往是一个非常关键的部分,因为几乎每一个程序员在编写代码时都会遇到不可预期的问题。熟练掌握调试工具将使您的开发过程更加顺利,也将帮助您更高效地解决问题。 本章节将涵盖的内容在这一章节中,您将学习到:
通过学习这些调试技能,您将能够更轻松地发现和解决代码中的问题,使您的开发效率更高。 那么,让我们马上开始吧! 123 - 调试基础调试入门及断点使用在本视频及接下来的几个视频中,我们将学习如何在 Visual Studio 中使用调试功能,帮助您定位并修复代码中的 bug。调试的核心目的是消除程序中的 bug,而这些 bug 有时很容易找到,例如代码在运行时崩溃;但有时会出现逻辑错误,这种错误不会被 IDE 报告,通常只能由开发人员或用户在运行中发现。 为了演示调试功能,我创建了一个简单的工具:派对好友邀请工具。这个小工具用于挑选一部分名字较短的好友,以便邀请他们参加派对。在这个示例中,我们假设要邀请名字最短的三位朋友,工具将根据名字长度进行排序并选择。 示例代码说明这段代码包括几个关键部分:
由于这是逻辑错误,代码不会报错,但会返回错误结果。例如,输出了 "Michelle" 和 "Angelina" 等较长名字的朋友,而不是最短的几个名字。这种错误在列表较短时容易发现,但在实际开发中,我们可能从外部数据源(如 XML 文件或网络数据)获取好友列表,这时调试工具就显得尤为重要。 断点和调试功能为帮助理解调试过程,我们可以设置断点,通过单步调试分析代码运行的每一步,观察变量的状态和变化。以下是一些基本的调试操作: 1. 设置断点在代码行左侧点击灰色条,或使用快捷键 F9,设置断点。断点将停止代码的运行,使您可以逐行查看代码的执行。 2. 开始调试点击 调试 > 开始调试,或按 F5 开始调试程序。程序将在断点处暂停,显示当前代码的执行位置。 3. 单步执行
4. 查看变量值将鼠标悬停在变量上可以查看当前值。Visual Studio 还提供了“监视窗口”(Watch Window),允许您同时监视多个变量的值,便于对比观察。 示例:调试和单步执行在调试过程中,我们可以:
这样逐步观察变量的值,可以发现 结论通过调试和设置断点,我们可以轻松追踪代码的执行情况,并在每一行代码执行后查看变量的状态。这种方式非常适合查找和解决逻辑错误。接下来的视频中,我们将进一步深入调试功能,探索如何高效管理多个变量状态,使调试过程更加简便和直观。 124 - 局部变量与自动变量自动和本地变量窗口,以及断点的高级使用在本视频中,我们会更深入地了解 自动变量(Autos) 和 本地变量(Locals) 窗口的使用,以及如何有效地管理和操作断点。我们会复习如何设置断点,同时进一步分析断点的使用和管理方法,这对于大型项目和复杂代码尤为重要。 断点的管理在上一个视频中,我们在代码中添加了一个断点,以便在调试过程中暂停程序并观察变量状态。在这个示例中,我们的代码量较少,仅使用了几个断点。如果代码变得复杂,并有多个文件和许多断点,可以使用 断点窗口 来更好地管理和查看所有断点。 如何打开断点窗口
断点窗口会显示所有断点的位置,例如 启用或禁用断点点击断点图标,可以直接移除断点。如果只想暂时停用断点,可以右键点击断点并选择“禁用断点”,此时断点标志会变成白色,表示已禁用,但仍然保留在代码中,可以随时重新启用。 自动变量(Autos)窗口自动变量窗口 会自动显示当前断点附近的变量,因此不需要手动监视变量。自动变量窗口会显示最相关的变量,例如在主方法中的 当我们使用 逐过程执行(Step Over) 时,窗口会更新,显示新生成或修改的变量。例如,当 本地变量(Locals)窗口本地变量窗口 显示当前方法中的所有变量,也就是当前作用域中的变量。与自动变量窗口不同,自动变量窗口只显示关键变量,而本地变量窗口则显示所有局部变量。 本地变量的使用场景
监视(Watch)窗口监视窗口 允许你手动添加需要观察的变量。可以随时添加变量并观察它们在执行过程中的变化。以下是操作步骤:
变量值的修改在调试过程中,我们还可以手动修改变量的值以观察程序如何响应。例如,可以将 示例:使用自动变量、本地变量和监视窗口调试假设当前断点在
这样可以帮助我们发现一些潜在的问题。例如, 总结通过使用 自动变量、本地变量 和 监视窗口,我们能够在调试过程中更直观地观察和控制代码中的变量,逐步查找并修复错误。这种方法在处理大型项目和复杂逻辑时尤为有用。 在下一个视频中,我们将修复代码中的 bug,并进一步探讨如何避免类似的问题。 125 - 调试:创建列表副本并解决一些bug使用调试工具修复逻辑错误在这个视频中,我们将修复代码中的一个 bug,并展示如何使用调试工具帮助我们发现和解决问题。这个示例是一个较为简单的程序,但它仍然包含一些常见的逻辑错误。我们通过逐步分析代码中的每个方法,了解它们的作用,然后利用调试工具一步步找到问题的根源。 确认问题所在的方法
设置断点并使用调试工具
解决其他潜在问题
修复方法:使用列表副本
进一步修复:防止越界错误
总结通过本视频的内容,我们学习了如何使用调试工具定位和解决代码中的逻辑错误,并如何在程序中合理使用副本来保护原始数据。调试和防止越界错误都是编写健壮代码的重要步骤。在下一节视频中,我们将继续优化代码并确保程序能够在各种情况下正常运行。 126 - 调试:调用栈抛出错误与防御性编程解决程序中的问题并理解防御式编程在本视频中,我们将最终解决之前程序中的问题,并讨论防御式编程的概念。防御式编程旨在编写更加健壮和安全的代码,以提前预测并防止潜在问题的发生。特别是在我们不确定数据来源或数据格式时,例如数据可能来自数据库或网络,这种编程方式显得尤为重要。通过提前验证数据的完整性,可以有效避免运行时异常,确保代码更稳定。 识别并捕获潜在的错误
防止传递空值有时候,数据可能在传递时为空,例如从数据库读取的数据失败。这种情况下可以检查传入参数是否为 if (list == null)
{
throw new ArgumentNullException("list", "The list should not be empty. Please check the data source.");
} 通过捕获空列表的情况,我们确保不会执行依赖于此列表的后续操作,避免了运行时异常。 使用调用堆栈(Call Stack)分析错误调试过程中,我们可以使用**调用堆栈(Call Stack)**窗口来跟踪方法调用顺序:
防御式编程的重要性防御式编程的核心在于提前预见潜在问题,确保程序在遇到非预期输入或数据时能够安全退出,而不会导致系统崩溃或产生不可预期的行为。通过这种方式,程序在各种边界条件下都会更具稳定性,特别是在与外部数据源(如数据库、网络请求等)交互时。 总结与反馈在本视频中,我们学习了如何通过有效使用调试工具和防御式编程来使代码更具鲁棒性和稳定性。这些技巧不仅适用于小型示例程序,更适用于大型项目中的复杂逻辑。在调试和编码过程中,如果您有任何问题,欢迎提供反馈,因为这将帮助改进教程内容并提升其质量。 继续保持良好的编程习惯,并在下一节视频中学习更多 C# 编程技巧。 |
9 - 继承与更多关于OOP127 - 欢迎来到继承继承(Inheritance)简介在本章中,我们将学习继承的概念。继承是一种面向对象编程的核心机制,它允许一个类(子类)从另一个类(父类或基类)继承属性和方法。通过继承,子类可以直接获得父类中的功能,同时也可以进行扩展和定制。这种机制使得代码复用变得更加高效,减少了重复代码,提高了代码的可维护性。 继承的概念继承可以类比为我们在人类基因中遗传的特性——我们会从父母那里继承一些基因,甚至是行为特征。类似地,继承指的是一个类从另一个类继承功能和属性。父类将自身的属性和方法传递给子类,子类则可以在继承这些功能的基础上进行扩展或重写。 继承通常用于以下几个目的:
基类和派生类在继承中,有以下几个重要的概念:
在代码中,继承的语法通常如下: class ParentClass
{
public void ShowMessage()
{
Console.WriteLine("Hello from Parent Class");
}
}
class ChildClass : ParentClass
{
// ChildClass inherits from ParentClass
} 在这个例子中, 继承的优势继承的主要优势包括:
接口(Interface)在继承章节中,我们还将了解接口。接口定义了一组不包含实现的功能,这些功能将由实现接口的类来提供具体实现。接口通常用于确保类具备某些特定功能,而不关心其具体实现细节。 接口的使用通常如下: interface IPrintable
{
void Print();
}
class Document : IPrintable
{
public void Print()
{
Console.WriteLine("Printing document...");
}
} 在这个例子中, 挑战和练习在本章学习过程中,我们将设置两个挑战任务,帮助您巩固所学内容。通过这些挑战,您将实践如何定义和使用继承、基类、派生类和接口。 希望您在接下来的视频中学到关于继承的更多内容并享受学习的过程! 128 - 继承介绍继承的概述欢迎回来!在本视频中,我们将继续深入学习继承,并且先了解其定义和用途。在后续视频中,我们将通过演示进一步探讨其实际应用。 继承的定义继承允许我们基于已有的类来定义新类,这使得应用的创建和维护变得更加简单。继承提供了代码复用的机会,加快了实现速度,因为:
此外,继承不仅仅可以用于我们自己创建的类,还可以对导入的库中的类进行继承,从而更广泛地复用功能。 继承的示例示例 1:汽车类假设我们有一个Car(汽车)基类,它具有通用的属性和方法:
基于这个Car类,我们可以派生出具体的汽车类型,如RaceCar(赛车)和StreetCar(街车):
通过继承,每种汽车类型可以共用通用的汽车属性和方法,同时根据特性添加新的属性和方法。 示例 2:员工类在企业中,我们可以定义一个Employee(员工)基类:
基于Employee类,可以派生出不同类型的员工,如Designer(设计师)和Engineer(工程师):
尽管设计( 总结与下一步这些示例展示了继承的基本概念和应用方式。接下来,我们将在演示中进一步深入了解继承的实际应用,探讨基类、派生类的实现,以及如何利用继承来构建灵活的程序架构。 让我们在下一个视频中开始演示! 129 - 简单的继承示例C# 继承基础讲解欢迎回来!在本视频中,我们将学习 C# 中的继承,并使用简单的例子来介绍其概念。继承是编程中的一个重要概念,为我们提供了代码复用的强大功能。通过继承,我们可以减少重复代码,实现更加高效的开发。我们会尽量保持内容简洁明了,以帮助你更好地理解。 继承的定义继承是面向对象编程(OOP)的核心概念之一,它允许我们定义一个类(子类),以重用、扩展或修改另一个类(父类)的行为。具体来说:
为什么使用继承?如果我们有多个类具有相似的代码或功能,但在细节上略有不同,我们可以将这些重复的代码提取到一个单独的类中,然后让各个类继承它。这可以是方法、功能或属性,从而让代码更加简洁,便于维护。 继承示例:创建电器设备类假设我们要实现一个电器设备管理系统,并开始有两个类 Radio(收音机) 和 TV(电视) ,每个类中都包含一些属性和方法。比如:
这两个类中包含了许多相同的属性和方法,比如开关状态和品牌属性、打开和关闭方法等。 利用继承优化代码要避免重复代码,我们可以创建一个新的基类,比如 ElectricalDevice(电器设备),将通用属性和方法放入这个基类中。 创建基类
|
10 - 多态与更多OOP文本文件141 - 多态简介多态性:面向对象编程的第三个核心支柱在本章节中,你将学习面向对象编程的第三个也是最后一个核心概念——多态性(Polymorphism)。多态性在继承的基础上进一步扩展了对象的使用方式,使代码更加灵活和高效。以下是本章节的学习要点:
了解多态性的重要性多态性是使继承真正灵活并广泛应用的关键概念。它使我们能够以统一的方式对不同类型的对象进行操作,而无需了解每个对象的具体类型。例如,在父类中定义的一个方法可以在多个子类中被重写,每个子类都可以提供自己的实现。这使得程序能够根据实际对象的类型来动态地调用合适的方法实现。 通过学习多态性,你将学会如何在代码中实现更高的抽象,并让你的程序具备良好的可扩展性和可维护性。你会发现,使用多态性不仅能够减少代码的重复,还能让代码结构更清晰,功能更强大。 本章目标
开始学习让我们开始这个关于多态性的章节!在接下来的内容中,我们将逐步实现这些概念,并通过实际的代码示例,帮助你更好地理解和运用多态性。准备好迎接面向对象编程的更高层次吧! 142 - 多态参数多态性示例及新关键词的应用在本视频中,我们通过一个多态性示例来深入了解多态的实现,同时还会讲解一些重要的关键词,如 创建一个通用的 Car 类首先,我们创建一个
public class Car {
public int HP { get; set; }
public string Color { get; set; }
public Car(int hp, string color) {
this.HP = hp;
this.Color = color;
}
public void ShowDetails() {
Console.WriteLine("HP: " + HP + ", Color: " + Color);
}
public virtual void Repair() {
Console.WriteLine("Car was repaired.");
}
} 创建 BMW 和 Audi 类,并继承 Car 类接下来,创建
public class BMW : Car {
private string Brand = "BMW";
public string Model { get; set; }
public BMW(int hp, string color, string model) : base(hp, color) {
this.Model = model;
}
public new void ShowDetails() {
Console.WriteLine("Brand: " + Brand + ", HP: " + HP + ", Color: " + Color);
}
public override void Repair() {
Console.WriteLine("BMW " + Model + " was repaired.");
}
}
public class Audi : Car {
private string Brand = "Audi";
public string Model { get; set; }
public Audi(int hp, string color, string model) : base(hp, color) {
this.Model = model;
}
public new void ShowDetails() {
Console.WriteLine("Brand: " + Brand + ", HP: " + HP + ", Color: " + Color);
}
public override void Repair() {
Console.WriteLine("Audi " + Model + " was repaired.");
}
} 使用多态性和
|
特点 | 抽象类 | 接口 |
---|---|---|
实现与否 | 抽象类可以包含部分实现或没有实现的方法 | 接口完全没有实现,只是方法声明 |
构造函数 | 可以包含构造函数和字段 | 不能包含构造函数或字段 |
多继承支持 | C# 支持类实现多个接口,提供多重继承的能力 | C# 只允许单继承,但可以通过实现多个接口实现类似的多继承 |
方法实现 | 子类必须实现抽象方法,但可以直接继承非抽象方法 | 实现接口的类必须实现接口的所有成员 |
使用抽象类或接口的时机
- 使用抽象类:如果需要派生类拥有一些通用功能,可以选择抽象类。抽象类适用于共享核心特性和行为的场景,并定义了派生类的基本特征。
- 使用接口:如果只需要定义一个通用契约,用接口更合适。接口适合定义不一定属于类核心特性但能增强类功能的功能。例如,为多种类提供可编辑的能力,可以定义一个
IEditable
接口,而不需要创建一个共同的基类。
类比:抽象类 vs. 接口
- 抽象类:定义对象的核心特性,说明了“对象是什么”(例如:所有动物类都具备的基本特性)。
- 接口:定义对象的功能或行为契约,说明了“对象可以做什么”(例如:
IEditable
表示对象可以编辑,而IPrintable
表示对象可以打印)。
总结
- 抽象类:用来定义一个对象的本质特性。
- 接口:用来定义一个对象可以做的事情。
通过理解这些概念,我们可以在接下来的课程和例子中更好地应用这些原则来编写灵活且可维护的代码。
148 - 从文本文件读取
如何从文本文件读取内容
在本视频中,我们将学习如何从文本文件中读取内容。我们创建了一个简单的 .txt
文件,并放置在一个名为 Assets
的文件夹中。文本文件内容如下:
Hello world
Dennis here
这是一个简单的文本文件,您可以自己创建一个类似的小文件来跟随本视频的演示。
方法一:读取整个文本文件内容
首先,我们可以使用 System.IO
命名空间中的 File
类,该类提供了多种方法来读取、打开和处理文件内容。我们将使用 ReadAllText
方法来读取整个文件的内容。
示例代码
// 保存文件内容的字符串
string text = System.IO.File.ReadAllText(@"C:\YourPath\Assets\textfile.txt");
// 输出文件内容
Console.WriteLine("Text file contains following text: \n{0}", text);
// 保持控制台窗口打开
Console.ReadKey();
在上述代码中:
ReadAllText
方法会读取指定文件的全部内容并返回一个字符串。- 路径前的
@
符号确保文件路径中的反斜杠不被解释为转义字符。 - 将文件内容存储到
text
变量中,并使用Console.WriteLine
输出内容。
运行结果
Text file contains following text:
Hello world
Dennis here
这样就可以读取整个文本文件的内容,并在控制台中显示。
方法二:逐行读取文本文件内容
另一种读取文件的方式是按行读取,适合需要逐行处理的情况。我们将使用 File.ReadAllLines
方法,它会将每一行内容存储在一个字符串数组中。
示例代码
// 按行读取文件内容到字符串数组
string[] lines = System.IO.File.ReadAllLines(@"C:\YourPath\Assets\textfile.txt");
// 输出文件的每一行内容
Console.WriteLine("Contents of text file:");
foreach (string line in lines)
{
Console.WriteLine("\t" + line);
}
// 保持控制台窗口打开
Console.ReadKey();
在上述代码中:
ReadAllLines
方法会读取文件的每一行内容,并将其存储到字符串数组lines
中。- 使用
foreach
循环逐行输出每行内容,并在输出行前加入一个制表符\t
,使输出更加清晰。
运行结果
Contents of text file:
Hello world
Dennis here
此方法逐行读取文件内容,并在控制台中显示。
应用示例
文件读取功能可用于各种应用场景,比如:
- 在游戏中读取并显示高分排行榜。
- 加载配置文件,读取配置信息。
- 处理存储在文件中的日志记录。
总结
在 C# 中可以通过 System.IO.File
类中的 ReadAllText
方法读取整个文件内容,也可以使用 ReadAllLines
方法逐行读取内容。选择哪种方法取决于需求:若需要一次性读取整个文件,可以使用 ReadAllText
;若需要逐行处理,则使用 ReadAllLines
。
这两种方法简洁且实用,适合在日常的文件操作中使用。
149 - 写入文本文件
如何将内容写入文本文件
在本视频中,我们将学习如何将内容写入文件。之前我们已经学会了如何读取文件内容,现在我们来看看如何将数据写入文件,有多种不同的写入方法可供选择。
方法一:按行写入文件
首先,我们可以使用字符串数组 string[]
,将每一行内容分别存储,然后写入文件。这个方法适用于我们希望在文件中逐行写入内容的情况。
示例代码
// 创建字符串数组,每一行存储为一个元素
string[] lines = { "First line", "Second line", "Third line" };
// 使用 File.WriteAllLines 方法写入文件
System.IO.File.WriteAllLines(@"C:\YourPath\Assets\textfile2.txt", lines);
在上面的代码中:
WriteAllLines
方法将lines
数组中的每一行内容写入到指定路径的新文件textfile2.txt
中。
运行结果
在文件 textfile2.txt
中,内容将显示为:
First line
Second line
Third line
方法二:写入完整文本内容
如果您想将完整的字符串写入文件,而不是逐行写入,可以使用 File.WriteAllText
方法。
示例代码
// 提示用户输入文件名
Console.WriteLine("Please give the file a name:");
string fileName = Console.ReadLine();
// 提示用户输入文件内容
Console.WriteLine("Please enter the text for the file:");
string input = Console.ReadLine();
// 将用户输入的内容写入指定文件
System.IO.File.WriteAllText($@"C:\YourPath\Assets\{fileName}.txt", input);
在上面的代码中:
- 我们使用
WriteAllText
方法,将用户输入的内容写入用户指定的文件名。 - 通过
$@"C:\YourPath\Assets\{fileName}.txt"
生成动态路径。
运行结果
如果用户输入了以下内容:
- 文件名:
testfile
- 文件内容:
Hi there, this is a test.
在 testfile.txt
文件中,内容将显示为:
Hi there, this is a test.
方法三:使用 StreamWriter 类写入文件
使用 StreamWriter
是另一种方法,它允许我们逐行写入文件内容,可以更加灵活地控制写入的内容。StreamWriter 适用于需要按条件选择写入内容的情况。
示例代码
// 创建 StreamWriter 实例并指定文件路径
using (System.IO.StreamWriter file = new System.IO.StreamWriter(@"C:\YourPath\Assets\mytext.txt"))
{
// 逐行检查并写入包含 "third" 的行
foreach (string line in lines)
{
if (line.Contains("third"))
{
file.WriteLine(line);
}
}
}
在上面的代码中:
StreamWriter
创建或覆盖指定文件mytext.txt
。foreach
循环遍历lines
数组,仅将包含单词 "third" 的行写入文件。
运行结果
在文件 mytext.txt
中,只会显示包含 "third" 的行:
Third line
方法四:追加内容到文件
在某些情况下,我们可能需要追加内容到已有文件中,而不是覆盖文件。我们可以通过 StreamWriter
实现,并指定 append
参数为 true
。
示例代码
// 追加内容到文件
using (System.IO.StreamWriter file = new System.IO.StreamWriter(@"C:\YourPath\Assets\mytext2.txt", true))
{
file.WriteLine("Additional line");
}
在上面的代码中:
- 我们使用
StreamWriter
并设置第二个参数为true
,以追加内容到文件mytext2.txt
中,而不是覆盖现有内容。 - 在文件中追加一行
Additional line
。
运行结果
在 mytext2.txt
文件的末尾,内容将显示为:
Additional line
总结
在 C# 中,您可以使用多种方法将内容写入文件:
WriteAllLines
:适合逐行写入内容。WriteAllText
:适合写入整个文本内容。StreamWriter
:提供了更多写入控制,可以根据条件选择性地写入内容或追加内容。
每种方法都适用于不同的使用场景,具体选择取决于需求。例如,可以使用 WriteAllLines
写入高分排行榜,或使用 StreamWriter
追加日志条目。
150 - 多态总结
接下来,进入高级 C# 主题
恭喜你完成了多态性的学习!现在,你已经理解了多态性的工作原理、它的概念、如何确保类不能从其他类继承,以及如何重写方法等等这些重要的内容。通过这些知识,你可以编写更复杂的程序,并具备了在团队中合作的能力。
在团队中编程比单独编程更具挑战性,因为不同的代码片段可能相互影响,导致程序无法顺利运行。而多态性是一个很好的工具,帮助我们在多人协作中避免代码冲突,确保程序更具可扩展性和可维护性。
展望下一个章节
接下来我们将进入 高级 C# 主题。在这一章节中,我们将学习一系列不同的知识点,这些知识可能难以归类到特定的章节或主题中,但它们都是你在 C# 编程生涯中会频繁用到的重要技能。
这章内容包含各种实用的高级技术,希望能为你打下坚实的 C# 基础,提升编程技能,使你的代码更高效、更灵活。
所以,让我们一起进入下一章吧!
11 - 高级C话题151 - 高级话题简介本章介绍欢迎回来!在本章中,你将学习许多不同的主题,虽然它们彼此之间并没有直接联系,但仍然是编程中的重要组成部分。具体来说,你将了解以下内容: 结构体 (Structs)首先,你将学习什么是结构体。结构体是编程中用来组织相关数据的一种方式。你会明白如何定义结构体以及它们在编程中的作用和用法。 敌人 (Enemies)在游戏开发或仿真等领域,理解“敌人”如何在系统中表示和操作非常重要。本节将介绍如何定义和管理“敌人”对象或实体。 访问修饰符 (Access Modifiers)接下来,你会学习访问修饰符。访问修饰符用于控制程序中不同代码段之间的可见性和访问权限。了解如何使用访问修饰符可以帮助你保护数据,确保只有必要的部分可以访问或修改特定数据。 数学类 (Math Class)数学类提供了各种数学运算的功能,例如三角函数、指数、对数等。本章会介绍如何使用数学类进行这些常用的计算操作。 随机类 (Random Class)在许多应用中,生成随机数是常见需求。随机类为此提供了方法,可以用来生成随机整数、浮点数等。本章会演示如何正确使用随机类生成所需的随机数据。 状态类 (State Class)状态类用于管理程序中状态的变化。它在游戏开发或复杂应用中尤为重要,因为它可以帮助你组织状态的不同变化并对其进行控制。 正则表达式 (Regular Expressions)正则表达式是匹配字符串模式的强大工具,对于文本处理和数据验证非常有用。虽然学习正则表达式可能有些困难,但它在编程中是非常重要的。本节会详细解释如何编写和使用正则表达式。 数字 (Numbers)你还会看到编程语言中“数字”的概念。不同编程语言对数字的定义和使用有所不同,但它们在基础概念上有相似之处。本节将帮助你理解各种编程语言对数字的不同处理方式。 垃圾回收器 (Garbage Collector)垃圾回收器是编程语言用于管理内存的一种机制。它会自动回收不再使用的对象或数据以释放内存。本节会解释垃圾回收器的工作原理及其重要性。 抽象 (Abstract)抽象是面向对象编程中的一个重要概念。你将了解如何定义抽象类和抽象方法,并在实际开发中灵活运用这些概念来实现代码的重用和扩展。 其他内容本章还包含许多其他重要的概念和主题,这些知识将帮助你进一步理解编程的基本原理和高级功能。 结语现在,我们开始深入学习这些内容,享受这一章节带来的知识吧! 153 - 访问修饰符访问修饰符介绍欢迎回来!在本视频中,我将介绍“访问修饰符”,它们允许你授予或限制代码的访问权限。为了更好地理解访问修饰符的作用以及它们背后的原理,我们需要了解一些相关的概念。 面向对象编程中的访问修饰符和封装将字段和方法标记为特定的访问修饰符是面向对象编程 (OOP) 的一部分,它提高了代码的安全性,是封装的核心内容。封装是 OOP 的一个重要概念,在面向对象编程语言(如 C#)中,封装指的是两个相互关联但略有不同的概念,有时也指这两者的结合:
使用Setter和Getter例如,我们可以使用setter和getter方法来提高程序的安全性。在接下来的课程中,当我们学习属性时,我们将深入了解如何通过属性执行许多类似的操作。 常用的访问修饰符接下来,我们将依次介绍几个常用的访问修饰符,并通过示例进行说明。 私有访问修饰符 (Private)
class ClassOne {
private int age = 18;
private void Walk() {
// 方法代码
}
}
class ClassTwo {
void AccessExample() {
ClassOne firstClass = new ClassOne();
// 无法访问 firstClass.age 或 firstClass.Walk(),因为它们是私有的
}
} 在上面的例子中, 公共访问修饰符 (Public)
class ClassOne {
public int age = 18;
public void Walk() {
// 方法代码
}
}
class ClassTwo {
void AccessExample() {
ClassOne firstClass = new ClassOne();
// 可以访问 firstClass.age 和 firstClass.Walk()
}
} 在这个例子中,由于 受保护的访问修饰符 (Protected)
class ClassOne {
protected int age = 18;
protected void Walk() {
// 方法代码
}
}
class ClassTwo : ClassOne {
void AccessExample() {
// 可以直接访问 age 和 Walk 方法
}
} 在这个例子中, 内部访问修饰符 (Internal)
namespace ProjectNamespace {
internal class ClassOne {
internal int age = 18;
internal void Walk() {
// 方法代码
}
}
class ClassTwo {
void AccessExample() {
ClassOne firstClass = new ClassOne();
// 可以访问 firstClass.age 和 firstClass.Walk()
}
}
} 在这个例子中, 访问修饰符的最佳实践如何正确地使用访问修饰符?通常,声明一个新的类成员或方法时,建议使用最严格的访问修饰符来确保代码的安全性。通常从 使用访问修饰符的原因访问修饰符为你的方法和变量提供了完全的控制。一个简单的例子是年龄变量 ( 假设你希望可以设置年龄,但如果将 总结访问修饰符不仅提供了代码的安全性和控制力,还帮助你实现数据封装,使得代码更健壮、灵活。在后续视频中,我们将进一步探讨访问修饰符、封装以及更多相关概念。 154 - 结构体结构体 (Structs) 简介欢迎回来!在本视频中,我们将讨论结构体 (structs)。结构体与类 (classes) 非常相似,但它们有一个显著的区别:类是引用类型 (reference types),而结构体是值类型 (value types)。这意味着创建一个类对象时,它可以是空的,但结构体必须包含一个值。 创建一个结构体我们先创建一个结构体来了解基本用法。假设我们创建一个名为 struct Game {
public string Name;
public string Developer;
public double Rating;
public string ReleaseDate;
} 在这里,我们定义了一个简单的 实例化结构体对象接下来,我们创建一个 Game game1;
game1.Name = "Pokémon Go";
game1.Developer = "Niantic";
game1.Rating = 3.5;
game1.ReleaseDate = "2016-07-01"; 这样我们就创建了 显示结构体信息为了输出这些信息,可以使用以下代码: Console.WriteLine($"Game name is {game1.Name}");
Console.WriteLine($"Game was developed by {game1.Developer}");
Console.WriteLine($"Rating is {game1.Rating}");
Console.WriteLine($"Release date is {game1.ReleaseDate}"); 运行程序后,你会看到输出的 结构体与类的区别结构体和类的相似性显而易见,例如,它们都可以包含变量和方法,但两者有一些重要的区别:
示例方法 - Display可以在结构体中创建方法来显示信息,例如: public void Display() {
Console.WriteLine($"Name: {Name}");
Console.WriteLine($"Developer: {Developer}");
Console.WriteLine($"Rating: {Rating}");
Console.WriteLine($"Release Date: {ReleaseDate}");
} 然后,通过 自定义构造函数虽然结构体不能包含无参数的构造函数,但可以定义参数化构造函数。例如: public Game(string name, string developer, double rating, string releaseDate) {
Name = name;
Developer = developer;
Rating = rating;
ReleaseDate = releaseDate;
} 这样就可以通过构造函数直接初始化结构体: Game game1 = new Game("Pokémon Go", "Niantic", 3.5, "2016-07-01"); 但是,请注意,必须初始化所有字段,否则会报错,因为结构体中的每个成员都需要被赋值。 结构体 vs 类的简要总结
结构体和类都可以用于组合具有逻辑关系的多个变量,可以包含方法和事件,并且可以支持接口的实现。尽管一般情况下,类的实例存储在堆上,而结构体的实例存储在栈上,但也有一些例外情况,需要根据具体需求和性能考量来决定选择结构体还是类。 进一步学习资源建议查看官方文档或参考 StackOverflow 等平台,获取更多关于结构体和类的深入信息,以便更好地理解两者的差异和应用场景。 155 - 枚举枚举 (Enum) 简介欢迎回来!在本视频中,我们将讨论枚举 (enum) 的概念。枚举基本上是一组常量,具有不可变的特性。它通常被放置在命名空间的级别上,以便整个库可以访问它。枚举适用于固定的一组值,比如一周的七天。 定义枚举 - 以“Day”为例假设我们创建一个代表“天”的枚举,因为一周只有七天,所以这个枚举将只包含以下值: enum Day {
Monday,
Tuesday,
Wednesday,
Thursday,
Friday,
Saturday,
Sunday
} 这个 使用枚举现在我们定义了一个 Day fr = Day.Friday; 这里, Day a = Day.Friday;
Console.WriteLine(fr == a); // 输出: True 在这里, 我们还可以将枚举值直接打印到控制台: Console.WriteLine(Day.Monday); // 输出: Monday 获取枚举的整数值可以将枚举值转换为整数,以查看它的索引。以下是示例: Console.WriteLine((int)Day.Monday); // 输出: 0
小挑战 - 创建月份枚举接下来,我们来创建一个包含一年的月份的枚举 enum Month {
January,
February,
March,
April,
May,
June,
July,
August,
September,
October,
November,
December
} 现在,我们可以用相同的方式访问 修改枚举的起始值我们还可以更改枚举的起始值。例如,默认情况下,枚举索引从 enum Month {
January = 1,
February,
March,
April,
May,
June,
July,
August,
September,
October,
November,
December
} 这样, 可以通过以下方式检查 Console.WriteLine((int)Month.February); // 输出: 2 自定义枚举值我们还可以为特定的枚举项自定义值。例如,将 enum Month {
January = 1,
February,
March,
April,
May,
June,
July = 12,
August,
September,
October,
November,
December
} 在这种情况下, Console.WriteLine((int)Month.August); // 输出: 13 总结枚举 (enum) 的关键要点在于它们提供了一组共享的常量,使库或应用程序保持一致性。枚举在整个命名空间中共享同一组常量值,这样不必在每个类中重复定义这些常量。使用枚举可以让代码更具可读性,简化了固定值的管理。 156 - 数学类数学类 (Math Class) 介绍欢迎回来!在本视频中,我们将讨论数学类 (Math Class)。数学类不仅仅是我们在学校学习的数学,而是提供了一些常量和静态方法,用于执行各种数学计算,比如三角函数、对数函数等。你无需自己编写这些方法,它们已经在 使用 Math 类要使用 向上取整 -
|
12 - 事件与委托165 - 委托简介事件和委托 (Events and Delegates) 简介欢迎回来!本章中,我们将深入探讨 事件 (Events) 和 委托 (Delegates),这是理解之后内容的重要基础。这是一个相对复杂的主题,但在学习之后,你将会更好地理解 WPF 中的代码运作,例如按钮点击时的机制。 虽然事件和委托涉及的技术细节比较复杂,但它们的核心概念对于编写灵活、动态的代码至关重要。通过学习这些内容,你不仅可以像驾驶员一样使用代码,还可以像机械师一样了解代码内部的工作原理。 为什么需要学习事件和委托?在 WPF 应用程序中,事件和委托用于处理用户交互。比如,当用户点击按钮时触发某个事件。这个事件并不是简单的执行一个操作,而是依赖于委托来定义要执行的具体方法,类似于将任务交给一个“代理”来完成。 为了理解 WPF 中的事件驱动编程,深入掌握事件和委托将帮助你在复杂情况下灵活控制代码行为。掌握这些知识也让你在处理代码中的事件响应、数据传递和操作时更具自由度。 事件和委托的基本概念
本章内容预览
总结理解事件和委托将帮助你更好地驾驭代码,构建出灵活、动态的应用程序。在之后的 WPF 中,这些知识将成为创建交互式用户界面的基础。本章的学习内容可能会稍显复杂,但掌握它们将使你的编程能力迈上新台阶。 让我们开始本章的学习! 166 - 委托介绍什么是委托 (Delegate)?在简单的术语中,委托 是一种可以存储对方法的引用的类型。当你调用该委托时,它引用的方法将被调用。让我们通过一个示例来说明这个概念。 假设我们正在开发一个 UI 库,供其他开发人员用于构建移动应用。我们提供了一个按钮类 (Button Class),它具有以下几个属性:
现在,按钮还缺少一个关键功能:定义点击时执行的代码。 挑战:点击事件的实现作为这个 UI 系统的开发者,我们不知道使用我们系统的其他开发人员希望按钮点击后执行的具体逻辑。因此,我们无法在按钮类中直接提供一个点击方法来满足所有需求。这时,委托成为了解决这一问题的理想选择。 通过提供一个委托来存储点击事件的方法引用,我们可以让其他开发人员定义自己的方法并将其分配给按钮的点击事件。这样,每个按钮可以在被点击时执行不同的逻辑。 定义点击事件的委托我们可以将点击事件定义为委托。下面是如何定义这个点击事件的委托类型:
代码如下: private delegate void OnClickDelegate(); 这里我们定义了一个名为 创建委托变量接下来,我们将创建一个该委托类型的变量来存储具体方法的引用: public OnClickDelegate onClick; 在这里,我们定义了一个公共变量
使用委托假设我们在设计器中添加了一个按钮(例如名为
我们可以编写如下代码: void SendButtonClick() {
// 执行发送消息的逻辑
ConnectToNetwork();
SendMessage();
ShowMessageSentDialog();
} 现在我们有了一个方法 sendButton.onClick = SendButtonClick; 注意,这里我们没有写 委托的运行机制在底层,如果鼠标悬停在按钮上并点击了它,我们的 UI 系统会调用该按钮的 通过这种方式,使用我们 UI 系统的开发者可以创建他们自己的方法,并将这些方法分配给我们的委托,而我们可以在满足条件时调用它们。 委托的更多用法除了点击事件,委托还可以用于处理其他事件。例如,我们可以创建应用启动或关闭时的委托,并触发相应的方法。 总结委托的概念和应用不仅限于事件处理,它是 C# 中一种强大而灵活的功能。通过它,程序可以在运行时决定调用的具体方法,从而实现高度的动态性。 在接下来的视频中,我们将学习如何使用 C# 中的内置委托类型,并逐步创建自己的委托类型,类似于本例中实现的点击事件委托。希望你享受本章的学习内容,我们下个视频见! 167 - 委托基础使用现有委托:Predicate 委托示例欢迎回来!现在我们已经了解了委托的基本概念,让我们实际操作一下。在 C# 中,有许多内置的委托可以用来简化代码。我们将以 示例场景假设我们有一个字符串列表 什么是
|
13 - WPF(Windows Presentation Foundation)替代品(2023年8月底前)175 - WPF简介欢迎来到 WPF 章节在本章节中,你将学习如何使用Windows Presentation Foundation (WPF) 命名空间或库来编写美观的用户界面 (UI)。在这里,你将通过 XAML 和 C# 代码两种方式来创建用户界面。
两种方式的优缺点你可以通过拖拽控件来快速构建用户界面,这是最便捷的方法,但同时也限制了你对界面细节的控制。因此,如果你想要完全掌控界面样式和行为,理解如何通过代码来操作控件将非常有帮助。在本章节中,你将学习到:
从控制台到 GUI 界面经过前面章节的学习,我们一直在使用灰色(甚至是黑色)的命令行界面,这种界面虽然实用,但对用户的吸引力不强。而现在,我们将学习如何构建更具视觉效果的图形用户界面 (GUI),从而让应用程序不再单调。你将能够控制点击按钮的效果,定义界面上的响应逻辑,并将代码直接影响到用户体验上。 让我们开始吧!本章节将带领你一步步深入,逐渐掌握 WPF 的基础知识与高级概念。希望你和我一样兴奋,准备好创建自己的用户界面!让我们立即开始吧! 176 - WPF及其使用时机介绍欢迎来到 WPF 章节!在本视频中,我们将为 Hello World WPF 应用程序设置环境。按照以下步骤操作,以确保你能够顺利创建并运行你的第一个 WPF 应用程序。 第一步:安装 .NET 桌面开发工具包
第二步:创建新项目
认识 XAML 和设计界面XAML 和设计视图
使用工具箱添加控件
编辑 TextBlock
运行你的 Hello World 应用程序
挑战练习
示例在 <TextBlock Text="Hello, Dennis" ... /> 在 <Window x:Class="WPF01.MainWindow" ... Title="My First GUI"> 结果当你运行代码时,你会看到一个窗口,显示 "My First GUI" 作为标题,内容为 "Hello, [你的名字]"。这个应用程序是一个简单的演示,它为我们后续学习更多 WPF 控件和功能奠定了基础。 在接下来的视频中,我们将深入学习如何使用其他控件,如按钮和输入框,并进一步掌握 WPF 的 XAML 和 C# 代码编写方式。希望你对这个过程感到兴奋,让我们在下一节中探索更多 WPF 的可能性吧! 177 - XAML基础与代码后置欢迎回来 WPF 章节在本视频中,我们将深入探讨 XAML(发音为“Zemo”)。XAML 是一种基于 XML 的语言,允许我们用代码来编写用户界面(UI),从而不必通过拖放来构建界面元素和调整属性。 你可能会想:“我更喜欢视觉化的操作,直接拖放就可以。”确实,拖放是一种便捷的方法,但用 XAML 可以获得更强的灵活性和控制能力,并可以实现更复杂的功能,例如数据绑定、依赖属性等,这些功能在创建可维护的 UI 时非常重要。 XAML 基础XAML 和 HTML 类似,使用开闭标签的结构。如果你熟悉 HTML,你会对它比较熟悉:
示例:创建一个按钮<Button Content="Click Me" Height="50" Width="100"/> 在上面的 XAML 代码中:
XAML 的命名空间在 XAML 文件顶部,你会看到诸如
添加注释使用以下方式在 XAML 中添加注释,便于说明代码: <!-- 这是一个 XAML 注释,便于说明代码功能 --> 创建多个按钮可以在 XAML 中创建多个按钮: <Button Content="Click Me" Height="50" Width="100" />
<Button Content="Hi There" Height="50" Width="100" /> 不过要注意,如果按钮在同一个网格(Grid)中,它们会彼此覆盖。我们将来会深入讲解网格布局如何使用来定位元素。 运行应用程序运行应用程序,你会看到按钮可以被点击、显示动画效果等。这些简单的功能都是 WPF 提供的默认行为。 XAML 中的其他属性你可以设置按钮的 字体大小 等更多属性: <Button Content="Click Me" FontSize="32" Width="150"/> 另一种设置内容的方法除了通过 <Button>
Click Me
</Button> 自定义按钮内容可以通过 WrapPanel(包裹面板)添加多种内容在按钮中: <Button Width="200" Height="100">
<WrapPanel>
<TextBlock Text="Multi" Foreground="Blue"/>
<TextBlock Text="Color" Foreground="Red"/>
<TextBlock Text="Button" Foreground="White"/>
</WrapPanel>
</Button> 运行代码时,你将看到一个多色文本按钮。 使用 C# 代码创建 UI我们可以通过 C# 代码生成相同的 UI 结构,这就是所谓的 Code Behind(代码隐藏)。每个 XAML 文件都有一个对应的 示例:在代码隐藏中创建按钮
在这个构造函数中,我们可以用代码创建和设置 UI 元素,例如按钮和布局网格: // 创建网格
Grid grid = new Grid();
this.Content = grid; // 将网格设为窗口内容
// 创建按钮
Button button = new Button();
button.FontSize = 26;
// 创建 WrapPanel
WrapPanel wrapPanel = new WrapPanel();
button.Content = wrapPanel;
// 创建 TextBlock 并添加到 WrapPanel
TextBlock txt1 = new TextBlock { Text = "Multi", Foreground = Brushes.Blue };
TextBlock txt2 = new TextBlock { Text = "Color", Foreground = Brushes.Red };
TextBlock txt3 = new TextBlock { Text = "Button", Foreground = Brushes.White };
wrapPanel.Children.Add(txt1);
wrapPanel.Children.Add(txt2);
wrapPanel.Children.Add(txt3);
// 将按钮添加到网格中
grid.Children.Add(button); 上面的代码通过创建 网格、按钮 和 WrapPanel,并将 总结
本视频我们探讨了 XAML 和代码隐藏文件中的设置 UI 方法。接下来的视频我们将深入学习更多 WPF 的控件和布局技术,敬请期待! 178 - StackPanel、ListBox的可视与逻辑树欢迎回来在本视频中,我们将介绍 StackPanel、ListBox、逻辑树和视觉树的概念。这些是 WPF 中管理布局和界面层次结构的关键概念。 1. StackPanel首先,我们将删除之前的 Grid,因为还没有深入使用 Grid。取而代之,我们将使用 StackPanel,它可以将元素垂直或水平堆叠在一起。以下是如何实现的: <StackPanel>
<TextBlock Text="Hello, World" HorizontalAlignment="Center" Margin="20"/>
</StackPanel> 在上述代码中,我们使用 2. ListBox接下来,我们添加一个 ListBox,展示多个项目。ListBox 用于显示一个项目列表,用户可以从中选择。以下是如何定义一个简单的 ListBox: <ListBox Height="100" Width="100">
<ListBoxItem Content="Item 1"/>
<ListBoxItem Content="Item 2"/>
<ListBoxItem Content="Item 3"/>
</ListBox> 在这里,ListBox 默认占据 StackPanel 中的宽度,且高度刚好能够显示它的项目内容。我们还设置了 运行效果运行代码后,我们将看到一个包含 3. 添加 Button 和事件处理我们可以添加一个按钮并在点击时触发一个事件。例如,添加一个 <Button Content="Click Me" Click="Button_Click" Margin="20"/> 在代码后置文件 private void Button_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show("Thanks for clicking me!");
} 运行效果当我们运行应用程序并点击按钮时,会弹出一个消息框,显示文本 “Thanks for clicking me!”。 4. 逻辑树与视觉树逻辑树逻辑树代表了 XAML 布局中的元素结构。例如,以下是我们界面布局的逻辑树结构:
逻辑树提供了 WPF UI 结构的简单视图,帮助我们了解布局层次。 视觉树视觉树则更为详细,展示了所有在应用程序中渲染的可视元素,包括 XAML 中没有显式定义的元素。我们可以通过设置断点进入调试模式,并打开 WPF Tree Visualizer 来查看视觉树。 视觉树不仅包含了逻辑树中的元素,还包括在 UI 中呈现它们所需要的附加元素。例如,ListBox 在视觉树中可能会包含
总结
理解这些概念可以帮助我们更好地组织和调试 WPF 应用程序。在下一个视频中,我们将深入研究事件的处理。 179 - 路由事件:直接冒泡与隧道欢迎回来在本视频中,我们将深入探讨 XAML 中的事件,特别是 WPF 的路由事件(Routed Events)。WPF 提供了三种路由事件:直接事件、冒泡事件和隧道事件。我们将详细介绍每种事件的工作方式,并通过实际示例来演示它们的使用方法。 1. 路由事件概述路由事件是 WPF 中的事件类型,允许事件沿着视觉树或逻辑树传播,以便其他元素处理它们。以下是三种主要的路由事件类型:
2. 直接事件直接事件只在事件源元素上触发,不会在树结构中传播。以下是创建一个按钮的示例,点击该按钮将触发直接事件。 <Button Content="Click Me" Width="150" Height="100" Click="Button_Click"/> 在代码后置文件 private void Button_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show("Button was clicked - Direct Event");
} 运行代码后,当点击按钮时会显示消息框“Button was clicked - Direct Event”。直接事件只会在按钮本身触发,不会向其他元素传播。 3. 冒泡事件冒泡事件会从事件源开始,向上沿着视觉树传播。如果事件未被处理,它将继续向上传播,直到根元素。例如,我们可以使用 <Button Content="Click Me" Width="150" Height="100" MouseUp="Button_MouseUp"/> 在代码后置中添加处理器: private void Button_MouseUp(object sender, RoutedEventArgs e)
{
MessageBox.Show("Mouse Button went up - Bubbling Event");
} 当我们点击并释放鼠标按钮时,该事件触发并显示消息框。如果在树中的其他元素中未处理该事件,事件会继续向上传播。这就是冒泡事件的特性。 4. 隧道事件隧道事件从根元素开始向下传播到事件源。这种事件的常见用法是预览事件(例如 以下是使用 <Button Content="Click Me" Width="150" Height="100" PreviewMouseUp="Button_PreviewMouseUp"/> 在代码后置中添加处理器: private void Button_PreviewMouseUp(object sender, RoutedEventArgs e)
{
MessageBox.Show("Mouse Button went up - Tunneling Event");
} 此事件在按钮内触发,但它会沿着视觉树向下传播,在抵达事件源之前可能被上层元素拦截。运行程序并点击按钮时,该事件会在释放鼠标按钮时立即触发。 5. 更多隧道事件和冒泡事件示例为了展示隧道和冒泡事件的差异,我们可以添加更多的事件处理器,例如 <Button Content="Click Me" Width="150" Height="100"
PreviewMouseLeftButtonDown="Button_PreviewMouseLeftButtonDown"
PreviewMouseRightButtonUp="Button_PreviewMouseRightButtonUp"/> 在代码后置中添加处理器: private void Button_PreviewMouseLeftButtonDown(object sender, RoutedEventArgs e)
{
MessageBox.Show("Left Mouse Button went down - Tunneling Event");
}
private void Button_PreviewMouseRightButtonUp(object sender, RoutedEventArgs e)
{
MessageBox.Show("Right Mouse Button went up - Tunneling Event");
} 通过这些处理器,我们可以观察到不同类型事件的行为:
总结WPF 中的路由事件提供了灵活的事件处理机制,允许事件在视觉树中传播。理解不同类型的事件有助于我们更有效地控制事件流。以下是这三种事件的关键区别:
掌握这些事件类型,我们可以在 WPF 应用程序中更灵活地处理用户交互。 181 - 网格欢迎回来在本视频中,我们将学习如何使用 Grid 布局。我们之前讨论过 StackPanel 和 WrapPanel,它们分别允许我们将元素垂直堆叠或按行排列。而 Grid 则提供了更强大的布局控制,允许我们将元素定位在一个网格中。我们将从简单的 2x2 网格开始,并逐步深入。 1. 创建基本的网格布局首先,我们需要在 XAML 文件中定义列和行。可以使用 <Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100"/>
<ColumnDefinition Width="100"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
</Grid> 上面的代码创建了一个网格,它包含 2 列,每列宽度为 100 像素,和 1 行,行高根据内容自动调整。 2. 向网格添加元素接下来,我们可以在网格中添加按钮,并使用 <Button Content="Button 1" Grid.Column="0" Grid.Row="0"/>
<Button Content="Button 2" Grid.Column="1" Grid.Row="0"/> 这会将 Button 1 放在第 0 列,第 0 行;Button 2 则放在第 1 列,第 0 行。 运行代码后,我们可以看到这两个按钮分别被放置在两个不同的列中。注意,这些列的宽度为 100 像素。 3. 使用自动(Auto)和星号(*)宽度除了固定宽度之外,我们还可以使用
例如,将第一列的宽度设置为 <ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/> 4. 控制宽度的比例如果我们希望列之间的宽度按比例分配,可以使用多倍的星号。例如,我们希望第一列的宽度是第二列的两倍,可以设置如下: <ColumnDefinition Width="3*"/>
<ColumnDefinition Width="2*"/> 这会将总宽度分为 5 等份,第一个列占 3/5,第二个列占 2/5。 5. 使用行定义我们还可以定义多行,并控制每行的高度。可以使用类似的方式定义 <Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions> 上面的代码会创建 2 行,且高度均等。 6. 定位到不同的单元格我们可以通过指定 <Button Content="Button 1" Grid.Column="0" Grid.Row="0"/>
<Button Content="Button 2" Grid.Column="1" Grid.Row="0"/>
<Button Content="Button 3" Grid.Column="0" Grid.Row="1"/>
<Button Content="Button 4" Grid.Column="1" Grid.Row="1"/> 7. 练习:创建 3x3 网格布局练习:请尝试创建一个 3x3 的网格,其中包含 8 个按钮,最后一个位置放置一个 解决方案首先,我们增加额外的列和行定义: <Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions> 接着,我们添加按钮和 <Button Content="1" Grid.Column="0" Grid.Row="0"/>
<Button Content="2" Grid.Column="1" Grid.Row="0"/>
<Button Content="3" Grid.Column="2" Grid.Row="0"/>
<Button Content="4" Grid.Column="0" Grid.Row="1"/>
<Button Content="5" Grid.Column="1" Grid.Row="1"/>
<Button Content="6" Grid.Column="2" Grid.Row="1"/>
<Button Content="7" Grid.Column="0" Grid.Row="2"/>
<Button Content="8" Grid.Column="1" Grid.Row="2"/>
<TextBlock Text="Text" Grid.Column="2" Grid.Row="2"
HorizontalAlignment="Center" VerticalAlignment="Center" FontSize="16"/> 这将创建一个 3x3 网格,包含 8 个按钮和一个居中的文本块。 8. 组合布局通过结合使用 Grid、StackPanel 等布局容器,我们可以实现灵活的布局控制。例如,可以将 <StackPanel>
<Grid>
<!-- 网格内容 -->
</Grid>
<Button Content="Extra Button" Height="100"/>
</StackPanel> 总结通过学习 Grid 布局,我们可以轻松地将控件精确地定位到窗口中的不同位置。这种布局方式使得用户界面设计变得灵活且易于控制。在后续视频中,我们将探索更多的 WPF 控件和布局,创建更加美观和复杂的界面。 182 - 数据绑定欢迎回来现在您已经了解了依赖属性,接下来是数据绑定的部分,因为它们是相互关联的。在本视频中,我们将介绍四种不同的数据绑定模式:
让我们通过示例来详细了解这些绑定模式的使用。 基础示例:连接 TextBox 和 Slider我们将创建一个 <StackPanel>
<TextBox Width="100" Margin="50"/>
<Slider Minimum="0" Maximum="100"/>
</StackPanel> 运行这段代码后,您会看到一个可以输入文本的 1. OneWay 单向绑定首先,让 <TextBox Width="100" Margin="50" Text="{Binding ElementName=mySlider, Path=Value, Mode=OneWay}"/>
<Slider x:Name="mySlider" Minimum="0" Maximum="100"/> 在 2. TwoWay 双向绑定如果我们希望 <TextBox Width="100" Margin="50" Text="{Binding ElementName=mySlider, Path=Value, Mode=TwoWay}"/>
<Slider x:Name="mySlider" Minimum="0" Maximum="100"/> 运行后,您会发现无论是滑动滑块还是在文本框中输入新值,两个控件的值都会实时同步更新。 3. OneWayToSource 单向绑定至源使用 <TextBox Width="100" Margin="50" Text="{Binding ElementName=mySlider, Path=Value, Mode=OneWayToSource}"/>
<Slider x:Name="mySlider" Minimum="0" Maximum="100"/> 在这种情况下,您在 4. OneTime 单次绑定在 public MainWindow()
{
InitializeComponent();
mySlider.Value = 30;
myTextBox.Text = mySlider.Value.ToString();
} 在这里,我们在 数据更新触发器如果我们希望文本框中的数据在输入时就能同步到滑块,可以使用 <TextBox Width="100" Margin="50" Text="{Binding ElementName=mySlider, Path=Value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
<Slider x:Name="mySlider" Minimum="0" Maximum="100"/>
绑定其他属性我们不只可以绑定 <TextBox Width="100" Margin="50" Background="{Binding ElementName=mySlider, Path=Background}"/>
<Slider x:Name="mySlider" Minimum="0" Maximum="100" Background="Aqua"/> 在此例中, 总结在本视频中,我们学习了四种不同的数据绑定模式及其应用。通过这些模式,您可以灵活地控制数据的流向,从而实现更复杂的交互逻辑。在后续视频中,我们将继续探讨更多 WPF 和数据绑定的高级功能。 183 - INotifyPropertyChanged接口欢迎回来在本视频中,我们将讨论 使用 StackPanel 而非 Grid这次,我打算使用一个 接下来,在标签下面,我将添加一个文本框,用于输入数值。我不会使用 在 复制并修改代码我们已经了解了绑定路径和模式的含义。现在我们复制这段代码并在此处粘贴两次。第一个文本框将绑定到 变量命名你可能会问,为什么我使用 创建新类并实现接口为了使用 为了使用 现在可以看到接口变成了绿色,这是 Visual Studio 中接口的标准颜色。接下来,我需要实现接口中的事件 实现属性更改事件我们需要创建一个名为 创建私有字段和属性为了处理这些数据,我们首先创建三个私有字段:
更新界面并测试为了测试这些更改,我们需要在 总结在这次实现中,我们创建了一个类 通过这种方式,我们可以实现数据驱动的 UI,确保 UI 和数据始终保持同步。当用户输入数据时, 你可以根据需要进一步扩展这个例子,例如增加更多的输入框,或者将其修改为进行乘法等其他操作。 184 - ListBox与当前匹配列表欢迎回来!在这个视频中,我们将创建一个列表框(List Box)。如你所见,我们有一个小列表框,其中包含一些条目。比如拜仁慕尼黑(Bayern Munich)有3个积分或进球,皇家马德里(Real Madrid)有2个进球,而比赛即将结束。所以,我们这里使用了多个不同的元素,而这些元素我们还没有使用过。当我选择一个条目并点击这个按钮“显示已选”,一个小的消息框会弹出,告诉我已选择的条目信息,或者至少在这个例子中,它只是给我这些信息。当然,你可以在这里添加更多的信息,你甚至可以构建一个小应用程序,从互联网上获取数据并不断更新,显示当前比赛的即时比分。 好吧,让我们开始吧!我将为此创建一个新项目,命名为“list box”。我们先把这个名字定下。我将使用 XAML,所以我将把它拖到界面上。我们目前的界面或者窗口依然存在,但现在我想使用网格布局(Grid),当然,这里需要用到多个行(Rows)。所以让我们先创建列定义(Column Definition)、行定义(Row Definition)之类的东西。顺便提一下,我们实际上只需要两列,所以不会太复杂。我们只需要两列,而不需要多行,因此我们不需要行定义。如果你记得如何做,那当然很好,掌握这些技能总是有益的。所以你首先需要列定义,然后在这里你可以定义列。我将定义一列宽度为“*”的列,和一列宽度为100的列。实际上,我是想在这里创建一个按钮,作为第二列。 好的,现在我们有了两列。左边这一列将包含列表框(ListBox),右边这一列则包含一个可以点击的按钮,一旦点击就可以获取比赛的更多信息。那么现在我们来创建一个列表框。我将使用 接下来,我想为列表框创建一个特定的项目模板(Item Template)。 这五列分别是:第一列显示球队1,第二列显示第一个得分,第三列显示第二个得分,第四列显示第二支球队,最后一列显示进度(进度条)。接着,我们将放置多个文本块(TextBlock)。第一个文本块放在第一列,显示球队1的名称,第二个文本块显示球队1的得分。接下来,第三个文本块显示第二支球队的名称,第四个文本块显示第二支球队的得分。最后,我们放置一个进度条,用来显示比赛进行的进度。 进度条的最小值设置为0,最大值设置为90分钟(因为足球比赛是90分钟),并且我们将绑定进度条的值到 那么接下来,我们需要在代码后端(Code-behind)中定义一些数据。在代码后端,我将创建一个类,命名为 在主窗口中,我创建了一个 一旦这些数据准备好,我们将通过数据绑定(Data Binding)将这些数据展示在界面上。我们将把 接下来,我在界面上添加了一个按钮,用于显示选择的比赛详情。按钮的点击事件会触发 最后,我挑战你去尝试添加更多的比赛,你可以选择其他种类的比赛,进度条可以显示比赛进行的时间。你只需简单地复制粘贴比赛对象,并修改球队名称和得分。 在此之后,你可以运行程序,看到界面上显示的所有比赛以及它们的进度信息。你还可以进一步改进界面,例如调整得分显示的位置或为进度条添加动画效果等。 视频的最后,我们还可以扩展这个类,加入更多的信息,比如球员名单等。你可以创建一个新的属性 这是我们视频的总结,涵盖了如何使用 185 - ComboBox欢迎回来。在这个视频中,我们将介绍组合框(Combo Box),你可以在这里看到一个例子。它的数据源包含了所有我们可以使用的颜色。假设我们想使用“淡紫红”(Pale Violet Red),就像我们之前已经展示过的那样。我们可以直接选择它。正如你所看到的,我们的组合框已经选择了这个颜色,甚至展示了关于这个颜色名称的完整美丽文本。 好了,现在我们开始创建一个新项目来演示,我将把它命名为“WP F ten C combo box”,你可以根据自己的喜好命名。但我们需要在这个项目中创建一个示例。我们首先创建一个 Stack Panel(堆叠面板)。所以,我想用 Stack Panel 代替 Grid。我这里会把 XAML 代码放大一点,因为设计界面很漂亮,但更重要的是看到你正在编写的代码。 接下来,我将在这里创建一个叫做 Combo Box 的控件,基本上就是这个。这个是我的组合框,我可以给它一个名称,所以我将它命名为 现在,这个组合框需要一个组合框项模板(ComboBoxItemTemplate),这是我们在上一个视频中使用过的,类似于我们使用 ListBox 时所做的。这里就有了这个组合框项模板。我们需要一个数据模板(DataTemplate),否则你会看到它提示错误。只要我们有了这个数据模板,就可以继续了,我会删除这里多余的几行代码。 在这里,我想要一个 Stack Panel,因为我想把所有内容堆叠在一起,并且它应该是水平排列的。接下来,这个 Stack Panel 应该包含一个矩形(Rectangle)。顺便说一下,这个 Stack Panel 将会是我们组合框内的内容,它应该包含一个矩形,我将给它填充颜色。所以,我将通过绑定名称来填充它,并且矩形的宽度设置为 32 像素,高度也设置为 32 像素。然后,我想为这个矩形添加一个 5 像素的边距(Margin),这样四个方向都会有一点空隙。 接下来,应该有一个文本块(TextBlock),它显示颜色名称。所以这个文本块的文本内容应该绑定到名称( 好了,这就是我们在 XAML 中需要的所有代码。现在,我们可以继续编写后台代码(code-behind)。因为我们需要的东西就是这个“名称”属性。让我们进入后台代码,在这里,我将直接访问我们的组合框控件 接下来,我会设置这个组合框的 这个类是 因此,我们只需要一个包含所有这些颜色的组合框,你可以选择像巧克力色(Chocolate),深灰色(Dark Slate Gray)等等颜色。正如你所看到的,你可以将 186 - CheckBox欢迎回来。在这个视频中,我们将创建一个披萨配料应用程序,它允许我们在想要披萨送达时随时添加配料。所以,当我们点披萨时,我们可以选择添加配料,比如萨拉米香肠、蘑菇或者额外的马苏里拉奶酪。如果我们将所有这些配料都添加上,正如你所看到的,顶部的“添加所有”选项会被激活,并且它有三种状态,如你所见,它有一个“是”(True)状态,一个空状态,和一个“否”(False)状态。 好了,这就是我们要创建的内容。接下来,让我们开始吧。首先,我们需要为界面腾出更多空间,我将使用 Stack Panel 代替 Grid。再次强调,Stack Panel 的使用是因为 Grid 设置起来比较麻烦,特别是当你需要一个比较简单的布局时,Stack Panel 会更加方便。 接下来,我想要添加一个标签(Label),只是一些文本,文本的字体粗细为加粗(bold)。文本内容将是“披萨配料”。然后,我需要一个复选框(CheckBox)。我将创建一个名为
因此,这就是为什么我们需要三种状态。然后,我们可以添加选中和未选中的事件处理方法(checked/unchecked)。所以我将手动创建一个新的事件处理程序。你们已经知道如何创建事件处理程序,可以自己试着做一下。接下来,让我们创建这个事件处理程序。我们将其命名为 当复选框被选中时,事件 接下来,我们为复选框添加文本:“添加所有”。现在,我们已经设置了一个大复选框,接下来在它下面创建一个 Stack Panel,其中包含另外三个复选框。我们为每个配料(如萨拉米、蘑菇、马苏里拉)都创建一个复选框。每个复选框都会有自己的事件处理方法。我为每个复选框创建一个单独的事件处理方法,命名为 复选框文本的显示会通过嵌套的 TextBlock 来实现。例如,萨拉米复选框会显示为“辣味”(spicy),并且通过设置 接着,其他两个复选框(蘑菇和马苏里拉)也按照相同的方式创建,但没有像萨拉米那样增加额外的文本效果。复选框文本分别为“蘑菇”和“马苏里拉”。 为了使界面更加整齐,我为“添加所有”复选框和下面的三个复选框之间增加了一个边距(例如 5 或 10 像素),使它们看起来属于同一组。这样一来,“添加所有”复选框就与下面的配料复选框紧密关联。 现在,XAML 文件已经设置好了。接下来,我们需要进入后台代码(code-behind)并实现这些方法。我们已经声明了这些方法,但是它们目前还没有执行任何操作。我们可以通过以下方式修改它们:
下面是实现这个逻辑的代码示例: private void CBAllCheckedChanged(object sender, RoutedEventArgs e)
{
bool newVal = CBAllToppings.IsChecked == true;
CBSalami.IsChecked = newVal;
CBMushrooms.IsChecked = newVal;
CBMozzarella.IsChecked = newVal;
}
private void SingleCheckedChanged(object sender, RoutedEventArgs e)
{
if (CBSalami.IsChecked == true && CBMushrooms.IsChecked == true && CBMozzarella.IsChecked == true)
{
CBAllToppings.IsChecked = true;
}
else if (CBSalami.IsChecked == false && CBMushrooms.IsChecked == false && CBMozzarella.IsChecked == false)
{
CBAllToppings.IsChecked = false;
}
else
{
CBAllToppings.IsChecked = null; // Tri-state
}
} 通过这种方式,我们就能够控制复选框的三种状态——选中、未选中以及空状态。 现在,你可以运行这个应用,当你点击“添加所有”复选框时,所有配料都会被自动选择或者取消选择。如果你手动选择或取消某个配料,其他配料和“添加所有”复选框的状态也会相应改变。 这就是如何使用复选框和三种状态来创建一个披萨配料应用程序。当然,你也可以像我这样,给所有复选框使用相同的事件处理方法,或者你可以为每个复选框创建独特的方法,甚至使用消息框来提示用户他们选择了哪些配料。 非常简单明了的实现方式。如果你有任何问题,欢迎留言!下个视频我们将继续学习下一个主题。 187 - ToolTip欢迎回来。在这个视频中,我想展示一个简单而快速的功能——如何为控件添加提示框(Tooltip)。为了演示这个功能,我创建了一个新的 WPF 项目,并在 Grid 中添加了一个按钮(Button)。按钮有一个名为 然后,我还可以给按钮添加文本,文本内容是“悬停查看更多信息”。同时,我将按钮的宽度设置为 150 高度设置为 100,使其稍微小一点。 现在,我们来看一下效果。你可以看到,当我们将鼠标悬停在按钮上时,提示框显示了“我是一个提示框,我很有用”。这就是如何使用提示框(Tooltip),它可以应用于多种不同的控件,不仅仅是按钮,也适用于其他控件,比如文本块(TextBlock)。 接下来,我创建了一个文本块(TextBlock),并为其添加了一个提示框(Tooltip)。当我将鼠标悬停在文本块上时,提示框会显示“请输入下面的年龄”这样的信息。这可以非常有效地帮助用户理解控件的功能。 当然,文本块本身不能接收文本输入,但如果我们在它下面添加一个文本框(TextBox),我们同样可以为文本框添加提示框。所以,提示框非常适用于为用户提供额外的信息,特别是在某些地方可能不够清楚时。 好了,这就是提示框的基本用法。在下一个视频中,我们将学习如何使用单选按钮(Radio Buttons)。敬请期待! 188 - 单选按钮与图像欢迎回来。在这个视频中,我们将学习如何使用单选按钮(Radio Buttons)。如你所见,你可以从工具箱中拖动一个单选按钮(Radio Button)到你的 UI 中,然后你会在 Grid 中看到它,并且它会有一些属性,这些我们之前已经看到过。不过,在这个视频中,我不会直接使用工具箱中的单选按钮,而是通过 XAML 来构建 UI,使用一个 StackPanel 来包含我们的控件。 首先,在 StackPanel 中,我想要一个标签(Label),标签上显示一些文本,比如“你喜欢我吗?”。接着,我会设置这个标签的字体加粗(FontWeight),并设置字体大小为 20。这样,标签就完成了。 然后,接下来我们需要为用户提供三个选择:是、否,或者也许,选项之间没有其他选择。这就是单选按钮的优势——在一个单选组中,只有一个按钮会被选中。 创建第一个单选按钮我们首先创建一个单选按钮,并使其更加个性化,不仅仅是一个简单的按钮。我们将它放在一个 接着,我会为 StackPanel 和其他控件添加一些间距(Margin),使界面看起来更整洁。例如,我们为 StackPanel 设置顶部间距,并且为文本块设置左侧间距为 5 像素。这样,控件之间的间距就得到了合理的调整。 创建第二个单选按钮接下来,我们复制第一个单选按钮,放置到它的旁边,并将其颜色改为红色。按钮的文本改为“否”,并将文本颜色改为红色。通过这种方式,我们创建了第二个选项。 创建第三个单选按钮第三个选项是“也许”,不同于前两个选项,我们将使用一张图片而不是矩形。首先,我删除矩形,使用 完成界面这样,我们就完成了三个单选按钮,分别是“是”,“否”和“也许”。当你运行程序时,你会看到这三个按钮,并且它们是一个单选组的成员,只能选择其中一个选项。单选按钮的优势是,它们总是只会有一个选项处于选中状态,这与复选框(Checkbox)不同,复选框允许多个选项被选中。 设置默认选中项如果你希望在程序启动时默认选中某个选项,你可以通过 添加事件处理如果你希望在用户选择某个选项时触发事件,可以通过 你也可以修改事件处理程序的名称。比如,将 事件处理和响应通过这种方式,我们可以为每个按钮添加不同的事件处理程序,比如当“是”被选中时显示“谢谢”,当“否”被选中时显示“请说是”。此外,我们还可以在代码中手动触发 总结通过这个示例,你学会了如何使用单选按钮以及如何为它们添加图像和事件处理程序。你还了解了如何设置默认选中项,并且学会了如何在事件发生时响应用户的选择。 189 - 属性数据与事件触发在这个视频中,我想讨论一下属性数据和事件触发器,因为它们在动态调整界面时非常重要。举个例子,我将调整文本内容。当我将鼠标悬停在文本上时,文本的颜色和外观都会发生变化。接着是这个内容,“你好,伙伴”。你可以看到它的大小变得更大了,这里有一个动画效果。然后我们有一个复选框,旁边有一个文本显示“没有”,当我点击它时,文本会显示“哦,当然有人在这里,那就是我”。是的,我可以点击它。这些是我们将在本视频中讨论的内容,它们可以帮助你在制作用户界面时更加灵活,让界面在运行时自适应变化,并且提升用户体验。所以,让我们先创建一个新项目,我将它命名为“WPA 14C”,表示“属性数据和事件触发器”。我这里使用了网格和堆叠面板的组合。所以我将从一个堆叠面板开始。这样堆叠面板就创建好了。在堆叠面板中,我放置了一个网格。然后在网格里,我放置了一个文本块,显示类似“Hello, beloved world”之类的内容。接着我设置字体大小为32。我要让它居中,所以我设置它的水平对齐方式为“center”,并且垂直方向也需要居中,所以我设置垂直对齐方式为“center”。就这样,我们的文本“Beloved World”已经完成了,并且我稍微增大了一些XAML文件的尺寸。 在这个文本块中,我可以去更改一些样式。例如,我可以进行多种调整,并且为了使用样式触发器,我需要添加一个样式。首先,我为文本块添加样式: 首先,我需要一个setter,它将修改“foreground”属性,即我想将文字颜色设置为绿色。因此,它会将“foreground”属性的值设置为绿色。然后,我希望有一个触发器来触发这个更改,所以下一步我会用 举个例子,我现在有绿色的文本,当鼠标悬停时,我希望它变成红色。所以,setter会将 这是一个文本块,代码结构大致是这样:我们有一个包含文本块的网格,它位于堆叠面板中。现在我调整了文本块的样式,通过添加样式标签,指定目标类型为文本块。接着,我用setter直接设置样式,而不需要触发器的帮助。然后,我们加入样式触发器,设置了鼠标悬停时的触发条件。当 此外,还可以看到,当鼠标离开时,颜色会恢复到原本的绿色,下划线也会消失。就是说,当触发器的条件不再满足时,设置会恢复到初始值,这就是样式触发器的一个特性。 接下来,我们要再创建一个文本块,这个文本块会放置在一个新的网格中。这样,我可以有多个网格层叠在一起。这里,我使用了另一个文本块,显示“Hello buddy”,字体大小设置为24,确保它居中对齐。然后,我将为这个文本块添加样式,像之前一样使用 首先,我们定义事件触发器的条件,这里是 现在我们来运行代码,看看效果。当鼠标进入时,文本会变得非常大,但它不会自动回到原来的大小。这个时候,我留了一个小小的挑战给你:请添加另一个事件触发器,使得当鼠标离开文本块时,字体大小能回到原来的24。 这里的提示是,你不需要重新编写所有的内容,只需要创建另一个事件触发器,并将其条件改为 我复制了之前的事件触发器,并将 最后,我们来看看复选框的实现。复选框的内容是“Is someone there?”当勾选复选框时,下面的文本内容应该发生变化。我使用了数据绑定(Data Binding),通过绑定 总结一下,视频展示了三种不同类型的触发器:样式触发器(Style Trigger),事件触发器(Event Trigger)和数据触发器(Data Trigger)。这些触发器可以帮助你在不编写后台代码的情况下,动态调整界面的显示效果。希望你能够理解这些内容,并尝试在XAML中创建更复杂的界面逻辑。 190 - PasswordBox欢迎回来。在本视频中,我们将结合使用文本框和密码框,来创建一个简单的登录界面。让我们开始吧。 首先,我将放大XAML视图,并且不使用网格(Grid)布局,而是使用堆叠面板(StackPanel),因为它可以将控件垂直堆叠。首先,我将添加一个标签(Label),这样用户可以知道应该输入哪种信息。我们从“用户名”开始,然后在其下方添加一个文本框(TextBox),并为文本框命名为 <StackPanel Margin="10">
<Label Content="Username" />
<TextBox Name="TB_username" />
</StackPanel> 如你所见,文本框距离边缘太近了,因此我为堆叠面板添加了一个10像素的边距,确保所有控件都有适当的间距。 接下来,我将添加另一个标签,内容为“密码”。然后,在标签下方添加一个密码框(PasswordBox),以便用户输入密码。 <Label Content="Password" />
<PasswordBox Name="PB_password" /> 现在,密码框已添加完成。接着,我们为登录按钮(Button)添加一个操作,按钮的文本内容为“登录”,并且我为按钮设置了一个点击事件。 <Button Content="Log in" Margin="5" Click="OnLoginClicked" />
</StackPanel> 事件处理接下来,在按钮的事件处理函数中,我们可以显示一个消息框(MessageBox),内容为“欢迎,用户名”,这样用户点击“登录”后就会看到自己的用户名。 private void OnLoginClicked(object sender, RoutedEventArgs e)
{
MessageBox.Show("Welcome, " + TB_username.Text);
} 测试登录功能我们在文本框中输入用户名(例如 "Dennis"),然后在密码框中输入密码(例如 "password")。由于密码框会自动隐藏输入的字符,所以你会看到显示为点(●)。点击“登录”按钮后,会弹出一条消息,显示“欢迎,Dennis”。 <MessageBox Show="Welcome, Dennis" /> 修改密码框显示字符如果你不希望密码框显示点(●),而是想使用其他字符来代替这些点,可以通过设置 <PasswordBox Name="PB_password" PasswordChar="*" /> 示例运行再次运行程序时,如果输入密码,将会显示为星号(*)。 使用其他字符显示密码你也可以将密码显示的字符更改为其他任何符号,比如字母“H”: <PasswordBox Name="PB_password" PasswordChar="H" /> 这样,输入的密码就会显示为“H”。 限制密码长度密码框还可以设置最大长度(MaxLength)。通过设置这个属性,你可以限制用户输入密码的最大字符数。例如,如果你设置最大长度为8: <PasswordBox Name="PB_password" MaxLength="8" /> 此时,用户的密码将不能超过8个字符。如果尝试输入更多字符,密码框会自动限制输入。 小心使用最大长度虽然限制最大密码长度可以防止用户输入过长的密码,但要小心设置过短的最大长度。一些用户可能会希望使用更长的密码,这样的限制可能让他们感到不便。如果最大长度设置过短,用户可能会因为无法输入足够长的密码而感到沮丧。通常,设置20到25个字符的最大长度会更为合理。 总结通过结合使用 191 - WPF总结好的,现在我们完成了这一章节,虽然这章节内容比较长、复杂,但也非常吸引人。因为我们终于能够看到一个用户界面,终于可以看到我们编写的代码实际呈现出来了。在这部分内容中,我们学习了一些相对复杂的知识点,比如数据绑定、依赖属性、 所以即使现在看起来有些困难,但只要你不断地练习,自己多做几次,最终你会掌握这些内容的,别担心。 好了,这就是本章节的内容。我们学习了如何创建用户界面,如何将这些内容串联起来,看到我们到目前为止所学的所有代码如何汇聚在一起形成一个较为复杂的部分。但这并不是结束。在课程的后续部分,我们还会再次使用 WPF 来创建更多的用户界面。所以,我们下一个章节再见! |
14 - WPF(Windows Presentation Foundation)192 - 安装WPF工作负载欢迎来到本节课程。在这一节中,我们将学习如何创建一个 WPF 项目。首先,你需要打开 Visual Studio 安装程序。在左下角搜索框中输入 Visual Studio,然后选择你的 Visual Studio 版本。现在,我选择的是 2022 版,然后点击“修改”。 接下来,给它一点时间来加载。我们将进入工作负载页面。请注意,如果你是 Linux 或 macOS 用户,你将无法找到或安装 "Dotnet Desktop Development" 工作负载(即用于构建 WPF 和 Windows Forms 的工作负载)。因此,如果你使用的是 Windows 电脑,请勾选这个框,然后点击“安装”。在下载并安装该工作负载后,你就能开始使用 WPF。 然而,如果你是 Linux 或 macOS 用户,你将无法运行和构建 WPF 应用程序。原因是 WPF 是专门为 Windows 电脑设计的。因此,你有三种选择:
这点需要特别提醒。如果你使用的是 Windows 电脑,勾选此框并点击“安装”,等待它安装完成。接下来,我们将在下一个视频中设置项目。 193 - 创建WPF项目现在我们来看看如何设置一个新的 WPF 项目。再次提醒,如果你使用的是 macOS 或 Linux 系统,你将无法创建 WPF 应用程序。所以我建议你可以选择观看视频而不进行实际操作,或者使用 Windows 电脑、安装虚拟机等方式进行跟随学习。 好了,接下来我们开始吧。我们在这里创建一个新项目,首先搜索 WPF,给它一点时间加载。在这里你会看到 WPF 应用程序,它是用来创建 .NET WPF 应用程序的项目类型。我们选择它,注意这是 C# 项目,而不是 Visual Basic 或 F#,所以请选择 C# 版本,然后点击“下一步”。 接下来,我们为项目命名。接下来几节课我们将创建一个 WPF 演示应用程序,所以我会将项目命名为 "demo"。你可以将它放置在任何位置,点击“下一步”。 现在选择框架。我们打开框架选项,看看这里有 .NET Core 3.1、5、6 和 7。值得一提的是,"Core" 这个词已经被移除,因此 .NET 5、6 和 7 也可以看作是 Core 版本,但它们已经完全去除了 "Core" 的名称。我选择的是 .NET 7,它是目前最新的版本。你也可以选择 .NET 7,这是我推荐的版本。不过,如果你看到有 .NET 9 的版本,也可以尝试使用它。 接下来点击“创建”,项目就会被创建出来,创建完成后,你应该看到类似的界面。我们将在下一节课中详细探索这个界面。不过首先,我可以告诉你,你将看到两个主要的窗口:一个是设计窗口,它显示了应用程序的图形化界面,另一个是底部的 XAML 窗口,它显示的是应用程序的 XAML 代码表示。XAML 代表的是扩展应用程序标记语言(Extensible Application Markup Language),你可以直接使用它来创建用户界面元素。 好了,我们在下一节课中继续学习。 194 - WPF项目结构与代码后置文件你刚刚创建了你的第一个 WPF 应用程序。嗯,至少是项目对吧?现在你应该看到的界面是这样的:在右侧是“解决方案资源管理器”,然后你有一个设计窗口,在底部是 XAML 窗口。如果你没有看到这种布局,可以点击 Visual Studio 的 窗口 菜单,选择 重置窗口布局,将布局重置为默认设置。 在我们开始创建图形用户界面并深入研究 XAML 和设计器之前,我想简要介绍一下 WPF 项目的整体结构。正如我所说,这是我们项目的可视化表示。所以当我们运行调试模式时,只需编译并启动应用程序,你就会看到一个应用窗口打开,这就是我们的项目。 这里有一个小工具栏,它仅用于调试目的。也就是说,如果你直接启动可执行文件(编译后的文件),它是不会显示这个工具栏的。它只是用于调试时查看应用程序的状态。 目前你的应用程序是空的,但它按预期工作。很好,让我们稍微检查一下解决方案。在 解决方案资源管理器 中,我们可以看到 MainWindow.xaml 文件。如果我们打开它,你会看到一个所谓的“代码隐藏”文件,它位于 XAML 文件后面。这里是我们的 XAML 文件,而在它后面有一个同名的代码文件,它的扩展名是 接下来我们打开 MainWindow.xaml.cs,这个文件是主窗口的代码隐藏文件。查看这个文件,我们可以看到这是一个名为 再看一眼 解决方案资源管理器,你还可以看到一个 App.xaml 文件及其代码隐藏文件。这是我们应用程序的入口点,就像控制台应用程序中的 另外需要知道的是,我们可以有多个窗口。现在我们只有一个主窗口及其相关的代码隐藏文件,但我们可以创建更多的窗口,每个窗口都有自己的代码隐藏文件。 好了,现在你已经准备好开始使用 XAML 创建你的第一个图形用户界面元素了。让我们在下一节课中开始吧。 195 - 创建我们的第一个GUI元素欢迎回来。现在我们来看看我们当前的应用程序。正如我在上一节视频中提到的,当前应用程序的图形用户界面的 XAML 表示就在下方的 XAML 窗口中。如果我稍微放大一些,你可以看到一些键值对。这看起来有点像 HTML,在这里我们有一个 现在,在 你现在看到的左侧是 工具箱,如果你没有找到它,你可以通过点击 视图 菜单中的 工具箱 来打开它,或者你可以直接按下快捷键 Ctrl + Alt + X。按下这些快捷键后,工具箱将出现在左侧。 现在,让我们打开 常用 WPF 控件。我们选择一个 Label 标签,并将它拖放到我们的 WPF 应用程序中。我这样做是为了向你展示,当我们创建一个新元素时,比如这个标签(Label),你会看到它在 XAML 表示中创建了一个新的元素。所以,在 我们创建了一个标签,它有一些新的属性或者键值对,比如 我之所以要向你展示这些,是因为我们可以使用 C# 来动态地调整这些属性的值。比如,我们可以通过 C# 获取这个标签(Label),访问它的 过去,在开发 Windows Forms 应用时,我们只需要打开工具箱,把所有的控件拖拽到应用程序中。而在 WPF 中,我们利用 Grid 系统来保持应用的响应式布局。这个系统比 Windows Forms 更现代,也是一种动态构建应用程序的方式,所以你仍然可以拖放元素,但这不是非常推荐的做法。这是因为我们不希望使用那些“魔术式”的硬编码位置、外边距之类的设置。例如,左边距显示为 380,像这种硬编码的位置我们不想使用,我们希望它尽可能响应式。 所以,你目前需要记住的一点是,每当你在应用程序中创建一个新的图形元素时,你会看到它在 XAML 表示中作为一个新元素被写下。每个元素都有许多可以调整的属性,这一点非常重要。 196 - 创建带有列和行的网格在本节课中,我们将探讨 Grid 控件。首先,我想提到一点,在第八行(例如,我的代码中)你可以看到标题是 Main Window,这正是你在图形用户界面中看到的 Main Window。我们可以为其设置一个高度和宽度,比如150和800。如果我们想调整这些值,只需在这里修改数字即可。现在,应用程序的大小被固定为500像素。我只是想展示一下,你完全可以调整这些属性。 接下来,我们有一个 Grid,但目前它看起来不像一个网格。Grid 是由列和行组成的。可以把它想象成 Excel 表格,例如,你有三列,然后有三行或者五行。如果我们想要在 Grid 中创建行,我们需要添加 Column Definitions 和 Row Definitions。 首先要提到的是,我们确实有不同的标签。在这里,我们有一个 Grid 标签。这是一个开标签(open tag),与开标签对应的,我们始终有一个闭标签(closing tag),可以通过查找斜杠( 创建列定义为了设置我们的网格列定义,我们只需打开一个新的标签,创建一个开标签 Grid.ColumnDefinitions,然后自动添加闭标签。这样,我们节省了很多时间和精力。 在 Column Definitions 标签内,我们可以创建我们的列定义。让我们创建一个新的 ColumnDefinition 标签。我们可以为第一列指定一些属性。WPF 中有些文本是自闭合的,所以我们可以简化它。无需使用开闭标签,我们可以将开标签和闭标签合并,并在标签末尾加上斜杠。这样,我们就不能在标签中间添加内容,但仍然可以使用该列,并且可以为其添加一些属性。 例如,我们将该列的宽度设置为100像素。在这种方式下,我们就创建了第一列。 查看效果现在,让我们看一下应用程序的效果。虽然你可能看不出太大区别,但我们继续往下做。接下来,我们复制并重复这些列定义,总共创建三列,其中中间的列宽度设置为200像素。 当你回到设计界面,你会注意到现在有了三列,分别是第一列、第二列和第三列。 创建行定义与列定义类似,我们也可以创建行定义。在 Grid 标签的同级位置,或者作为 Grid 标签的另一个子标签,我们打开 Grid.RowDefinitions 标签。闭标签会自动生成,我们点击 Enter 键后,就可以在其中创建行定义。 我们为每一行创建一个 RowDefinition 标签,并且设置它为自闭合标签。在这些行定义中,我们要设置每行的 Height,比如将第一行的高度设置为50像素。 接下来,我们复制这行定义,创建第二行,并将第二行的高度设置为100像素。 查看行效果现在,让我们再次查看应用程序。你会看到有两行,第一行的高度是50像素,第二行的高度是100像素。 问题的根源但是,你可能会注意到,这两行的高度并没有完全反映出我们所设置的值。例如,你可能希望看到第二行的高度是100像素,但实际显示的却比这个值大很多。问题出在 WPF 中,所有行或列的总高度或宽度并不会直接等于你在 XAML 中指定的数值。如果总和(例如,这里是150)没有匹配应用程序的总高度(例如,450像素),那么 WPF 会自动为最后一行或列分配剩余的空间。因此,最后一行的高度会填补剩余的空间,确保整体的高度和宽度与我们在应用程序中指定的值一致。 动态布局现在让我们看看如何使布局更具动态性,因为我们并不希望在很多情况下硬编码宽度和高度值(例如100像素、250像素等)。这样做在某些场景下有用,比如创建边框,但在大多数情况下,我们希望布局尽可能动态。接下来的课程中,我们将深入探讨如何使这个布局更灵活,这一点非常重要,请一定要关注! 197 - 固定、自动与相对大小让我们深入探讨一下 Grid 系统,以及如何使用它来定位元素,而不需要依赖任何硬编码的数值(像魔法数字一样)。在我们之前的代码中,我们已经创建了行和列,并且添加了一个 Label 控件。现在,我们将使用 Grid 系统来定位这个标签。 设置标签的位置首先,在我们的 Label 控件中,我将删除所有属性,除了 Content 属性。现在,标签只有 Content 属性。如果我们想要定位它,可以使用 Grid.Row 属性。通过这个属性,我们可以指定标签所在的行。 例如,假设我们有两行:第一行的索引是 0,第二行的索引是 1。如果我们希望将标签放入第二行,我们只需将 Grid.Row 设置为 1。此时,标签会自动移动到第二行。 设置列的位置同样的方式适用于列。如果我们希望标签位于中间的列,我们可以使用 Grid.Column 属性,指定列的索引。假设我们有三列,第一列的索引是 0,第二列的索引是 1,第三列的索引是 2。如果我们想将标签放在中间的列,只需将 Grid.Column 设置为 1。这样,标签就会被放置到中间的列。 自动调整列和行的大小接下来,我将展示如何使用 Grid 来自动或相对地设置列和行的大小。我们将创建第三行,并将其高度设置为 50 像素,这样我们就有了三行三列。 然而,目前应用程序看起来并不像一个均匀的网格。如果我们希望中间的列和行(即包含标签 Hello World 的地方)能够自动调整大小,我们可以使用 Auto 关键字。通过在列或行的高度或宽度设置为 Auto,它将根据内容的大小自动调整。例如,如果我们调整 Hello World 的文本为更长的内容(比如改为 “Abcdefg”),那么包含该标签的列的宽度也会自动增加,以适应更长的文本。 使用相对尺寸另外,还有一个非常有用的符号是星号(
同样的方式也适用于行。如果我们设置了第一行的高度为 50 像素,最后一行的高度也是 50 像素,那么中间的行将自动占据剩余的空间。如果我们设置总高度为 450 像素,第一行和最后一行分别占据 50 像素,那么剩下的 350 像素将会被分配给中间的行。 总结恭喜你,现在你已经学会了如何使用 Grid 系统来动态定位和调整元素,而不需要依赖硬编码的定位、填充或边距等数值。通过这种方法,你可以创建更灵活、响应式的布局,适应不同的屏幕大小和内容变化。 198 - 创建一个完美的网格使用相对大小创建完美的网格如果我们想要创建一个完美的网格布局,实际上非常简单。我们可以对每一行和每一列使用相对尺寸来实现。现在,我将为所有的列和行分配相对大小(使用星号 设置列的相对大小首先,我们为每一列设置相对大小,方法是为每一列的宽度使用 <Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions> 通过这种方式,我们已经为每一列分配了相同的空间,每列的宽度会根据总宽度均匀分配。 设置行的相对大小接下来,我们对每一行进行相同的设置,使用 <Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions> 结果现在,所有的列和行都使用了相对大小 总结通过将 199 - WPF挑战:重建此GUIWPF 挑战:创建布局在这一课中,我们将继续专注于布局,特别是网格布局。你将有一个挑战,尝试自己重现一个简单的 WPF 应用程序布局。这主要是为了让你对使用 XAML 创建图形用户界面变得更加熟悉。 挑战描述你需要创建一个界面,其外观与我所创建的布局类似。这个练习的重点是让你习惯在 WPF 中使用网格布局(Grid)。我们暂时不关注功能实现,而是专注于布局的设计。 布局要求
提示
创建步骤
完成后在完成这个挑战后,你应该能够看到一个包含两个标签和两个按钮的布局,所有控件根据你在 XAML 中定义的行和列位置进行自动排列。通过使用相对布局方式,你的界面将在不同的屏幕尺寸下自适应调整,保持响应式。 如果在完成这个任务时遇到困难,可以查看下一节课,我们将从头开始一起做一遍。 200 - WPF挑战解决方案:重建此GUI本节讲解:布局挑战及列和行跨越在本节课程中,我们将继续探讨上一个视频中的挑战,并一起重现之前创建的布局。你将学到如何通过 XAML 使用网格布局来控制元素的位置,以及如何利用网格的属性来使布局更具响应性。 布局要求首先,我们需要通过增加更多的行和列来创建外部间距。这是为了确保我们的布局在视觉上更加整洁,并且在需要时能为控件提供足够的空间。
步骤说明
实现布局通过以上步骤,你应该能创建出一个具有外部边距的网格布局,内部包括两个标签和两个按钮。你可以看到标题和样本文本标签自动适应列宽,并且按钮在底部对齐。 列和行跨越接下来,你将学到如何通过 ColumnSpan 和 RowSpan 来让控件跨越多列或多行。这样,当你增加内容时,控件能够自动扩展其宽度或高度,而不被限制在单个列或行内。 例如,如果你希望标题标签跨越多个列以适应更长的文本,可以使用 <Label Content="Title of App" Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="3" HorizontalAlignment="Center" VerticalAlignment="Center" /> 通过 总结通过这个练习,你应该对使用 XAML 来设计布局变得更加熟悉。你学会了如何:
在下节课中,我们将继续深入探讨如何使用 ColumnSpan 和 RowSpan 来进一步提升布局的灵活性。 201 - 列跨越与行跨越本节讲解:列跨越和行跨越在本节课程中,我们将继续深入学习 什么是 ColumnSpan 和 RowSpan?
在 实现 ColumnSpan 和 RowSpan我们以示例文本
观察结果在 XAML 代码中应用 当你运行应用时,界面会呈现一个干净的布局,去除了辅助线,最终呈现的应用看起来如下:
这就是你使用 总结通过本节课程,你已经学会了如何使用 ColumnSpan 和 RowSpan 来调整控件的布局,使其能够跨越多个列或行。你也学会了如何通过这些属性来动态调整控件的大小和位置,确保它们适应不同的内容大小。 接下来,我们将进入下一阶段,开始学习如何结合 C# 编程来为你的应用程序添加功能。 祝贺你完成了网格系统的学习!这是一个非常好的起步,接下来让我们继续探索更多精彩的内容。 202 - 用C#创建GUI元素在 WPF 中使用 C# 动态创建元素在 WPF 中,你不仅可以使用 XAML 来定义界面元素,还可以通过 C# 代码动态地创建和操作这些元素。今天我们将介绍如何在 C# 中使用代码创建、设置位置并将元素添加到布局中。 创建按钮并将其添加到网格中在本节中,我们将展示如何在 C# 中创建一个按钮并将其动态添加到 1. 重置布局首先,我们将重置窗口布局,确保我们可以重新开始,并且能够访问 this.ResetLayout(); 通过此步骤,我们将移除所有按钮,并仅保留 2. 定义按钮并设置属性接下来,我们将在 在 C# 中,我们首先需要创建一个按钮实例。每当我们创建一个控件时,都需要使用 C# 的面向对象方式进行实例化。例如: Button myButton = new Button(); 这样,我们就创建了一个新的按钮对象 3. 设置按钮的内容按钮的内容可以通过设置 myButton.Content = "A"; 这样,按钮上就会显示文本 "A"。 4. 设置按钮的位置接下来,我们需要将按钮添加到 Grid.SetRow(myButton, 3); // 将按钮放置在第三行
Grid.SetColumn(myButton, 4); // 将按钮放置在第四列 5. 查找并访问
|
15 - WPF项目:货币转换器第一部分232 - WPF货币转换器项目概述与设置欢迎回来。那么,我们来看看在这个视频中我们将构建的内容。我们将使用C#和WPF来构建这个应用程序。WPF(Windows Presentation Foundation)将允许我们创建一个货币转换器,正如你在这里看到的,界面上有图像和一个漂亮的输入框,我们可以在其中输入数值,然后可以将一种货币转换成另一种货币。好的,但我们从这个简单的例子开始。正如你看到的,我可以将我在这里输入的任意金额转换成另一种货币。比如从美元到欧元,或者从印度卢比到欧元,来看一下。我们可以看到₹1,337等于€15.72,或者$0.73,大概是这样的。然后我可以点击清除按钮,以清空数据并重新开始。那么,我们来看看€420兑换多少美元。我们可以看到,它兑换成476美元。好了,现在我们先用WPF构建这个界面,然后再添加功能。我们将在后台代码中进行这些操作。因此,首先我们创建一个新的项目。为了让它正常工作,我们需要确保一件事,就是我们有一个独立的组件。所以你可以去安装“NuGet包管理器”这个组件。你可以在这里搜索它,然后安装它。这个是我们所需要的。此外,如果你需要其他的功能,可能还想安装SQL Server,或者ASP.NET,当然,如果你需要使用ASP.NET的话。但在这里我们不会使用ASP.NET,暂时就这些。所以你可以启动你的Visual Studio Community版,比如我的是2019版,但其他版本也应该是一样的。然后你可以继续创建一个新的项目。重要的是要选择WPF应用程序 .NET框架, 你可以在这里搜索它,并一直滚动到找到WPF .NET框架,因为我们需要这个模板。选择这个模板,给它命名,我将它命名为“currency_converter_static”,因为我们将使用静态数字和货币转换的值,然后我们就可以创建这个项目了。顺便提一下,如果你想要跟随一个逐步的指南,包括代码和所有内容,你可以点击视频描述中的链接。那里会有这个博客文章的链接,里面包括所有的代码、许多截图以及非常详细的逐步指南,你可以在观看视频的同时跟着做。 项目创建完毕后,你会看到一个主窗口的XAML文件被打开,同时也会看到主窗口的代码文件。你应该了解这些文件,因为你至少需要理解C#的基础知识,并且要明白XAML部分的内容,它是WPF的部分。WPF实际上是一种XML格式的语言。所以我们将使用XML来创建我们的UI。XAML中定义的内容会被翻译成界面,基本上我们要重建视频开头展示的界面。如果你想查看项目中还有什么文件,可以打开解决方案资源管理器,你会看到你有一个 接下来,我们来看一下主窗口的XAML文件。你会看到,默认为我们创建了一个特定高度和宽度的窗口。如果你现在运行这个应用程序,你将看到一个空的窗口,它的高度为450像素,宽度为800像素,正如你在这里看到的那样。这基本上就是我们的应用程序。如果你愿意,你可以继续拖放一些元素到窗口中。例如,从工具箱里拖动一个按钮进去,然后重新运行应用程序,你会看到按钮出现在窗口中。这个按钮目前什么都不做,但你可以看到它位于网格内,并且其内容显示为“按钮”,你可以将其更改为“Hello”。然后,在顶部,你可以看到这个按钮现在显示为“Hello”而不是“按钮”。然后,按钮的一些属性会自动分配给它,比如水平对齐方式,它是左对齐的,还有边距属性,比如左边距为388像素,顶部边距为116像素。如果你拖动这个按钮,你会看到这些数值会发生变化。它的垂直对齐是顶部对齐,并且它的宽度是75像素。你可以随时将这个宽度改为100像素,那样按钮就会变得更宽。好的,但我不打算以这种方式进行操作,因为,当然,我可以通过拖放元素来做这些,但我更喜欢编写代码来控制UI,因为这给了我更多对UI的控制。正如我所说,你当然可以打开工具箱,查看你可以在WPF项目中使用哪些控件。工具箱顶部显示的是常用控件,如指针、边框、按钮、复选框、图片等,然后是所有WPF控件,里面有更多控件可以使用。你还可以看到拖放这些元素到项目中的时候,自动为它们分配了一些属性。例如,如果我拖一个标签控件进去,你会看到它有 好了,让我们开始吧。 233 - WPF货币转换器:矩形与渐变欢迎回来首先,我将进行一些更改,我要修改这个窗口的标题。我将它从“main window”改为“currency converter”(货币转换器)。接下来,我将调整窗口大小,设置为 现在,由于设置了 了解窗口的 XML 名称空间和布局接下来,让我们来看看窗口的结构。我们正在使用 <Grid>
<Grid.RowDefinitions>
<RowDefinition Height="62"/>
<RowDefinition Height="83"/>
<RowDefinition Height="54"/>
<RowDefinition Height="105"/>
<RowDefinition Height="150"/>
</Grid.RowDefinitions>
</Grid> 如你所见,这里我们有了五个行定义,每一行的高度都已指定。接下来我们要定义列,但暂时我们只关注行。每一行都将在 添加内容到网格中现在,我们开始往网格中添加内容。我打算从添加一个 <Border Grid.Row="2" Width="800" BorderBrush="Red" BorderThickness="5" CornerRadius="10">
<Rectangle Fill="Red">
<Rectangle.Fill>
<LinearGradientBrush StartPoint="0,0" EndPoint="1,0">
<GradientStop Color="#EC2075" Offset="0"/>
<GradientStop Color="Red" Offset="1"/>
</LinearGradientBrush>
</Rectangle.Fill>
</Rectangle>
</Border> 我们创建了一个 渐变色的设置在这个矩形内,我们使用了
使用
|
16 - 使用数据库与C240 - 数据库简介数据库章节介绍欢迎来到数据库章节。在这一章中,我们将学习如何使用数据库,或者说“数据库”(根据你的发音习惯而定,两个都可以)。在这里,我们将深入了解如何在程序中使用大量数据、如何创建数据,甚至如何建立一个数据库,以及与之相关的各个组成部分。这是编程中非常重要的一部分。如果你想成为一名全职的 C# 开发者,那么了解如何创建和使用数据库非常关键。更重要的是,你将学会如何从数据库中获取数据,并对这些数据进行处理。 在本章结束时,你将具备如何与数据库交互的基本能力,并能够在你的程序中高效地使用这些数据。接下来,我们还将通过 LINQ 来提升效率,这部分内容将在下一章中介绍。但问题是,在掌握 LINQ 之前,你需要先了解数据库的基本知识。因此,让我们从数据库的基础知识开始,然后再进入更高级的部分。 学习目标
我们将在接下来的内容中深入探讨这些概念,并通过实践帮助你掌握数据库的使用方法。 241 - 设置MS SQL Server和VS进行数据库工作数据库设置与连接讲解欢迎来到这节课程。我是 Yannick,Dennis 是我的共同讲师。时间已经过去了一段时间,我们觉得有必要更新这节课程。所以,在这节课中,我们将介绍如何设置 SQL Server 实例以及如何在 Visual Studio 中设置连接。 在旧版本的这节课程中,我们使用的是 Microsoft SQL Server 2017,但该版本已不再维护,现在我们开始使用 Microsoft SQL Server 2019 Express。所以,如果你跟随这个视频操作,请搜索 SQL Server 2019 Express,并点击第一个链接进行下载。你也可以直接访问微软官网,向下滚动直到看到如下窗口,选择你的语言。我会选择英语,然后点击下载,开始下载 SQL Server 2019 Express 安装程序。 一旦下载完成,打开它,开始安装实例。选择“自定义”安装,然后选择一个文件夹用于安装,点击“安装”。安装可能需要一些时间,安装完成后,你将进入 SQL Server 安装中心,在这里我们可以设置并创建一个新的 SQL Server 独立安装实例。选择第一个选项,点击进入。接下来将弹出一个窗口,开始配置 SQL Server。点击“下一步”,等待加载完成。继续点击“下一步”,直到看到这个窗口,显示“执行 SQL Server 新安装或向现有安装中添加内容”。 由于我已经多次安装过 SQL Server,因此这一步对我来说可能稍显不同。但我们还是选择进行全新的 SQL Server 2019 安装。选择第一个选项,点击“下一步”。接着,接受许可协议并再次点击“下一步”。此时可以选择一些功能。我会给你一个我正在安装的功能概览:将安装数据库引擎服务,取消勾选“机器学习服务和语言”选项,这样可以节省很多空间。SQL Server 复制功能保持勾选,全文和语义提取功能也勾选。对于共享功能,默认所有选项都勾选了,确保你知道我正在安装哪些内容。 继续点击“下一步”,选择默认实例,并获得 MySQL 服务器名称作为默认实例名称。你也可以选择自己的名字。我会从之前视频中复制 Pantagruel 的 SQL 作为实例名称,以便你在接下来的教学中明白我为何选择这个名称。接下来,实例 ID 会自动更新为“Pantagruel's SQL”,然后点击“下一步”,等待加载完成。 接着,选择 SQL Server 数据库引擎并点击“下一步”。此时可以设置身份验证模式。我们可以选择 Windows 身份验证模式,但我建议选择混合模式,这样你就可以设置自己的账户和密码。请写下密码并确保记住它,以便以后登录。为了方便演示,我会使用简单的密码“123456”。不过,在实际应用中,你应该选择更复杂的密码,尤其是在生产环境中。 接下来,点击“添加当前用户”,你会看到这里显示的是我的计算机用户名。通常情况下,安装时它会自动选择当前用户,所以请确保这个字段不为空。然后点击“下一步”。最后,你将看到一个屏幕,显示“完成 SQL Server 2019 安装,产品更新成功”。一切安装完成后,点击“关闭”按钮。 安装 SQL Server Management Studio现在,我们已经安装了 SQL Server 实例,但如果我们希望以图形化界面查看和管理数据库、表格、条目,执行 SQL 查询等操作,就需要安装一个工具,叫做 SQL Server Management Studio(SSMS)。重申一下,刚刚安装的 SQL Server 本身并没有图形用户界面。如果你希望通过 GUI 来管理数据库,你需要安装 SQL Server Management Studio。 点击相应的链接,就会打开一个新的页面。你可以看到“下载 SQL Server Management Studio”的选项,点击免费下载安装。下载完成后,按照提示进行安装,安装过程中无需特别注意任何选项。安装完成后,打开它,搜索“SQL Server Management Studio”或简称“SSMS”,你就能看到 Microsoft SQL Server Management Studio 18。 打开 Microsoft SQL Server Management Studio 后,你应该会看到一个“连接到服务器”的窗口。你可能已经看到服务器名称被自动填充,如果没有,可以点击下拉箭头,选择“浏览更多”并打开“数据库引擎”,找到你之前设置的 SQL Server 实例名(在我的例子中是 Pantagruel's SQL)。选择该实例并点击“确定”,然后在身份验证方式中选择 SQL Server 身份验证,输入设置时的管理员账户(默认是 SA)以及你的密码,然后点击“连接”。 现在,你应该已经成功连接到 SQL Server 实例,并在左侧看到“数据库”节点。此时你可能只看到系统数据库,因为我们还没有创建任何自定义数据库。一旦创建数据库,它将显示在此处。SQL Server Management Studio 是一个非常方便的工具,帮助你以可视化的方式查看和管理数据库。 在 Visual Studio 中设置 SQL Server 连接接下来,我们将在 Visual Studio 中设置与数据库的连接。此时,我使用的是 Visual Studio 2019,但对于任何版本的 Visual Studio,过程应该是类似的。在 Visual Studio 中,找到“服务器资源管理器”面板,如果你没有看到它,可以点击“视图”然后选择“服务器资源管理器”来打开它。 另外,注意 SQL Server 对象资源管理器,你也可以通过它连接到 SQL Server 或创建新数据库。但现在我们主要使用服务器资源管理器。在“服务器资源管理器”中,右键点击“数据连接”,然后选择“创建新的 SQL Server 数据库”。 正如之前所说,我们并没有创建新的数据库,只是安装了 SQL Server 实例。所以,现在我们来设置一个新的数据库。在“服务器名称”下拉框中选择你的 SQL Server 实例名称。如果你幸运的话,你可能会看到它。如果没有,可以在 SQL Server Management Studio 中右键点击你的 SQL Server 实例,选择“属性”,在这里可以看到服务器名称,复制它,然后回到 Visual Studio 填写这个名称。 接着,使用 SQL Server 身份验证,输入管理员账户(如 SA)和密码,然后选择“保存密码”。为数据库命名,我这里命名为“panel DB”,你也可以取其他名字。点击“确定”,就创建了我们的数据库。你可以看到它出现在“数据连接”下。 如果你再回到 SQL Server Management Studio,右键点击刷新,你将看到新创建的数据库(panel DB)。此时,我们已经成功创建了一个 SQL Server 实例,连接它,并通过 Visual Studio 创建了一个 SQL 数据库。 总结到此为止,我们已经完成了 SQL Server 实例的安装、配置和数据库创建过程。接下来,你可以开始在项目中使用这个数据库进行开发和管理了。感谢观看! 242 - 数据集和表的介绍与设置欢迎回来。在本视频中,我们将创建我们的第一个表格。在我们开始之前,我想向你展示一下本章节的最终成果。正如你所看到的,我这里有一个名为“Main Window”的窗口,其中包含了多个不同的控件。所以我们有标签控件,有列表框,还有多个按钮和多个列表框。你在这里看到的是多个动物园(zoos),嗯,这些名字可能已经被我稍微修改过了,但你可以在这里选择一个动物园,你将看到该动物园内的相关动物。 假设我们有一个位于东京的动物园,里面有鲨鱼,嗯,实际上有多条鲨鱼,还有鳄鱼、壁虎、鹰等等动物。而在纽约,我们有不同种类的动物。纽约的动物园比较小,只有一只鳄鱼,但后来我们决定给纽约动物园添加一只猴子。所以我们将把这只猴子添加到纽约动物园。你可以看到,这里列出了相关的动物。 然后,一旦我点击右侧的某个动物,或者点击某个动物园,底部的文本框将改变其值。它会将城市名称的值显示在文本框中。例如,这里我有“Cairo 2”,我想更新这个值。所以我将删除数字“2”,然后更新这个动物园。现在,你可以看到,“Cairo”已经更新。我也可以对我的“米兰动物园”做同样的操作。我将把“米兰”更新为“blah blah zoo”(一个虚构的动物园名称),然后彻底删除它。同样的操作我也会对我们的“44动物”进行,完全删除这只动物。 总体来说,你可以看到,我们现在有多个不同的元素需要考虑。在这里,我们使用了多个不同的表格。例如,我们有一个“动物园”表,一个“动物”表,以及一个将这两个表连接在一起的中间表,这将是我们下一步挑战的内容。接下来,我们将构建这个用户界面,虽然它不算是最漂亮的,但它非常实用,能够帮助你理解数据库的基本构建和如何在 C# 和一些 SQL 中使用数据库。 好的,那我们就开始吧。所以我们返回到 Visual Studio。你可以看到,我在这里有两个连接,而你应该只有一个连接。我还创建了另一个连接,这是我刚才用来展示演示的连接。接下来,我们将使用我新创建的连接。正如你所看到的,这里还没有任何表格,所以我们需要创建一个新表格。右击“表格”选项,选择“新建表格”,这时会弹出一个小窗口。 接下来,我将把服务器资源管理器稍微隐藏一下,让它不那么干扰。现在看起来好多了。我们的小表格有一个 ID 列,类型为整数,不允许为空,并且没有默认值。你可以看到这里有一个小钥匙图标,这意味着这是主键列。你基本上是在这里创建表格的列。我创建了一个名为“ID”的列,现在我将创建另一个列,命名为“location”(位置)。然后,我不允许它为空。接下来,我需要更改数据类型。虽然这里的数据类型和 C# 中的类型不完全相同,但它们有很多相似之处。例如,你可以看到日期、字符、位(bit)、整数等类型。我希望使用的是“varchar(50)”类型,这允许我在“location”列中存储最多50个字符的字符串。 另一个需要更改的重要设置是,我希望将 ID 列设置为“身份列”。我会在左侧属性面板中选择“身份列”(identity specification),然后将“是否为身份”设置为“是”。这样,每次插入新记录时,ID 会自动递增。这意味着每一条记录都会有一个唯一的 ID,就像我们在继承示例中提到的通知 ID 等一样。 现在,我们基本上完成了表格的设置。接下来,我要更改表格的名称。我不希望它叫“table”,而是想给它取一个更合适的名字——“Zoo”表格。注意,我使用了单数形式的“Zoo”,而不是复数形式“Zoos”,因为有很多理由支持这种做法。如果你有兴趣了解更多理由,可以查阅 StackOverflow 上的一篇精彩回答。 好了,我们现在有了一个名为“Zoo”的表格,但它还没有创建出来。你可以看到现在我们还处于 T-SQL 选项卡中,这里是创建表格的地方。当你点击顶部的“更新”按钮时,SQL 代码将会执行,进而在数据库中创建名为“Zoo”的表格。该表格将包含一个“ID”列(类型为整数,且不允许为空,作为主键),还有一个“location”列(类型为“varchar(50)”并且不允许为空)。 点击“更新”按钮后,你会看到数据库没有立刻变化。如果刷新一下,你就会看到“Zoo”表格已经出现了,并且该表格包含“ID”和“location”两个字段。 接下来,我想访问这个“Zoo”表格并修改它的值。你可以右击“Zoo”表格,然后选择“显示表格数据”(Show Table Data),这样就会弹出一个表格数据视图。在这里,你将看到“ID”和“location”两个字段,目前的“location”是空的(null),因为我们还没有插入数据。接下来,我将插入一些位置数据,比如“New York”。你会看到,ID 为 1 的记录已经插入,location 设置为“New York”。接着,我可以继续添加其他位置数据,比如“Tokyo”,“Berlin”,“Cairo”和“Milano”。当然,你可以继续添加更多的城市。 至此,我们已经在 Zoo 表格中插入了多个不同的城市,每个城市都有一个唯一的 ID,ID 从 1 到 5。 接下来,我们将创建一个新的项目,因为我们已经在服务器资源管理器中配置好了表格,但还没有创建实际的项目。我们新建一个项目,命名为“WPF Zoo Manager”,然后点击 OK。 创建好 WPF 项目后,我们接下来要做的是添加数据源。点击“数据源”按钮,如果你没有看到这个选项,可以右击项目名称,选择“添加数据源”。然后,选择“数据库”作为数据源类型,点击“下一步”,选择数据集作为数据库模型。接下来,选择你之前创建的数据库连接。 然后点击“完成”,这样你就可以看到你的数据源已被添加。接下来,你可以右击“Zoo”表格,选择“编辑数据集”进行设计。这里会显示数据库设计,展示“Zoo”表格的结构和操作。 我们还需要在主窗口中设置 SQL 字符串。在代码中,我们需要使用“配置管理器”(Configuration Manager)来读取连接字符串。为了让它能够正常工作,我们需要添加一个对“System.Configuration”程序集的引用,然后在代码中导入“using System.Configuration”。 设置完这些后,我们就可以在下一步视频中继续处理与 SQL 部分相关的应用程序开发了。 243 - 关系或关联表欢迎回来!在本视频中,我们将创建两个表,并设置它们,以便能够编写我们在上一个视频中看到的那个小程序。好了,让我们开始吧,首先在这里创建一个新表。我打开服务器资源管理器中的“表”部分,选择正确的数据连接,然后创建一个新表。点击“添加新表”。新表应该命名为 现在,让我们往这个表中添加一些数据。点击“显示数据”或者“表数据”,然后在这里我将添加一些动物。例如,我将添加一条鲨鱼,然后是小丑鱼、猴子、狼、壁虎、鳄鱼、猫头鹰和鹦鹉。就这样,我输入了这些八个动物的名字。你也可以使用其他名字,这只是一些常见的动物名称。可以看到,每个动物都有一个唯一的ID,并且每次输入一个新的值时,ID都会自动递增。现在我们已经在表中有了这八个动物。 接下来,我想做的是创建一个动物和动物园之间的关系。这样,鲨鱼可以出现在纽约的动物园里,也可以出现在迈阿密、米兰或柏林的动物园里。类似地,狼可以在迈阿密出现,也可以在纽约出现,但不一定是这样。所以我需要创建一个关系表,用来连接动物和动物园。接下来,让我们创建这个新表。它将有一个ID,并且包含一个 接下来,我们需要做一些配置,才能查看这些数据,或者让这些动物和动物园的数据可用。为此,我们需要更新数据源。为此,我将按下 接下来,我们回到服务器资源管理器,修改 假设我们已经知道了每个动物和动物园的关系,接下来可以进行数据填充。比如我们可以设置 现在,我们如何查看这些数据呢?我们可以创建一个新的查询。我点击“新建查询”,然后编写SQL查询语句。在这个查询中,我可以选择所有来自 总之,这就是我们如何通过外键和关系表来管理动物和动物园之间的关系。在下一个视频中,我们将使用C#编写相应的代码来操作这些数据。 244 - 在ListBox中显示数据开始构建用户界面(UI)欢迎回来。现在你已经知道如何创建你的第一个查询、如何设置数据库等,接下来我们就可以开始处理UI并做一些疯狂的事情了。所以我们先来看看主窗口的XAML文件。我们可以像这样保持文件打开,稍微关闭一下其它部分,这样可以获得更多空间,实际上这样就好了。好,首先我们需要什么?我们需要一个列表框。在这里我使用了一个 为了在主窗口中添加内容,我先放大一点。实际上我们不需要太大的缩放,因为我们只需要查看代码是如何添加的。接下来,我将开始拖放所需的组件。首先,我需要一个标签(label),所以我拖放一个标签并将它放置在左上角,标签内容修改为"Zoo List"。然后,我将添加一个 设置
|
17 - WPF项目货币转换器第二部分252 - WPF货币转换器:构建一个带数据库集成的货币转换器欢迎回来。好的,让我们开始吧。今天视频中我们要构建的是一个货币转换器,正如你所看到的那样。在第一部分视频中,我们已经构建了一个基本的货币转换器,大家应该已经看过了。在第一部分中,我们使用了静态数据,而这一次我们将使用数据库。所以我们会扩展这个功能,添加两个不同的标签页。一个标签页用于货币转换器,另一个标签页用来输入不同的汇率或特定货币的汇率值。 例如,这里欧元是基本货币,€100 就是 €100,十分基础;但对于 100 美元,我能兑换 85 欧元;对于 100 英镑,我可以兑换 111 欧元。这样数据库就设置好了。你当然可以稍后根据需要调整数据库。你可以通过这个 UI 删除数据库中的条目,或者直接通过这个 UI 编辑数据库中的条目。 而且有趣的是,你将学习到数据库的基础知识。你将更多地了解 WPF。顺便说一下,我强烈建议你查看一下第一部分视频,因为我不会再详细讲解 WPF 的代码,尤其是在 XAML 中发生的内容,因为这些内容在上一期视频中已经讲解过了。你看到的是我们构建的用户界面。而现在我们引入了标签页,这是我们要介绍的一个新功能。另外,我们还有这个网格,它是一个数据网格。我们将使用这个数据网格来显示数据库中的数据。 我们将设置一个 SQL 数据库,获取数据库中的数据并将其显示在这里。我们将能够进行调整,比如编辑和删除等操作。好了,假设现在我有这三种货币,现在我可以查看 100 印度卢比可以兑换多少欧元。例如,100 印度卢比大约能兑换 1.13 欧元。我们就假设 100 印度卢比可以兑换 1 欧元。然后,我点击了这个行,US 美元的汇率发生了变化,但现在如果我创建一个新条目,比如再次创建 US 美元,金额是 88,虽然我不确定,但这不太重要,重点是核心功能。 现在你可以看到,我们在这里有了 US 美元的汇率。如果我现在切换到货币转换器标签页,我可以从这里选择这些值。这些货币名称现在会出现在下拉框里,我可以选择任何我想要的。例如,我想知道 1000 印度卢比能兑换多少欧元?结果是 10 欧元。然后,1000 欧元能兑换多少美元呢?是 1136 美元,尽管现在的汇率稍微有所不同。但没关系,货币汇率波动很大。所以你可以使用这个软件每天调整这些设置,这样你就会有每日的汇率,并可以使用这个货币转换器。当然,要做到这一点,你需要手动获取当前的汇率数据,当然你也可以直接使用谷歌来查询汇率。 但基本上,这个软件的作用是帮助你创建任何类型的数据库软件,基于你在这个视频中学到的知识,你将能够构建自己的复杂数据库软件,并且你甚至可以把它卖出去。非常棒的内容。 好了,不管怎样,我建议我们开始构建这个软件。和往常一样,至少几乎每次,我的视频下方都会有一个完整的文章,其中包含所有的代码,你可以通过文章中的指导跟着做。如果你不想手动输入代码,你也可以直接复制网站上的代码。这样你就可以按照文本形式的指南操作,所有的代码都可以找到。如果你不想手动打字,所有内容都会提供给你。 这次视频中,我们会设置一个数据库,所以你需要了解如何设置数据库。在这里你可以跟着视频学习如何设置。但当然,视频中我也会展示所有的步骤。好的,但还是建议你检查一下我的网站。那么,让我们开始吧。就像我之前说的,我会使用上一期视频中的代码。如果你没有上一期视频中的代码,可以去看那期视频并跟着做,这样会帮助你更好理解,或者你也可以从网站上复制代码,省去手动输入的麻烦。 关于 WPF 的代码我不会详细解释,因为我们主要关注数据库内容。好,准备好了吗?让我们开始调整用户界面。首先,我们需要有标签页。为了将标签页添加到我们的 UI 中,我们需要添加一个名为 TabControl 的控件。我要在哪儿添加呢?在最外层,即在 window 标签的开始位置。在我们的 window 标签中有一个 grid,这个 grid 基本上就是我们整个 UI 的框架。在上一期视频中,我们的 UI 是一堆堆叠的面板,包含五行。现在,我要把整个界面放入一个标签页中。所以目前为止的一切都应该放在一个标签页内。 我把整个 grid 移除后,你会看到它现在只有一行。我点击了这边的减号,现在我要使用 TabControl,并给它起个名字,叫做 TabMain。接着我使用了一个叫做 TabStripPlacement 的属性,它允许我设置标签页的位置,默认设置标签页在顶部,因为在 PC 上我们通常把标签放在顶部。这样,我们就有了一个基本的 TabControl。在这个 TabControl 内部,我们可以创建多个标签页。每个标签页叫做 TabItem,我们可以给它起个名字。我把第一个标签页命名为 converter,并为它设置一个标题,告诉用户它是做什么的。 现在,我把之前的整个 grid 拷贝到这个 TabItem 中,并给它加上标题 Currency Converter。我启动软件看看效果,看看这个标签页是否能正常显示。你可以看到,现在我们有了一个名为 currency converter 的标签页,虽然它的实际名称是 TB converter,但我们在标题栏显示的是 Currency Converter。 接下来给你一个小挑战:在 TabControl 中创建另一个标签页。暂停视频并尝试一下。强烈推荐你在学习后立即动手实践。 我将创建一个新的 TabItem,命名为 TB master,并设置标题为 Currency Master。然后,我们可以从简单的内容开始,只是放一个标签,看看它是否正常显示。标签的内容设置为 Hello World。保存并刷新后,我发现它没有热重载,但没关系,我停止并重新启动程序,现在我就可以看到新建的标签页 Currency Master,里面有个显示 Hello World 的标签。 这样,你就学会了如何在 WPF 中创建不同的标签页,非常简单。 253 - WPF货币转换器:设计货币转换的用户界面欢迎回来!如您所见,我们现在要创建这个界面。它与之前的界面非常相似。我们有一个红色的框,并且顶部有粉色的文字。我们不仅仅有一个文本编辑框,也没有组合框,而是有两个输入框,按钮位置和之前的类似,底部有两个按钮,按钮的文本略有不同。此外,我们还没有图标,实际上,底部是有图标的。接下来就是一个数据网格。好,那么我们如何实现这些呢? 我将从我的网站上直接复制代码,因为我会逐行解释这段代码。您当然可以自己手动构建一切,因为实际上您可以从中学到很多东西。所以,我将给出一些构建步骤的指导。我们现在先来分析这段代码。 1. 网格布局在代码中,您可以看到我们使用了一个有五行的网格布局。换句话说,界面被划分成了五个不同的部分:
为了实现这一点,您需要定义一个网格,并为每一行设置不同的行高。例如,定义行的大小如下: <Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*" />
<RowDefinition Height="*" />
<RowDefinition Height="*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
</Grid> 这样一来,网格就会有五行,每行的高度相等,当然您可以根据需要调整每一行的高度值。 2. 复制和粘贴代码接下来,我将直接粘贴代码,并逐一解释每一部分。首先,我们需要创建一个 <Border BorderBrush="Black" BorderThickness="1">
<Rectangle Fill="Red" Width="100" Height="100"/>
</Border> 这个 接下来,您需要添加按钮和其他UI元素。例如: <Button Name="EditButton" Content="Edit" Width="75" Height="30"/>
<Button Name="DeleteButton" Content="Delete" Width="75" Height="30"/> 这两行代码就是实现图标按钮的部分。这些按钮会有相应的点击事件,允许您编辑或删除数据。确保在后台代码中定义相应的事件处理方法。 3. 处理资源文件在UI代码中,您看到有几个资源被引用,例如按钮的图标。您需要将这些图标添加到项目中的资源文件夹下。例如,您可以将图标图片添加到 <Image Source="Images/edit_button.png" />
<Image Source="Images/delete_button.png" /> 确保在项目中正确引用了这些文件,并且在代码中使用了正确的文件路径。如果没有正确设置资源文件,可能会导致程序无法找到图标。 4. 处理事件和逻辑在前端UI部分中,我们已经创建了布局和按钮,但这些按钮并不具备实际的功能。为了让它们有效,您需要在代码后台定义按钮点击事件。例如: private void SaveButton_Click(object sender, RoutedEventArgs e)
{
// 处理保存逻辑
}
private void CancelButton_Click(object sender, RoutedEventArgs e)
{
// 处理取消逻辑
} 您可以通过右键点击事件触发器,选择“转到定义”来自动生成这些方法。如果自动生成没有生效,您可以手动添加方法并链接到按钮的事件。 5. 编写数据网格相关的事件如果您有一个数据网格来显示数据,您还需要为数据网格设置选中行变化的事件处理函数。例如: <DataGrid Name="CurrencyGrid" SelectionChanged="CurrencyGrid_SelectionChanged"/> 在后台代码中,您需要为 private void CurrencyGrid_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
// 处理选择变化
} 6. 调试和测试在您实现了上述步骤后,运行程序看看它是否能够正常显示。您可能会遇到一些错误,比如找不到资源文件或未实现的事件方法。在这种情况下,检查您的文件路径,确保所有资源都正确添加到项目中。 7. 总结通过这些步骤,您可以创建一个类似的界面,并且在后台实现相关逻辑。您可能需要逐步进行调试,以确保所有功能正常工作。如果有任何疑问,建议再次查看第一部分的内容,确保您理解了UI和后台逻辑的搭建方式。 希望这些指导能帮助您顺利构建自己的货币转换器界面! 254 - WPF货币转换器:理解数据网格功能和属性介绍数据网格让我们深入了解这个数据网格(DataGrid),因为这是我们在上一个视频中没有涉及到的一个新功能。数据网格基本上是这个整个框架——从上到下的矩形框。数据网格内包含列(Columns),每列有不同的定义。在我们当前的例子中,我们有四列:一个数据网格文本列(DataGridTextColumn),两个模板列(TemplateColumn),以及另一个数据网格文本列。 数据网格的结构为了创建这样的数据网格,我们可以为其设置多个属性。例如,我们可以设置高度、宽度、边距等。在此示例中,宽度设置为480像素,背景被设定为透明。我们还设置了一个属性 数据网格也需要一个名称,便于在代码中访问。在这里,我们将数据网格命名为 选择单元格变化事件(SelectedCellsChanged)一个重要的事件是 例如,当你点击某一行并更改金额后,所选行中的数据将自动更新。虽然没有点击“添加”按钮,但系统仍然会知道是哪个单元格被选中了,这就是通过 选择单元格的单位在这个事件中,你还可以定义选择单元格的单位。比如,可以选择“单个单元格”,或者选择“整行”。在我们当前的设置中,我们选择了单个单元格,因此每次选择一个单元格时,只会选中该单元格。如果你设置为“整行”,那么点击任何单元格都会选中整行。 滚动条的设置滚动条(VerticalScrollbar)设置为 列的定义数据网格内的列有不同的定义方式。我们有一些列没有显示名称,例如显示图标的列,另一些列则显示内容,如“金额”和“货币名称”。
数据绑定与显示数据网格支持数据绑定,我们将列绑定到相应的字段。例如,“金额”和“货币名称”列绑定到数据中的 例如,我们将数据绑定到一个数据库,这样每一行会显示不同的货币金额和货币名称。通过设置 设置列属性我们设置了以下几个列属性:
编辑与删除按钮数据网格中包含两个图标按钮:一个是“编辑”按钮,另一个是“删除”按钮。这些按钮通过 数据网格的功能这些列和绑定配置确保数据网格的功能性和可用性。虽然目前的数据网格界面已经基本构建完成,但数据的具体操作还没有完全实现。接下来,我们将使用数据库进行数据填充,并通过代码来控制数据的显示与修改。 通过这些配置,数据网格不仅提供了显示数据的能力,还支持用户与数据的交互,如选择、编辑、删除操作。所有这些功能通过 255 - WPF货币转换器:为货币转换设置数据库创建新文件夹和数据库首先,我们需要创建一个新的文件夹。由于我的程序正在运行,因此我无法直接创建该文件夹,所以我首先停止程序,然后在 Solution Explorer 中右击并选择 Add New Folder。我将这个文件夹命名为 database。 接下来,在 database 文件夹中,我们需要创建一个新的项目。右击文件夹,选择 Add New Item,然后选择 Data,选择 Service-Based Database。接着,我们给它取个名字,比如 currency converter,当然你也可以将其命名为 currency converter data。此时,你会看到一个名为 currency converter.mdf 的文件。 当你双击这个文件时,Server Explorer 会在左侧打开。在这里,你会看到 Data Connections 下显示了 Tables,虽然目前没有任何表、视图或者函数。我们希望创建一个表,因此右击 Tables 选择 Add New Table。 创建表在弹出的 DBO.table design 窗口中,我们可以设计表结构。你可以直接手动创建表的列,也可以通过 SQL 代码来定义。我们使用 SQL 代码来创建表。以下是我们创建表的 SQL 代码: CREATE TABLE Currency_Master
(
ID INT IDENTITY(1,1) PRIMARY KEY, -- 主键,自动递增
Amount FLOAT NULL, -- 金额列,可以为空
Currency_Name NVARCHAR(50) NULL -- 货币名称列,最大长度50个字符,可以为空
);
在 SQL 中,主键 是用于唯一标识每一行数据的字段。如果你有大量数据,比如10 million个记录,如果没有主键,可能会出现数据混乱的情况。比如有两个名字相同的人(如John Smith),你可能无法区分他们。因此,ID 列作为主键会确保每一行都有唯一的标识。 一旦创建了表,点击 Update 来保存更改并更新数据库。接着刷新 Server Explorer,你会看到 Currency_Master 表已经创建成功,并且包含了 ID, Amount, 和 Currency_Name 三列。 设置数据库连接为了让我们的应用程序能够访问这个数据库,我们需要在 app.config 文件中设置连接字符串。打开 app.config 文件,并在其中的 部分添加以下内容: <connectionStrings>
<add name="ConnectionString" connectionString="Data Source=(LocalDB)\MSSQLLocalDB;AttachDbFilename=|DataDirectory|\CurrencyConverter.mdf;Integrated Security=True" providerName="System.Data.SqlClient"/>
</connectionStrings>
连接字符串解析
总结目前,我们已经完成了以下任务:
下一步,我们将继续编写代码,利用这些设置连接数据库并操作数据。 256 - WPF货币转换器:实现数据库的SQL连接欢迎回来!我们现在继续我们的项目,首先让我们进入 添加所需的命名空间为了使这些对象能够正常工作,我们需要确保已添加正确的命名空间。为了做到这一点,我将使用 定义连接、命令和适配器接下来,我们将定义以下三个对象:
为了实现这些,我们还需要定义一些字段来存储临时的数据:
这些字段将在类的不同方法中使用,所以我们把它们定义在 创建连接到数据库的方法接下来,我们将创建一个方法来连接到 SQL 数据库。这个方法被命名为 public void myCon()
{
string con = ConfigurationManager.ConnectionStrings["ConnectionString"].ConnectionString;
SqlConnection conn = new SqlConnection(con);
conn.Open();
} 绑定 ComboBox 数据接下来,我们需要从数据库中获取数据并将其绑定到 ComboBox 中。为了实现这个目标,我们需要对 首先,我们将创建一个新的 public void BindCurrency()
{
SqlConnection conn = new SqlConnection(con);
SqlCommand cmd = new SqlCommand("SELECT ID, CurrencyName FROM CurrencyMaster", conn);
SqlDataAdapter da = new SqlDataAdapter(cmd);
DataTable dt = new DataTable();
da.Fill(dt);
if (dt != null && dt.Rows.Count > 0)
{
comboBoxCurrencyName.ItemsSource = dt.DefaultView;
}
} 处理 SQL 数据和数据表在这里,我们执行 SQL 查询,并将结果填充到 完成数据操作通过这段代码,我们从数据库中提取了数据,并将其设置为 ComboBox 的 关闭数据库连接完成数据操作后,我们需要确保关闭 SQL 连接。通过以下代码来关闭连接: conn.Close(); 完整代码示例以下是连接到数据库并绑定 ComboBox 的完整方法: public void myCon()
{
string con = ConfigurationManager.ConnectionStrings["ConnectionString"].ConnectionString;
SqlConnection conn = new SqlConnection(con);
conn.Open();
}
public void BindCurrency()
{
SqlConnection conn = new SqlConnection(con);
SqlCommand cmd = new SqlCommand("SELECT ID, CurrencyName FROM CurrencyMaster", conn);
SqlDataAdapter da = new SqlDataAdapter(cmd);
DataTable dt = new DataTable();
da.Fill(dt);
if (dt != null && dt.Rows.Count > 0)
{
comboBoxCurrencyName.ItemsSource = dt.DefaultView;
}
conn.Close();
} 数据库操作总结
通过以上步骤,我们成功地将数据库中的数据绑定到应用程序的 ComboBox 中,这就是数据库交互的基础。在接下来的步骤中,我们将实现更多的功能,比如对数据库的修改、更新、删除等操作。 257 - WPF货币转换器:实现保存按钮功能欢迎回来!现在到了关键的一步,我们需要确保点击保存按钮时能够执行相应的功能。虽然我已经运行了应用程序,并且它似乎没有问题,但是如果我们查看“Currency Master”部分,它并不会做任何事情。如果点击保存或取消按钮,什么也不会发生,因为我们还没有实现相关的功能。 在你的主窗口 ( 添加保存按钮的点击事件我们需要为 try
{
// 数据库操作的逻辑
}
catch (Exception ex)
{
MessageBox.Show($"Error: {ex.Message}", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
} 如果点击保存按钮时出现了错误,弹出的消息框将显示具体的错误信息。 检查用户输入接下来,我们要确保用户输入了有效的数据。首先,我们需要检查金额输入框( if (string.IsNullOrEmpty(TextAmount.Text))
{
MessageBox.Show("Please enter an amount", "Information", MessageBoxButton.OK, MessageBoxImage.Information);
TextAmount.Focus();
return;
}
if (string.IsNullOrEmpty(TextCurrencyName.Text))
{
MessageBox.Show("Please enter a currency name", "Information", MessageBoxButton.OK, MessageBoxImage.Information);
TextCurrencyName.Focus();
return;
} 保存数据到数据库当两个输入框都有值时,我们执行保存操作。在执行保存之前,我们首先检查 if (CurrencyID > 0)
{
MessageBoxResult result = MessageBox.Show("Are you sure you want to update?", "Confirmation", MessageBoxButton.YesNo, MessageBoxImage.Question);
if (result == MessageBoxResult.No)
{
return; // 如果用户选择"否",则退出
}
} 执行 SQL 更新操作如果用户确认更新,我们继续执行数据库操作。我们需要通过 using (var conn = new SQLiteConnection(connectionString))
{
conn.Open();
string sql = "UPDATE CurrencyMaster SET Amount = @Amount, CurrencyName = @CurrencyName WHERE ID = @ID";
using (var cmd = new SQLiteCommand(sql, conn))
{
cmd.Parameters.AddWithValue("@Amount", TextAmount.Text);
cmd.Parameters.AddWithValue("@CurrencyName", TextCurrencyName.Text);
cmd.Parameters.AddWithValue("@ID", CurrencyID);
cmd.ExecuteNonQuery();
}
conn.Close();
} 关闭连接每次打开数据库连接后,我们都需要确保在操作完成后关闭连接。确保没有连接泄漏,并避免出现其他错误。 conn.Close(); 成功保存后的提示一旦更新操作成功执行,我们会通过消息框通知用户数据已成功更新。 MessageBox.Show("Data updated successfully", "Information", MessageBoxButton.OK, MessageBoxImage.Information); 完整代码总结
以上就是当点击保存按钮时执行的完整功能。 258 - WPF货币转换器:添加新的货币条目继续保存逻辑:添加新行新行插入逻辑当 首先,我们建立数据库连接。与更新操作相似,我们通过 INSERT INTO currency_master (amount, currency_name)
VALUES (@amount, @currency_name);
SQL 命令中使用了 command.Parameters.AddWithValue("@amount", textAmount.Text);
command.Parameters.AddWithValue("@currency_name", textCurrencyName.Text); 执行 SQL 命令后,我们关闭数据库连接,确保连接得到正确释放。关闭连接是很重要的,如果不关闭连接可能会导致资源泄漏或者错误。 清除输入字段成功插入新数据后,我们调用
代码实现如下: private void ClearMaster()
{
textAmount.Text = "";
textCurrencyName.Text = "";
btnSave.Content = "Save"; // 重置保存按钮文本
GetData(); // 重新加载数据
currencyID = 0; // 重置currencyID
} 加载数据:
|
18 - Linq261 - Linq简介欢迎来到 Linq 章节在本章节中,您将学习如何使用一个名为 **Linq ** 的第三方库。这个库将使您能够更高效地使用数据库,并且我们将探讨如何使用您可以在线找到的第三方库。 第三方库的使用每当您需要使用第三方库时,首先要做的就是查阅该库的文档,深入了解它的功能和用法。如今有许多第三方库,每个库都为编程带来了不同的特色,它们能够在您开发程序时提供更简便的功能,从而让您的工作变得更加轻松。 使用 Linq 库本章的重点是 Linq 库,它可以帮助我们将之前学到的知识以更加简洁和高效的方式结合在一起。通过 Linq ,您将能够更加高效地操作数据库,减少代码量,使您的编程工作更加流畅。 总结接下来,我们将深入了解如何使用 Linq 来简化数据库操作,并结合您到目前为止学到的内容,提升编程效率。敬请期待下一视频的讲解。 262 - Linq温和介绍欢迎回来在本视频中,我将向您简要介绍 Link。Link 是一种用于从数据源中检索数据的工具,通过使用 Link 查询操作,您可以从不同类型的数据源中获取数据,这正是 Link 强大的地方。无论是数组、数据库、XML 文件,还是其他多种数据源,都可以用于查询操作。 查询操作的三部分查询操作包括三个步骤:
这三个步骤是获取或调整数据所必需的基本步骤。 示例用法我们来看一个简单的示例:打印出一个字符串数组中按名称排序的条目。假设我们有一个名为 步骤 1:数据源数据源是我们要查询的数据,示例中是 步骤 2:查询创建查询的创建使用 Link 查询语法: var query = from name in names
orderby name ascending
select name; 我们遍历所有的名字,按字母顺序对它们进行升序排序,然后选择每个名字。 步骤 3:查询执行接下来,我们可以使用 foreach (var i in query)
{
Console.WriteLine(i);
} 输出将会是:
在这个例子中,我们仅仅是重新排序了数据,而没有改变原始字符串的内容。我们以不同的顺序打印出来而已。 另一个示例现在,我们来看另一个示例:打印出整个数组中的条目,按大小排序,但忽略小于 5 的值。假设我们有一个数字数组 步骤 1:数据源数据源是包含数字的数组 步骤 2:查询创建查询创建如下所示: var query = from number in numbers
where number > 5
orderby number descending
select number; 我们首先筛选出大于 5 的数字,然后按降序排序(从大到小),最后选择这些数字。 步骤 3:查询执行查询执行使用 foreach (var i in query)
{
Console.WriteLine(i);
} 输出将会是:
其中,数字 4 被排除在外,因为它不满足大于 5 的条件。 总结这只是 Link 的一个非常基础的介绍,展示了如何从数组中获取和操作数据。但实际上,您可以从各种数据源中获取数据,Link 在这些数据源之间的表现是一致的,这就是 Link 的真正强大之处。无论数据源是数组、XML 文件还是数据库,Link 都能以相同的方式工作。 接下来,我们将深入探讨如何在数据库和其他数据源中使用 Link。希望您喜欢这个内容,并且准备好学习更高级的技术,您将能够将这些技能应用到更实际的编程中。 期待在下一个视频中与您见面! 263 - Linq演示欢迎回来在本次演示中,我们将开始使用 Link。所以,让我们先创建一个新的控制台应用程序,我将它命名为 目标我们的目标是从这个数组中提取出所有的奇数。实现这一功能的方法有很多种。例如,我们可以使用取余操作符(modulo),通过检查每个数字是否能被 2 整除且余数不为 0 来判断它是否为奇数。这样的方法是有效的,但我们将采用一种更为精妙的方式。 步骤
代码示例将上述步骤结合在一起,我们的代码如下: static void OddNumbers(int[] numbers)
{
Console.WriteLine("Odd numbers:");
// 使用 LINQ 从数组中获取奇数
IEnumerable<int> oddNumbers = from number in numbers
where number % 2 != 0
select number;
// 打印奇数
foreach (var odd in oddNumbers)
{
Console.WriteLine(odd);
}
// 打印所有数字
foreach (var i in numbers)
{
Console.WriteLine(i);
}
} 运行结果如果我们运行这段代码,输出将如下所示:
总结在这个示例中,我们通过 Link 从简单的整数数组中提取出了所有的奇数。这展示了 Link 的强大之处:即使我们只用一个简单的数组,Link 也能以类似 SQL 的方式进行数据处理。更酷的是,Link 不仅限于数组,它同样适用于数据库、XML 文件等其他数据源。 接下来,我们将在下一个视频中探讨 Link 的更多功能,敬请期待! 264 - Linq与列表和我们的大学管理者第一部分欢迎回来这是第二个 Link 演示。在这个演示中,我们将准备一些稍后会用到的内容。首先,我们将创建两个额外的类,然后再创建第三个类来管理这两个类。这两个类将用于创建有关大学和学生的对象。让我们开始吧。 创建大学类我们首先创建一个 public class University
{
public int ID { get; set; } // 大学ID
public string Name { get; set; } // 大学名称
// 打印大学信息
public void Print()
{
Console.WriteLine($"University {Name} with ID {ID}");
}
} 创建学生类接下来我们创建一个 public class Student
{
public int ID { get; set; } // 学生ID
public string Name { get; set; } // 学生姓名
public string Gender { get; set; } // 性别
public int Age { get; set; } // 年龄
public int UniversityID { get; set; } // 外键,关联到大学
// 打印学生信息
public void Print()
{
Console.WriteLine($"Student {Name} with ID {ID}, Gender {Gender}, Age {Age} from university with ID {UniversityID}");
}
} 创建大学管理器类接下来,我们将创建一个 public class UniversityManager
{
public List<University> Universities { get; set; } // 存储大学列表
public List<Student> Students { get; set; } // 存储学生列表
// 构造器,初始化数据
public UniversityManager()
{
Universities = new List<University>();
Students = new List<Student>();
// 添加大学
Universities.Add(new University { ID = 1, Name = "Yale" });
Universities.Add(new University { ID = 2, Name = "Beijing Tech" });
// 添加学生
Students.Add(new Student { ID = 1, Name = "Carla", Gender = "Female", Age = 17, UniversityID = 1 });
Students.Add(new Student { ID = 2, Name = "Toni", Gender = "Male", Age = 21, UniversityID = 1 });
Students.Add(new Student { ID = 3, Name = "Lila", Gender = "Female", Age = 19, UniversityID = 2 });
Students.Add(new Student { ID = 4, Name = "James", Gender = "Transgender", Age = 22, UniversityID = 2 });
Students.Add(new Student { ID = 5, Name = "Linda", Gender = "Female", Age = 22, UniversityID = 2 });
}
// 获取所有男性学生
public IEnumerable<Student> GetMaleStudents()
{
var maleStudents = from student in Students
where student.Gender == "Male"
select student;
return maleStudents;
}
// 获取所有女性学生
public IEnumerable<Student> GetFemaleStudents()
{
var femaleStudents = from student in Students
where student.Gender == "Female"
select student;
return femaleStudents;
}
} 使用 Link 查询列出男性和女性学生接下来,我们将使用创建的 class Program
{
static void Main(string[] args)
{
// 创建大学管理器对象
UniversityManager um = new UniversityManager();
// 获取并打印所有男性学生
var maleStudents = um.GetMaleStudents();
Console.WriteLine("Male Students:");
foreach (var student in maleStudents)
{
student.Print();
}
// 获取并打印所有女性学生
var femaleStudents = um.GetFemaleStudents();
Console.WriteLine("Female Students:");
foreach (var student in femaleStudents)
{
student.Print();
}
// 防止控制台窗口立即关闭
Console.ReadKey();
}
} 运行结果当运行程序时,控制台将显示以下内容:
总结通过这次演示,我们已经使用 Link 查询来筛选出男性和女性学生。Link 的强大之处在于它能够简洁地操作数据集,且不仅适用于数组,还可以与列表、数据库、XML 等其他数据源配合使用。 在下一节中,我们将继续扩展 265 - 使用Linq进行排序和过滤欢迎回来在本视频中,我们将对学生进行排序,并且还会查看如何查找特定大学的所有学生。让我们先创建一个方法。我在我的 排序学生首先,我创建一个新的变量 var SortedStudents = from student in students
orderby student.Age
select student; 在这里,我们使用了 接下来,我们将学生按年龄排序后输出到控制台。我们使用一个 Console.WriteLine("Students sorted by age:");
foreach (var student in SortedStudents)
{
student.Print();
} 运行并输出排序结果运行程序后,控制台会显示按年龄排序的学生信息。例如,学生 Carla(年龄17岁)会排在最前面,Layla(19岁)会排在第二位,依此类推。 查找特定大学的所有学生接下来,我们要创建一个方法,查找所有来自特定大学的学生。假设我们要查找来自北京理工大学(Beijing Tech)的所有学生。为了实现这个目标,我们需要根据学生的 创建一个新的方法 public void AllStudentsFromBeijingTech()
{
var BGTStudents = from student in students
join university in universities
on student.UniversityID equals university.ID
where university.Name == "Beijing Tech"
select student;
Console.WriteLine("Students from Beijing Tech:");
foreach (var student in BGTStudents)
{
student.Print();
}
} 在这里,我们使用了 输出北京理工大学的所有学生运行程序时,控制台会显示来自北京理工大学的所有学生的信息。例如,学生 Frank 和 Tony(在北京理工大学,性别男,年龄分别为22岁和21岁)会显示在控制台上。 挑战:根据用户输入的大学 ID 查找学生现在的挑战是创建一个方法,接受一个 方法的创建如下: public void AllStudentsFromUni(int ID)
{
var myStudents = from student in students
join university in universities
on student.UniversityID equals university.ID
where university.ID == ID
select student;
Console.WriteLine($"Students from university {ID}:");
foreach (var student in myStudents)
{
student.Print();
}
} 获取用户输入并调用方法在 Console.WriteLine("Enter university ID:");
string input = Console.ReadLine();
int inputID = int.Parse(input);
AllStudentsFromUni(inputID); 运行程序并输出结果在运行程序时,输入 其他排序方式除了按年龄排序,我们还可以使用其他方式对数据进行排序。以下是使用 var sortedInts = from i in someInts
orderby i
select i; 这样会按升序排列数组中的整数。如果要按降序排列,可以使用 var reversedInts = sortedInts.Reverse(); 或者,使用 LINQ 的 var reversedSortedInts = from i in someInts
orderby i descending
select i; 总结我们在本视频中展示了如何排序学生列表、查找特定大学的学生以及根据用户输入的大学 ID 查找该大学的所有学生。我们还探讨了多种排序方法,如升序、降序等。通过使用 LINQ 和其他集合操作,我们能够灵活地处理和排序数据。 希望本视频对你有所帮助,下一步我们将在接下来的课程中扩展更多功能,敬请期待! 266 - 基于其他集合创建集合欢迎回来!现在你已经了解了如何反转、排序数组和列表,及其他一些与 合并列表并创建新集合我们将创建一个新集合,这个集合不再包含学生的原始信息(如 ID、姓名、性别、年龄和大学 ID),而是只包含学生姓名和他们所在大学的名称。具体来说,我们希望知道每个学生的姓名以及他们所在大学的名称。 首先,我们创建一个新的集合,使用一个 public void StudentAndUniversityNameCollection()
{
var newCollection = from student in students
join university in universities on student.UniversityID equals university.ID
orderby student.Name
select new { StudentName = student.Name, UniversityName = university.Name };
foreach (var collection in newCollection)
{
Console.WriteLine($"Student {collection.StudentName} from University {collection.UniversityName}");
}
} 代码解析
调用方法并运行在
总结通过这段代码,我们演示了如何使用 LINQ 来合并两个集合(学生和大学)并创建一个新的集合,其中仅包含学生的姓名和大学的名称。我们没有涉及学生的 ID 或其他数据,只选择了我们感兴趣的字段。这种方法类似于 SQL 中的 你还可以看到,使用 LINQ 来操作集合可以非常高效,尤其是在没有使用数据库时。你可以使用这种方法来处理 XML、集合、数据库等多种数据源,而无需依赖数据库适配器或复杂的代码逻辑。 接下来的视频我们将探讨更多 LINQ 的高级用法。敬请期待! 267 - Linq与XML回到视频内容在这一视频中,我们将学习如何将 LINQ 与 XML 结合使用。我创建了一个名为 Link with XML 的项目,并且编写了一个字符串,叫做 Students XML,它包含了一些 XML 数据。接下来我们将展示如何使用 XML 数据与 LINQ 进行操作。 XML 数据结构在这个示例中,XML 文件包含了一个名为 <students>
<student>
<name>Toni</name>
<age>21</age>
<university>Yale</university>
</student>
<student>
<name>Carla</name>
<age>17</age>
<university>Yale</university>
</student>
<student>
<name>Leila</name>
<age>19</age>
<university>Beijing Tech</university>
</student>
</students> XML(可扩展标记语言)是一种用于结构化数据的语言,在很多场合中都有应用。例如,许多网站使用 XML 来共享数据,通过 API 将数据以 XML 格式提供,你可以通过读取这些 XML 文件来将它们集成到你的程序或网站中。 加载 XML 文件为了开始使用 XML 数据,我们首先需要添加命名空间 using System.Xml.Linq;
string studentsXml = "<students>...</students>"; // 你的 XML 数据
XDocument xDoc = XDocument.Parse(studentsXml); 使用 LINQ 解析 XML通过 LINQ,我可以从 var students = from student in xDoc.Descendants("student")
select new
{
Name = student.Element("name").Value,
Age = student.Element("age").Value,
University = student.Element("university").Value
}; 这样, 输出 XML 数据我们可以遍历 foreach (var student in students)
{
Console.WriteLine($"Student {student.Name}, Age {student.Age}, University {student.University}");
} 这段代码将输出:
按年龄排序接下来,我们可以使用 LINQ 对学生进行排序。例如,我们可以按学生的年龄排序,代码如下: var sortedStudents = from student in students
orderby student.Age
select student;
foreach (var student in sortedStudents)
{
Console.WriteLine($"Student {student.Name}, Age {student.Age}, University {student.University}");
} 这样,我们就可以按年龄升序输出学生信息,结果如下:
添加更多学生信息在 XML 文件中,我们还可以为每个学生添加额外的信息。例如,我们可以为每个学生添加学期信息(例如,Tony 是第六学期),并在 LINQ 查询中将其包括进来。 我们先修改 XML 数据,增加一个 <students>
<student>
<name>Toni</name>
<age>21</age>
<university>Yale</university>
<semester>6</semester>
</student>
<student>
<name>Carla</name>
<age>17</age>
<university>Yale</university>
<semester>1</semester>
</student>
<student>
<name>Leila</name>
<age>19</age>
<university>Beijing Tech</university>
<semester>3</semester>
</student>
<student>
<name>Frank</name>
<age>25</age>
<university>Harvard</university>
<semester>10</semester>
</student>
</students> 然后,修改 LINQ 查询以包括 var studentsWithSemester = from student in xDoc.Descendants("student")
select new
{
Name = student.Element("name").Value,
Age = student.Element("age").Value,
University = student.Element("university").Value,
Semester = student.Element("semester").Value
};
foreach (var student in studentsWithSemester)
{
Console.WriteLine($"Student {student.Name}, Age {student.Age}, University {student.University}, Semester {student.Semester}");
} 输出将包括学期信息:
总结通过使用 LINQ 和 XML,您可以轻松地将结构化数据(如学生信息)加载到您的程序中,并进行各种操作,如排序、筛选和查询。XML 在很多应用场景中都非常有用,尤其是当你需要处理外部数据源时,LINQ 提供了一个强大而简便的工具来处理这些数据。 268 - 设置LinqToSQL项目在这个视频中,我们将使用 Link 和 SQL。让我们开始创建一个新的项目。在这种情况下,我将使用一个 WPF 项目,因为我希望以一种漂亮的方式显示数据。所以我将这个项目命名为 Link to SQL 或 SQL,然后按下 OC 创建项目。我想在这里使用一个数据网格。因此,我将在这里创建一个新的数据网格,命名为 Main Data Grid。这个数据网格应该显示我们数据库中的所有数据,并与 SQL 一起使用。 创建数据网格在此,我们设置数据网格的水平对齐方式为居中,实际上让我们将其调整为左对齐。左对齐就好。然后设置高度为 400,并设置四个方向的边距为 10。接着,我们设置垂直对齐方式为顶部。现在关闭这个数据网格。这样,我们就有了这个数据网格,但如你所见,它还没有设置宽度。所以,我将宽度设置为 450 或者类似的数值,或者可能是 520,差不多是我们主窗口宽度的整个宽度。所以,正如你看到的,500 就是合适的宽度。如果我们希望高度为 400,那我们就应该相应地增加窗口的大小。 连接服务器好了,现在你有了这个窗口,我们可以继续设置服务器连接。让我们转到视图,然后选择“服务器资源管理器”或按快捷键 Ctrl + Alt + SW,它将打开数据连接屏幕或服务器资源管理器。在这里,我将创建一个新表。就我而言,我有两个连接,所以我将使用其中一个,接着我创建一个新表。这个表应该叫做 创建
|
19 - WPF项目货币转换器与GUI数据库和API第三部分275 - WPF货币转换器:使用API和JSON获取实时货币值大家好!在这个视频中,我将向你们展示如何使用我们在前两个视频中用API构建的货币转换器。因此,你不需要使用你在数据库示例中看到的代码。实际上,你甚至不需要看过数据库示例,因为我们将使用我们在静态示例中创建的代码。以静态的WP F货币转换器为例,我们使用的是静态数据,我们将扩展它,使其能够使用来自API的数据。这意味着我们可以从互联网获取数据,并使用这些数据来进行最精确的货币转换,获得最新的货币价值和汇率。基本上,这就是这个想法。你当然可以查看这篇文章,我们为此创建了完整的文章。你可以在其中找到所有的代码、各种解释等内容。 我们将使用的API来自Open Exchange Rates,这个网站提供汇率服务,我们每个月可以免费调用最多1000次。这对于学习来说非常棒,但如果你想将其用于一个你要出售的服务,例如,你需要付费。但正如我所说,它是免费的,适合测试,这正是我们需要的。所以你可以随时去查看他们的定价页面,了解他们的计划。在这里你可以看到开发者计划、企业计划、无限计划,但这些和我们无关。我们感兴趣的是免费的计划,它提供每小时更新,以美元作为基准货币,并允许每月最多1000次请求,这非常好。因此,你可以选择免费的计划,然后注册才能使用。 如果你想跟着我一起操作,你可以在这里注册。一旦你注册完毕,你就需要获得你的应用ID,这个ID就是我们进行API调用时需要用到的。我们将使用这个ID来发出请求并获取数据。如何获取数据呢?我们将以这种格式获取数据,即JSON格式。JSON是一种非常有用的数据格式,可以用文本以一种可读的方式展示数据。所以,基本上它将数据分解开来,这不是你通常看到的数据表,而是以一种文本格式呈现,你仍然可以很好地阅读。 我们将获得的数据包括免责声明、许可证、时间戳、基准货币和汇率。汇率部分包含不同货币的汇率,基准货币将是美元。例如,你将看到,1美元相当于3.6某种货币,或者1美元相当于1.39澳大利亚元等。这些值就是我们将要获取的内容。我们可以利用这些数据来调整我们的程序,使其能够获取最新的货币和汇率,而不需要像之前的视频那样手动输入它们,也不需要像上一个视频中那样在数据库中为它们单独设置表格。 现在我们来看看我们的项目。这是一个静态项目。正如我所说,如果你还没看过这个视频,一定要去看看我之前的视频,在那里我展示了如何构建WPF部分和其他内容,因为在这个视频中我将不会涉及WPF部分。我只会关注API相关的内容。 为了使这个项目正常工作,我们需要额外的类,因为我们获取的数据是以某种格式返回的。正如我所说,获取的数据格式就是这个JSON格式。因此,我们需要创建一些类来表示这些数据。比如我们会创建一个名为 首先,我将在主窗口类中创建一个新的类,命名为 接下来,我们需要获取汇率。正如我之前提到的,汇率是比较复杂的,因为它包括多个不同的汇率。所以我将创建一个额外的类,命名为
接下来,我们需要创建一个方法来获取数据。我们将使用异步任务(async task)来进行API请求。这是因为从网络上获取数据时,我们需要异步执行操作,以免在等待响应时阻塞主线程,使得应用程序冻结。因此,我们将使用 让我们来看看这个方法。这里,我使用了 任务和结果任务的目标是获取数据并返回一个特定类型的结果对象。在这个案例中,目标是返回一个“root”对象类型的数据,数据可以进一步用于不同的功能,如货币汇率的计算。为了实现这一点,首先我们创建了一个新的 异常处理由于在网络请求过程中可能会发生错误,我们需要使用 HTTP 客户端和超时设置我们使用 var client = new HttpClient();
client.Timeout = TimeSpan.FromMinutes(1); 异步请求为了避免阻塞主线程并导致应用程序卡顿,我们使用了异步的 var response = await client.GetAsync(url); 处理响应获取到的响应数据是一个字节流,无法直接使用。因此,我们首先需要将其转换为字符串,这样我们才能处理它。使用 var responseString = await response.Content.ReadAsStringAsync(); JSON 转换接下来,我们需要将字符串格式的响应数据转换为 JSON 对象,以便我们能够在程序中使用它。为此,我们使用 Root rootObject = JsonConvert.DeserializeObject<Root>(responseString); 使用 JSON 对象一旦将 JSON 数据转换为 C# 对象,我们可以像访问普通对象属性一样,访问这些数据。比如,我们可以轻松地获取 var timestamp = rootObject.timestamp; 这使得我们可以方便地处理来自API的数据,而不需要手动解析复杂的 JSON 结构。 返回响应如果 HTTP 响应状态码表示请求成功(例如 200),我们将继续处理返回的数据。如果状态码不为 200,则我们返回一个空的 if (response.IsSuccessStatusCode)
{
return JsonConvert.DeserializeObject<Root>(responseString);
}
else
{
return new Root();
} 绑定货币数据在 private async void GetValue()
{
Root val = await GetDataAsync(url);
BindCurrency(val);
} 汇率转换我们通过调用外部API获取货币汇率数据,并使用返回的汇率来进行货币转换。我们也做了一些调整,确保计算的正确性,比如使用正确的汇率方向(例如从 USD 转换为 EUR)。 double convertedAmount = amount * rates[currency]; 结论通过这个过程,我们学会了如何:
这些步骤展示了如何从网络获取数据并在程序中使用它,无论是处理货币汇率数据,还是其他类型的 API 数据。 |
20 - 编程面试练习 |
21 - 线程278 - 线程简介欢迎回来在本章节中,我们将学习线程。这个章节是在我完成这门课程后添加的,因为我收到了很多反馈,大家都提到线程的相关内容。就像委托章节一样,我决定将新章节添加到课程中。因此,如果你觉得课程中缺少了某些非常重要的内容,请随时告诉我,我会将其添加到课程中。 本章节目标在这个章节中,我们将学习线程的基础知识,包括如何确保线程不会重叠以及如何确保线程能够重叠。我们还将探讨线程池的概念,如何在后台运行线程,如何连接线程,并确保线程仍然存活,而不是已经完成并“死掉”。 学习目标
小结本章节为课程增添了线程管理的内容,接下来我们会进一步讲解每个部分的具体实现。我希望你能喜欢这个章节!在下一段视频中,我们将开始深入讨论线程的相关概念。 279 - 线程基础欢迎回来在本视频中,我们将深入探讨线程。我们将从线程的基本概念开始,学习如何创建线程以及如何创建多个线程。让我们通过控制台的输出开始。 基础线程操作首先,我将创建多个输出,例如四个不同的“Hello World”输出,分别是:“Hello World 1”、“Hello World 2”、“Hello World 3”和“Hello World 4”。当我们运行这些代码时,首先需要确保控制台的窗口保持打开,因此需要使用 这对我们来说并不是什么新鲜的事情,之前我们已经多次见过这样的操作。但是,线程有一个很重要的类,叫做 using System.Threading; 这样我们就可以使用线程相关的功能了。 使用线程现在我们可以使用 例如,我可以在每个输出语句后加入 线程暂停
调试线程为了查看线程的工作状态,我们可以在代码中添加调试点。通过在调试窗口中查看线程信息,我们可以看到当前程序运行的线程。具体操作是在调试窗口中选择“线程”(Threads)视图,或者使用快捷键 当我们运行程序时,我们可以看到主线程(Main Thread)正在运行。如果添加了多个线程,我们可以在调试窗口中看到它们的状态。例如,如果我们创建了多个线程,并且在调试过程中逐步执行,我们会看到每个线程的状态及其运行情况。 创建多个线程接下来,我们将创建多个线程,以实现并行执行。在默认情况下,代码是顺序执行的,即一个接着一个。但如果我们希望并行执行任务,可以创建多个线程,并将这些任务分配给不同的线程。 我们可以通过以下代码创建一个新的线程: Thread thread1 = new Thread(() =>
{
Console.WriteLine("Thread 1");
Thread.Sleep(1000); // 模拟延迟
});
thread1.Start(); 上述代码中,我们创建了一个新的线程,并在该线程中执行了一些操作。 多个线程并行执行我们可以创建多个线程,并让它们并行执行。例如,创建多个线程分别输出 "Thread 1"、"Thread 2" 等。通过修改代码,创建多个线程并启动它们,你会发现这些线程几乎是同时启动的,它们并行执行。 在调试过程中,你可以看到主线程和其他线程同时在执行。这表明我们实现了多线程并行执行。 线程的执行顺序尽管我们在代码中设置了线程的顺序,线程的执行顺序并不一定会与我们预期的一致。由于线程是并行执行的,它们的执行时间会受到许多因素的影响,例如线程的工作负载、CPU 的调度等。因此,即使线程是在特定的顺序中启动的,它们的执行结果可能并不会按顺序显示。 例如,尽管线程是按顺序启动的(Thread 1, Thread 2, Thread 3, Thread 4),它们的输出顺序可能会有所不同,因为它们的执行时间可能不同。某些线程可能会先完成,而其他线程则需要更长的时间才能执行完成。 线程的注意事项使用多线程时,需要特别小心。虽然多线程可以提高程序的执行效率,但也可能带来一些问题。比如,线程之间可能会相互干扰,导致程序行为异常。因此,学习如何正确使用线程是非常重要的。 如果你的程序运行在多核处理器上,线程可以在不同的 CPU 核心上并行执行,从而提高程序的效率。例如,四核处理器可以同时运行四个线程。但如果你的计算机只有一个处理器,所有线程仍然会在同一个核心上轮流执行。 结论通过本视频,我们了解了如何创建和使用线程。线程可以让我们并行执行任务,提高程序的效率。但是,线程的执行顺序和行为可能会有一定的随机性,因此在使用线程时需要小心,确保线程之间的协调和同步。 280 - 线程开始和结束完成欢迎回来在上一期视频中,你学习了如何使用线程,以及如何设置线程并简单地启动它们。在本期视频中,我们将重点介绍任务的完成。我们希望使用线程做一些事情,只有当某个特定任务完成后,才继续执行后续的操作。让我们来看一下如何实现这一点。 创建任务完成源我将创建一个新的变量,称为 TaskCompletionSource<bool> taskCompletionSource = new TaskCompletionSource<bool>(); 调试与运行接下来,我们运行代码,看看发生了什么。注意,尽管没有显示 Console.WriteLine("Task not completed yet."); 任务的完成为了确保任务完成,我们需要使用线程。我们创建一个新线程并在其中执行任务逻辑。与我们之前直接启动线程的方式不同,这次我们先创建线程对象,但不立即启动。通过这种方式,我们可以稍后再启动它并控制执行。 Thread thread = new Thread(() => {
Thread.Sleep(1000); // 模拟任务执行
taskCompletionSource.SetResult(true); // 设置任务完成
}); 启动线程我们需要通过 thread.Start(); // 启动线程 查看线程的 ID在这个过程中,我们可以查看线程的 ID。通过使用 Console.WriteLine($"Thread number: {Thread.CurrentThread.ManagedThreadId}"); 打印线程状态我们还可以通过 Console.WriteLine($"Thread {Thread.CurrentThread.ManagedThreadId} started.");
Thread.Sleep(5000); // 等待 5 秒
Console.WriteLine($"Thread {Thread.CurrentThread.ManagedThreadId} ended."); 任务完成后的结果在任务完成后,我们可以通过 Console.WriteLine($"Task was done: {taskCompletionSource.Task.Result}"); 线程执行顺序执行代码时,你会看到线程的执行顺序并不总是按照我们在代码中的书写顺序来执行。即使线程在代码中被顺序启动,线程的执行顺序可能会因为操作系统的调度而发生变化。 例如,即使我们在代码中创建了多个线程,它们的执行时间可能不同,导致某些线程先执行完毕,而其他线程可能需要更长时间才能完成。 总结在本期视频中,你学习了如何使用 在下一期视频中,我们将继续深入探讨如何同时创建多个线程,并研究操作系统如何调度这些线程的执行。 281 - 线程池和后台线程欢迎回来在本期视频中,我们将讨论线程池(Thread Pools)。我们将创建多个线程,看看它们是如何工作的。接下来,我们将先创建一个基础线程,并查看它的行为,然后再介绍线程池的使用。 基础线程的创建首先,我们创建一个非常基础的线程,不涉及任何任务完成源等额外的东西。我们将简单地创建线程,并让它等待一段时间(比如 1 秒),然后启动它。 Thread thread = new Thread(() => {
Thread.Sleep(500); // 等待 500 毫秒
Console.WriteLine($"Thread {Thread.CurrentThread.ManagedThreadId} started.");
});
thread.Start(); // 启动线程 在这里,我们使用 使用
|
22 - 单元测试和测试驱动开发TDD286 - TDD介绍欢迎参加我们的测试驱动开发课程恭喜你加入了这个课程,这意味着你对软件工程职业有着认真的态度。在这里,我要祝贺你开始这门课程,并祝愿你在软件工程行业的未来一切顺利。 在这门课程中,你将学习自动化测试与手动测试之间的差异。你将学习如何设置测试项目,并且我们将讨论多种技术,如“给定-当-然后(Given-When-Then)”、红绿重构模式(Red-Green-Refactor)、测试的可信度、"魔鬼代言人"游戏、参数化测试、在编写测试后发现替代路径、遵循TDD(测试驱动开发)法则、以及测试应用层等内容。 课程内容我们从回顾如何手动测试应用程序开始。你将学习手动测试与自动化测试的区别。同时,我们会立刻开始,创建一个项目并编写第一个测试。 在第一章结束时,你将了解什么是测试优先开发(Test-First Development),以及如何编写良好的断言消息。最后,我们将安装一些用于编写单元测试的库。 通过本课程的学习,你将掌握测试驱动开发的核心概念,并能够应用到实际的开发工作中。 287 - 什么是测试驱动开发测试驱动开发的简介如果你在谷歌搜索什么是测试驱动开发(TDD),你会看到许多简短的描述,但你可能并不会从中获得真正有价值的信息。定义是没有错的,你也足够聪明,但问题在于,TDD与其他方法不同。你之前可能学过一些软件开发的内容,而TDD并不是通过几个简单的句子就能解释清楚的。因此,与其从理论的介绍开始,我会通过一个实际的例子来引导你了解TDD的基本概念。 手动测试是什么?我们先从一个熟悉的话题开始:什么是手动测试?我们如何测试我们的应用程序? 首先,我们写一些基础代码,然后运行它并进行测试,检查它是否按预期工作。如果没有,可能会发现一些bug,或者需求发生了变化。那么我们就需要修改代码,再次运行进行测试,依此类推。 问题是,每次你修改代码时,可能会破坏一些已经正常工作的功能。为了确保程序仍然按预期工作,你必须每次修改代码时都对所有受影响的功能进行测试。这个过程非常耗时,并且不可靠。每次修改代码,你对程序的信心就会降低,信心的下降会导致更差的开发体验和更低的生产力。另一方面,测试所需的时间呈指数增长,每次运行程序时,你可能会忘记测试某些重要的内容。 那么,是否有更好的方法呢?你可能会问,是否有更好的方法来解决这些问题?答案是肯定的!这就是我们在这里学习的原因。与其每次修改代码后都进行手动测试,我们可以编写一些代码来测试程序中最关键的部分,这样每次修改代码时,只需要运行这段测试代码。这不仅速度更快,而且更可靠,解决了很多手动测试中存在的问题。 自动化测试的例子我来通过一个例子再解释一次。假设我们有一个应用程序,它的功能是求两个数字的和。我们该如何测试这个功能呢? 当我们运行程序时,输入两个数字,点击求和按钮,我们应该看到的结果是什么?两个数字相加的结果当然是 4,对吧?如果你不想每次都手动测试这个功能,我们可以为此编写代码进行测试。 假设我们有一个处理这个用户界面背后逻辑的求和函数,不论它的技术如何,我们可以编写一些代码来测试这个求和函数。 我们先忘掉界面技术,只考虑应用程序的核心逻辑部分——也就是这个求和函数。我们要测试的是这个函数是否按预期工作。我们可以编写代码来检查求和函数是否能正确计算出 2 和 2 的和。 编写测试代码这个测试实际上和我们手动测试时做的测试是一样的,只不过是自动化的。每次我修改求和函数时,我都可以运行这个测试方法,自动检查它是否按预期工作。如果我破坏了这个功能,测试就会抛出异常。 接下来,我们把测试代码移到一个方法中,这个方法就叫做测试方法。我们称求和方法为系统,而在这个上下文中,测试方法就是用来验证系统是否成功满足期望的。 我们期望 2 加 2 的结果是 4。你可以看到方法的名字准确地描述了我们要测试的内容。现在,忘掉复杂的术语,我们有一个简单的测试方法,它告诉我们要在这个方法的逻辑中测试什么。然后我们只需调用求和方法,传入参数并检查结果。这样,每次调用这个自动化的测试方法时,它就会自动进行测试。 通过这种方式,我们可以在每次修改代码后,自动验证功能是否仍然按预期工作,极大地提高了开发效率和信心。 288 - 创建项目并编写第一个测试创建第一个测试方法现在我们进入 Visual Studio 2022,接下来我将创建一个新项目。如果你正在跟着课程一起学习,请立即打开 Visual Studio 并创建一个项目。我强烈建议你这样做。不要只是坐在那里看视频,打开 Visual Studio,创建一个项目,并通过实际操作来跟上课程的进度。我们的目标是为你提供最好的学习体验,而这需要你积极参与。 创建 XUnit 测试项目首先,创建一个新的项目,我们将使用一个名为 XUnit 的测试框架。只需搜索 XUnit 并创建一个 XUnit 测试项目。XUnit 最初是由 NUnit 框架的作者创建的。你可能知道 NUnit 这个测试框架,或者你可能已经听说过这个术语。然而,XUnit 也可以在 .NET Core 上运行,支持 Windows、Linux 和 Mac OS。现在,只需选择 XUnit,点击“下一步”,然后给它命名为“Calculator”,接着点击“下一步”并选择 .NET 6,最后点击“创建”。 编写求和函数接下来,我们编写之前在视频中定义的求和函数。在你的代码中,输入如下内容: public int Sum(int left, int right)
{
return left + right;
} 我们创建了一个返回类型为 编写测试方法现在,让我们来看一下你在测试类中可能看到的 C# 脚本。这里我们有一个名为 为了快速测试我们刚刚创建的 if (Sum(2, 2) != 4)
{
throw new System.Exception("Test failed: 2 + 2 should equal 4.");
} 这里,如果 现在你已经有了一个简单的测试方法, 运行测试在 Visual Studio 中,你可能认为可以通过点击顶部的绿色播放按钮或“开始”按钮来运行测试,但其实这是不对的。要运行测试,你可以右键点击测试方法并选择“运行测试”。 这样会打开一个新的“测试资源管理器”窗口,你将看到测试正在执行。稍等片刻,测试结果会显示出来。你会看到类似于“UnitTest1:Test1”的测试方法名称,这就是你刚才测试的那个方法。我们可以看到结果是成功的,因为它通过了测试,显示“通过了 1 个测试”。 如果你更改测试条件,例如将 改进命名和代码结构我们刚刚创建了第一个测试方法,并看到了通过或失败的结果。现在我们需要改进一下命名。我们将以更加专业的方式重构项目,确保命名符合最佳实践。 小结恭喜你,你刚刚创建了第一个测试方法!你学会了如何编写一个简单的测试方法、如何运行测试,并理解了测试结果的两种可能性:成功或失败。接下来,我们会继续探索如何在项目中应用更加专业的测试实践。 289 - 重构和添加域引入真实的计算器项目现在,请注意,我们刚才创建的项目仅用于测试场景。这个项目包含了我们刚刚定义的 创建领域层项目首先,右键点击你的解决方案,而不是项目本身,然后添加一个新的项目到解决方案中。这种做法在很多真实世界的应用中都会遇到,通常会有一个测试项目和一个包含算法和业务逻辑代码的项目。接下来,搜索 Class Library(类库),然后选择它。
点击 下一步,选择 .NET 6,然后点击 创建。 修改类文件接下来,我们需要重命名 移动求和方法在 UnitTest1 类中,你会看到我们之前创建的
在测试项目中添加引用现在,我们已经将
修改命名空间和类的访问权限接下来,我们需要调整命名空间和方法的访问权限,以确保测试项目能够访问到计算器的逻辑代码。
修改代码引用修改完命名空间后,我们需要确保在测试项目中正确引用 Calculator 类。
var calculator = new Calculator();
var result = calculator.Sum(2, 2); 运行测试现在,我们已经将所有的类和命名空间整理好了,接下来可以运行测试了。记住,在测试驱动开发(TDD)中,每次修改代码后,都要运行测试以确保代码仍然正常工作。
你会看到测试通过了。即使我们做了很多改动,添加了新项目,更新了类名和命名空间,只要运行测试,最终都能确保我们的代码是正确的。 小结通过这一步,我们已经成功地将计算器的业务逻辑从测试项目中提取到了 Domain 项目中,并通过创建项目引用和调整命名空间,使得测试项目能够访问到计算器的方法。最重要的是,我们运行了测试并验证了修改后的代码仍然正确。 现在,你已经掌握了如何组织代码、管理项目之间的引用以及如何在 TDD 中进行验证,确保代码在进行修改后仍然能够正常工作。 290 - 添加Web API介绍 Web 项目目前,我们有两个项目:一个是测试项目,另一个是包含计算器业务逻辑的 领域层 项目(Domain)。这些项目的结构符合专业应用程序的常见做法。在接下来的步骤中,我们将进一步扩展应用程序,添加一个新的 ASP.NET Core Web API 项目。这样,应用程序就可以通过网络与外部系统进行交互,这也是专业软件开发的常见做法。 创建 ASP.NET Core Web API 项目首先,右键点击解决方案并添加一个新的项目,这次我们选择 ASP.NET Core Web API。如果你之前没有接触过 ASP.NET Core,那么你真的错过了很多东西。在这里,我们只是想展示如何将计算器功能暴露为 Web API 接口,而不是涉及部署和托管。
Web API 简介简单来说,Web API 允许我们通过 HTTP 协议在 Web 上公开功能。我们将计算器的功能暴露为 API,这样用户就可以通过 Web 请求来使用我们的计算器方法。虽然我们不会在这里深入探讨部署和托管的问题,但要理解,创建 Web API 的主要目的是让外部应用能够通过请求访问计算器服务。 添加对领域层的引用接下来,我们需要将 Domain 项目添加为 Web 项目的引用。这个步骤非常重要,因为 Web 项目需要访问 Domain 项目的代码,才能调用计算器的业务逻辑。
Web 项目不需要引用 Test 项目,因为 Test 和 Domain 之间是唯一的联系,Web 项目只需要连接到 Domain 项目。 修改 Web API 控制器在 Web 项目中,默认有一个 WeatherForecastController。我们需要将其修改为与我们的计算器逻辑相关的控制器。首先,右键点击 WeatherForecastController 并重命名为 CalculateController。同时,重命名文件为 CalculateController.cs。 然后,删除原有的天气预报代码,并将 创建 Web API 方法我们将通过如下的代码创建一个 API 方法,来处理加法操作: [ApiController]
[Route("api/[controller]")]
public class CalculateController : ControllerBase
{
[HttpGet("add/{left}/{right}")]
public IActionResult Add(int left, int right)
{
var calculator = new Calculator();
var result = calculator.Sum(left, right);
return Ok(result); // 返回计算结果
}
} 在这个代码中,我们定义了一个新的 Add 方法,它接收两个 URL 参数: 运行 Web API现在,我们已经创建了一个 Web API,可以通过访问 更新功能代码如果我们需要更改计算逻辑,例如从加法改为减法,修改会非常简单。我们只需修改 Calculator 类中的 Sum 方法(例如,将 测试和验证接下来,我们可以运行测试,以确保 Calculator 类的功能没有被破坏。我们可以通过右键点击测试方法并选择 运行测试 来验证。 如果我们更改了计算逻辑(例如,从加法变为减法),测试会失败。然后我们可以使用 Test First 方法来处理这种情况,确保我们的代码在实现新功能之前是正确的。这个过程将确保我们的更改不会破坏现有的功能。 小结通过这些步骤,我们成功地将计算器功能暴露为一个 Web API,并实现了以下功能:
这个过程展示了如何构建一个现代化、模块化的应用程序,其中包含了领域层、测试层和 Web 层的分离。这是构建专业应用程序的推荐方式。 291 - 测试优先的方法写代码的顺序与测试驱动开发 (TDD)我们刚才编写了 重写
|
23 - UNITY基础322 - Unity基础简介欢迎回来在本章中,您将学习 Unity 的基础知识。首先,我想说几句关于接下来内容的事情。接下来的两章内容最初并不是这门课程的一部分,而是我 Unity 课程中的内容。所以我为您精心准备了一些额外的内容,作为一个额外的奖励,提供给您更多有趣的小项目,帮助您在编程方面取得更好的进展。 Unity 和 C# 编程Unity 是一个游戏开发引擎,它使用 C# 作为核心编程语言。因此,在接下来的章节中,我们将编写大量的代码,使用我们在之前章节中学到的所有知识来制作游戏。如果您对游戏开发不感兴趣,当然也可以跳过这些内容,继续做自己的项目,因为您已经有能力开始自己的创作了。 但如果您希望从不同的角度更深入地学习 C#,并看到它在游戏开发中的应用,这将有助于您成为一个更好的程序员,并且可以帮助您更好地理解到目前为止所学的一切。因为我们将以一种不同的视角去看待它,这种不同的视角能帮助您更清晰地理解之前学到的知识。 Unity 的基本概念在这一章中,我们将介绍 Unity 的基本概念,这些基础知识将帮助您创建自己的游戏。当然,我们还会一起开发一些游戏。如果您想制作更多的游戏,您可以随时访问我的 YouTube 频道,我有一个频道叫做 Tutorials EU,网址也是 tutorials.eu,在这里您可以找到更多的项目、更多的游戏开发内容,以及一些特别的项目。如果您想去那里学习更多内容,帮助您成为一个更好的开发者,那就赶紧去看看吧。 注意:过渡不会很平滑但请注意,这一部分内容和之前的 C# 课程有所不同。由于这一部分并非最初课程的一部分,因此在风格和方向上会有所不同。过渡可能不会那么平滑,所以请您做好准备。 感谢非常感谢您坚持到现在。我很高兴看到您仍然跟随到这个阶段,您可能是少数(大约 5% 或 10%)能够坚持到这里的人。做得好!期待在下一节视频中与您见面。 324 - Unity界面概述欢迎在本视频中,我们将一起了解 Unity 的用户界面。首先,打开 Unity,然后点击 新建 按钮创建一个新项目。接着为项目命名,我将其命名为 Unity Basics,并选择 3D 作为项目类型。如果我们已经有一些想要添加的资源包,比如图形对象或已有的功能代码,可以在这里添加它们。接着设置保存路径,选择一个文件夹来存储你的项目。你还可以启用 Unity Analytics,它会收集项目数据,并提供与类似项目的对比基准以及玩家行为的分析。然而,考虑到我们当前并不需要发布游戏,所以我们暂时不启用它。 接下来,点击 创建 按钮,等待几秒钟,Unity 将会启动。启动后,您会看到一个包含多个区域和屏幕部件的界面。接下来,我们将逐一介绍这些界面部分,帮助你更好地理解它们。 Unity 界面组成
更多功能
总结
这些组成部分让我们可以有效地管理和开发游戏项目。但这只是基本界面,我们将会在后续的视频中介绍如何自定义界面,调整它以便更好地适应你的开发需求。 在下一个视频中,我们将继续深入了解如何自定义 Unity 的界面。感谢您的收看,我们下次见! 325 - 创建自己的布局自定义 Unity 界面布局在本视频中,我们将学习如何更改 Unity 的界面布局。你可以通过调整布局来根据需要优化你的开发环境。方法很简单,首先在界面的右上角,你会看到一个按钮,默认情况下会显示为 Default。点击这个按钮,你会看到多种布局选项。 可用的布局选项
创建和保存自定义布局你可以根据自己的工作习惯创建并保存自定义的布局。比如,我创建了一个叫做 DPI Layout 的布局,它的结构是:
如果你想尝试自定义布局,可以按照以下步骤操作:
修改和删除布局
额外的窗口你还可以通过 窗口 菜单添加更多的视图。例如,你可能需要一直打开 动画编辑器(Animator),在这种情况下,你可以把它作为一个单独的窗口添加并固定在界面上。同样,如果你希望随时访问 资产商店,也可以把它添加到界面中。 不过请注意,某些窗口可能会占用较多的系统资源,所以在设计布局时需要考虑到性能。 总结你可以随时创建、保存并调整你自己的布局,确保你的开发环境符合个人习惯。随着你使用 Unity 的深入,你会逐步了解哪种布局最适合自己的开发工作流。 感谢你的收看,我们下个视频再见! 326 - 玩家移动创建玩家移动脚本在本视频中,我们将学习如何让一个物体在游戏中移动,并且这个物体会由玩家控制。你将学会如何使用用户输入以及基本的物理学。我们将会深入探讨物理学的内容,但这将在接下来的几期视频中详细讲解。 设置场景对象
修改物体颜色
创建玩家移动脚本
调整摄像机
为球体添加物理属性
运行游戏
总结我们创建了一个简单的玩家控制脚本,通过用户输入来控制球体的移动,同时使用物理引擎使得球体具有真实的物理行为。接下来,我们会进一步探讨物理学和脚本编写,帮助你更好地理解游戏对象的行为并创建自己的游戏。 在后续的课程中,我们会详细讲解如何使用 RigidBody 和其他物理组件,帮助你构建更复杂的游戏机制。 327 - 确保我们正确更改视频概述在本视频中,您将学习如何确保在游戏播放模式下进行的更改是安全的。因为在播放模式下所做的任何更改,如果不小心保存下来,可能会对场景产生不可预期的影响。 播放模式下的更改让我们首先开始游戏并进入场景视图。在游戏视图中,我们可以看到当前对象的状态。比如,我们将一个立方体的缩放调整为每个方向放大5倍,并且旋转它,使得X和Y轴的旋转角度为55度。此时,立方体变得非常大。 当我们停止游戏时,您会看到立方体返回到它的初始状态,这就是播放模式的特点之一。所有在播放模式下所做的更改都会在停止游戏后丢失,恢复为之前的状态。 更改播放模式的提示颜色Unity提供了一个非常有用的功能来提醒您当前是否处于播放模式。通过在播放模式下改变编辑器界面的颜色,您可以清楚地看到哪些更改仅会影响游戏播放时的状态,而不会影响场景本身。
颜色效果当您启动游戏时,编辑器的顶部和层级面板的颜色会发生变化,从而帮助您确认当前是否处于播放模式。这确保了您不会不小心在播放模式下修改不应更改的场景内容。 通过这种方式,您可以避免误操作,比如在播放模式下修改物体的属性,而这些修改在停止游戏时会被丢弃。 小结设置播放模式提示颜色是一个非常简单但又非常有效的工具,可以帮助您清晰地意识到当前游戏的状态。这样,您就能避免在不该修改的情况下对场景进行更改。 328 - 物理基础视频概述在本视频中,您将学习Unity 3D中的物理基础。我们将首先快速回顾几个最重要的概念:刚体(Rigid Body)、碰撞器(Collider) 和 触发器(Trigger),并分别详细讲解它们的作用和使用方法。 刚体(Rigid Body)刚体是一个物理组件,它包含了关于物体质量的信息。通过刚体,您可以使物体受到物理引擎的影响,进而实现与其他物体的交互。刚体的属性包括:
碰撞器(Collider)碰撞器用于确定物体与其他物体是否发生碰撞。Unity中有多种类型的碰撞器,每种都适用于不同类型的物体:
触发器(Trigger)触发器是一种特殊的碰撞器,它不会阻挡物体,而是允许物体通过。当您启用一个碰撞器的 Is Trigger 属性时,该碰撞器就会成为一个触发器。触发器的特点是:
小结通过本视频,您应该对Unity 3D中的物理系统有了基本的了解,特别是刚体、碰撞器和触发器的使用。在接下来的教程中,我们将深入讲解各种碰撞器以及触发器的使用方法。 329 - RigidBody物理体刚体(Rigid Body)讲解在本视频中,我们将深入探讨刚体(Rigid Body)。首先,我将保存我的场景,以确保我们在进行任何操作之前都有备份。保存场景非常重要,您应该定期进行保存。 保存场景
设置场景接下来,我将选择我们的玩家角色,因为它已经包含了一个刚体组件。我们会逐一查看刚体的多个属性。
调整场景布局为了更好地查看游戏效果,我将使用自定义布局,这样就可以同时看到 场景视图 和 游戏视图。当前,2D视图已激活,所以我看不到3D场景。我们可以禁用2D模式,切换到3D视图,方便观察物体的行为。 修改刚体的质量(Mass)在游戏模式下,我将按下 播放按钮,并开始查看球体的行为。首先,我会打开 层级视图,并选择玩家对象。 接下来,我们来看一下刚体的 质量(Mass) 属性:
速度与质量的关系为了让球体重新开始移动,我将 速度(Speed) 设置为 200。现在,球体开始缓慢移动,但仍然比较慢。接着,我将质量调整回 1,并将速度设置为 50。此时,球体的移动速度变得非常快,甚至有时会从屏幕上飞出。 添加阻力(Drag)为了限制球体的最大速度,我将 阻力(Drag) 属性设置为 10。这样即使我继续按住键盘,球体的速度也不会无限增加,而会受到限制。阻力的作用是逐渐减缓物体的速度,直到它达到某个固定的速度。 启用重力(Gravity)如果我启用 重力(Gravity),球体会受重力影响开始下落。如果禁用重力,球体将悬浮在空中,不会再向下掉落。 启用运动学(Kinematic)如果将 运动学(Is Kinematic) 设置为启用,球体将不会再受物理引擎的影响,无法移动。这意味着即使我们在玩家控制脚本中添加了施加力的代码,球体也不会响应这些物理操作。这种功能可以用于墙壁等物体上,让它们不会受物理引擎的影响,但仍然起到阻挡作用。 插值(Interpolate)如果角色动画依赖于物理系统,使用 插值(Interpolate) 可以使物体的动画和物理计算更加平滑,这样角色的动作不会因为物理引擎的计算而出现不协调的情况。 碰撞检测(Collision Detection)默认情况下,刚体的碰撞检测为 离散(Discrete)。这对于大多数情况来说已经足够了。如果您的物体移动非常快,可能需要使用 连续动态(Continuous Dynamic) 碰撞检测来确保即使物体快速运动,也不会错过碰撞事件。大多数情况下,离散模式已经足够,而且对资源的消耗也较少。 约束(Constraints)我们可以通过约束来冻结物体的某些位置或旋转。例如,如果我不希望球体在Y轴方向上下落,可以通过冻结 Y轴位置 来实现。这时,即使启用了重力,球体也不会向下掉落。 如果冻结了 X轴位置,球体就不能再向左右移动,只能沿Z轴方向前进或后退。 同样,我们也可以冻结物体的旋转。例如,冻结 X轴旋转,那么球体就只能绕Z轴旋转,不会再绕其他轴旋转。 示例与实验接下来,我将为玩家添加一个立方体对象。通过调整立方体的尺寸(如设置为1x1x1),可以观察到当立方体与球体交互时,球体的行为发生了变化。 如果将旋转冻结,只允许沿某个方向旋转,我们就可以防止球体过度旋转,保持物体在物理世界中的稳定性。这对于不希望物体做出过多旋转的场景非常有用。 结论刚体是任何需要受到物理引擎影响的物体必不可少的组件。通过调整刚体的属性,您可以精确控制物体的运动、旋转、重力影响以及其他物理行为。在接下来的教程中,我们将进一步探讨 碰撞器(Colliders) 和它们如何与刚体一起工作,帮助您实现更精确的物理交互。 小贴士不要忘记经常保存您的场景,以确保您的工作不丢失! 330 - 碰撞体及其不同类型在本视频中,您将学习 Unity 中的不同碰撞体类型。正如您在演示中已经看到的那样,碰撞体有多种类型,并且我们已经使用过其中的两种类型,实际上甚至是三种。现在让我们来看看这些碰撞体类型。例如,我们的玩家有一个球形碰撞体,您可以在这里看到它。让我们点击玩家,您会看到玩家周围有一个绿色的线圈,这就是我们的球形碰撞体。它自动获取了一个半径值 0.5,这个值正好适合我们玩家的大小。所以如果我将这个值改为 1,球形碰撞体就会变得比我们所看到的图形还要大。 接下来我们启用玩家的重力并运行游戏,看看会发生什么。我们切换回场景视图。在这里,您可以看到我们的球在地下的上方,所以它看起来好像在飞,但是其实这与球形碰撞体有关。因为球形碰撞体与地下的网格碰撞体发生了碰撞。网格碰撞体是碰撞体的第二种类型,它的外观基于其网格。通过点击网格,您可以看到右下角显示了它的形状,而这个网格是非常具体的,它可以根据物体的形状来变化。比如,如果您的角色是汽车,那么网格就可以代表汽车的整个表面。 网格碰撞体通常用于表面,而网格碰撞体就是给这些表面添加碰撞体。在我们的例子中,地下物体使用了网格碰撞体。您可以看到,从底部没有碰撞体,而从顶部有这个绿色的颜色。我们只在顶部给它添加了这个绿色的颜色。如果我们想让网格碰撞体有更高的层级,可以启用凸起(Convex)选项,如您所见,它会在物体的顶部和底部都添加碰撞体。您甚至可以放大它,这样它的碰撞体就会变得更大。所以,如果您看一下现在的效果,球就会悬浮在这块物理表面上。 如果我们将球形碰撞体的半径减少到 0.5,球依然会悬浮在这块物理碰撞体上。所以碰撞体的大小不一定非要和物体本身的大小一致,但在大多数游戏中,使它们一致是有意义的。如果您的游戏设计需要碰撞体与物体大小不一致,当然也可以进行调整。您还可以更改碰撞体的中心位置,至少在这个例子中可以做到。例如,我可以将碰撞体的中心位置设置为比球高一点,这样如果我们查看时,Y轴位置就会变成 1。此时我们的球就会处于地下物体内部。正如您现在看到的那样,物理效果依然按照预期正常运行。 好,我们将球形碰撞体的半径恢复到 0.5,保持默认值。接下来我们来看一下其他类型的碰撞体,比如胶囊碰撞体。您可以看到,它的形状就像一个胶囊,此外它还有中心、半径和高度等属性,您甚至可以更改碰撞体的方向。例如,Y轴是向上的,X轴是向右的,Z轴是向前的。 现在我们将其恢复为 Y 轴,因为它是标准值。接下来我们启动游戏,尝试让球撞击这个胶囊碰撞体,看看会发生什么。我们尝试让它掉下来,但正如您所见,它并没有掉下去。为什么会这样?它是不是太强了,还是为什么呢?您可以自己想一想,可能是什么原因呢? 是的,原因就是它缺少刚体。我们可以给胶囊添加一个物理刚体,然后再次尝试让它和球碰撞,看看会发生什么。现在我们可以看到,胶囊会掉下来。这样它就会自动获得物理属性,您可以用它来创建保龄球游戏或多米诺骨牌游戏之类的内容。 接下来是盒形碰撞体。我们创建一个立方体,您可以看到,立方体周围也有一个绿色的线圈,这就是盒形碰撞体。您可以改变盒形碰撞体的大小,您可以看到,现在我将 X 轴的值更改为 2,Y 轴或 Z 轴也可以按需求调整。您可以使碰撞体比实际物体大,正如您所看到的那样。 当然,您也可以移动碰撞体的中心。现在您可以将它移到 1 的位置,这样它就会处于立方体的中心,或者将它移动到立方体的顶部。此时立方体会穿过碰撞体,但碰撞体仍然存在并悬挂在地面上。为了让物理效果生效,我们还需要为立方体添加一个刚体。现在,您可以看到,立方体会从底部掉下去,尽管它的图形在底部,但物理效果会作用在碰撞体上。 接下来,我将删除立方体和胶囊,只保留玩家。假设我们想要一个弹性的地下物体,我们将球放回顶部。如果我们想让地下物体变得有弹性,我们需要添加一个物理材质。您可以看到,地下的网格碰撞体还没有任何材质,我们可以创建一个新的物理材质。在资产菜单中选择创建并选择物理材质,命名为弹性地面。 弹性地面的物理材质有动态摩擦、静态摩擦和弹性等属性,您可以根据与物体碰撞的物体来调整摩擦和弹性。我们将摩擦设置为 0,并将弹性设置为 1。弹性值的范围是 0 到 1,弹性为 1 意味着物体会以相同的力度反弹回来。现在我们将这个弹性地面材质应用到地下物体上,并启动游戏。此时,您会看到我们的球开始反弹。 为什么它反弹不回去呢?我们需要为玩家添加相同的弹性材质。现在,您会看到球和地下物体都会反弹,而且反弹的力度与之前一样。根据您的设置,它会越来越高,因为弹性会影响每次反弹的力度。如果我们将材质的摩擦结合设置为平均,反弹结合设置为平均,那么两者的反弹效果会相乘,从而产生更大的反弹效果。 接下来,我们看一下摩擦的效果。我将从玩家身上移除弹性地面材质,添加一个新的摩擦地面材质。我们可以设置动态摩擦和静态摩擦为 1。现在我们启动游戏,球开始下落,并且在我移动球时,球的速度会逐渐减慢。这就像是路面与非常光滑的地面之间的差异,如果没有摩擦,地面就像冰面一样滑;而摩擦为 1 时,就像是非常粗糙的地面,物体会被减速。 总结一下,当您想给物体添加碰撞体时,可以选择不同类型的碰撞体。碰撞体的添加会显著改变物体的行为和物理反应。最后,别忘了保存您的场景,期待在下一个视频中,我们将研究触发器。 331 - 触发器欢迎回来!在本视频中,我们将探讨触发器(Trigger)的概念。你已经见识过物理体(Rigidbody)和碰撞器(Collider)等内容,现在让我们来添加一个新组件:触发器(Trigger)。其实,触发器并不是一个新组件,它仅仅是碰撞器的一种特殊状态。你可以将一个碰撞器设置为触发器。例如,我们的玩家可以是触发器,或者我们可以在游戏中创建一个新的对象作为触发器。 创建并设置触发器首先,创建一个新的3D对象(Cube),然后将其位置重置。接着将其稍微移动一下,上下调整位置,调整Y轴缩放为5,X轴缩放为2,Z轴缩放为3。现在我们有了一个墙壁对象。为了方便演示,可以将墙壁位置调整到侧边,并将X轴设置为1,Z轴设置为4。此时我们有一个较大的立方体墙壁。 我们想要实现的效果是:当玩家穿过或球体穿越这个立方体时,某些事情会被触发,例如墙壁消失。为了做到这一点,我们将给立方体添加一个“Is Trigger”选项,表示这个立方体不再作为一个实际的墙壁,而是变成了一个触发器。 当你设置了“Is Trigger”后,碰撞器会变得不再阻挡对象。所以球体将会忽略它,直接穿过它。如果我们希望这个墙体在被球体或玩家穿过时触发某种效果,我们就需要创建一个新的脚本。 创建触发器脚本接下来,我们创建一个新的C#脚本,命名为 using UnityEngine;
public class TriggerScript : MonoBehaviour
{
private void OnTriggerEnter(Collider other)
{
Debug.Log("Triggered");
}
} 上面这个脚本中, 测试触发器当你运行游戏时,当球体穿过这个触发器时,控制台会打印出“Triggered”。每次球体通过触发器时,都会打印一次。 触发器销毁对象接下来,我们改进脚本,使得当触发器被激活时,销毁这个墙体对象。修改 using UnityEngine;
public class TriggerScript : MonoBehaviour
{
private void OnTriggerEnter(Collider other)
{
Debug.Log("Triggered");
Destroy(gameObject); // 销毁包含此脚本的游戏对象
}
} 当球体穿过触发器时,墙体对象就会被销毁。通过这种方式,你可以实现让物体消失的效果。 多个触发器和物理效果接下来我们将多个触发器对象复制,并排列在一起。每个立方体都有一个触发器组件,并且在球体穿过时会销毁自己。为了更好地测试效果,我们需要为每个立方体添加物理属性,确保它们能够被物理系统影响。我们给立方体添加 物理引擎和触发器现在,你可以看到,当球体碰到这些立方体时,如果立方体上启用了触发器,它会消失。如果没有启用触发器,它将不会消失。 创建多米诺骨牌效果通过将这些触发器对象组合起来,你可以创建一个多米诺骨牌效果。每个立方体在被击中后都会倒下。为了增强效果,我们可以将球体的大小增大,调整它的 小结到目前为止,我们已经展示了如何使用触发器来改变游戏对象的行为。通过触发器,你可以让游戏对象在被触发时消失、改变状态,或者触发其他事件。触发器非常适合用来实现如门的开关、物品收集或障碍消除等效果。 在以后的视频中,我们将会更多地使用碰撞器、刚体和触发器,帮助你深入理解这些物理概念,并在实际游戏中使用它们。 希望这些内容对你有所帮助,我们将在下一个视频中继续深入探讨! 332 - 预制件和游戏对象欢迎回来!在本视频中,我想和大家谈论一个非常重要的概念,这不仅是 Unity 开发中的关键,也是保持项目整洁的好方法。到目前为止,我们创建了一些资产和游戏对象,但我们没有很好地整理资产文件夹,也没有使用一个非常重要的功能——预制体(Prefabs)。今天,我将向你们展示如何正确使用预制体并保持项目的整洁。 保持项目整洁首先,我们应该对不同类型的资产创建不同的文件夹。例如,可以创建一个“Scripts”文件夹,里面存放所有的脚本。比如我们的玩家移动脚本(Player Movement)和触发器脚本(Trigger Script),以及以后创建的所有新脚本,都应该放到“Scripts”文件夹中。 接下来是“Materials”文件夹。你可以创建一个文件夹专门存放普通材料(Materials),还可以再创建一个“Physics Materials”文件夹来存放物理材质,但我个人习惯将它们放在同一个文件夹中。只要将材料文件拖放到“Materials”文件夹中即可。 至于场景文件(Scenes),你可以选择将其保存在资源文件夹之外,也可以创建一个专门的“Scenes”文件夹。个人来说,我更喜欢将场景直接放在资源文件夹根目录下,但你也可以根据需要创建一个专门的文件夹来存放。 保存场景一个非常重要的注意事项是保存场景。在关闭项目之前,确保保存你的场景,因为场景在关闭时并不会自动保存。如果你没有保存场景,可能会丢失场景中所有的游戏对象。场景中的每个对象都属于“游戏对象”(Game Objects)。比如主相机(Main Camera)、定向光(Directional Light)、玩家(Player)等等,都是游戏对象。 如果我们创建一个新的空对象(Empty Object),它除了“Transform”组件外,不包含任何其他信息。每个游戏对象,即使是空对象,都至少会有一个“Transform”组件。你可以通过这种方式创建各种类型的游戏对象,包括2D和3D对象。 使用预制体(Prefabs)如果你希望让某些游戏对象在不同的场景中共享或者想要在程序中重复使用某些对象,那么你可以将它们转换为预制体。例如,我们想把当前的立方体(Cube)作为预制体,这样我们就可以在以后任何时候重复使用它。 方法很简单:将立方体拖放到“Assets”文件夹中,这时它就会变成一个名为“Cube.prefab”的预制体。你可以随时将它拖回场景中,调整位置,并且可以重复使用多个立方体。 例如,现在我删除了场景中的所有立方体,但我可以随时将预制体拖入场景中,并且不必每次都重新创建它。这使得我们可以方便地在不同的场景中使用这些预制体,甚至在程序中动态地实例化它们。 预制体的作用通常,我们会将很多对象创建成预制体,尤其是那些你希望在多个场景中重复使用的对象。例如,玩家对象就是一个必须作为预制体存在的对象。因为当你切换到不同的关卡时,你可能希望在每个关卡中都能重新生成玩家,并且保持玩家的状态和能力。通过预制体,你可以方便地在不同场景中复用同一个玩家对象。 创建并整理预制体文件夹现在,我们已经有了这个立方体的预制体,并且我们想将所有的预制体整理到一个文件夹里。我们可以创建一个“Prefabs”文件夹,然后将所有的预制体拖放到该文件夹中。这样,我们的项目就变得更加整洁,也能更好地管理和使用这些预制体。 总结预制体(Prefabs)是 Unity 游戏开发中非常重要的工具,能够帮助你将游戏对象复用到多个场景中,甚至在程序中动态生成和控制这些对象。因此,理解和正确使用预制体是开发过程中不可忽视的一部分。 通过本视频,你不仅了解了什么是游戏对象,还学会了如何创建和使用预制体。接下来的视频中,我们将讨论组件(Components)这一概念,敬请期待! 333 - 组件和更多关于预制件的内容在 Unity 中,一个非常基础但非常强大的功能就是组件(Components)。组件是非常强大的工具,它们允许我们为对象添加功能、外观或者物理效果。例如,我们的玩家对象有一个 Transform 组件、一个球体组件(Sphere)、刚体组件(Rigidbody)等,这些组件为玩家对象添加了功能和外观。这是你需要理解的一个非常重要的概念,因为每当你想要为某个对象添加不同的行为时,就需要使用组件。 组件的作用例如,当前我们的玩家有玩家移动的功能(Player Movement),但是假设你希望玩家能射击,那么你就需要为玩家对象创建一个新的组件。这个组件将是一个脚本组件(Script Component),你可以创建一个新的脚本来处理射击行为,或者你可以在现有脚本的基础上修改。通过这种方式,你可以为对象添加不同的功能或效果。 添加和移除组件你可以根据需要为对象添加组件,也可以随时移除它们。例如,假设你希望为玩家添加粒子效果,你可以为玩家对象添加一个粒子系统(Particle System)。另外,关于物理效果,虽然到目前为止我们只有一个刚体组件(Rigidbody)和一个球形碰撞器(Sphere Collider),你也可以为玩家添加更多的物理组件。如果你想为球体添加一个额外的碰撞器,你可以添加一个盒形碰撞器(Box Collider)。这样,玩家对象就有了两个碰撞器:一个球形碰撞器和一个盒形碰撞器,两个组件都添加到玩家对象上。 复制和粘贴组件如果你希望将某个组件从一个对象复制到另一个对象,可以很轻松地完成。例如,如果你希望将玩家移动组件(Player Movement)添加到其他对象上,你可以通过复制该组件,并粘贴到另一个对象中。具体操作是:在目标对象的 Inspector 面板中,点击 "Copy Component" 按钮,然后选择另一个对象,点击 "Paste Component as New"。这样,你就可以把这个组件复制到新的对象上。你可以根据需要删除不合适的组件。 调整组件顺序如果你希望改变组件的顺序,Unity 也允许你通过拖动组件的顺序来调整。比如,如果你想将玩家移动组件(Player Movement)向上移动,只需按住并拖动它。需要注意的是,这种操作可能会影响预制体实例。如果你在场景中的实例上进行此操作,Unity 会提示你是否继续,可能会影响该预制体的实例。因此,建议你在处理预制体时,直接操作预制体本身,而不是场景中的实例。 应用和保存组件的更改如果你在场景中的实例上对组件进行了修改,并希望这些修改应用到预制体上,可以点击 "Apply" 按钮来保存这些更改。假设我们的玩家预制体的移动速度设置为 100,但是场景中的玩家实例的速度被设置为 20。如果你希望将这种速度修改应用到预制体中,只需点击 "Apply"。这样,你的玩家预制体的速度也会变为 20。值得注意的是,只有点击 "Apply" 后,修改才会反映到预制体上,而场景中的实例的修改不会影响到预制体,除非你手动应用这些更改。 总结组件是 Unity 游戏开发中至关重要的组成部分,能够让你为对象添加各种功能、外观和行为。你可以根据需求动态添加、移除、复制和调整组件。此外,对于预制体(Prefabs)的管理和修改也非常重要,通过理解如何应用更改,你可以保持项目的一致性和可维护性。 334 - 保持层次结构整洁在 Unity 中,随着游戏中对象的数量不断增加,尤其是在一些游戏中,你可能会遇到这种情况:你的层级视图(Hierarchy)中会有成百上千个不同的对象。例如,当你通过编程创建子弹时,游戏中会有很多子弹。你射出子弹,敌人也会射出子弹,甚至所有人都会射出子弹,到处都是子弹。最终,你的层级视图就会被这些子弹填满,而你将无法看到其他重要的游戏对象了。那么,我们该如何解决这个问题呢? 使用文件夹来管理对象一个简单的解决方案就是使用文件夹来组织对象。文件夹可以帮助你更好地管理和组织大量的对象,让你更容易找到需要的游戏对象。 创建文件夹例如,在之前的视频中,我创建了一个游戏对象,现在我将删除它,并重新创建一个新的空游戏对象。我们可以重置该对象的 Transform 组件,使其位于原点(0,0,0)。现在,我们可以将该空游戏对象作为文件夹使用。可以将它重命名为“Bullets”,然后将所有的子弹对象拖放到这个文件夹内。 示例:使用文件夹来管理多实例对象如果我们有多个相同的对象实例,使用文件夹也能帮助我们保持层级视图的清晰。例如,我们将一个名为 "Cube" 的对象重命名为 "Domino"(多米诺骨牌),然后将所有的多米诺骨牌对象放入 "Dominos" 文件夹。通过这种方式,如果我们复制这个对象,新的实例也会自动被放入文件夹中。 当你创建了文件夹后,你可以像操作普通对象一样操作它。你可以展开和折叠文件夹,在层级视图中隐藏不需要看的部分,只显示文件夹名称,这样可以大大减少混乱。 文件夹的其他应用文件夹的使用不仅限于相同类型的对象。它们也适用于以下几种情况:
总之,使用文件夹是一种非常有效的管理方法,尤其是在项目变得复杂时。只要在层级视图中看到大量对象时,就可以考虑使用文件夹来提高可读性和可维护性。 总结通过创建和使用文件夹,你可以将相关的游戏对象组织在一起,使你的层级视图更加整洁,操作更加方便。在本课程中,我们也将频繁使用文件夹来组织我们的游戏对象。所以,不要忘记在项目中合理使用文件夹。 335 - 类结构在本视频中,我们将来看一下我们新创建的行为脚本,这个脚本就是我们在上一个视频中创建的。如果你自己想创建一个脚本,你可以右键点击你的资源(Assets)文件夹,选择 Create,然后点击 C# Script。另一种创建脚本的方法是,点击你某个游戏对象(例如,一个 Cube),然后点击 Add Component,在右侧你可以创建一个新脚本。在这里你可以给脚本命名,例如命名为 New Behavior Script,并选择编程语言。如果你想用 JavaScript 编程,也可以选择 JavaScript。之后点击创建,脚本就会被添加到你的资源文件夹中。但我这里不会使用这种方法,因为我已经有了这个脚本。那么我们打开这个脚本,通常情况下,如果我们双击它,脚本会在我们的集成开发环境(IDE)中打开,例如 Visual Studio。如果你使用的是 MonoDevelop,脚本也会在那个环境中打开,或者任何支持 C# 和 Unity 的其他 IDE。尽管如此,我还是强烈建议使用 Visual Studio。 了解新行为脚本让我们来看看这个新创建的行为脚本。我们已经有了 16 行代码,尽管我们还没有写过任何实际的代码或符号。当前的内容包括三行 脚本结构解析我们有一个公共类 方法的实际应用接下来,我们可以简单地在 print("Start method was called"); 然后在 print("Update method was called"); 这样一来,当我们运行游戏时,我们就能看到 Unity 控制台中打印出 "Start method was called" 和 "Update method was called"。这样我们就能知道
例如,如果我们有 60 帧每秒,那么 在 Unity 中测试脚本现在让我们保存这个脚本,并回到 Unity 编辑器。如果我们此时运行游戏,我们会发现控制台没有任何输出。这是因为我们还没有将脚本添加到任何游戏对象上。为了测试它,我们需要将 我们可以通过以下三种方式之一来添加脚本:
添加脚本后,再次运行代码,你会看到控制台中的输出发生了变化。控制台会显示 "Start method was called" 只被调用了一次,而 "Update method was called" 已经被调用了很多次。这样,你就可以直观地看到 注意性能优化你可以看到 注释和代码结构另外,代码中的双斜杠 所有的代码块,如类和方法,都被大括号 总结在本视频中,我们介绍了如何创建并使用 Unity 脚本,以及 336 - Mathf和随机类在本视频中,我想向你介绍一些我们还没有使用过的类,这些类可以帮助我们实现很多功能,而我们无需自己编写这些功能。总的来说,我想向你展示的是,Unity 和 C# 中有一些内置的类,你可以利用这些类来实现一些常见的功能,而不用从头开始编写代码。 使用随机功能首先,我们要讨论的是 随机数,这是视频游戏中非常常见和重要的功能。在很多情况下,游戏需要有一些随机性,这样玩家就无法预测接下来会发生什么,从而增加游戏的趣味性。如果你想在游戏中引入随机性,你不必自己编写随机生成的代码。你可以直接使用一个已经内置的类—— Random 类。这个类专门用于生成随机数据,Unity 提供了一个特定版本的这个类。 我们来看一下 使用
|
24 - UNITY使用Unity构建Pong游戏338 - Pong介绍欢迎来到 Pong 游戏章节!在这一章中,我们将制作一个 Pong 游戏 的克隆版本,这款游戏最早的版本可以追溯到上世纪七十年代,是当时的巨大成功,几乎是我所知道的第一款电子游戏。我们将重新制作这款游戏,基本上复现它的玩法。 游戏内容简介如你所见,这个游戏包含了玩家 1 和玩家 2,每个玩家都有一个 球拍 和一个 飞来的球。我们将在游戏中使用音效,并且设有 计分系统,每次球被击打后,球的速度都会加快,这使得游戏变得越来越有挑战性,因为球拍的速度不会变化,玩家需要更加迅速地反应。 这是我们在本章中最终要实现的目标,你可以看到:
本章学习内容我们将使用多个场景来实现游戏的不同部分:
在这个过程中,你将学到很多不同的技能:
开始动手这章的内容会非常有趣,涵盖了很多不同的技术。我们将一步步走过这个过程,最终完成一个完整的 Pong 游戏。顺便提一下,本章还配有文本材料,你可以在学习过程中通过阅读这些材料来帮助理解。 好啦,准备好了吗?让我们进入下一个视频,开始制作 Pong 游戏吧! 339 - 基础UI元素欢迎回来。在本视频中,我们将看看如何使用 UI 元素,特别是 文本。我创建了一个新的项目,叫做 Pong Basics。在这里,我想要创建一个 UI 元素。首先,在 层级视图 中右键点击,选择 UI,你会看到多个选项,包括文本、图像、原始图像、按钮等等。现在我们先使用 文本。正如你所见,新的文本已经出现在屏幕的左下角。如果我们缩小视图到合适的角度,就能看到文本的位置。 Canvas 渲染模式这是我们当前的 Canvas 渲染模式,设置为 Screen Space Overlay(屏幕空间叠加)。这个模式的作用是将我们分配给 UI 层的内容,叠加到游戏视图的顶部,无论游戏视图中有什么物体,都不会影响 UI 层。 例如,如果我们添加一个 3D 立方体 并将其位置设置为 (0, 0, 0),它会出现在游戏视图的某个位置,像是一个物体。你可以看到,游戏视图本身在相对于整个 UI 层来说显得非常小。UI 层(即 Canvas)覆盖在上面,通常会占据更大的区域。 其他渲染模式除了 Screen Space Overlay 之外,还有其他几种渲染模式:
在本课程中,我们会使用不同的渲染模式,所以下面的视频或章节中会详细讲解这些模式。 修改文本内容在 Canvas 内部,我们创建了一个 文本 元素。创建 UI 元素时,Unity 会自动为我们创建一个 Canvas 和一个事件系统。如果没有之前的 UI 元素,Canvas 和事件系统会自动生成。我们的文本元素是 Canvas 的子元素,所以它被放在了 Canvas 文件夹内。 接下来,我们可以更改文本的内容。默认的文本是 New Text,我们可以将其修改为 0,但你会发现这个数字几乎看不见。接下来,我们将字体大小设置为 50,但你可能会发现它依然看不见,这是什么原因呢?是因为文本框的 高度和宽度 不足以容纳更大的字体。文本框的高度默认为 30,所以需要增加它的高度才能看到文字。我们将其高度调整为 60,这样就能看到文本了。 解决文本溢出问题如果你想保留文本框的高度为 30 但依然能看到文本,怎么办呢?你可以使用 溢出设置,设置 水平溢出 和 垂直溢出,这样文本就会显示出来,即使它被切掉了一部分。 另外,你可以调整文本的位置。可以通过 Transform 工具 来拖动文本框,或者直接在 Transform 面板中输入数值来精确调整位置。 对齐和锚点文本的位置还受到 对齐 和 锚点 设置的影响。例如,我们可以设置文本的 对齐方式,比如选择将文本水平和垂直方向都居中对齐。这样文本就会准确地显示在 Canvas 中间。如果你将锚点设为 左上角,文本就会固定在左上角,调整后,文本的 位置 X 和 Y 会发生变化。 修改文本颜色和字体文本的颜色也可以很容易地调整。你可以选择将文本颜色更改为 红色、白色、绿色 等等,或者选择你喜欢的任何颜色。比如,我们可以将文本设置为 粉红色,它与背景的蓝色很好地对比,十分醒目。 你还可以调整 行间距,如果文本有多行,可以通过行间距控制行与行之间的距离。行间距默认为 1,你可以增加它来得到更大的行距。 更改字体最后,你还可以随时更改字体。Unity 默认的字体是 Arial,但你也可以选择其他字体。如果你需要额外的字体,可以从 Asset Store 下载,或者从你电脑上导入任何字体文件。下载字体时要小心,确保你拥有该字体的使用权,因为一些字体是收费的。你可以搜索免费的或免版权的字体来使用。 小结以上就是关于 文本 元素的基本操作。接下来,在下一个视频中,我们将展示如何通过代码来动态更改文本内容。 340 - 基础通过代码访问文本欢迎回来。在本视频中,我们将展示如何通过编程更改文本元素的值。首先,让我们创建一个空对象,并命名为 Game Manager。这个 Game Manager 对象可以用来收集分数、管理游戏状态等。如果你想要添加一些功能,比如改变文本的内容,我们可以创建一个新的脚本,命名为 TextBehavior。接下来,让我们打开这个脚本。 编写脚本在 Unity 中,如果你要操作文本元素,你需要在代码中添加以下命名空间: using UnityEngine.UI; 这是一个非常重要的命名空间,否则你将无法创建或操作 Text 对象。你会看到有一个名为 Text 的类,它是 Unity 提供的用来将文本渲染到屏幕上的默认类。 我们将创建一个公共的 Text 变量,并命名为 public Text myText; 初始化和绑定文本在 Start() 方法中,我们可以初始化这个文本对象。你可以通过查找 Text 对象来实现,也可以像下面这样直接将它绑定到组件: void Start() {
myText = GetComponent<Text>();
} 这样,你就可以在 Unity 编辑器中将 Text 元素拖到这个字段中进行绑定了。 监听按键并更新文本现在,我们将设置一个监听事件,监听 Space 键的按下。如果按下空格键时,我们会改变文本的内容。我们将创建一个整数变量 textNumber 并初始化为 0,每次按下空格键时将其增加 1,同时更新文本显示的内容。 void Update() {
if (Input.GetKeyDown(KeyCode.Space)) {
int textNumber = 0; // 初始化文本数字
textNumber += 1; // 每次按下空格,数字加1
myText.text = textNumber.ToString(); // 更新文本显示
}
} 完善文本显示在上面的代码中,我们已经设置了文本的内容,每次按下 Space 键时,文本将显示增加后的数字。接下来,我们可以尝试其他按键操作,比如按下 S 键时,显示 "S was pressed"。 if (Input.GetKeyDown(KeyCode.S)) {
myText.text = "S was pressed"; // 当按下 S 键时,显示文本 "S was pressed"
} 在 Unity 中测试回到 Unity 编辑器,我们的 GameManager 对象现在需要绑定 Text 元素。你只需要将 Canvas 中的文本对象拖动到 TextBehavior 脚本的 myText 字段中。 接着,运行游戏并按下 Space 键,文本内容应该会随着每次按下 Space 键而增加。你还可以尝试按下 S 键,查看文本是否正确更新为 "S was pressed"。 调整文本框大小如果你发现文本框不能完全显示文本,可以调整文本框的宽度和高度。例如,你可以将文本框的宽度调整为 300,这样可以显示更多的文本内容。 保存场景在进行这些更改时,记得保存你的场景。可以选择文件菜单中的 保存场景,并给场景命名为 Main 或其他你喜欢的名字。 结语通过这些步骤,你现在已经学会了如何在 Unity 中操作文本元素并通过编程动态更改其内容。在接下来的 Pong 游戏 中,我们将继续使用文本元素,并且在大多数游戏中,UI 元素(如文本、按钮等)都是必不可少的。 341 - 基础按钮欢迎回来。在本视频中,我们将了解按钮的使用。按钮非常重要,因为它们允许我们在场景之间切换,例如可以用来选择游戏中的关卡、设置菜单等。按钮在 UI 中非常常见,因此了解如何使用它们非常重要。 创建按钮为了在 UI 中添加按钮,我们需要在 Canvas 中创建一个按钮。在 Canvas 上右击,选择 UI,然后选择 Button。这样会在 Canvas 中添加一个按钮,并且按钮本身包含一个子元素,即 Text。你可以随时修改这个 Text,它与我们之前见过的文本类似,唯一的不同之处是它会根据父元素(按钮)的大小进行伸缩。因此,按钮中的文本会占据整个按钮的空间。 按钮的属性按钮本身有一些常用的属性,比如 Transform,我们之前已经操作过;它有宽度、高度等设置。按钮还包含一个 Image 组件,里面有一个名为 Source Image 的属性,你可以随时更改按钮的外观。如果你想更改按钮的外观,可以将一个新的图像拖动到 Source Image 属性中。 实现按钮的交互到目前为止,当我们按下按钮时,按钮会做出按下的反馈,但并不会发生任何其他事情。为了让按钮做出响应,我们需要编写一个脚本,定义按钮按下时应该执行的功能。你可以选择将该脚本添加到一个空对象上,然后将这个脚本绑定到按钮的 On Click 事件上,或者你也可以直接将脚本添加到按钮上。 创建按钮行为脚本我们将创建一个名为 ButtonBehavior 的脚本,并为按钮编写交互逻辑。当前我们希望按钮按下时,能够增加分数。首先,在脚本中,我们将访问 TextBehavior 脚本,从而更新显示的分数。 using UnityEngine.UI;
public class ButtonBehavior : MonoBehaviour {
public Text scoreText; // 引用文本
// 按钮按下时调用的方法
public void OnButtonPressed() {
scoreText.text = "Button was pressed"; // 当按钮按下时,更新文本
}
} 绑定按钮脚本在 Unity 中,我们已经创建了 ButtonBehavior 脚本,现在我们需要将其绑定到按钮的 On Click 事件中。
这样,每次按下按钮时,OnButtonPressed 方法就会被触发,从而更新文本。 测试按钮功能现在,我们可以运行游戏并测试按钮功能。点击按钮时,文本应更新为 “Button was pressed”。 如果你按下其他按键,比如 Space 或 S,文本将相应地更新为不同的内容(例如 “S was pressed” 或 “Button was pressed”)。 结语通过这些步骤,你学会了如何在 Unity 中使用按钮及其交互功能。下一节视频我们将介绍如何使用按钮来切换不同的场景。 342 - 基础切换场景欢迎回来。在本视频中,我将向你展示如何从一个场景跳转到另一个场景。首先,我们来创建一个新的场景。我们可以通过多种方式来创建场景,例如创建一个新场景,或者复制我们的主场景。我们这次直接创建一个新的场景,命名为 Level One。 创建新场景
场景之间的跳转现在,我们希望在 Level One 场景中点击按钮后返回到主场景,而在主场景中点击按钮时跳转到 Level One。为此,我们首先需要修改按钮的行为。打开按钮的行为脚本,并将 修改按钮行为脚本我们不再需要设置文本,而是需要使用 using UnityEngine.SceneManagement; // 引用场景管理器
public class ButtonBehavior : MonoBehaviour {
public void OnButtonPressed() {
// 载入场景
SceneManager.LoadScene("Level One"); // 这里的字符串是场景的名称
}
} 在上面的代码中,我们通过调用 场景添加到构建设置当你尝试从一个场景加载另一个场景时,可能会遇到一个错误,提示场景没有添加到构建设置中。解决这个问题的方法是:
记住每个场景的索引位置,比如 Main Scene 是 0,Level One 是 1。 测试场景跳转
按钮跳转功能的挑战现在你已经能从主场景跳转到 Level One 场景,但如果你希望从 Level One 场景返回主场景,你需要在 Level One 中也添加一个按钮,并为其编写相应的跳转功能。 你可以通过以下步骤来完成:
using UnityEngine.SceneManagement;
public class ButtonBehavior : MonoBehaviour {
public void MoveToScene(int sceneID) {
SceneManager.LoadScene(sceneID);
}
}
完成场景跳转
动态场景跳转除了通过按钮触发场景跳转外,你还可以在游戏逻辑中根据需要动态加载场景。例如,当玩家死亡时,自动跳转到 Game Over 场景,或当玩家完成某个关卡后跳转到下一个关卡。你可以在任何需要的地方调用 总结在本视频中,你学会了如何使用按钮和方法在不同的场景之间跳转。你还学到了如何通过 SceneManager 动态加载场景,并根据按钮点击来触发场景切换。这种功能非常适用于游戏中的场景切换,如从主菜单进入游戏、从一个关卡进入下一个关卡等。 希望你能将这些知识应用到你的项目中,祝你编程愉快! 343 - 基础播放声音欢迎回来。在本视频中,我们将讨论如何在游戏中使用声音及其功能。你可以通过 Google 搜索“免版税游戏音效”,找到许多提供免费声音资源的网站,比如 1000 多个免费的音效、音乐曲目和游戏循环音效等资源。 免费声音资源例如,你可以访问一些网站,如 Free Game Music,它提供了非常适合游戏的背景音乐。你还可以尝试 Free Sound,这是一个由 Carsten Frosch 创建的网站,提供了多种音效资源,如太空音效、神秘的声音等。如果你需要一个光束的声音,只需要在这些网站中下载相应的音效即可。 Unity 中使用声音我们来看看如何将声音导入到 Unity 并在游戏中播放。首先,你可以通过 Unity 的资源商店下载免费的音乐包。例如,可以下载由 Vertex Studio 提供的 Absolutely Free Music 资源包。你可以选择下载整个包,或者只导入你需要的音乐文件。 导入音效下载并解压资源包后,你会看到一个包含音频文件的文件夹。在 Unity 中,你可以将该音频文件拖拽到你的场景中。接下来,我们要将声音添加到一个对象上,比如一个立方体(Cube):
控制音量和播放设置现在,音效已经被添加到立方体上。你可以调整以下属性:
通过代码控制声音你还可以通过代码来控制声音的播放。让我们创建一个新的脚本,命名为 SoundBehavior,并将其添加到立方体对象上。这个脚本将在玩家按下空格键时播放音乐。 using UnityEngine;
public class SoundBehavior : MonoBehaviour {
public AudioSource music; // 声明一个音频源
void Update() {
// 检查玩家是否按下空格键
if (Input.GetKeyDown(KeyCode.Space)) {
StartSound();
}
}
void StartSound() {
if (music != null) {
music.Play(); // 播放音乐
}
}
} 为音频源赋值在脚本中,我们首先声明了一个 接下来,将音频源(Audio Source)拖拽到 music 变量中,以确保脚本能够找到并播放音乐。 控制音频播放状态你还可以控制音乐的播放状态,例如:
void Update() {
if (Input.GetKeyDown(KeyCode.Space)) {
if (music.isPlaying) {
music.Pause(); // 暂停音乐
} else {
music.Play(); // 播放音乐
}
}
} 在这个实现中,当玩家按下空格键时,如果音乐正在播放,它将暂停;如果音乐没有播放,它将开始播放。 控制音频的其他功能除了基本的播放和暂停,
例如,你可以使用 总结今天你学到了如何在 Unity 中使用音效,并通过代码控制它们的播放。你可以:
通过这些功能,你可以让你的游戏更加生动和有趣。如果你对音效的其他功能感兴趣,可以进一步探索 AudioSource 类的其他方法和属性。 344 - Pong项目大纲欢迎来到本项目大纲和本视频。在这一部分,我将展示本章的最终结果,并告诉你如何构建这个项目。我会快速介绍一下,不想深入细节,因为你现在应该已经学到了大部分所需的知识。如果有些内容缺失,你可以参考文档,或者如果卡住了,可以观看接下来的视频,我会逐步讲解。如果你不想自己尝试构建游戏,也可以跟随视频一起做。 游戏场景介绍首先,让我们快速看一下我们正在构建的游戏。在我们的项目中,有多个不同的场景:游戏场景、游戏结束场景和主菜单。我们现在所打开的场景是 游戏场景,它包含了以下几个部分:
玩法设计
游戏实现细节我创建了一个 Canvas 画布,里面有两个得分标签:一个是玩家 1 的得分,另一个是玩家 2 的得分。你需要编写脚本来更新得分,根据球的运动轨迹来改变得分。
游戏过程演示
玩家与 AI
主菜单游戏的主菜单包括一个标题 "Pong",以及一个“Play”按钮,点击后会进入游戏场景。 球的控制方式
建议与总结我强烈建议你尝试自己动手构建这个游戏,因为通过实际构建,你能学到更多的知识。即使你只是跟随视频教程学习,也会有所收获。如果你发现视频中没有解释某些功能,或者在文档中找不到你需要的信息,请告诉我,我会尽量补充。但是如果你能自己解决问题,会更有助于你理解如何寻找解决方案,这对你未来构建自己的项目非常有帮助。 继续尝试构建游戏,或者跟着视频一起学习。希望在下一个章节见到你! 345 - 创建主菜单创建 Pong 游戏主菜单在本视频中,我们将开始为我们的游戏创建主菜单。首先,我们需要创建一个新项目。打开 Unity,然后点击“新建”按钮,创建一个新的项目。我们将项目命名为 Pong Clone,并选择 2D 游戏类型。组织名称保持默认即可。然后点击创建项目,这个过程可能需要几秒钟,完成后 Unity 将会以新项目重新启动。 一旦项目打开,我们可以看到默认的场景名称为“Untitled”,因为我们还没有保存该场景,并且在场景中可以看到主摄像头(Main Camera)。在我的布局中,如你所见,右侧是检查器(Inspector),底部是层级视图(Hierarchy),还有项目视图(Project)和控制台(Console)标签。在中间,我的游戏和场景视图中只看到摄像头。场景中没有背景,但在游戏视图中,游戏已经有了背景。 修改主摄像头背景颜色首先,我们要修改主摄像头的背景。点击右侧检查器中的 Main Camera,在摄像头组件下找到 Background 属性,点击该属性可以选择背景颜色。我们将背景颜色设置为白色。 这时游戏视图中的背景变成了白色,但在场景视图中,背景颜色并没有变化。这里需要注意的是,Unity 的 2D 设置已经激活。你可以随时激活它来查看 3D 模式下的效果。在摄像头的 Transform 组件中,Z 值默认为 -10。如果将其设置为 0,摄像头的位置会有所变化。 创建背景图像接下来,我们将创建一个 Quad 作为背景。在主摄像头下,右击并选择“创建 3D 对象”>“Quad”。这时会创建一个正方形对象,我们将其缩放为 1600x900(16:9 比例)。然后,将其重命名为 Background。 如果我们缩放视图,就会发现主摄像头相对较小,而背景则非常大。为了解决这个问题,我们需要缩放摄像头。在 Camera 组件中,将 Size 设置为 450,这样背景的大小就与摄像头一致。 设置背景材质背景的颜色现在已经是白色,但背景的材质还没有设置。我们可以创建一个新的材质,将其命名为 Black,然后将其应用到背景上。接着,在材质的 Albedo 属性中选择黑色。如果激活 Emission,背景颜色会更加纯黑,虽然在黑色情况下,启用与否影响不大,但其他颜色会有所不同。 调整背景位置现在,我们看到场景中的背景已经是黑色,但游戏视图中的背景仍然是白色。这个问题是由于摄像头的 Z 值小于背景的 Z 值。为了让背景显示在游戏视图中,我们需要将背景的 Z 值设置为大于摄像头的值,譬如设置为 10,这样背景就会显示出来。 保存场景当前场景已经完成了设置,我们可以保存它。在左下角点击 保存场景,并将其命名为 Main Menu。这样,我们的主菜单场景就准备好了。Unity 中的“场景”就像剧场中的不同场景一样,可以用来表示游戏中的不同状态,例如:主菜单、游戏场景、游戏结束场景等。 添加 UI 元素接下来,我们将为游戏添加一些 UI 元素。首先,我们将添加一个 Text,作为游戏的标题。在左侧的层级视图中,右击 Main Menu 并选择 UI > Text,这时 Unity 会自动为我们创建一个 Canvas 和一个事件系统。UI 元素会被放置在 Canvas 上,这意味着 Canvas 与游戏中的对象无关,它只负责显示 UI 元素。 我们将 Canvas 的名称更改为 Main Menu,然后在 Text 组件中,将文本颜色更改为白色。 调整 Canvas 和 Text可能你会发现,Canvas 的大小相对于游戏视图来说非常小。这是因为游戏视图的大小被调整得很大。通常情况下,Canvas 会比游戏视图大很多。如果我们希望 Canvas 与背景和摄像头的大小一致,可以在 Canvas 组件中将 Render Mode 设置为 Screen Space - Camera,然后将主摄像头拖入 Camera 属性中。接下来,调整 Canvas 的 Scale 设置为 Scale With Screen,并将参考分辨率设置为 1600x900。这样,无论屏幕尺寸如何变化,UI 都会根据屏幕大小进行扩展。 修改文本位置与字体由于文本太小且位置不合适,我们将调整其大小。将 Text 的宽度设置为 500,高度设置为 200,字体大小设置为 150。接着,为了让文本居中显示,我们将其 X 和 Y 坐标都设置为 0,这样它就会出现在屏幕的正中心。如果需要将其向上移动,可以调整 Y 坐标为 300。 添加“Play”按钮接下来,我们将添加一个 Play 按钮。在 Canvas 下,右击并选择 UI > Button,这时会自动创建一个按钮。将按钮的宽度设置为 200,高度设置为 100。然后,我们将按钮的颜色改为黑色,并将其文本颜色设置为白色。为了使按钮文本更加清晰,我们还将按钮的 Font Size 设置为 120。 调整按钮位置为了使按钮能够正常显示,我们需要调整它的尺寸。将按钮的高度设置为 130,这时你会看到按钮的文本溢出了。为了避免溢出,可以将按钮的高度调整为 300,然后将其位置设置为 Y = -200,使按钮位于屏幕底部。 改善文本和按钮的字体Unity 默认的字体非常普通,并不适合游戏风格。我们可以在网上寻找一些有趣的游戏字体。我找到了一款叫 Monroe 的字体,它非常适合我们的游戏。下载并导入字体文件后,我们可以将其应用到标题文本和按钮文本中,立即看到效果更为精致。 总结到此为止,我们已经完成了主菜单的创建,包含了游戏标题和一个播放按钮。下一步,我们将添加播放按钮的功能,使玩家能够点击按钮进入游戏。在下一视频中,我们将通过编写脚本实现这一功能。 记得保存你的场景,这样你的更改才会生效。保存好场景后,我们就可以进入下一个视频了。 346 - 切换场景和使用按钮欢迎回来。在这段视频中,我们将创建游戏场景的第一部分,并通过使用播放按钮从当前场景切换到下一个场景。为了使播放按钮能够执行功能,我们需要给它添加一个脚本,否则它不会有任何作用。所以,我们需要创建一个新的脚本。我右键点击,创建一个新的脚本,并将其命名为 "PlayButton"(播放按钮),因为在本视频中我将脚本保持简单,因为最终创建多个只完成一件事的脚本会更容易管理,每个脚本只需要专注于完成它必须执行的一个重要任务,像这个播放按钮脚本也是如此。所以,这个脚本将只负责这个按钮,唯一的任务就是将我们从当前场景带到下一个场景。 创建播放按钮的基本功能首先,我们需要为这个播放按钮赋予功能。我们如何做到这一点呢?我们将创建一个新的方法,这个方法是 public void PlayGame()
{
Debug.Log("Play Game was pressed");
} 这段代码的作用是,当按下播放按钮时,它会在控制台输出一条消息。接下来,保存这个脚本(按下 现在你可以看到,播放按钮脚本已经添加到按钮中了。接着,我们选择播放按钮本身作为 从一个场景切换到另一个场景现在我们知道按钮能够正常工作了,接下来我们要做的是让这个按钮实现从当前场景切换到下一个场景的功能。首先,我们需要创建一个新的场景。我们可以右键点击资产区,选择 "Create" -> "Scene",并命名为 "GameScene"(游戏场景),这是我们后续将实现游戏功能的场景。 进入新创建的游戏场景,你会看到这个场景是空的,没有任何东西。在这里,我们需要设置一个与主菜单场景相同的主相机和背景。你可以猜到,如何将这些设置从主菜单场景复制到游戏场景中,我来给你演示。 使用预制体(Prefab)共享主相机回到主菜单场景,选中主相机,将其拖动到资产区,从而创建一个预制体。你会看到在资产区中出现了一个新的主相机预制体。接下来,切换到游戏场景,删除原有的主相机(此时场景中的摄像头已经消失)。然后将刚才的主相机预制体拖到游戏场景中,你会发现它继承了主菜单场景中的所有设置,例如相机的大小(450),背景材质和黑色背景。 使用场景管理器切换场景接下来,我们需要在脚本中添加代码,让按钮实现从一个场景切换到另一个场景的功能。为了实现这个功能,我们需要使用 using UnityEngine.SceneManagement; 通过 SceneManager.LoadScene("GameScene"); 记住,传入的场景名称必须与实际场景名称完全一致,否则会报错。保存脚本后,回到 Unity 编辑器,重新运行游戏。点击播放按钮后,我们会发现场景切换成功了,控制台也没有错误。 添加场景到构建设置如果你在运行时遇到错误,提示 "Scene could not be loaded because it has not been added to the build settings"(无法加载场景,因为它没有被添加到构建设置),这时你需要将场景添加到构建设置中。在 Unity 中,打开 "File" -> "Build Settings"(或者使用快捷键 Shift + Command + B),将你希望包含在构建中的场景从资产区拖到 "Scenes In Build" 列表中。完成后,重新运行游戏,你就能顺利切换场景了。 整理资产文件夹在项目中,我们应该保持资产的整洁。尽管目前只有几个文件,但随着游戏开发的进展,组织结构会变得非常重要。我们可以创建多个文件夹来管理不同的资产,例如:
对于场景,你可以选择将所有场景文件保存在 "Assets" 文件夹的根目录下,或者你也可以创建一个专门的场景文件夹来存放这些场景文件。 总结现在,我们已经学会了如何创建和使用按钮,如何从一个场景跳转到另一个场景,以及如何组织和管理我们的游戏资产。下一步,在下一段视频中,我们将开始填充游戏场景,并进行一些整理工作。 347 - 构建我们的游戏场景游戏场景设置现在我们已经有了主菜单,接下来我们将进入游戏场景。在这个场景中,我们需要添加一些元素:墙壁、两个球拍、玩家一和玩家二的名字、分数以及非常重要的中间的球。让我们开始吧。 创建墙壁首先,我们需要为游戏创建墙壁。我们将使用立方体来做这些墙壁。首先创建一个新的3D对象,选择立方体,并将其命名为“墙壁上方”。此时,立方体会非常小,不太适用,因此我们需要调整其比例。我们将X轴的缩放设置为1400,Y轴的缩放设置为5。接着,将其位置调整到Y轴的350,位置就像是上方的墙壁。 接下来,我们需要为这个墙壁添加一个白色材质。我们将创建一个新的材质,命名为“白色”,然后为其设置白色发光效果。最后,把这个材质拖拽到我们的墙壁上。 现在我们已经有了上方墙壁。我们可以复制这个墙壁并命名为“墙壁下方”。唯一需要调整的是Y轴的位置,将其值设置为-350。接下来,我们创建左右墙壁。右墙壁和左墙壁的X轴缩放不需要改变,依旧是5,但是我们需要把Y轴缩放设置为670。然后调整位置,右墙壁的X轴位置为700,左墙壁为-700。 现在,我们已经完成了四个墙壁的创建,接下来是中间的分隔线墙壁。 创建中间的分隔线墙壁我们将创建一个新的空游戏对象,并命名为“中间墙壁”。这个墙壁将用于显示中间的虚线分隔线。首先,创建一个新的立方体,并将其命名为“中间墙壁1”。然后,我们需要调整它的比例,X轴设置为5,Y轴设置为100。接着,将白色材质拖拽到该墙壁上。 为了创建多个虚线,我们将复制这个墙壁。首先将第一个墙壁位置调整到(200, 0, 0),然后将其他两个墙壁分别放置到(140, 0, 0)和(280, 0, 0)的位置。 创建球拍接下来,我们创建两个球拍,分别对应玩家一和玩家二。首先,创建一个新的空游戏对象,命名为“球拍”。然后,创建两个立方体,分别命名为“球拍玩家一”和“球拍玩家二”。为它们设置合适的大小,分别为X轴150,Y轴20,并调整位置,玩家一的球拍设置为-600,玩家二的球拍设置为600。记得为球拍添加白色材质。 创建球接下来是球的创建。我们创建一个新的立方体,并命名为“球”。设置球的大小为X轴和Y轴为20,并将球的位置调整到(-100, 0, 0)。 创建UI HUD(显示玩家信息)我们接下来将创建HUD(头部显示区域),显示玩家的名字和分数。首先,创建一个UI文本,命名为“玩家一标签”,并将其拖拽到一个新的空游戏对象下,这样玩家一的名字和分数就会集中在同一个游戏对象中,方便管理。 我们还需要调整Canvas的设置。选择“画布”对象,在“画布”设置中,将其模式设置为“屏幕空间-摄像机”,并指定主摄像机。然后将画布缩放模式设置为“按屏幕缩放”,以便在不同分辨率下,UI能够自适应。 调整玩家一标签的位置,将其放置到左上角,位置为X轴125,Y轴54。接着调整“玩家一”的Canvas,设置X轴为-350,Y轴为300,宽度为700,高度为200。 设置字体和文本样式接下来,调整字体。我们使用Monroe字体,并将字体颜色改为白色。调整字体大小,确保它能够清晰可见。然后,修改玩家一的分数标签。复制“玩家一标签”,并将其命名为“玩家一分数”。将分数标签的位置调整到右下角,设置X轴为-100,Y轴为50。将文本内容设置为0,并居中对齐。 设置玩家二的UI为了为玩家二设置UI,复制玩家一的UI元素。改变玩家二的标签为“玩家二标签”和“玩家二分数”。调整玩家二标签的位置,将其放置到右上角,分数标签放置到左下角。记得调整相应的位置和对齐方式。 完成游戏场景现在,我们已经完成了游戏场景的布局。我们有了墙壁、球拍、球和中间的虚线分隔。HUD显示了玩家的名字和分数。 接下来,我们将实现这些元素的功能,在下一个视频中将编写相关的代码来使游戏场景开始工作。此时,您可以试着重新构建这个场景,根据自己的喜好调整值、颜色和字体,享受创建游戏的过程。 348 - 2D与3D碰撞体和刚体在这段视频中,我们将处理我们的球。到目前为止,我们的球只是场景中间的一个小方块,我们需要为它添加功能,使它能够左右飞行,来回反弹,每当它碰到球拍时都会反弹。当然,我们还希望它能够反弹到顶部和底部的墙壁。现在我们暂时不让它与侧墙发生碰撞,但我们将为此添加一些功能,以便进行测试。接下来我们开始修改场景中的球。 首先,我们的球需要有一个碰撞器(Collider),目前它已经有一个了。接下来我们需要为球添加一个物理材料(Physical Material)。到目前为止,球没有物理属性,只有一个碰撞检测。换句话说,球只是一个能够发生碰撞的物体。如果我们希望球能动起来,就需要给它添加一个刚体(RigidBody)。如果我们希望球能够反弹,还需要添加一个物理材质。我们先来添加刚体。 添加刚体首先我们要为球添加刚体(RigidBody)。如果我们想要使用物理效果,比如让球运动,我们必须确保球有一个物理刚体。点击“添加组件”(Add Component),选择“Physics”并添加一个“RigidBody 2D”。 接下来,我们会遇到一个错误:不能同时添加2D刚体和现有的3D碰撞器(Box Collider)。因为Box Collider(3D)不能与2D的RigidBody组件一起工作,所以我们需要删除当前的3D碰撞器,并添加2D碰撞器。 修改碰撞器删除现有的Box Collider(3D),然后添加一个Box Collider 2D。在这里,偏移量(Offset)应设置为零。完成后,我们就能给球添加2D刚体了。 创建物理材质接下来,我们需要创建一个物理材质(Physics Material),这个材质将赋予我们的球物理特性,比如摩擦力和反弹力。我们进入资产(Assets)文件夹,右键选择“Create” -> “Physics 2D Material”,并命名为 物理材质有两个重要的属性:
现在将创建的物理材质拖入到球的Box Collider 2D组件中。 测试反弹效果为确保球能正常反弹,我们将重力的比例(Gravity Scale)设置为100,这样球会更快地掉下来。现在我们可以看到,球在碰到底部墙壁时会反弹,并且反弹的速度和高度是相同的。 修改墙壁的碰撞器由于我们的球使用了2D碰撞器(Box Collider 2D),而墙壁现在仍然使用的是3D的碰撞器,因此我们需要将所有墙壁的碰撞器从Box Collider(3D)改为Box Collider 2D。我们将逐一修改:
修改球拍的碰撞器同样地,我们还需要修改球拍的碰撞器,将它们的碰撞器从Box Collider(3D)改为Box Collider 2D。然后,我们将偏移量(Offset)设置为零,并将大小(Size)设置为 测试球拍的反弹现在,球应该能反弹到球拍上了。为了让球从左到右飞行,我们还需要为球添加一个脚本。目前,球只是受重力影响,并没有其他运动。我们将在下一段视频中为球添加脚本,使它能够从左到右飞行。 349 - 左右移动我们的球欢迎回来。在本视频中,我们将为我们的球添加功能,使其能够左右移动并与球拍碰撞,这是我们预期的功能。接下来,我们当然还需要让我们的球拍也能移动,这是我们将在后续视频中做的事情。完成这些后,游戏就差不多完成了。我们现在开始为球创建一个新的脚本,命名为“BallMovement”。接着,我们可以创建并在 Visual Studio 中打开这个脚本。 创建和设置脚本在 Visual Studio 中,我们将看到 public float movementSpeed; // 控制球的基本移动速度 此外,我们还需要一个变量来控制每次球与球拍碰撞后球的加速值,以便随着游戏进展,游戏难度逐渐增加。我们可以创建一个 public float extraSpeedPerHit; // 每次碰撞后球的加速值 接着,我们需要一个最大速度值,这样球就不会一直加速,最终变得无法捕捉。我们设置一个上限 public float maxExtraSpeed; // 最大加速速度 还需要一个计数器,来记录球已经与球拍碰撞了多少次。这个计数器会帮助我们计算球的加速。 private int hitCounter; // 碰撞计数器 移动球的函数现在我们可以创建一个方法来控制球的移动。这个方法将是公开的,允许外部脚本调用。我们可以命名它为 public void MoveBall(Vector2 direction)
{
direction = direction.normalized; // 标准化方向,确保它的大小为1
float speed = movementSpeed + (hitCounter * extraSpeedPerHit); // 计算速度
speed = Mathf.Min(speed, maxExtraSpeed); // 确保速度不会超过最大值
Rigidbody2D rb = this.gameObject.GetComponent<Rigidbody2D>(); // 获取刚体组件
rb.velocity = direction * speed; // 应用速度
} 使用协程来移动球为了避免在每帧都调用 public IEnumerator StartBall(bool isPlayerOne = true)
{
hitCounter = 0; // 重置碰撞计数器
yield return new WaitForSeconds(2f); // 等待2秒
Vector2 startDirection = isPlayerOne ? new Vector2(-1, 0) : new Vector2(1, 0); // 判断是玩家1还是玩家2开始
MoveBall(startDirection); // 调用 MoveBall 方法
} 启动协程在启动球的移动时,我们会调用协程 StartCoroutine(StartBall(true)); // 假设是玩家1开始 在 Unity 中测试现在我们已经完成了球的基础移动逻辑。接下来,在 Unity 中测试时,我们需要调整球的质量和重力比例。为了让球变得非常轻,我们将质量设置为 0.01,并且将重力比例(Gravity Scale)设置为 0,因为球不需要受到重力影响。 Rigidbody2D rb = this.gameObject.GetComponent<Rigidbody2D>();
rb.mass = 0.01f; // 设置质量
rb.gravityScale = 0f; // 去除重力影响 设置参数在 Unity 中,我们将为 movementSpeed = 400f;
extraSpeedPerHit = 50f;
maxExtraSpeed = 1000f; 测试游戏当我们点击播放按钮时,球将等待 2 秒后开始向左移动(因为是玩家1开始),然后来回反弹。在开始时,球的速度应该是恒定的,不会变化,直到我们碰撞球拍时,球的速度才会增加。 增加碰撞计数为了让球在每次碰撞后加速,我们需要创建一个方法 public void IncreaseHitCounter()
{
if (hitCounter * extraSpeedPerHit < maxExtraSpeed)
{
hitCounter++; // 增加碰撞计数器
}
} 这个方法将被球拍脚本调用,以便在每次碰撞时增加球的速度。我们将在后续的视频中详细讲解如何实现球拍的脚本。 结束语到目前为止,我们已经完成了球的基础移动逻辑,并通过调整碰撞计数来增加游戏的难度。我们还在 Unity 中进行了一些测试,并调整了球的运动参数。下一步,我们将继续完善球拍的控制逻辑,确保游戏可以正常进行。 感谢收看,我们在下一个视频中继续! 350 - 球拍移动欢迎回来。在这个视频中,我们将处理我们的球拍。到目前为止,球拍无法移动,这是我们要在本视频中解决的问题。为此,我们需要两个脚本,一个用于玩家一的球拍,另一个用于玩家二的球拍。我们先添加一个新组件,创建一个新的脚本,命名为 记录玩家一的脚本在 我们需要一个 获取输入在方法内部,我们需要检查用户是否按下了控制球拍移动的按钮,并把结果保存为一个浮动的
这样,我们就能根据输入的值来控制球拍的移动。 public float movementSpeed = 200f;
void FixedUpdate() {
// 获取玩家输入的垂直方向
float v = Input.GetAxis("Vertical");
// 获取当前球拍的刚体组件
Rigidbody2D rb = GetComponent<Rigidbody2D>();
// 修改刚体的速度,保持X轴不变,Y轴根据玩家输入进行调整
rb.velocity = new Vector2(0, v * movementSpeed);
} 配置输入在 Unity 中,我们通过
完成这些步骤后, 物理设置为了让球拍不受重力的影响,我们需要为球拍添加 球拍移动的调试在 Unity 中,我们需要做一些调试来确保球拍能按预期移动。首先,我们为 处理玩家二的球拍现在,我们需要为玩家二添加 确保玩家二的球拍也有 调整难度为了让游戏的难度不平衡,你可以调整玩家一和玩家二的球拍速度。例如,给玩家二的球拍一个更高的移动速度,这样玩家二就会更容易击中球。反之,你也可以降低玩家一的球拍速度,以增加游戏的挑战性。 清理工作一旦完成了所有设置,我们需要做一些清理工作。将 下一步在下一个视频中,我们将进一步优化球拍的物理效果。目前,球只有在与球拍碰撞时沿水平方向(X 轴)移动,而我们希望在碰撞的不同位置(球拍的顶部或底部)产生不同的反弹效果。这样可以让 Pong 游戏更具挑战性和趣味性。 351 - 正确反弹欢迎回来。在本视频中,我们将处理球的反弹逻辑,使得游戏更加互动并且有趣。你可以看到,球从左侧飞来并撞击到球拍时,如果它撞到球拍的底部,球应该朝下反弹。所以 Y 值应该是负数。如果它撞到球拍的顶部,球应该朝上反弹,所以 Y 值应该是正数。为了实现这一点,我们需要添加一个脚本,这个脚本将被添加到球体上。我们将创建一个新的脚本,命名为 步骤 1:创建
|
25 - UNITY使用Unity构建Zig Zag克隆358 - 章节介绍欢迎来到 Zig Zag 克隆游戏章节在这个章节中,我们将一起制作 Zig Zag 游戏。Zig Zag 是一个非常基础的 3D 游戏,操作也非常简单。游戏唯一的输入就是 点击屏幕,这意味着玩家只需要在正确的时机点击屏幕,就可以玩得非常开心。尽管如此,Zig Zag 依然是一个非常上瘾且有趣的游戏,我真的很佩服那些设计出这种简单却如此吸引人的游戏的人。 游戏特点
本章内容在制作这个游戏的过程中,你将学到以下内容:
我们将会详细讲解如何从头开始构建这个游戏,包括关卡设计、物理效果、动画控制、粒子系统的实现等内容。你将能够通过这次练习学到很多关于 3D 游戏开发的技巧。 准备开始所以,准备好开始制作这个简单但又极具魅力的 Zig Zag 游戏了吗?让我们一起深入探索游戏开发的奥秘,看看如何用最少的输入打造出让玩家欲罢不能的游戏体验!我希望你已经迫不及待了,快来加入我们吧! 下一段视频我们将正式开始创建这个游戏,敬请期待! 359 - Zig Zag介绍欢迎来到 Zig Zag 克隆游戏章节在这个章节中,你将学习如何构建一款成功的 Zig Zag 克隆游戏。这款游戏类似于目前非常流行的 无尽跑酷游戏,其玩法简单但充满挑战,玩家可以享受几乎 无尽的游戏体验。你将学习如何使用 3D 角色,如何根据自己的需求自定义玩家的运动方式,而不是依赖预设的行为,如何创建 可收集物品 来增加得分,还会学习如何创建 3D 世界,并且更重要的是,如何让这个世界通过 程序化生成 扩展。最后,你还会学习如何在游戏中使用声音效果以及如何通过重置场景来重置游戏。 本章内容概览
本章目标这个章节的目标是让你能够从零开始构建一个功能齐全的 Zig Zag 风格游戏,并且在过程中学习游戏开发的许多关键概念和技巧。无论你是刚接触 3D 游戏开发还是已经有一定经验,本章都会给你带来宝贵的实践经验,帮助你在游戏开发的路上走得更远。 享受这个过程我希望你能享受这个章节,学到新的技能,并在构建游戏的过程中获得乐趣!在接下来的章节里,我们将深入探讨每一个环节,帮助你完成这款令人上瘾的游戏。 准备好开始了吗?让我们一起动手创建这个精彩的游戏吧! 360 - 基础通过代码实例化创建对象欢迎回来在这个视频中,我们将学习如何实例化对象。为了演示这个概念,我们首先创建一个新的 3D 对象。我将使用一个立方体,并希望通过代码将这个立方体实例化多次。 步骤 1:创建立方体对象首先,我们创建一个立方体并稍微旋转它,以便更好地查看。你可以为它添加一个材质,这样更容易区分它。或者,我们可以将背景颜色设置为纯色,这样立方体就会更清晰地显示出来。 步骤 2:保存立方体为 Prefab接下来,我们将立方体保存为一个 Prefab,这样可以在代码中实例化多个立方体。
步骤 3:创建代码脚本现在,我们要在空对象上添加一个脚本,命名为 using UnityEngine;
public class InstantiateCubes : MonoBehaviour
{
public Transform prefab; // 用于存储立方体的 Prefab
void Start()
{
for (int i = 0; i < 10; i++) // 创建 10 个立方体
{
// 通过 Instantiate 创建克隆对象
Instantiate(prefab, new Vector3(i * 3.0f, 0, 0), Quaternion.identity);
}
}
} 步骤 4:连接 Prefab 和运行代码
步骤 5:修改实例化位置如果我们希望立方体从不同的起始位置开始,可以调整实例化的位置。例如,将 X 坐标设置为 Instantiate(prefab, new Vector3(-15 + i * 3.0f, 0, 0), Quaternion.identity); 步骤 6:通过用户输入实例化对象除了在 using UnityEngine;
public class InstantiateCubes : MonoBehaviour
{
public Transform prefab; // 用于存储立方体的 Prefab
private int counter = 0; // 计数器,用于跟踪创建的立方体数量
void Update()
{
if (Input.GetKeyDown(KeyCode.Space)) // 按下空格键时创建新的立方体
{
Instantiate(prefab, new Vector3(-15 + counter * 3.0f, 0, 0), Quaternion.identity);
counter++; // 每次实例化时,增加计数器
}
}
} 在这种情况下,每当按下空格键时,新的立方体将在 X 坐标上按顺序排列,并且随着每次实例化,计数器都会递增。 步骤 7:运行并测试保存脚本并运行场景。每次按下空格键时,新的立方体就会出现在屏幕上,每个立方体之间的间距为 3 单位。通过这种方式,我们可以动态地创建对象。 小结现在你已经学会了如何通过代码实例化对象。无论是自动在开始时创建多个对象,还是通过用户输入动态生成对象,这个技巧都会在后续的项目中非常有用。希望你在下一个项目中能够使用这个技巧! 玩得开心,期待在下一个视频见到你! 361 - 基础Invoke和InvokeRepeating用于延迟调用和重复调用欢迎回来在这个视频中,我们将学习如何使用 步骤 1:创建
|
26 - UNITY使用Unity构建水果忍者克隆376 - 章节介绍开始构建《水果忍者》在本章中,我们将创建一款经典的游戏——水果忍者。这是一个非常好玩的游戏,我曾经在香港时玩了无数小时,甚至记得我曾通过火车从香港去中国。我的朋友,一位英国人,玩这款游戏非常厉害,打破了很多纪录,这让我非常有动力去打破他的记录。我也开始疯狂玩这款游戏,虽然这款游戏非常简单,但却极其有趣。你只需要滑动屏幕,切割水果,避免炸弹。这就是游戏的全部内容。 游戏构建目标本章的目标是帮助你创建一个类似的游戏,水果忍者,并且让你掌握以下内容:
个人经历分享回想起当时在香港的日子,我的朋友真的是水果忍者的高手,而我为了赶上他的记录,几乎每天都在玩这款游戏。虽然这款游戏玩法简单,但是它的游戏机制非常有趣。通过切割水果而避免炸弹,简单的游戏机制却能让人沉浸其中。 你将学到什么?通过构建这款游戏,你不仅可以学到游戏开发的基本概念,还可以:
分享与进步完成这个游戏后,我希望你能把它分享出去,无论是上传到Google Play商店,还是其他平台。如果你愿意,欢迎分享你上传的链接,看看你是如何将这个游戏做得更好的。我非常期待看到你根据这次教程所做的游戏,并且能够与你的朋友分享。 无论是水果忍者还是其他游戏,我希望你在开发过程中不断学习和进步,最终成为一名优秀的开发者。 感谢与鼓励感谢你跟随我一起完成本章内容,我期待在接下来的课程中与你共同探索更多有趣的游戏开发技巧。无论你将来想做什么样的游戏,保持对编程和开发的热情,一定能够实现你的目标。 祝你好运,期待在下个视频中见到你! 377 - 创建水果并将其爆炸构建水果忍者克隆游戏在本章中,我们将创建一个水果忍者的克隆游戏。如果你不熟悉这款游戏,可以去YouTube查找一些相关视频。它是一个非常基础的游戏,你需要切割水果。只需要简单地滑动屏幕,然后切割水果,水果会掉下来,你将获得相应的分数。游戏的目标是尽可能地获得更多的分数。当然,游戏中有一些东西会阻碍你的得分,那就是炸弹。一旦你切到炸弹,你就会输掉游戏。基本上就是这些内容,我们将构建这些基础功能。 创建项目首先,我们需要创建一个新的项目,并将其命名为Fruit Ninja clone(水果忍者克隆)。在我的例子中,我将其命名为V1,但你不需要这样做,除非你已经在项目中有了一个克隆版本。
导入资源接下来,我们将导入一些资源,这些资源可以从本章的讲座附件中下载。包括:
将所有这些资源拖到Unity的资源管理器中,并创建一个文件夹,命名为models,然后将这些资源和材质文件移入其中。 创建和调整橙子模型我们首先来处理橙子模型:
添加物理组件
创建Prefab和脚本
创建切割水果的功能在Fruit脚本中,我们需要实现一个切割水果的方法,使得水果在被切割时分成两部分: public GameObject slicedFruitPrefab;
void CreateSlicedFruit() {
// 在当前水果的位置和旋转下实例化切割后的水果
Instantiate(slicedFruitPrefab, transform.position, transform.rotation);
// 销毁当前的水果对象
Destroy(gameObject);
}
处理切割后效果
void CreateSlicedFruit() {
// 实例化切割后的水果
GameObject inst = Instantiate(slicedFruitPrefab, transform.position, transform.rotation);
// 获取切割后部分的刚体
Rigidbody[] rbs = inst.GetComponentsInChildren<Rigidbody>();
// 为每个切割部分添加随机旋转
foreach (var rb in rbs) {
rb.transform.Rotate(Vector3.forward * Random.Range(0f, 360f));
// 添加爆炸效果
rb.AddExplosionForce(Random.Range(500f, 1000f), transform.position, 5f);
}
// 销毁当前的水果
Destroy(gameObject);
} 测试切割效果
void Update() {
if (Input.GetKeyDown(KeyCode.Space)) {
CreateSlicedFruit();
}
}
结果和总结当你按下空格键时,你会看到橙子被切割成两部分,并且这两部分会以随机的旋转和爆炸力向不同方向飞出。这是一个基本的切割水果的实现,接下来,我们将实现水果的生成机制,使得水果可以自动生成并飞向空中,等待玩家去切割。 不要忘记保存场景,准备好进入下一章,继续构建你的游戏! 378 - 水果生成器项目目标在本视频中,我们的目标是生成一些水果,这些水果将从屏幕底部生成并快速飞向游戏场景。我们可以在后续阶段切割这些水果。目前我们无法切割它们,但以后会实现这一功能。此外,我们还希望水果生成的间隔时间是随机的,且每个水果生成的时间间隔不相同。 创建脚本
public GameObject fruitToSpawn;
public float minWait = 0.3f;
public float maxWait = 1.5f;
void Start()
{
StartCoroutine(SpawnFruits());
}
IEnumerator SpawnFruits()
{
while (true)
{
yield return new WaitForSeconds(Random.Range(minWait, maxWait));
Debug.Log("Fruit spawned");
}
} 配置 Unity 场景
添加旋转和力的作用
Destroy(fruit, 5f); 测试和调整
下一步计划下一步,我们将实现切割水果的功能,并将切割操作绑定到鼠标操作上。 379 - 创建我们的刀刃游戏开发 - 切割水果与刀刃实现欢迎回来!现在如果我们开始游戏并检查一个行为,那就是如果我们切割橙子,它们将永远存在于游戏中。我们已经确保橙子会在一定时间后被删除,但切割后的橙子却不会。因此,我们需要修改这一点。让我们进入脚本,具体来说是进入我们的 Fruit 脚本。 1. 切割后的水果删除在 Destroy(insert.gameObject, 5f); 这将使被实例化的切割水果(切割后的橙子)在 5 秒后被删除。现在我们来测试一下这个修改是否生效。我将切割水果并看看这些橙子切割后的物体是否会在 5 秒后被删除。正如你所看到的,它们确实消失了。这是非常重要的,如果不处理这些物体,可能会导致性能问题。虽然 PC 的硬件比较强大,问题不大,但在智能手机上,这可能会造成问题。所以一定要小心创建和删除游戏物体。 2. 创建刀刃接下来,我们需要创建刀刃。首先,我们创建一个空的游戏物体并命名为 Blade。然后,重置其位置。接下来,给它添加一个组件——Trail Renderer(拖尾渲染器)。拖尾渲染器会产生一个视觉效果,模拟刀刃移动时产生的尾迹。 现在,保存脚本并测试它。移动刀刃,调整 X 和 Y 的值,你会看到它创建了一个品红色的尾迹。由于我们尚未为刀刃设置正确的材质,所以它默认显示为品红色。接下来,我们需要为刀刃创建一个材质。 3. 创建刀刃的材质首先,进入 Assets 文件夹,创建一个新文件夹,命名为 Materials。然后,在该文件夹中创建一个新的材质,并将其命名为 Blade。我们选择一个淡蓝色作为颜色,或者你可以选择任何你喜欢的颜色。我们将颜色设置为稍微明亮一些的蓝色。然后,将该材质拖拽到刀刃的材质槽中,测试一下效果。如果你将刀刃移动来回,你就会看到蓝色的尾迹。 4. 让刀刃跟随鼠标接下来,我们要让刀刃的拖尾渲染器跟随鼠标。拖尾渲染器基于鼠标的世界坐标来移动。我们的游戏是 2D 游戏,但鼠标实际上位于 3D 世界中,所以我们将使用适合 2D 游戏的组件。 查看拖尾渲染器时,发现它保持存在的时间太长,这可能是由于默认的时间设置所致。将其修改为 0.2 秒,这样拖尾就会只存在 0.2 秒,生成短暂的拖尾效果。测试后你会发现拖尾更短,更符合预期。 5. 刀刃的宽度调整为了使效果更好,我们还需要调整拖尾的宽度。你可以通过调整拖尾的 Key 来设置不同的宽度。在拖尾渲染器的曲线编辑器中,创建新的关键帧并调整它们的位置。这样,刀刃的拖尾就会呈现出更像刀刃的形态。 6. 刀刃的物理设置为了让刀刃能够与物体发生物理互动,我们需要给刀刃添加 RigidBody2D 组件,并设置为 Kinematic,这样它就不会受到其他物体的影响,但仍然可以影响其他物体。我们还需要设置碰撞检测为 Continuous,以确保刀刃与物体的碰撞更加流畅。 7. 刀刃跟随鼠标的脚本为了让刀刃能够随着鼠标移动,我们需要编写一个脚本来实现这一功能。首先,我们在脚本中创建一个 RigidBody2D 变量,并在 Awake 方法中初始化它。接着,我们编写一个名为 SetBladeToMouse 的方法,使用 private void SetBladeToMouse() {
Vector3 mousePosition = Input.mousePosition;
mousePosition.z = 10; // 设置 Z 坐标以确保正确的位置
rb.position = Camera.main.ScreenToWorldPoint(mousePosition);
} 然后在 Update 方法中调用该函数,确保刀刃每帧都能根据鼠标位置更新。 8. 解决鼠标位置的 3D 坐标问题有时候,直接使用 9. 触发器与水果切割接下来,我们需要让刀刃切割水果。在 Fruit 脚本中,我们添加一个 OnTriggerEnter2D 方法。当刀刃与水果发生碰撞时,我们就切割水果。具体来说,当刀刃的碰撞器与水果的碰撞器相遇时,我们创建一个新的切割水果对象。 private void OnTriggerEnter2D(Collider2D collision) {
Blade b = collision.GetComponent<Blade>();
if (b != null) {
// 切割水果的逻辑
CreateSlicedFruit();
}
} 同时,我们需要给刀刃添加碰撞器,确保它是触发器,并且水果的碰撞器也设置为触发器。 10. 调整刀刃的碰撞器大小我们需要为刀刃设置适当的碰撞器大小,以便在切割水果时准确地检测到碰撞。通过调整刀刃的 Collider2D 的大小,使其与水果的大小匹配。测试时,如果刀刃与水果碰撞,水果就会被切割。 11. 切割水果现在,当刀刃碰到水果时,水果就会被切割成两部分。你可以测试一下,确认水果是否能够正确切割。 下一步在下一个视频中,我们将继续完善游戏,添加更多功能,优化游戏体验,提升游戏的视觉效果。 380 - GUI和炸弹创建游戏管理器我们首先要在游戏中添加一些UI元素,比如得分、最高分和一个计时器。为了实现这些功能,我们需要创建一个新的对象,命名为
添加得分文本和最高分文本接下来,我们设置一个显示得分和最高分的UI:
将分数功能添加到代码中现在,我们已经设置了UI,接下来要将分数更新逻辑连接到游戏中:
添加游戏结束机制为了使游戏更具挑战性,我们需要添加一些机制来停止游戏,例如计时器到期或切到炸弹时游戏结束。
随机生成炸弹和水果为了让炸弹出现得更有挑战性,我们需要在游戏中随机生成炸弹和水果。修改水果生成逻辑:
总结到这里,我们已经完成了游戏中得分、最高分显示、炸弹生成、以及游戏暂停等基本功能。在接下来的步骤中,我们可以进一步优化游戏的UI,添加更多的游戏玩法,例如计时器和重新开始游戏的功能。 381 - 游戏结束和重新开始欢迎回来。在我们游戏的当前行为中,每当我们碰到一个炸弹时,游戏就会停止。显然,这不是我们想要的最佳行为。我们期望的是,在炸弹触发时,能够展示一个“游戏结束”界面,或者类似的东西。对于我们的情况,我认为最好的选择是暂停背景中的游戏,同时弹出一个面板,显示“游戏结束”,并允许我们重新开始游戏。今天我们就要实现这一点。 创建游戏结束面板首先,让我们创建一个面板。我不想让它拉伸,只希望它居中,宽度设置为 500,高度设置为 250。这就是我们的面板了。接着,我们在面板内添加一个文本组件,显示“游戏结束”。我们将文本居中,并将其向面板的顶部移动。位置可以调整为 -30 或者 -40,以确保显示效果良好。然后将字体大小改为 48。 接下来,我们设置文本溢出,以确保文本能够完整显示。现在,文本应显示为“游戏结束”。为了避免混淆,我们将面板的名字改为 激活与隐藏面板现在,我们要根据游戏状态来激活或隐藏面板。如果游戏结束,我们希望显示游戏结束面板;如果游戏没有结束,我们则不显示它。 在我们的 游戏结束时显示面板当游戏结束时,我们希望能激活该面板,因此在游戏结束时,我们调用 添加分数显示接下来,我们为面板添加一个显示分数的文本。我们可以复制现有的游戏结束文本,将其修改为 为了在脚本中操作,我们需要一个新的变量来存储 添加重启按钮我们还需要添加一个重启按钮,让玩家可以重新开始游戏。首先,创建一个 UI 按钮,文本设置为“Restart”,并将其位置调整到合适的位置。接下来,我们为按钮添加一个 重启游戏功能在
我们可以通过查找带有标签“Interactable”的所有游戏对象,然后销毁它们。确保所有互动对象(如炸弹、橙子和切割橙子)都被赋予该标签。在 完整实现现在,我们可以测试游戏。在游戏过程中,获取分数后,碰到炸弹时,游戏会暂停,并显示游戏结束界面。点击重启按钮时,游戏会重置,分数清零,并销毁所有当前的游戏对象。 总结通过这一系列的步骤,我们实现了以下功能:
在接下来的视频中,我们将为游戏添加高分功能,目前虽然显示了分数,但我们还没有保存任何分数数据。我们将在之后的教程中完成这个部分。 382 - 添加高分欢迎回来!在本视频中,我们将添加高分功能。正如你在这里看到的,当前显示的“最佳得分”是零。接下来,我们将实现一个功能,使其能够真正记录和显示最高得分。 创建高分文本变量首先,我们需要在 public Text highScoreText; 这个变量将帮助我们访问和更新高分文本。 在
|
27 - 感谢您完成课程390 - 感谢您完成课程1. 课程结束感言好了,你已经完成了这门课程。在这两年的时间里,我意识到自己没有在课程的结尾留下最终的感言,而现在是时候补上了。你可能注意到,我看起来比两年前好了一些,我希望你喜欢我现在的模样,但这并不是重点。这段视频的重点是感谢你。 我非常感激你依然陪伴着我走到这一步,感谢你在这段超过 30 小时的课程中一路走来。我知道要花费大量时间来完成这门课程并掌握其中的知识,但如果你认真且持续地学习,那么你已经成为了一名真正的开发者。我由衷地感激你做到了这一点,也感激能有机会教你这些内容。 2. 我的愿景与目标我的愿景是教授 1000 万人如何编程,听起来这个目标或许很疯狂,但它一直在我的心中。虽然你不能看到我写在纸上的愿景,但我可以告诉你,这个目标我已经为自己设定,并且深信不疑。 你可能会问,为什么是 1000 万人?我的想法是,如果我能帮助 1000 万人学会编程,那么其中的一部分人将会开发出改变我们所有人生活的软件。试想一下,如果像扎克伯格这样的天才程序员从我这里学习编程,虽然现在他已经是一个了不起的开发者,并且拥有像 Facebook 这样改变世界的巨大公司,但假设在未来有某个新的软件创新,可能我无法预见它的样貌,但有一个从我这里学习的开发者,或许就是你,能创造出改变全球的应用软件。 我相信从这 1000 万人当中,哪怕只有 1000 人成为杰出的开发者,那么他们的成功将是我教导的一部分。那就是我的目标,也就是我所坚持的理念。为了实现这一点,我一直在制作这些视频,去帮助更多的人实现梦想。 3. 鼓励与祝福因此,我非常感谢你坚持走完了这门课程,感谢你从我这里学习了编程。我希望你现在能够创造出一些伟大的软件,用它来改变世界,或者至少改变你自己的世界。其实改变世界不一定意味着影响全球,有时候,仅仅是改善你自己的生活、为你的家庭带来更好的未来,获得一份更好的工作,甚至创办自己的公司,开始创业,这些也是非常值得的目标。 甚至,如果你只是将编程当作一种爱好,做一个简单的游戏,带给几个人快乐,或者让一小部分人感到愉悦,那也是非常美好的。其实能做到这些,就已经很了不起了。 我衷心希望你能在这条编程的路上走得更远,取得更大的成就。我非常感谢你和我一起走过这段旅程,并祝你一切顺利! 4. 未来展望我还会继续开设更多课程,我会让这些课程越来越好,因为在这过程中,我自己也在不断学习、不断进步,不仅作为一名开发者,也作为一名讲师。所以,我希望你能继续关注我的课程,未来会有更多有用的内容和教学资源提供给你。 此外,我也将推出一个网站 tutorials.eu,这是我发布教程和编程相关博客文章的平台。今天我们将首次发布博客文章,未来,当你观看这段视频时,网站上可能已经有成百上千篇文章可以供你学习了。而且,我们将制作每篇博客的配套视频,让你不仅能通过文字学习,还能通过视频进一步理解。我们绝不会仅仅满足于写文章,而是会结合视频内容提供更深入的学习体验。 5. 结语说实话,我可能有点语无伦次了,但我真的非常感谢你与我一起完成这门课程,祝愿你在未来的编程之旅中一切顺利。 |
完整的C#大师课程 Complete C# Masterclass
1 - 你的第一个C程序与Visual Studio概述
1 - 引言
2 - 你想达成什么
3 - 安装Visual Studio社区版
5 - 第一个程序:你好,世界
6 - 你好,世界项目结构
7 - 理解新旧格式,以及如何在控制台中发出声音
8 - Mac上的你好,世界
9 - Visual Studio界面
13 - 第一章总结
2 - 数据类型与变量
20 - 更多数据类型及其限制
22 - 数据类型:整型、浮点型与双精度型
23 - 字符串数据类型及其方法
24 - 值类型与引用类型
25 - 编码标准
26 - 控制台类及其方法
27 - 命名约定与编码标准
28 - 隐式与显式转换
29 - 将字符串解析为整数
30 - 字符串操作
31 - 一些字符串方法
32 - 如何使用转义字符在字符串中使用特殊字符
34 - 练习字符串1的解决方案
36 - 练习字符串2的解决方案
38 - 数据类型与变量挑战的解决方案
39 - 使用var关键字
40 - 常量
42 - 数据类型总结
3 - 函数、方法及如何节省时间
43 - 方法简介
44 - 函数与方法介绍
45 - 无返回值的方法
46 - 有返回值和参数的方法
48 - 方法挑战的解决方案
49 - 用户输入
50 - 尝试-捕获与最终
51 - 运算符
52 - 方法总结
4 - 决策
53 - 决策简介
54 - C语言中的决策制作介绍
55 - 尝试解析简介
56 - IF与ELSE IF尝试解析
57 - 嵌套IF语句
59 - IF语句挑战的解决方案
60 - 开关语句
62 - IF语句挑战2的解决方案
63 - 增强IF语句:三元运算符
65 - 增强IF语句:三元运算符挑战的解决方案
66 - 决策总结
5 - 循环
67 - 循环简介
68 - 循环基础
69 - For循环
70 - Do While循环
71 - While循环
72 - break与continue
74 - 循环挑战的解决方案
75 - 循环总结
6 - 面向对象编程(OOP)
76 - 对象简介
77 - 类与对象介绍
78 - 我们的第一个自定义类
79 - 构造函数与成员变量
80 - 使用多个构造函数
82 - Visual Studio中的快捷方式
84 - Visual Studio中的代码片段
85 - 理解方法与变量的私有与公共
86 - C#中的设置器
87 - C#中的获取器
88 - C#中的属性
89 - 自动实现属性
90 - 只读与只写属性
91 - 成员与终结器(析构函数)
92 - 对象总结
7 - C中的集合
93 - 数组简介
94 - 数组基础
95 - 声明与初始化数组及长度属性
96 - Foreach循环
97 - 为什么使用Foreach
98 - 多维数组
99 - 嵌套For循环与二维数组
100 - 嵌套For循环与二维数组两个例子
101 - 挑战:井字棋
102 - 锯齿数组
103 - 锯齿数组或多维数组
104 - 挑战:锯齿数组
105 - 将数组作为参数使用
107 - params关键字
108 - 为什么使用params
109 - 使用params获取多个数字的最小值
110 - 概述:通用与非通用集合
111 - ArrayLists
112 - 列表
113 - 哈希表
114 - 哈希表挑战
115 - 字典
116 - 编辑与删除字典中的条目
117 - 队列与堆栈概述
118 - C#中的堆栈
119 - 队列
120 - 数组总结
8 - 调试
122 - 调试简介
123 - 调试基础
124 - 局部变量与自动变量
125 - 调试:创建列表副本并解决一些bug
126 - 调试:调用栈抛出错误与防御性编程
9 - 继承与更多关于OOP
127 - 欢迎来到继承
128 - 继承介绍
129 - 简单的继承示例
130 - 虚拟与重写关键字
131 - 继承演示
132 - 继承挑战:视频发布与带回调的计时器
134 - 继承挑战2:员工、老板与实习生的解决方案
135 - 接口简介
136 - 创建并使用自己的接口
137 - IEnumerator与IEnumerable
138 - IEnumerable示例1
139 - IEnumerable示例2
140 - 继承结束语
10 - 多态与更多OOP文本文件
141 - 多态简介
142 - 多态参数
143 - 密封关键字
144 - 关联关系
145 - 抽象
146 - 抽象与多态的关键字
147 - 接口与抽象类
148 - 从文本文件读取
149 - 写入文本文件
150 - 多态总结
11 - 高级C话题
151 - 高级话题简介
153 - 访问修饰符
154 - 结构体
155 - 枚举
156 - 数学类
157 - 随机类
159 - 正则表达式
160 - 日期时间
161 - 可空类型
162 - 垃圾回收器
163 - Main参数解释(第一部分)
164 - 使用用户输入创建CMD应用的Main参数解释
12 - 事件与委托
165 - 委托简介
166 - 委托介绍
167 - 委托基础
168 - 创建自己的委托
169 - 匿名方法
170 - Lambda表达式
172 - 事件与多播委托
173 - 委托结束语
13 - WPF(Windows Presentation Foundation)替代品(2023年8月底前)
175 - WPF简介
176 - WPF及其使用时机介绍
177 - XAML基础与代码后置
178 - StackPanel、ListBox的可视与逻辑树
179 - 路由事件:直接冒泡与隧道
181 - 网格
182 - 数据绑定
183 - INotifyPropertyChanged接口
184 - ListBox与当前匹配列表
185 - ComboBox
186 - CheckBox
187 - ToolTip
188 - 单选按钮与图像
189 - 属性数据与事件触发
190 - PasswordBox
191 - WPF总结
14 - WPF(Windows Presentation Foundation)
192 - 安装WPF工作负载
193 - 创建WPF项目
194 - WPF项目结构与代码后置文件
195 - 创建我们的第一个GUI元素
196 - 创建带有列和行的网格
197 - 固定、自动与相对大小
198 - 创建一个完美的网格
199 - WPF挑战:重建此GUI
200 - WPF挑战解决方案:重建此GUI
201 - 列跨越与行跨越
202 - 用C#创建GUI元素
203 - 元素属性:样式与定位
204 - 按钮点击与事件处理
205 - 待办事项列表应用简介与项目设置
206 - 创建网格按钮与文本框
207 - 创建滚动视图与堆栈面板
208 - 设置x名称属性以便访问
209 - 添加待办事项创建逻辑
210 - ContentControl与UserControl简介
211 - 创建用于登录的ContentControl与UserControl
212 - 设计LoginView
213 - 显示LoginView UserControl
214 - 创建与显示InvoiceView UserControl
215 - 数据绑定简介
216 - 设置要绑定的数据
217 - 单向数据绑定
218 - 双向数据绑定
219 - 单向到源的数据绑定
220 - 一次性数据绑定
221 - ListBox简介
222 - ListBox项源
223 - ListBox项模板
224 - ListBox访问选中的数据
225 - 下一个应用:登录功能
226 - 创建项目与登录用户控件
227 - 添加密码框
228 - 环境变量
229 - 使用环境变量登录
230 - 密码更改事件
231 - 如何继续
15 - WPF项目:货币转换器第一部分
232 - WPF货币转换器项目概述与设置
233 - WPF货币转换器:矩形与渐变
234 - WPF货币转换器:设置堆栈面板与标签
235 - WPF货币转换器:自定义按钮与实现点击事件
236 - WPF货币转换器:创建带有文本框与下拉框的输入字段
237 - WPF货币转换器:将值绑定到下拉框
238 - WPF货币转换器:输入验证与逻辑完成
16 - 使用数据库与C
240 - 数据库简介
241 - 设置MS SQL Server和VS进行数据库工作
242 - 数据集和表的介绍与设置
243 - 关系或关联表
244 - 在ListBox中显示数据
245 - 显示关联数据
246 - 在ListBox中显示所有动物
247 - 通过点击从表中删除
249 - 删除动物、移除动物和添加动物功能
250 - 更新我们表中的条目
251 - 数据库总结
17 - WPF项目货币转换器第二部分
252 - WPF货币转换器:构建一个带数据库集成的货币转换器
253 - WPF货币转换器:设计货币转换的用户界面
254 - WPF货币转换器:理解数据网格功能和属性
255 - WPF货币转换器:为货币转换设置数据库
256 - WPF货币转换器:实现数据库的SQL连接
257 - WPF货币转换器:实现保存按钮功能
258 - WPF货币转换器:添加新的货币条目
259 - WPF货币转换器:在数据库中插入和编辑数据
18 - Linq
261 - Linq简介
262 - Linq温和介绍
263 - Linq演示
264 - Linq与列表和我们的大学管理者第一部分
265 - 使用Linq进行排序和过滤
266 - 基于其他集合创建集合
267 - Linq与XML
268 - 为LinqToSQL设置项目
269 - 将对象插入数据库
270 - 使用关联表与Linq
271 - 更高级的表连接
272 - 删除和更新
273 - Linq总结
19 - WPF项目货币转换器与GUI数据库和API第三部分
275 - WPF货币转换器:使用API和JSON获取实时货币值
20 - 编码面试的练习
21 - 线程
278 - 线程简介
279 - 线程基础
280 - 线程开始和结束完成
281 - 线程池与后台线程
282 - Join和IsAlive
283 - 任务与WPF
285 - 线程总结
22 - 单元测试 驱动开发(TDD)
286 - TDD介绍
287 - 什么是TDD(测试驱动开发)
288 - 创建项目并编写第一个测试
289 - 重构和添加领域
290 - 添加Web API
291 - 测试优先方法
292 - 断言消息
293 - 流畅断言
294 - 测试条件和前提
295 - 设置航班项目
296 - 将场景翻译为测试
297 - 红绿重构
298 - 给定-当-然后模式及避免超订场景的发现
299 - 避免超订场景
300 - 测试可信度与逆向提问
301 - 对剩余座位数进行实际逆向提问
302 - 参数化测试
303 - 通过检查生产代码是否完整发现新场景
304 - 重构rememberbookings
305 - TDD规则
306 - 使用测试驱动开发规则取消预订场景
307 - 处理取消预订未找到预订
308 - 如何发现新场景
309 - 应用层测试
310 - 应用层预订场景第一部分
311 - 应用层预订场景第二部分
312 - 应用层预订场景第三部分
313 - 配置内存数据库
314 - 参数化预订航班测试
315 - 实现预订服务
316 - 重构预订服务
317 - 创建取消预订的测试
318 - 最终确定取消预订
319 - 命名约定
320 - 测试套件作为文档
321 - 应用层
23 - UNITY基础
322 - Unity基础介绍
324 - Unity界面概述
325 - 创建自己的布局
326 - 玩家移动
327 - 确保我们正确修改
328 - 物理基础
329 - RigidBody物理体
330 - 碰撞器及其不同类型
331 - 触发器
332 - 预制件与游戏对象
333 - 组件及预制件的更多内容
334 - 保持层级整洁
335 - 类结构
336 - Mathf和随机类
337 - Unity基础总结
24 - UNITY使用Unity构建游戏Pong
338 - Pong介绍
339 - 基础UI元素
340 - 基础通过代码访问文本
341 - 基础按钮
342 - 基础切换场景
343 - 基础播放声音
344 - Pong项目概述
345 - 创建主菜单
346 - 切换场景并使用按钮
347 - 构建我们的游戏场景
348 - 2D与3D碰撞器和Rigidbody为我们的球
349 - 左右移动我们的球
350 - 球拍移动
351 - 正确反弹
352 - 得分系统
353 - 重新开始一轮
354 - 游戏结束画面
355 - 为游戏添加声音
356 - 添加基础AI
357 - 章节总结
25 - UNITY使用Unity构建Zig Zag克隆
358 - 章节介绍
359 - Zig Zag介绍
360 - 基础实例化通过代码创建对象
361 - 基础Invoke与InvokeRepeating进行延迟和重复调用
362 - 基础PlayerPreferences保存数据
363 - 基础Raycast
364 - Zig Zag的设置
365 - 设置透视
366 - 移动角色
367 - 使相机跟随玩家
368 - 动画角色
369 - 开始游戏
370 - 重新开始游戏
371 - 收集水晶并增加分数
372 - 添加高分
373 - 添加粒子效果
374 - 背景音乐循环
375 - 程序生成我们的地图
26 - UNITY使用Unity构建水果忍者克隆
376 - 章节介绍
377 - 创建水果并将其爆炸
378 - 水果生成器
379 - 创建我们的刀
380 - GUI和炸弹
381 - 游戏结束与重新开始
382 - 添加高分
383 - 扩展游戏
384 - 为Android准备代码
385 - 在Android设备上测试
386 - 进行一些调整
387 - 为游戏添加Unity广告
388 - 将设备设置为开发者设备
389 - 添加声音
27 - 感谢您完成课程
390 - 感谢您完成课程
1 - Your First C Program And Overview Of Visual Studio
1 - Introduction
2 - What Do You Want To Achieve
3 - Installing Visual Studio Community
5 - Hello World First Program
6 - Hello World Project Structure
7 - Understanding the new and old Format and how to make a sound in the console
8 - Hello World on a Mac
9 - Visual Studio Interface
13 - Chapter 1 Summary
2 - DataTypes And Variables
20 - More Datatypes and Their Limits
22 - Datatypes Int Float and Double
23 - Datatype String And Some Of Its Methods
24 - Value vs Reference Types
25 - Coding Standards
26 - Console Class and some of its Methods
27 - Naming Conventions and Coding Standards
28 - Implicit and Explicit Conversion
29 - Parsing a String To An Integer
30 - String Manipulation
31 - Some String Methods
32 - How to use special characters in strings with the escape character
34 - Solution For Exercise Strings 1
36 - Solution For Exercise Strings 2
38 - Solution For The Challenge Datatypes And Variables
39 - Using The var Keyword
40 - Constants
42 - DataTypes Summary
3 - Functions Methods And How To Save Time
43 - Methods Intro
44 - Intro To Functions Methods
45 - Void Methods
46 - Methods With Return Value And Parameters
48 - Solution For The Challenge Methods
49 - User Input
50 - Try Catch and Finally
51 - Operators
52 - Methods Summary
4 - Making Decisions
53 - Making Decisions Intro
54 - Introduction To Decision Making In C
55 - Intro to TryParse
56 - IF And Else If Try Parse
57 - Nested If Statements
59 - Solution For The Challenge If Statements
60 - Switch Statement
62 - Solution For The Challenge If Statements 2
63 - Enhanced If Statements Ternary Operator
65 - Enhanced If Statements Ternary Operator Challenge Solution
66 - Making Decisions Summary
5 - Loops
67 - Loops Intro
68 - Basics of Loops
69 - For Loops
70 - Do While Loops
71 - While Loops
72 - break and continue
74 - Solution For The Challenge Loops
75 - Loops Summary
6 - Object Oriented Programming OOP
76 - Objects Intro
77 - Introduction To Classes And Objects
78 - Our First Own Class
79 - Constructors and Member Variables
80 - Using Multiple Constructors
82 - Shortcuts in VS
84 - Code Snippets in VS
85 - Understanding private vs public for methods and variables
86 - Setters in CSharp
87 - Getters in CSharp
88 - Properties in CSharp
89 - Auto Implemented Properties
90 - ReadOnly and WriteOnly Properties
91 - Members And FinalizersDestructors
92 - Objects Summary
7 - Collections in C
93 - Arrays Intro
94 - Basics of Arrays
95 - Declaring and Initializing Arrays and the Length Property
96 - Foreach Loops
97 - Why Foreach
98 - Multi Dimensional Arrays
99 - Nested For Loops And 2D Arrays
100 - Nested For Loops And 2D Arrays Two Examples
101 - Challenge Tic Tac Toe
102 - Jagged Arrays
103 - Jagged Arrays or Multidimensional Arrays
104 - Challenge Jagged Arrays
105 - Using Arrays As Parameters
107 - Params Keyword
108 - Why would we use Params
109 - Getting The Min Value Of Many Given Numbers Using Params
110 - Overview Generic and NonGeneric Collections
111 - ArrayLists
112 - Lists
113 - Hashtables
114 - Hashtables Challenge
115 - Dictionaries
116 - Editing And Removing Entries in a Dictionairy
117 - Queues and Stacks Overview
118 - Stacks in Csharp
119 - Queues
120 - Arrays Summary
8 - Debugging
122 - Debugging Intro
123 - Debugging Basics
124 - Locals and Autos
125 - Debugging Creating Copies of Lists and solving some bugs
126 - Debugging Call Stack Throwing Errors and defensive programming
9 - Inheritance And More About OOP
127 - Welcome to Inheritance
128 - Introduction To Inheritance
129 - Simple Inheritance Example
130 - Virtual and Override Keywords
131 - Inheritance Demo
132 - Inheritance Challenge Videopost and Timer with Callback
134 - Inheritance Challenge 2 Employees Bosses and Trainees Solution
135 - Interfaces Intro
136 - Creating And Using Your Own Interfaces
137 - IEnumerator and IEnumerable
138 - IEnumerable Example 1
139 - IEnumerable Example 2
140 - Inheritance Outro
10 - Polymorphism And Even More On OOP Text Files
141 - Polymorphism Intro
142 - Polymorphic Parameters
143 - Sealed Key Word
144 - Has A Relationships
145 - Abstract
146 - Abstract and as is Keyword Polymorphism
147 - Interfaces vs Abstract Classes
148 - Read from a Textfile
149 - Write into a Text File
150 - Polymorphism Summary
11 - Advanced C Topics
151 - Advanced Topics Intro
153 - Access Modifiers
154 - Structs
155 - Enums
156 - Math Class
157 - Random Class
159 - Regular Expressions
160 - DateTime
161 - Nullables
162 - Garbage Collector
163 - Main Args Explained part 1
164 - Main Args Explained Using User Input Create A CMD App
12 - Events and Delegates
165 - Delegates intro
166 - Delegates Introduction
167 - Delegates Basics
168 - Creating your own Delegates
169 - Anonymous Methods
170 - Lambda Expressions
172 - Events and Multicast Delegates
173 - Delegates Outro
13 - WPF Windows Presentation Foundation Replaced end of August 2023
175 - WPF Intro
176 - Introduction To WPF And When To Use It
177 - XAML Basics and Code Behind
178 - StackPanel Listbox Visual and Logical Tree
179 - Routed Events Direct Bubbling and Tunneling
181 - Grid
182 - Data Binding
183 - INotifyPropertyChanged Interface
184 - ListBox and a List of Current Matches
185 - ComboBox
186 - CheckBox
187 - ToolTip
188 - RadioButtons and Images
189 - Property Data and Event Triggers
190 - PasswordBox
191 - WPF Summary
14 - WPF Windows Presentation Foundation
192 - Installing the WPF workload
193 - Creating a WPF project
194 - WPF project structure and code behind files
195 - Creating our first GUI Element
196 - Creating a grid with columns and rows
197 - Fixed auto and relative sizing
198 - Creating a perfect grid
199 - WPF Challenge Recreate this GUI
200 - WPF Challenge Solution Recreate this GUI
201 - Column span and row span
202 - Creating GUI elements with C Sharpmp4
203 - Element properties for styling and positioning
204 - Button click and event handlers
205 - Todo List application intro and project setup
206 - Creating the grid button and text box
207 - Creating the scrollview and stackpanel
208 - Setting x name attributes for access
209 - Adding the todo creation logic
210 - Introduction ContentControl and UserControl
211 - Creating ContentControl and UserControl for login
212 - Designing the LoginView
213 - Displaying the LoginView UserControl
214 - Creating and displaying InvoiceView UserControl
215 - Data Binding introduction
216 - Setting up the data to bind
217 - OneWay data binding
218 - Two Way Databinding
219 - One Way To Source Databinding
220 - One Time Databinding
221 - ListBox introduction
222 - ListBox ItemSource
223 - ListBox ItemTemplate
224 - ListBox accessing selected data
225 - Next application login functionality
226 - Creating the project and login user control
227 - Adding the password box
228 - Environment variables
229 - Using the environment variable for login
230 - Password change event
231 - How to move on
15 - WPF Project Currency Converter Part 1
232 - WPF Currency Converter Project overview and setup
233 - WPF Currency Converter Rectangles and Gradients
234 - WPF Currency Converter Setting Up Stack Panel and Labels
235 - WPF Currency Converter Customizing Buttons and Implementing Click Events
236 - WPF Currency Converter Creating Input Fields with Text Box and Combo Box
237 - WPF Currency Converter Binding Values to Combo Boxes
238 - WPF Currency Converter Input Validation and Completing the Logic
16 - Using Databases With C
240 - Databases Intro
241 - Setup MS SQL Server and VS For DB work
242 - Intro And Setting Up Our DataSet And Table
243 - Relationship or Associative Tables
244 - Showing Data in a ListBox
245 - Showing Associated Data
246 - Displaying all Animals In The ListBox
247 - Deleting From A Table With A Click
249 - Delete Animals Remove Animals and Add Animals Functionality
250 - Updating Entries in Our Tables
251 - Databases Outro
17 - WPF Project Currency Converter Part 2
252 - WPF Currency Converter Building a Currency Converter with Database Integration
253 - WPF Currency Converter Designing the User Interface for Currency Conversion
254 - WPF Currency Converter Understanding Data Grid Functionality and Properties
255 - WPF Currency Converter Setting up a Database for Currency Conversion
256 - WPF Currency Converter Implementing SQL Connections for Database
257 - WPF Currency Converter Implementing Save Button Functionality
258 - WPF Currency Converter Adding a New Currency Entry
259 - WPF Currency Converter Inserting and Editing Data in the Database
18 - Linq
261 - Linq Intro
262 - Linq gentle Introduction
263 - Linq Demo
264 - Linq with Lists and our University Manager Part 1
265 - Sorting and Filtering with Linq
266 - Creating collections based on other collections
267 - Linq with XML
268 - Setting up the project for LinqToSQL
269 - Inserting Objects into our Database
270 - Using assiociative tables with Linq
271 - Joining tables next level
272 - Deleting and Updating
273 - Linq Outro
19 - WPF Project Currency Converter with GUI Database and API Part 3
275 - WPF Currency Converter Using Live Currency Values Using An API And JSON
20 - The exercises for your coding interviews
21 - Threads
278 - Threads Intro
279 - Threads Basics
280 - Thread Start and End Completion
281 - ThreadPools and Threads in The Background
282 - Join And IsAlive
283 - Tasks and WPF
285 - Threads Outro
22 - Unit Testing Test Driven Development TDD
286 - TDD Introduction
287 - What is TDD Test Driven Development
288 - Create Project and Write First Test
289 - Refactoring and Adding Domain
290 - Adding Web API
291 - Test First Approach
292 - Assertion Message
293 - Fluent Assertions
294 - Test Conditions and Prerequisites
295 - Setting Up Flight Project
296 - Translating a Scenario to Test
297 - Red Green Refactor
298 - Given When Then Pattern And Avoid Overbooking Scenario Discovery
299 - Avoid Overbooking Scenario
300 - Test Trustwhortiness And Devils Advocate
301 - Practical Devils Advocate For Remaining Number of Seats
302 - Paremeterized Tests
303 - Discovering new scenarios by checking if the production code is complete
304 - Refactoring rememberbookings
305 - Rules of TDD
306 - Scenario Cancel bookings using TestDriven Development Rules
307 - Handle Cancel Booking No Booking Found
308 - How You Discover New Scenarios
309 - Application Layer Testing
310 - Scenario Application Layer Booking Part One
311 - Scenario Application Layer Booking Part Two
312 - Scenario Application Layer Booking Part Three
313 - Configure In Memory Database
314 - Parameterize Book Flights Test
315 - Implementing Booking Service
316 - Refactoring Booking Service
317 - Create Test for Cancelling Bookings
318 - Finalize Cancel Booking
319 - Naming Conventions
320 - Test Suit as Documentation
321 - Application Layer
23 - UNITY Basics
322 - Intro Unity Basics
324 - Overview of the Unity Interface
325 - Creating your own Layout
326 - Player Movement
327 - Making Sure We Make Changes Correctly
328 - Physis Basics
329 - RigidBody A Physical Body
330 - Colliders And Their Different Types
331 - Triggers
332 - Prefabs And GameObjects
333 - Components And More On Prefabs
334 - Keeping The Hierarchy Tidy
335 - Class Structure
336 - Mathf And Random Class
337 - Unity Basics Outro
24 - UNITY Building the Game Pong with Unity
338 - Pong Introduction
339 - Basics UI Elements
340 - Basics Accessing Text Through Code
341 - Basics Buttons
342 - Basics Switching Scenes
343 - Basics Play Sound
344 - Project Outline Pong
345 - Creating The Main Menu
346 - Switching Scenes and Using Buttons
347 - Building Our Game Scene
348 - 2D vs 3D Colliders and Rigidbody For Our Ball
349 - Moving Our Ball Left And Right
350 - Racket Movement
351 - Bouncing Off Correctly
352 - Scoring System
353 - Restarting A Round
354 - The Game Over Screen
355 - Adding Sound To The Game
356 - Adding a Basic AI
357 - Chapter Summary
25 - UNITY Building a Zig Zag Clone With Unity
358 - Chapter Intro
359 - Zig Zag Intro
360 - Basics Instatiating Creating Via Code An Object
361 - Basics Invoke And InvokeRepeating For Delayed Calls And Repeated Calls
362 - Basics Playerpreferences Saving Data
363 - Basics Raycast
364 - Setup For Zig Zag
365 - Setting The Perspective
366 - Moving The Character
367 - Make Camera Follow Player
368 - Animate The Character
369 - Start The Game
370 - Restart The Game
371 - Collecting Crystals And Increasing The Score
372 - Adding A Highscore
373 - Adding The Particle Effect
374 - Background Music Loop
375 - Procedural Creation Of Our Map
26 - UNITY Building a Fruit Ninja Clone With Unity
376 - Chapter Intro
377 - Create Fruits And Explode Them
378 - Fruit Spawner
379 - Creating Our Blade
380 - GUI and Bombs
381 - Game Over and Restart
382 - Adding The Highscore
383 - Extend The Game
384 - Prepare Code For Android
385 - Test On An Android Device
386 - Make Some Adjustments
387 - Adding Unity Ads to Your Game
388 - Setting Up Your Device as Developer Device
389 - Adding Sound
27 - Thank you for completing the course
390 - Thanks for finishing the course
The text was updated successfully, but these errors were encountered: