65.9K
CodeProject 正在变化。 阅读更多。
Home

用于物理应用程序数据输入的编辑器控件

2017年12月15日

CPOL

6分钟阅读

viewsIcon

10863

downloadIcon

175

用于物理数据输入的简单编辑控件。

引言

现代工程应用允许用户处理以不同测量单位测量的物理值,甚至选择所需的测量系统。为此,存在一个输入测量单位的机制。本文介绍了一种实现此类机制的方法——一个简单的WPF编辑器组件,用于输入量纲物理值,并以接近物理表示法(physical notation)的形式呈现输入数据。

背景

在常见的通用情况下,测量单位的表示法包括单位符号,例如“s”(秒)或“N”(牛顿),幂,例如“m2”(平方米),下标,例如“mmH2O”(毫米水柱),以及其他一些表示法(https://en.wikipedia.org/wiki/International_System_of_Units)。因此,处理测量单位的机制必须允许输入所有这些表示法。一种方法是将单位表示为字符串数据,例如“mm^2”(毫米平方),其中“^”符号表示幂。这种方法很简单,但数据表示有时难以阅读。另一种方法是创建一个复杂的编辑器,带有弹出菜单,用于选择不同的符号来输入幂、下标等。这种方法使数据表示接近物理表示法,但需要复杂的编程,并不总是用户友好。

另一方面,现代编程语言使用Unicode字符集来处理string数据。Unicode标准包括下标和上标符号(https://en.wikipedia.org/wiki/Unicode_subscripts_and_superscripts)。这足以以“自然物理”(natural physical)形式表示测量单位的表示法。存在一个问题——下标和上标符号无法用标准键盘输入。

因此,单位输入编辑器组件的主要思想是使用标准组件类之一,并提供一种方便的方法来输入下标和上标符号。这种方法结合了以下优点:

  • 以接近物理表示法的形式呈现测量单位
  • 简单的用户界面,输入快速
  • 易于编程实现

组件实现

组件的实现基于标准的WPF文本编辑组件——EditBox。新组件的主要功能算法是:监听文本编辑器的输入,并校正输入以获得数据的物理表示。例如,如果输入是“mm^2”,编辑器中的结果文本应为“mm2”。为了实现此功能,当按下特殊符号(例如示例中的幂“^”符号)时,我们的编辑器必须进入特殊状态,在该状态下将所有符号转换为相应的上标或下标。我们将使用幂符号“^”表示上标,下划线符号“_”表示下标。

首先,我们创建了一个辅助类,该类具有用于定义字符串符号是否为特殊符号之一以及用于将字符转换为相应上标或下标的函数。对于单位表示法脚本,仅需数字和“+”、“-”符号即可。该辅助类的接口如下:

CanBeScripted 方法定义了一个符号是否可以转换为上标或下标。

public static bool CanBeScripted(char x)
{
    bool result = char.IsDigit(x) || x == '+' || x == '-' || x == 'e';
    return result;
}

下一个方法‘ToSuperscript’将一个符号转换为相应的上标。

public static char ToSuperscript(char x)
{
    if (char.IsDigit(x))
    {
        return Symbols.SuperscriptDigits[int.Parse(x.ToString())];
    }
    else
    {
        switch (x)
        {
            case '+': return Symbols.SuperscriptPlus;
            case '-': return Symbols.SuperscriptMinus;
        }
        return x;
    }
}

其中‘Symbols类可以定义为:

public static class Symbols
{
    public static readonly char UnitExponentSign = '^';
    public static readonly char UnitSubscriptSign = '_';
    public static readonly char UnitFractionSign = '/';
    public static readonly char UnitSeparationSign = ' ';
    public static readonly List<char> SubscriptDigits = 
         new List<char>( new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' });
    public static readonly List<char> SuperscriptDigits = 
         new List<char>( new char[] { '°', '¹', '²', '³', '4', '5', '6', '7', '8', '?' });
    public static readonly char SuperscriptPlus = '?';
    public static readonly char SuperscriptMinus = '?';
    public static readonly char SubscriptPlus = '?';
    public static readonly char SubscriptMinus = '?';
    public static readonly char SubscriptEuler = '?';
}

类似地,‘ToSubscript’方法将一个符号转换为相应的下标。接下来的两个方法‘FromSuperscript’和‘FromSubscript’实现反向操作——将脚本化符号转换回原始字符。

IsScriptSymbol’方法检查一个符号是否为特殊字符之一。

public static int IsScriptSymbol(char x)
{
    if (x == Symbols.UnitExponentSign)
    {
        return 1;
    }
    else
    {
        if (x == Symbols.UnitSubscriptSign)
        {
            return -1;
        }
    }
    return 0;
}

该方法返回“1”表示上标模式,返回“-1”表示下标模式。还有一些其他辅助函数,其中最主要的是最后两个。让我们考虑第一个:

public static string ToScripted(string value)
{
    if (string.IsNullOrEmpty(value)) return string.Empty;
    else
    {
        string result = string.Empty;
        int sign = 0;
        for (int i = 0; i < value.Length; i++)
        {
            char x = value[i];
            int s = IsScriptSymbol(x);
            if (s != 0)
            {
                if (sign != 0 && s == sign)
                {
                    ScriptFormatError();
                }
                sign = s;
                continue;
            }                   
            if (char.IsLetter(x) || (x == Symbols.UnitFractionSign) || (x == ' '))
            {
                sign = 0;
            }
            if (sign != 0)
            {
                x = ToScript(x, sign);
            }
            result += x;
        }
        return result;
    }
}

该函数将整个string转换为相应的脚本化表示。它遍历string中的所有字符,如果一个字符是特殊字符(‘IsScriptSymbol’),则所有后续的、可以脚本化的符号都将被转换为相应的脚本,直到出现不可脚本化的符号或遇到另一个特殊符号。类似地,‘FromScripted’方法实现了逆操作。以下是一些原始string及其通过转换获得的脚本化表示的示例:

原始                      脚本化

g cm^-3                    g cm⁻³

N/m^2                       N/m²

kg m^2 A^-2 s^-3    kg m² A⁻² s⁻³

有了这个辅助类,我们现在可以创建编辑器组件了。我们继承自基类WPFUserControl,并在其中放置了标准的TextBox编辑器(完整的XAML代码可以在下载中找到)。该类声明了一些内部数据字段:

/// <summary>
/// Indexing
/// </summary>
protected internal int indexing;
/// <summary>
/// Not formatted string value
/// </summary>
protected internal string stringvalue = string.Empty;
/// <summary>
/// Formatted string value
/// </summary>
protected internal string formattedvalue = string.Empty;

indexing’字段用于存储特殊编辑模式(下标或上标)的当前值,‘stringvalue’用于存储原始string值,‘formattedvalue’用于存储string的脚本化表示。还有一些其他数据和函数。主要实现定义在EditBox事件处理程序中。第一个是PreviewTextInput处理程序:

private void textBoxValue_PreviewTextInput(object sender, TextCompositionEventArgs e)
{
    string s = e.Text;
    int l = s.Length;
    if (l == 1)
    {
        char c = s[0];
        int i = Formatter.IsScriptSymbol(c);
        if (i != 0)
        {
            indexing = i;
            e.Handled = true;
        }
        else
        {
            if (!Formatter.CanBeScripted(c))
            {
                indexing = 0;
            }
        }
    }
}

该方法拦截任何TextBox输入,并根据字符开启或关闭索引模式(在该模式下,下一个输入的字符将被转换为脚本化符号)。另一个处理程序用于‘TextChanged’事件,这是其部分代码:

int index = change.Offset;
char c = textBoxValue.Text[index];
if (c == '*')
{
    c = ' ';
}
InsertCharacter(index, c);

其中

private void InsertCharacter(int index, char c)
{
    int sign = 0;
    if (indexing != 0)
    {
        sign = indexing;
    }
    else
    {
        if (index > 0)
        {
            sign = Formatter.IsScript(FormattedValue[index - 1]);
        }
    }

    if (sign != 0)
    {
        c = Formatter.ToScript(c, sign);
    }
    FormattedValue = FormattedValue.Insert(index, c.ToString());
}

因此,该方法将任何乘法字符替换为空格(这接近于自然表示法,其中单位之间的乘法符号被省略),或者在考虑到可能转换为脚本化形式的情况下,将输入符号插入到格式化的string数据中。编辑器组件实现的完整源代码可在提供的下载中找到。

代码示例:材料属性编辑器

现在,让我们看一个使用该组件创建用于设置材料属性的应用程序的例子。为简化起见,选择的属性是以下三个:密度杨氏模量电阻。然后,材料属性编辑器应用程序是一个简单的WPF窗口,其中包含三个已实现类的编辑器:

我们的目标是允许用户输入这三个材料的属性数据,并检查输入数据是否与所需的物理量兼容(https://en.wikipedia.org/wiki/Physical_quantity)。按钮处理程序的代码如下:

private void Button_Click(object sender, RoutedEventArgs e)
{
    string sv1 = PhysEdit1.StringValue;
    string sv2 = PhysEdit2.StringValue;
    string sv3 = PhysEdit3.StringValue;
    try
    {
        ScalarValue density = ScalarValue.Parse(sv1);
        ScalarValue young = ScalarValue.Parse(sv2);
        ScalarValue resistance = ScalarValue.Parse(sv3);
        
        CheckCompatibility(new Density(), density);
        CheckCompatibility(new Stress(), young);
        CheckCompatibility(new ElectricalResistance(), resistance);

        MessageBox.Show("Ok!");
        this.Close();
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message);
    }
}

其中

private static void CheckCompatibility(PhysicalQuantity q, PhysicalValue v)
{
    if (!PhysicalQuantity.Compatible(q, v.Unit))
    {
        throw new ArgumentException("Value " + v.ToString() + " is not compatible with " + q.Name + ".");
    }
}

该处理程序仅从编辑器中获取string值,并使用PHYSICS库类将其转换为物理值,并检查其与相应物理量的兼容性。请注意,杨氏模量与‘Stress’(应力)量进行比较,因为它的量纲与应力物理量相同。

以下是材料属性正确数据输入的示例:

使用此输入按下按钮,我们会收到“Ok”消息。但是,如果数据错误:

(密度属性的数据量纲错误),我们会收到错误消息:

"Value 2000 kg/cm^2 is not compatible with Density."

结论

在本文中,我们考虑了一种创建用于物理数据输入的编辑器组件的方法。该方法基于使用Unicode上标和下标字符,以接近物理表示法的形式呈现测量单位。该方法允许以最少的代码编写来完成物理值输入的任务。设计的组件易于用户使用,并且允许使用标准键盘输入所有必需的数据,而无需使用复杂的弹出菜单或其他UI控件。

© . All rights reserved.