目录
第 2 步和第 3 步——在Prototype的主项目中创建LabeledTextBox控件并将其打磨至完美
第 4 步——通过将代码的可重用部分移动到通用项目来重构代码
介绍
什么是原型驱动软件开发(PDD)
原型驱动软件开发(PDD)与测试驱动开发(TDD)类似,但通常在TDD不可行时使用(例如,用于可视化开发)或用于比单元测试涵盖的功能更大的功能(在这种情况下,PDD并且可以练习TDD——PDD用于更大的功能,TDD用于更精细的功能)。
原型驱动开发是一种开发类型,在这种开发下,对于每个新的重要功能,开发人员都需要经过以下步骤:
- 创建一个新的Visual Studio项目,甚至是一个包含运行原型的Main(...)方法的解决方案。原型项目应引用主应用程序项目的项目和包的子集。
- 在Main Prototype项目中创建原型代码。
- 将原型代码打磨至完美。
- 通过将其可重用部分移动到它引用的可重用项目来重构代码,以便主应用程序代码也可以访问它们。
- 在主项目中使用可重用的代码。
第3项和第4项可以互换实现——可以将一些代码移至可重用项目,然后继续处理原型,然后将更多代码移至可重用项目。
之后,可以使用原型项目或解决方案来测试、调试或修改创建的功能。
这是PDD循环的示意图:
PDD的优势
PDD的主要优点是它允许更快地进行原型设计、实验和调试,因为我们只编译和重新启动精益原型,而不是大型和繁重的主应用程序。
以下是PDD优势的完整列表:
- 该原型允许您非常快速地试验和调试功能,而无需等待整个解决方案/应用程序启动。
- 通常,模拟和注入功能原型所需的部分比为整个应用程序制作模型更容易。例如,创建后端数据的模型来测试单个控件可能比创建模型来使整个应用程序正常工作要容易得多。这个想法将在未来的一些关于IoC(控制反转)和注入的文章中进行探讨。
- PDD有助于将代码分解成小的、可管理的块,从而改进关注点的分离。
- 在PDD下,每个重要功能在开发或修改时都会进行测试,从而提高代码的整体质量。
- 创建一个特性后,在一个编译和运行速度快的小型原型中修改或修复它比作为一个笨重且启动缓慢的大型应用程序的一部分更容易。
- 原型和单元测试对于代码分析也很有用。事实上,如果你有一些新代码要学习,从使用的角度理解一些复杂方法或类的最佳方法是使用一些原型或测试来实际使用该方法或类。如果原型和测试还不存在,您可以随时创建它们以分析一些新代码。
PDD缺点
与TDD不同,PDD基本没有缺点。
在测试驱动开发(TDD)文章中,我写了TDD的一些潜在问题——特别是,团队可能忘记了他们的主要目的是满足来自客户端或客户端代理的要求,而编写测试只是实现的手段更好的代码,而不是目的本身。我看到人们为每种方法编写测试,包括非常简单的方法,编写测试不会减少任何错误的可能性。我看到项目几乎完全停止,因为测试的数量非常多,特别是因为构建速度特别慢——我看到构建运行测试2天。
与TDD不同的是,PDD应该只针对重要功能进行实践,而不是针对自动化测试,而是为了加快功能开发、调试和修改并提高生成代码的质量。不必为每个构建编译和运行原型。如果某些原型与可重用项目中的代码不同步,这没什么可怕的。原型可以更新为仅在下次用于修改或修复其功能时进行编译。
PDD从何而来
大约15到13年前,我提出了PDD,从那时起一直在单独实践它,有时还会在我作为建筑师的项目中为整个团队介绍它。
以我的经验,它对于快速、高质量的开发非常有用,它在团队和个人层面提供了很好的关注点分离。
我不会惊讶地发现其他架构师和开发人员(尤其是那些处理桌面UI应用程序开发的人员)可能提出了类似的开发过程。
据我所知,本文是首次尝试系统化和公开分享这种开发方法论。
PDD示例的代码
编码示例本质上是一个练习,为了理解本文顶部的图表所指定的PDD过程,必须进行该练习。
示例的所有代码都是使用Visual Studio 2022、C# 10和.NET 6构建的,但绝对没有理由不能使用以前版本的VS、C#和.NET,只要对项目进行了一些微不足道的修改,并且解决方案文件,也许还有代码。
对于示例的可视化开发,我使用了Avalonia包。Avalonia是一个开源的多平台WPF++,我强烈推荐它用于桌面或Blazor UI开发。要了解有关Avalonia的更多信息,请在阅读在Easy Samples中使用AvaloniaUI进行多平台UI编码——第1部分——AvaloniaUI构建块。
了解WPF或其他XAML框架的人应该能够轻松理解本文中使用的所有Avalonia代码。如果您没有任何XAML经验,您仍然可以阅读并通读本文中解释的示例——如果您不了解示例的部分内容,只要您了解PDD流程即可。
该代码位于NP.PrototypeDrivenDevelopmentSample github存储库中。
存储库包含两个文件夹:
- EmptyApp——仅包含空原型应用程序以及解决方案/项目和文件夹结构——这是可以用来开始进行PDD练习的解决方案。
- CompleteApp——包含原型应用程序,一旦你完成了PDD练习,它就会出现。
这两个应用程序的结构,EmptyApp和CompleteApp非常相似,除了EmptyApp没有任何代码在创建项目时由Visual Studio生成,所以要详细说明初始文件目录和项目结构,我们将使用EmptyApp里面的代码。
应用程序的基本文件夹(例如EmptyApp)包含三个子文件夹:
- Src——对于特定于这个应用程序的项目,包括主应用程序项目(在我们的例子中,它只包含带有解决方案的主项目,没有其他内容)。
- Core——用于具有可在不同应用程序之间共享的通用代码的可重用项目。为了简单起见,我们只放置一个项目NP.Visuals(在我们的例子中)应该包含任何可重用的可视化代码。如果您使用Git,则可以将此类可重用项目放入单独的存储库中,然后将其作为git子模块放在Core文件夹下。
- Prototypes——包含原型项目的文件夹。最初(在EmptyApp文件夹中),它只包含一个原型NP.PDD.PrototypeToClone。该NP.PDD.PrototypeToClone解决方案包含所有需要的项目和包以及所需的参考资料。
你可以从NP.PDD.PrototypeToClone中为一个新的Prototype创建一个项目和一个解决方案,只需将它复制到一个新的名称并做一些重命名。这样,所有需要的项目、包和引用都将被保留,您不必手动添加它们。如何做到这一点将在PDD演练部分中详细描述。
这是该MainApp项目的解决方案资源管理器视图:
您可以看到NP.PDD.MainApp和NP.Visuals项目都引用了Avalonia包。
主项目有一些标准的Avalonia启动文件:Program.cs、App.axaml、App.axaml.cs、MainWindow.axaml和MainWindow.axaml.cs(扩展名“.axaml”在Avalonia中用于XAML文件和双扩展名“.axaml.cs"用于后面的代码)。
如果您尝试启动NP.PDD.MainApp项目,您将看到一个空窗口。
重要提示:PDD方法的许多优点仅在主应用程序较大且启动和编译缓慢时才显示出来,这通常是任何非平凡的UI应用程序的情况。然而,在我们的演示中,为了简单起见,我们从一个空的主应用程序开始。
PDD循环演练示例
初步信息和步骤
本节是关于什么的
在本节中,我们将详细描述如何经历一个完整的PDD周期。这个循环的目的是创建一个非常简单的Avalonia LabeledTextBox自定义控件,它将文本标签与Avalonia TextBox结合在一起。
如果您想了解和内化PDD,则必须完成本节中详述的步骤。
什么是Avalonia
对于那些刚接触Avalonia的人——Avalonia与WPF非常相似,但功能更强大,错误更少,并且可以在三个主要平台上运行——Windows、Linux和Mac以及通过blazor在浏览器中运行。对于那些不想切换到Avalonia的人,可以使用WPF构建一个非常相似的示例。
这是一篇关于Avalonia——在Easy Samples中使用AvaloniaUI进行多平台UI编码——第1部分——AvaloniaUI构建块。
Avalonia的Visual Studio设置
为了能够使用Avalonia,您需要通过单击Visual Studio中的EXTENSIONS -> Manage Extensions菜单项来安装适用于Avalonia的Visual Studio扩展,然后选择Online选项卡,找到“Avalonia for Visual Studio ...”扩展并按下“安装”按钮:
PDD循环
第 1 步——通过克隆现有项目创建新的原型项目
我们将调用我们的原型项目NP.PDD.LabeledTextBoxPrototype。
通常,应将旧原型复制到新文件夹,然后重命名解决方案、项目和命名空间。这比创建一个新的原型项目并在其中设置所有引用要快得多。
我们已经有一个空的原型项目NP.PDD.PrototypeToClone,我专门放置在那里用于克隆。
以下是从NP.PDD.PrototypeToClone中创建新原型项目NP.PDD.LabeledTextBoxPrototype的详细步骤:
- 将NP.PDD.PrototypeToClone文件夹复制到NP.PDD.LabeledTextPrototype文件夹。
- 重命名原型VS解决方案文件NP.PDD.LabeledTextBoxPrototype.sln。
- 在Visual Studio中打开新的原型解决方案。
- 在解决方案资源管理器中,重命名原型的主项目以匹配解决方案和文件夹名称(NP.PDD.LabeledTextBoxPrototype)。
- 通过执行以下步骤,将主原型项目中 的旧命名空间“Sample”重命名为“LabeledTextBoxPrototype”:
- 在主项目中打开一些.c文件,例如Program.cs。
- 选择命名空间的“Sample”部分。
- 使用Ctrl-Shift-F打开搜索窗口,切换到Replace文本框内的“Replace in Files”选项卡,输入“LabeledTextBoxPrototype” string。将“查找”选项更改为“当前项目”,将文件类型设置为“* .axaml ;*.cs”,然后按“替换全部”按钮:
现在原型项目已准备好进行编码。
第 2 步和第 3 步——在Prototype的主项目中创建LabeledTextBox控件并将其打磨至完美
右键单击NP.PDD.LabeledTextBoxPrototype项目选择Add -> New Item菜单项。在打开的对话框中,选择左侧的“代码”选项卡和右侧的“类”,命名类LabeledTextBox并按下按钮“添加”:
将创建新类LabeledTextBox。创建该public类并使其继承自 Avalonia TemplatedControl类:
using Avalonia.Controls.Primitives;
namespace NP.PDD.LabeledTextBoxPrototype
{
public class LabeledTextBox : TemplatedControl
{
}
}
现在将两个string类型的Styled属性添加到类——Text和Label。
提醒:Avalonia样式属性与WPF依赖属性基本相同。要快速创建Avalonia样式属性,您可以使用Avalonia Snippets中的avs代码段。片段安装说明可在同一URL获得。如果您想了解更多关于Avalonia Attached和Styled属性的信息,请阅读简单示例中的多平台Avalonia .NET Framework编程基本概念文章。
using Avalonia;
using Avalonia.Controls.Primitives;
namespace NP.PDD.LabeledTextBoxPrototype
{
public class LabeledTextBox : TemplatedControl
{
#region Label Styled Avalonia Property
public string Label
{
get { return GetValue(LabelProperty); }
set { SetValue(LabelProperty, value); }
}
public static readonly StyledProperty<string> LabelProperty =
AvaloniaProperty.Register<LabeledTextBox, string>
(
nameof(Label)
);
#endregion Label Styled Avalonia Property
#region Text Styled Avalonia Property
public string Text
{
get { return GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
public static readonly StyledProperty<string> TextProperty =
AvaloniaProperty.Register<LabeledTextBox, string>
(
nameof(Text)
);
#endregion Text Styled Avalonia Property
}
}
不要被只定义两个Styled属性的行数吓到——大部分代码是由avsp片段生成的,使用这个片段,可以在一两秒内创建一个Styled属性。
我们完成了C#文件。现在让我们创建一个ControlTemplate,以便为这个自定义控件提供一个可视化的表示。我们将首先在项目的MainWindow.axaml文件中执行此操作,然后将控件及其模板移动到通用项目NP.Visuals中。
打开几乎空的文件MainWindow.axaml。这是它一开始的样子(从NP.PDD.ProjectToClone项目复制后):
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="600"
Height="400"
x:Class="NP.PDD.LabeledTextBoxPrototype.MainWindow"
Title="NP.PDD.LabeledTextBoxPrototype">
</Window>
首先,让我们在主原型项目NP.PDD.LabeledTextBoxPrototype中添加一个指向C# 命名空间的XML命名空间——将以下行添加到<Window ...标记中:
xmlns:local="clr-namespace:NP.PDD.LabeledTextBoxPrototype"
使XAML代码变为:
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:NP.PDD.LabeledTextBoxPrototype"
Width="600"
Height="400"
x:Class="NP.PDD.LabeledTextBoxPrototype.MainWindow"
Title="NP.PDD.LabeledTextBoxPrototype">
</Window>
(添加的行以粗体显示)。
现在将新控件添加到主窗口的中心:
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:NP.PDD.LabeledTextBoxPrototype"
...>
<local:LabeledTextBox Label="The Text"
Text="Hello World!"
HorizontalAlignment="Center"
VerticalAlignment="Center"/>
</Window>
尝试运行应用程序——窗口仍然是空的,因为我们的控件没有控件模板,因此不会产生可视化树。
现在(不是很令人兴奋),让我们为控件内联构建模板——在<local:LabeledTextBox.Template>标签内:
<local:LabeledTextBox Label="The Text"
Text="Hello World!"
HorizontalAlignment="Center"
VerticalAlignment="Center">
<local:LabeledTextBox.Template>
<ControlTemplate TargetType="local:LabeledTextBox">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Label,
RelativeSource={RelativeSource TemplatedParent}}"
VerticalAlignment="Center"/>
<TextBlock Text=": "
VerticalAlignment="Center"/>
<TextBox Text="{Binding Text, Mode=TwoWay,
RelativeSource={RelativeSource TemplatedParent}}"
VerticalAlignment="Center"
Width="200"/>
</StackPanel>
</ControlTemplate>
</local:LabeledTextBox.Template>
</local:LabeledTextBox>
这是仅控制模板的代码:
<ControlTemplate TargetType="local:LabeledTextBox">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Label, RelativeSource={RelativeSource TemplatedParent}}"
VerticalAlignment="Center"/>
<TextBlock Text=": "
VerticalAlignment="Center"/>
<TextBox Text="{Binding Text, Mode=TwoWay,
RelativeSource={RelativeSource TemplatedParent}}"
VerticalAlignment="Center"
Width="200"/>
</StackPanel>
</ControlTemplate>
指定控件模板的TargetType 允许在模板中使用Binding绑定到TemplatedParent控件类中定义的属性(在我们的例子中,这样的类是LabeledTextBox)。
绑定的RelativeSource={RelativeSource TemplatedParent}部分意味着我们正在绑定到定义在TemplatedParent(意味着使用当前ControlTemplate作为其模板的对象)上的属性。
模板中有一个水平方向StackPanel,它水平排列标签,包含后跟冒号“ :”的TextBlock,后跟TextBox。TextBlock的文本绑定到包含模板的LabeledTextBox控件的Label Styled属性,而TextBox的文本绑定到同一控件的Text Styled属性。
现在启动应用程序,你瞧,你会看到标签“The Text”后面跟着一个TextBox,其包含 “Hello World!” string:
如果您知道如何使用类似Avalonia snoop的工具,您甚至可以检查当您更改TextBox中的文本时,其LabeledTextBox上的Text属性也会更改(因为双向绑定)。
提醒:要启动类似于Avalonia窥探的工具——单击应用程序的主窗口并按F12键。在这里您可以了解有关Avalonia Tool的更多信息。
注意:我们创建的自定义控件仅用于PDD演——现实生活中的自定义控件应该公开和绑定更多属性,例如,应该可以在TextBox中独立地更改标签的样式和文本的样式等。
第 2 步完成——我们有一个工作原型,其代码位于原型解决方案的主项目中,我们测试它是否有效。
由于我们的应用程序非常简单,我们还假设我们完成了第 3 步——将原型代码完善到完美,从这里,我们直接进入第 4 步——通过将代码的可重用部分移动到通用项目来重构代码.
第 4 步——通过将代码的可重用部分移动到通用项目来重构代码
在我们的例子中,我们将把我们的LabeledTextBox控制实现和控制模板转移到NP.Visuals项目中。
首先将文件LabeledTextBox.cs从NP.PDD.LabeledTextBoxPrototype项目拖到解决方案资源管理器中的NP.Visuals项目。之后,从原型的主项目中删除LabeledTextBox.cs文件。
将现在在NP.VisualsNP.Visuals项目中定义的LabeledTextBox类的命名空间更改为:
namespace NP.Visuals
{
public class LabeledTextBox : TemplatedControl
{
...
}
}
重申一下,LabeledTextBox.cs文件不应再存在于原型的主项目NP.PDD.LabeledTextBoxPrototype下,而应存在于其NP.Visuals下,并且其命名空间应更改为NP.Visuals。
现在更新MainWindow.asaml文件中对我们LabeledTextBox类的引用。首先,我们需要创建一个新的XML命名空间,它指向NP.Visuals程序集中的NP.Visuals命名空间。我们将以下行添加到<Window ...标记中:
xmlns:visuals="clr-namespace:NP.Visuals;assembly=NP.Visuals"
这样Window标签现在看起来像:
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:visuals="clr-namespace:NP.Visuals;assembly=NP.Visuals"
xmlns:local="clr-namespace:NP.PDD.LabeledTextBoxPrototype"
...>
添加的命名空间行以粗体显示。
现在我们将前缀local:更改为MainWindow.asaml文件中的任何位置的前缀visuals:。这是我们得到的:
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:visuals="clr-namespace:NP.Visuals;assembly=NP.Visuals"
...>
<visuals:LabeledTextBox Label="The Text"
Text="Hello World!"
HorizontalAlignment="Center"
VerticalAlignment="Center">
<visuals:LabeledTextBox.Template>
<!-- TargetType allows to use Binding to bind to the
Styled Properties defined on the control of the templated parent class
-->
<ControlTemplate TargetType="visuals:LabeledTextBox">
<!-- this StackPanel arranges the items contained within it horizontally -->
<StackPanel Orientation="Horizontal">
<!-- TextBlock whose text is bound to the Label Styled Property
of its template parent control -->
<TextBlock Text="{Binding Label,
RelativeSource={RelativeSource TemplatedParent}}"
VerticalAlignment="Center"/>
<!-- TextBlock displaying the colon followed by a space - ": "-->
<TextBlock Text=": "
VerticalAlignment="Center"/>
<!-- TextBox whose Text is two-way bound to the Text Styled Property
of its template parent control -->
<TextBox Text="{Binding Text, Mode=TwoWay,
RelativeSource={RelativeSource TemplatedParent}}"
VerticalAlignment="Center"
Width="200"/>
</StackPanel>
</ControlTemplate>
</visuals:LabeledTextBox.Template>
</visuals:LabeledTextBox>
</Window>
添加的前缀以粗体显示。您可以看到我在MainWindow.asaml文件中添加了一些文档,但除此之外,代码与以前完全相同——唯一的区别是现在我们引用了在不同程序集中和不同命名空间下定义的LabeledTextBox类。
尝试运行应用程序——它应该产生与以前完全相同的结果。
您还记得,为了方便起见,我们内联定义了我们的控件模板。我们想将它移到NP.Visuals项目中的XAML文件中。作为第一个子步骤,将其移出我们的<visuals:LabeledTextBox...标记并将其转换为XAML资源,但仍将其保留在同一个MainWindow.asaml文件中。
我们在XAML代码中创建<Window.Resources部分,并将我们的模板放在那里,并且x:Key="LabeledTextBoxTemplate"为其添加了一个属性。我们在StaticResource标记扩展中指定这个x:Key值来引用我们LabeledTextBox控制实例中的模板:
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:visuals="clr-namespace:NP.Visuals;assembly=NP.Visuals"
...>
<Window.Resources>
<ControlTemplate x:Key="LabeledTextBoxTemplate"
TargetType="visuals:LabeledTextBox">
<!-- this StackPanel arranges the items contained within it horizontally -->
<StackPanel Orientation="Horizontal">
<!-- TextBlock whose text is bound to the Label Styled Property
of its template parent control -->
<TextBlock Text="{Binding Label,
RelativeSource={RelativeSource TemplatedParent}}"
VerticalAlignment="Center"/>
<!-- TextBlock displaying the colon followed by a space - ": "-->
<TextBlock Text=": "
VerticalAlignment="Center"/>
<!-- TextBox whose Text is two-way bound to the Text Styled Property
of its template parent control -->
<TextBox Text="{Binding Text, Mode=TwoWayTemplate,
RelativeSource={RelativeSource TemplatedParent}}"
VerticalAlignment="Center"
Width="200"/>
</StackPanel>
</ControlTemplate>
</Window.Resources>
<visuals:LabeledTextBox Label="The Text"
Text="Hello World!"
Template="{StaticResource LabeledTextBoxTemplate}"
HorizontalAlignment="Center"
VerticalAlignment="Center"/>
</Window>
新行一如既往地以粗体显示。
应用程序仍应生成具有完全相同行为的完全相同的UI。
下一个子步骤是将我们的ControlTemplate为一个Style(样式比模板更好地用于创建视觉效果,因为使用样式您还可以定义视觉控件上使用样式本身的属性的默认值,而使用Templates时您只能定义视觉控制里面有什么)。
现在,我们将把Style保存在MainWindow.asaml文件中。
为了定义样式,我们需要为样式创建一个<Window.Styles部分(这与WPF不同,其中样式只是XAML资源)。以下是新代码的外观:
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:visuals="clr-namespace:NP.Visuals;assembly=NP.Visuals"
...>
<Window.Styles>
<Style Selector="visuals|LabeledTextBox.HorizontalLabeledTextBox">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="visuals:LabeledTextBox">
<!-- this StackPanel arranges the items contained
within it horizontally -->
<StackPanel Orientation="Horizontal">
<!-- TextBlock whose text is bound to the Label Styled Property
of its template parent control -->
<TextBlock Text="{Binding Label,
RelativeSource={RelativeSource TemplatedParent}}"
VerticalAlignment="Center"/>
<!-- TextBlock displaying the colon followed by a space - ": "-->
<TextBlock Text=": "
VerticalAlignment="Center"/>
<!-- TextBox whose Text is
two-way bound to the Text Styled Property
of its template parent control -->
<TextBox Text="{Binding Text, Mode=TwoWay,
RelativeSource={RelativeSource TemplatedParent}}"
VerticalAlignment="Center"
Width="200"/>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Styles>
<visuals:LabeledTextBox Label="The Text"
Text="Hello World!"
Classes="HorizontalLabeledTextBox"
HorizontalAlignment="Center"
VerticalAlignment="Center"/>
</Window>
关于Avalonia Styles的一些解释:Avalonia Styles的重要部分是Selector,它是一个定义样式应用位置string。请注意,选择器中的XML命名空间前缀由竖线字符'|'分隔,而不是冒号。这是因为冒号可以在选择器中用于某些不同的目的(本文不在此范围内)。
选择器visuals|LabeledTextBox.HorizontalLabeledTextBox意味着样式将应用于该HorizontalLabeledTextBox类的所有LabeledTextBox控件。现在,在控件本身内,我们可以指定它的Classes属性(在我们的例子中,它是Classes="HorizontalLabeledTextBox")。通常,可以指定多个类,然后,与WPF不同,我们可以一次将多个样式应用于同一个控件。
重新运行应用程序,它应该仍然运行完全相同。
下一个子步骤是在NP.Visuals 项目下创建一个项目文件夹Styles,并在其中创建一个Avalonia LabeledTextBoxStyles.axaml Styles文件。为了创建Avalonia Styles,在Solution Explorer中右键单击新创建的文件夹Styles文件,选择Add -> New Items菜单项,选择左侧的“Avalonia”选项卡,选择“Styles (Avalonia)”选项中间,将文件名更改为“LabeledTextBoxStyles.axaml”并按下按钮“添加”。
将命名空间Visuals添加到新创建的LabeledTextBoxStyles.axaml文件以指向同一项目中的NP.Visuals命名空间,并将样式从MainWindow.axaml文件移动到相同的LabeledTextBoxStiles.axaml文件中。这就是LabeledTextBoxStyles.asaml现在的样子:
<Styles xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:visuals="clr-namespace:NP.Visuals">
...
<Style Selector="visuals|LabeledTextBox.HorizontalLabeledTextBox">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="visuals:LabeledTextBox">
<!-- this StackPanel arranges the
items contained within it horizontally -->
<StackPanel Orientation="Horizontal">
<!-- TextBlock whose text is bound to the Label Styled Property
of its template parent control -->
<TextBlock Text="{Binding Label,
RelativeSource={RelativeSource TemplatedParent}}"
VerticalAlignment="Center"/>
<!-- TextBlock displaying the colon followed by a space - ": "-->
<TextBlock Text=": "
VerticalAlignment="Center"/>
<!-- TextBox whose Text is two-way bound to the Text Styled Property
of its template parent control -->
<TextBox Text="{Binding Text, Mode=TwoWay,
RelativeSource={RelativeSource TemplatedParent}}"
VerticalAlignment="Center"
Width="200"/>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Styles>
我们还需要在MainWindow.axaml文件中添加对新LabeledTextBoxStyles.axaml的引用:
<StyleInclude Source="avares://NP.Visuals/Styles/LabeledTextBoxStyles.axaml"/>
avares是一个神奇的词——Avalonia Resources的缩写,后跟程序集名称(NP.Visuals),后跟程序集中文件的路径(/Styles/LabeledTextBoxStyles.axaml)。
下面是我们的MainWindow.axaml文件现在的样子:
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:visuals="clr-namespace:NP.Visuals;assembly=NP.Visuals"
...>
<Window.Styles>
<StyleInclude Source="avares://NP.Visuals/Styles/LabeledTextBoxStyles.axaml"/>
</Window.Styles>
<visuals:LabeledTextBox Label="The Text"
Text="Hello World!"
Classes="HorizontalLabeledTextBox"
HorizontalAlignment="Center"
VerticalAlignment="Center"/>
</Window>
再次运行原型,应用程序的外观和行为仍然完全相同。
我们基本上完成了原型。我建议您保留它,以便将来对LabeledTextBox控件及其样式进行可能的修改和修复。
第 5 步——将可重用代码的调用添加到需要的主应用程序中
返回主应用程序并打开解决方案文件/src/NP.PDD.MainApp.sln。
解决方案中已经有NP.Visuals项目,因此,在项目中,您应该能够看到新添加的LabeledTextBox.cs和Styles/LabeledTextBoxStyles.axaml文件。
主项目已经引用了可重用的NP.Visuals项目。
对于我们的演示,只需添加一个可重复使用的LabeledTextBox控件就足以证明它也可以在主应用程序中工作。我们可以通过从原型的MainWindow.asam文件中复制代码来将其添加到lMainWindow中心。我们还可以复制代码以添加对Avalonia Styles文件的引用。
生成的主应用程序MainWindow.axaml文件现在看起来与原型的文件非常相似,唯一的区别在于应用程序的C#命名空间:
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:visuals="clr-namespace:NP.Visuals;assembly=NP.Visuals"
Width="600"
Height="400"
x:Class="NP.PDD.MainApp.MainWindow"
Title="NP.PDD.MainApp">
<Window.Styles>
<StyleInclude Source="avares://NP.Visuals/Styles/LabeledTextBoxStyles.axaml"/>
</Window.Styles>
<visuals:LabeledTextBox Label="The Text"
Text="Hello World!"
Classes="HorizontalLabeledTextBox"
HorizontalAlignment="Center"
VerticalAlignment="Center"/>
</Window>
启动主应用程序——它的外观和行为应该与我们的原型完全相同。
结论
本文描述了原型驱动开发(PDD)——一种快速开发高质量软件(尤其是UI软件)的新方法,我在过去13年多的时间里广泛并成功地使用了它。
任何个人开发人员和整个团队都可以实践PDD。
PDD的主要优点是它允许快速重新编译、重新启动和调试小型轻量级原型,而不是大型和沉重的主应用程序。
本文还展示了在Avalonia中创建自定义控件和重构代码以使其成为可重用项目的一部分并可供其他项目使用的步骤。
然而,主要强调的是PDD,所以如果您不了解与Avalonia相关的所有内容,请不要担心。
https://www.codeproject.com/Articles/5324212/Prototype-Driven-Development-PDD