简易摩尔质量计算器






4.68/5 (12投票s)
本文介绍如何创建使用 Windows 运行时组件的 UWP 应用程序。
目录
引言
本文介绍如何创建使用 Windows 运行时组件的通用 Windows 平台 (UWP) 应用。Windows 运行时组件是用 C 和 C++ 编写的。构建摩尔质量计算器时面临的挑战之一是在 UWP 应用和 Windows 运行时组件之间交换数据。在本文中,我将解释我如何实现这种数据交换。此外,该应用程序还使用了一个免费的图表库,本文也将对此进行讨论。可以通过本文顶部的链接下载源代码,可以此处下载安装包。
背景
摩尔质量计算器允许您计算分子的摩尔质量和组成。如果您想了解更多关于摩尔质量的信息,可以阅读本文。分子组成用于检查化合物的纯度。如果一位有机化学家合成了新化合物,则该化学家可以将实际组成与理论组成进行比较。
Using the Code
应用程序架构
摩尔质量计算器采用 MVVM 架构,这意味着没有代码隐藏,并且 MainView
具有相应的 ViewModel
,其中包含所有逻辑。View
的 DataContext
在 View
创建时设置,如下面的代码片段所示。
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
this.DataContext = new MainViewModel();
}
}
代码片段 1:MainPage
的 DataContext
设置为 MainViewModel
。
为了使 View
的事件能够传递到 ViewModel
,我使用了两个库:Mvvmlight
和 Microsoft.Behaviors.UWP.Managed
库。行为库允许用户选择一个 PIE 切片,然后将 SelectionChanged 事件传递给 ViewModel
。然后,ViewModel
通过设置状态属性来处理 SelectionChanged 事件,如代码片段 3 所示。
<Interactivity:Interaction.Behaviors>
<Interactions:EventTriggerBehavior EventName="SelectionChanged">
<Interactions:InvokeCommandAction Command="{Binding SelectedSliceCmd}"/>
</Interactions:EventTriggerBehavior>
</Interactivity:Interaction.Behaviors>
代码片段 2:SelectionChanged
事件绑定到 SelectedSliceCmd
。
private void OnSelectedSliceCmd(object prm)
{
SelectionChangedEventArgs args = (SelectionChangedEventArgs)prm;
if (args.AddedItems.Any())
{
Status = string.Format("{0} ({1:0.00}%)",
((Element)args.AddedItems[0]).element,
((Element)args.AddedItems[0]).relativeMassContent);
}
}
代码片段 3:SelectionChanged
事件触发 OnSelectedSliceCmd
命令处理程序。
从用户界面可以看出,用户可以在 textbox
中输入分子式。输入的公式使用正则表达式进行验证。使用的正则表达式如下所示。从代码可以看出,正则表达式验证区分大小写,这是因为所有元素都以大写字母开头。此语法用于将输入的分子标记为其对应的元素。
private static readonly string allowedElements = @"^(\(|\)|[0-9]|H|He|Li|Be|B|C|N|O|F|Ne|..U)*
代码片段 4:正则表达式只允许输入数字、括号和元素。
用于标记分子式的代码可以在此处找到。此代码允许您输入带嵌套括号的公式。为了使用标记算法,我将 C 代码嵌入了一个 Windows 运行时组件中。通过这个 Windows 运行时组件,我就可以从 UWP 应用调用标记算法。
使用模板对 PIE 图表进行样式设置
摩尔质量计算器使用 PIE 图表库来可视化分子组成。该库基于 Silverlight 图表库,可以作为 NuGet 包下载。使用此库时遇到的一个挑战是,在初始化和创建 PIE 图表时,每个 PIE 切片都会动态更改颜色。与动态着色相反,我希望为 PIE 图表中的每个元素指定固定的颜色。为此,每个元素的颜色都在代码隐藏中设置,如下面的代码片段所示。这种方法导致每个特定元素在图表中具有固定的背景颜色(例如,H=绿色,O=蓝色等)。
private int InitializeChartPalette(out string errorMsg)
{
int retVal = Constants.Failure;
errorMsg = string.Empty;
try
{
ResourceDictionaryCollection rdCollection = new ResourceDictionaryCollection();
if (PaletteCollection != null)
{
PaletteCollection.Clear();
PaletteCollection = null;
}
// Create a color palette entry for each data point
foreach (Element el in SelectedMolecule.Composition)
{
ResourceDictionary rd = new ResourceDictionary();
Style dataPointStyle = new Style(typeof(Control));
dataPointStyle.Setters.Add(new Setter(Control.BackgroundProperty, el.Brush));
dataPointStyle.Setters.Add(new Setter(Control.TemplateProperty,
App.Current.Resources["PieDataPointControlTemplate"] as ControlTemplate));
dataPointStyle.Setters.Add(new Setter
(DataPoint.DependentValueStringFormatProperty, "{0:0.00}"));
rd.Add("DataPointStyle", dataPointStyle);
Style dataShapeStyle = new Style(typeof(Shape));
dataShapeStyle.Setters.Add(new Setter(Shape.StrokeProperty, el.Brush));
dataShapeStyle.Setters.Add(new Setter(Shape.StrokeThicknessProperty, 2));
dataShapeStyle.Setters.Add(new Setter(Shape.StrokeMiterLimitProperty, 1));
dataShapeStyle.Setters.Add(new Setter(Shape.FillProperty, el.Brush));
rd.Add("DataShapeStyle", dataShapeStyle);
rdCollection.Add(rd);
}
PaletteCollection = rdCollection;
retVal = Constants.Success;
}
catch (Exception ex)
{
retVal = Constants.Failure;
errorMsg = string.Format("An unexpected error occurred: {0}", ex.Message);
}
return retVal;
}
代码片段 5:图表调色板在 ViewModel
中设置。
此外,图表使用 DataTemplates
和 ControlTemplates
进行样式设置。当鼠标悬停在 PIE 切片上时,工具提示会显示该元素的详细信息。
<ControlTemplate x:Key="PieDataPointControlTemplate" TargetType="charting:PieDataPoint">
<Grid>
<Path x:Name="Slice"
Data="{TemplateBinding Geometry}"
Fill="{TemplateBinding Background}"
Stroke="{TemplateBinding BorderBrush}">
<ToolTipService.ToolTip>
<StackPanel Orientation="Horizontal">
<ContentControl Content="{TemplateBinding IndependentValue }"/>
<ContentControl Content=" ("/>
<ContentControl Content="{TemplateBinding FormattedDependentValue}"/>
<ContentControl Content="%)"/>
</StackPanel>
</ToolTipService.ToolTip>
</Path>
</Grid>
</ControlTemplate>
代码片段 6:带有自定义工具提示的 Controltemplate
。
图表图例使用 style
和 controltemplate
进行自定义,这些可以在项目的字典中找到。自定义图例允许在图例中有许多元素时进行滚动。
UWP 和 Windows 运行时组件之间的接口
如前所述,C# UWP 应用需要与用 C 和 C++ 编写的 Windows 运行时组件进行通信。UWP 应用和 Windows 运行时组件之间的通信是通过 Platform::String
类型(Unicode 格式)实现的。在内部,Windows 运行时组件使用经典的 char*
类型,这是为了使用仅接受 char*
类型的外部分子解析器所必需的。由于 Windows 运行时组件中使用了两种类型,我创建了两个转换方法来在这些类型之间进行转换。Windows 运行时组件执行所有计算,并将包含组成的字典返回给 UWP 应用,如下面的代码片段所示。
IMap<String^, double>^ FormulaParser::ParseElements(int *errorCode)
{
double elementComposition = 0;
int retVal = SUCCESS;
map<String^, double> stlMap;
try
{
//Make sure that the formula and the calculatedMoleMass are set
if (formula != NULL && calculatedMoleMass > .5)
{
Atom_count *iterator = parse_formula_c(formula);
while (iterator != NULL)
{
std::map<const char*, double, ltstr>::iterator search =
periodicTableOfElements.find(iterator->element_symbol);
elementComposition =
(((search->second * iterator->count) / calculatedMoleMass) * 100) ;
stlMap.insert(std::pair<String^, double>
(StringFromAscIIChars(iterator->element_symbol), elementComposition));
iterator = iterator->next;
}
}
else
{
retVal = NO_FORMULA_SPECIFIED;
SetLastError("Specify a formula using the method SetFormula(String^ moleculeFormula).");
}
}
catch (Exception^ ex)
{
retVal = GENERAL_ERROR;
SetLastError(ex->Message);
}
return ref new Map<String^, double>(stlMap);
}
代码片段 7:Windows 运行时组件返回一个包含组成的字典。
public int CalculateComposition(out string errorMsg)
{
int retVal = Constants.Failure;
errorMsg = string.Empty;
try
{
Composition.Clear();
IDictionary<string, double> calculatedComposition
= parser.ParseElements(out retVal);
if (retVal == Constants.Success)
{
foreach (var element in calculatedComposition)
{
string elementName = element.Key;
double massContent = element.Value;
Composition.Add(new Element()
{
element = elementName,
relativeMassContent = massContent,
Brush = elementColors[elementName]
});
}
}
else
{
errorMsg = parser.GetLastError();
}
}
catch (Exception ex)
{
retVal = Constants.Failure;
errorMsg =
string.Format("An unexpected error occurred: {0}", ex.Message);
}
return retVal;
}
代码片段 8:UWP 应用从 Windows 运行时组件接收字典。
使用 MsTests 进行单元测试
NUnit 目前不支持 UWP 项目,因此我不得不改用 MSTests。可以在本文中找到可用的替代框架。要创建测试项目,您可以导航到创建新项目 -> Visual C# -> Windows -> Universal 并选择 Unit Test App (Universal Windows),然后需要将您的项目引用到 Unit Test App 中,之后就可以开始覆盖您的源代码了。下面的示例显示了一个测试用例。
public void TestCase001()
{
bool equal;
int retVal = Failure;
string errorMsg = string.Empty;
double expectedElementComposition;
string Formula = "C8H10N2O", formulaSummary;
double expectedMolarMass = 150.18, actualMolarMass;
Dictionary<string, double> expectedComposition = new Dictionary<string, double>()
{
{ "C", 63.98 },
{ "H", 6.71 },
{ "N", 18.66 },
{ "O", 10.65 },
};
FormulaParser parser = new FormulaParser();
retVal = parser.SetFormula(Formula, out formulaSummary, out actualMolarMass);
Assert.AreEqual(Success, retVal);
equal = Math.Abs(expectedMolarMass - actualMolarMass) <= allowedDifference;
Assert.IsTrue(equal);
IDictionary<string, double> actualComposition = parser.ParseElements(out retVal);
Assert.AreEqual(Success, retVal);
Assert.AreEqual(expectedComposition.Count, actualComposition.Count);
foreach (KeyValuePair<string, double> kvp in actualComposition)
{
expectedElementComposition = expectedComposition[kvp.Key];
equal = Math.Abs(expectedElementComposition - kvp.Value) <= allowedDifference;
Assert.IsTrue(equal);
}
}
代码片段 9:用于验证计算的测试用例。
UWP 部署
创建部署包
创建 UWP 应用程序后,您希望它能到达目标受众。应用清单文件 (package.appxmanifest.xml) 包含创建应用部署包所需的属性和设置。例如,清单文件中的属性描述了用作应用磁贴的图像以及用户旋转设备时应用支持的方向。Visual Studio 具有清单设计器,可以轻松更新清单文件,而无需编辑文件的原始 XML。双击应用清单文件时会显示清单设计器。清单设计器允许您配置应用的部署。每个选项卡都包含您可以配置的信息。
在应用清单文件中,有一个专门用于视觉资产的部分,允许您为 UWP 应用程序指定所有必需的磁贴。我使用了这个 Visual Studio 扩展来指定所有必需的磁贴。使用此扩展,您可以右键单击方形 SVG 或 PNG 文件,然后选择“生成 UWP 磁贴”,之后将生成所有推荐的磁贴大小。此外,该扩展还会更新您的 Package.appxmanifest
,因此您无需手动操作。
在“打包”选项卡中,您可以输入发布数据。在这里,您可以选择要用于签名应用的证书。所有 UWP 应用都必须使用证书签名。为了旁加载应用包,您需要信任该包。必须在设备上安装证书才能信任该包,有关详细信息,请参阅此处。
在 Visual Studio 2015 中,您可以使用向导创建应用包,然后将其上传到 Windows 应用商店或用于旁加载。要启动向导,请右键单击项目并选择“商店”->“创建应用包”。在第一个对话框中,向导会询问您是否要创建 Windows 应用商店包。如果您在此处选择“否”,Visual Studio 将不会生成您用于商店提交所需的 .appxupload
包。如果您只想旁加载您的应用以在内部设备上运行,则可以选择“否”选项。在本例中,我只想旁加载该应用程序,因此我选择了“否”。将来,我将通过 Windows 应用商店提供摩尔质量计算器。如果您想将应用程序部署到 Windows 应用商店,您需要拥有 Windows 开发中心(Windows Dev Center)的开发者帐户。如果您还没有开发者帐户,向导将帮助您创建一个。
在下一个向导窗体中,您可以选择应用的目標体系结构。请确保在“选择和配置包”对话框中选择所有三个体系结构配置(x86、x64 和 ARM)。这样,您的应用就可以部署到最广泛的设备上。在“生成应用包”列表框中,选择“始终”。这使得商店提交过程更加简单,因为您只需要上传一个文件(.appxupload
)。单个包将包含部署到每个处理器体系结构设备的必要程序包。
接下来单击“创建”以生成您的 appxupload 包。创建安装包后,您将看到下面的窗体。
如果您要在 Windows 应用商店中发布您的应用,则需要使用认证套件(Certification Kit)来验证您的应用。认证过程完成后,并且您的应用通过了认证,您就可以将您的应用上传到商店了。请确保上传正确的文件。它可以在您的根文件夹中找到,并且以 .appxupload 文件扩展名结尾。
使用旁加载安装 UWP 应用程序
UWP 应用无法像桌面应用那样直接安装到您的设备上。通常,您从商店下载这些应用,然后它们就被安装到您的设备上。另外,您也可以通过旁加载将应用安装到您的设备上,而无需将其提交到商店。这允许您使用在部署过程中创建的应用包 (.appx) 来安装和测试它们。使用应用包,您可以将应用程序本地分发,就像行之有效的业务 (LOB) 应用一样。要将您的应用包旁加载到 Windows 10 设备上,您需要执行以下两个步骤:
- 在目标设备上启用开发者模式。
- 将应用包文件夹复制到目标设备并使用 PowerShell 进行安装。
要启用开发者模式,请转到“开始”->“设置”->“更新与安全”->“开发者选项”并选择“开发者模式”。
摩尔质量计算器的应用包可以使用此链接下载。您需要将正确的体系结构文件夹(x86、x64 或 ARM)复制到您的计算机。接下来,右键单击 Add-AppDevPackage.ps1 文件,然后选择“用 PowerShell 运行”并按照提示进行操作。应用包安装完成后,您将在 PowerShell 窗口中看到此消息:您的应用已成功安装。如果您想在 Windows 10 移动设备上旁加载您的应用,则必须使用WinAppDeployCmd.exe 工具。
安装后,摩尔质量计算器将出现在开始菜单中。
关注点
- Visual Studio 允许您组合 C 和 C++ 代码。为了使用 C 函数,您需要设置
_CRT_SECURE_NO_WARNINGS
预处理器指令以抑制安全警告。
参考文献
- 打包 UWP 应用
- 在代码隐藏中动态着色项
- 使用 Silverlight 工具包设置图表样式
- 一个最小化的 MVVM UWP 应用
- 商店应用中的图形和图表
- 自定义图例样式
- WinRTXamlToolkit,使用的图表库
- 如何将字典从 C++ DLL 获取到 C#
- 自定义工具提示
- Chemistry-MolecularMass
历史
- 2016 年 7 月 2 日:版本 1.0.0.0 - 发布文章