【问题标题】:Elegant Solution for Readonly Values只读值的优雅解决方案
【发布时间】:2013-03-27 14:33:13
【问题描述】:

我正在开发一个 WPF 应用程序,其窗口大小和组件位置必须在初始化时动态计算,因为它们基于我使用的主要 UserControl 大小和一些其他次要大小设置。因此,目前,我将这些常量值放在我的 Window 代码中,如下所示:

public const Double MarginInner = 6D;
public const Double MarginOuter = 10D;
public const Double StrokeThickness = 3D;

public static readonly Double TableHeight = (StrokeThickness * 2D) + (MarginInner * 3D) + (MyUC.RealHeight * 2.5D);
public static readonly Double TableLeft = (MarginOuter * 3D) + MyUC.RealHeight + MarginInner;
public static readonly Double TableTop = MarginOuter + MyUC.RealHeight + MarginInner;
public static readonly Double TableWidth = (StrokeThickness * 2D) + (MyUC.RealWidth * 6D) + (MarginInner * 7D);
public static readonly Double LayoutHeight = (TableTop * 2D) + TableHeight;
public static readonly Double LayoutWidth = TableLeft + TableWidth + MarginOuter;

然后,我只是在我的 XAML 中使用它们,如下所示:

<Window x:Class="MyNS.MainWindow" ResizeMode="NoResize" SizeToContent="WidthAndHeight">
    <Canvas x:Name="m_Layout" Height="{x:Static ns:MainWindow.LayoutHeight}" Width="{x:Static ns:MainWindow.LayoutWidth}">

嗯……没什么好说的。它有效......但它看起来太丑陋了,我想知道是否有更好的解决方案。我不知道...可能是设置文件、绑定、内联 XAML 计算或其他任何东西...可以让它看起来更好的东西。

【问题讨论】:

    标签: c# wpf xaml resize constants


    【解决方案1】:

    我通常将我的应用程序的所有静态设置放在一个称为通用类的静态或单例类中,例如 ApplicationSettings(或 MainWindowSettings,如果这些值仅由 MainWindow 使用)

    如果这些值是用户可配置的,它们会进入 app.config 并加载到静态类的构造函数中。如果没有,我只是在我的静态类中对它们进行硬编码,以便以后轻松找到/更改它们。

    public static class ApplicationSettings
    {
        public static Double MarginInner { get; private set; }
        public static Double MarginOuter { get; private set; }
        public static Double StrokeThickness { get; private set; }
    
        static ApplicationSettings()
        {
            MarginInner = 6D;
            MarginOuter = 10D;
            StrokeThickness = 3D;
        }
    }
    

    对于 XAML 中的计算值,我通常使用我编写的 MathConverter,它让我可以编写与数学表达式的绑定,并将值传递给它以使用。

    我在博客上发布的版本只是IValueConverter,但很容易扩展为IMultiValueConverter,因此它可以接受多个绑定值。

    <Setter Property="Height">
       <Setter.Value>
          <MultiBinding Converter="{StaticResource MathMultiConverter}"
                        ConverterParameter="(@VALUE1 * 2D) + (@VALUE2 * 3D) + (@VALUE3 * 2.5D)">
             <Binding RelativeSource="{x:Static ns:ApplicationSettings.StrokeThickness }" />
             <Binding RelativeSource="{x:Static ns:ApplicationSettings.MarginInner}" />
             <Binding ElementName="MyUc" Path="ActualHeight" />
          </MultiBinding>
       </Setter.Value>
    </Setter>
    

    通常我会将所有这些乱七八糟的 XAML 隐藏在某处的样式中,这样它就不会弄乱我的主要 XAML 代码,并且只需在需要的地方应用样式。

    这是我用于 IMultiValueConvter 的转换器代码的副本

    // Does a math equation on a series of bound values. 
    // Use @VALUEN in your mathEquation as a substitute for bound values, where N is the 0-based index of the bound value
    // Operator order is parenthesis first, then Left-To-Right (no operator precedence)
    public class MathMultiConverter : IMultiValueConverter
    {
        public object  Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
        {
            // Remove spaces
            var mathEquation = parameter as string;
            mathEquation = mathEquation.Replace(" ", "");
    
            // Loop through values to substitute placeholders for values
            // Using a backwards loop to avoid replacing something like @VALUE10 with @VALUE1
            for (var i = (values.Length - 1); i >= 0; i--)
                mathEquation = mathEquation.Replace(string.Format("@VALUE{0}", i), values[i].ToString());
    
            // Return result of equation
            return MathConverterHelpers.RunEquation(ref mathEquation);
        }
    
        public object[]  ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
    
    public static class MathConverterHelpers
    {
        private static readonly char[] _allOperators = new[] { '+', '-', '*', '/', '%', '(', ')' };
    
        private static readonly List<string> _grouping = new List<string> { "(", ")" };
        private static readonly List<string> _operators = new List<string> { "+", "-", "*", "/", "%" };
    
    
        public static double RunEquation(ref string mathEquation)
        {
            // Validate values and get list of numbers in equation
            var numbers = new List<double>();
            double tmp;
    
            foreach (string s in mathEquation.Split(_allOperators))
            {
                if (s != string.Empty)
                {
                    if (double.TryParse(s, out tmp))
                    {
                        numbers.Add(tmp);
                    }
                    else
                    {
                        // Handle Error - Some non-numeric, operator, or grouping character found in string
                        throw new InvalidCastException();
                    }
                }
            }
    
            // Begin parsing method
            EvaluateMathString(ref mathEquation, ref numbers, 0);
    
            // After parsing the numbers list should only have one value - the total
            return numbers[0];
        }
    
        // Evaluates a mathematical string and keeps track of the results in a List<double> of numbers
        private static void EvaluateMathString(ref string mathEquation, ref List<double> numbers, int index)
        {
            // Loop through each mathemtaical token in the equation
            string token = GetNextToken(mathEquation);
    
            while (token != string.Empty)
            {
                // Remove token from mathEquation
                mathEquation = mathEquation.Remove(0, token.Length);
    
                // If token is a grouping character, it affects program flow
                if (_grouping.Contains(token))
                {
                    switch (token)
                    {
                        case "(":
                            EvaluateMathString(ref mathEquation, ref numbers, index);
                            break;
    
                        case ")":
                            return;
                    }
                }
    
                // If token is an operator, do requested operation
                if (_operators.Contains(token))
                {
                    // If next token after operator is a parenthesis, call method recursively
                    string nextToken = GetNextToken(mathEquation);
                    if (nextToken == "(")
                    {
                        EvaluateMathString(ref mathEquation, ref numbers, index + 1);
                    }
    
                    // Verify that enough numbers exist in the List<double> to complete the operation
                    // and that the next token is either the number expected, or it was a ( meaning 
                    // that this was called recursively and that the number changed
                    if (numbers.Count > (index + 1) &&
                        (double.Parse(nextToken) == numbers[index + 1] || nextToken == "("))
                    {
                        switch (token)
                        {
                            case "+":
                                numbers[index] = numbers[index] + numbers[index + 1];
                                break;
                            case "-":
                                numbers[index] = numbers[index] - numbers[index + 1];
                                break;
                            case "*":
                                numbers[index] = numbers[index] * numbers[index + 1];
                                break;
                            case "/":
                                numbers[index] = numbers[index] / numbers[index + 1];
                                break;
                            case "%":
                                numbers[index] = numbers[index] % numbers[index + 1];
                                break;
                        }
                        numbers.RemoveAt(index + 1);
                    }
                    else
                    {
                        // Handle Error - Next token is not the expected number
                        throw new FormatException("Next token is not the expected number");
                    }
                }
    
                token = GetNextToken(mathEquation);
            }
        }
    
        // Gets the next mathematical token in the equation
        private static string GetNextToken(string mathEquation)
        {
            // If we're at the end of the equation, return string.empty
            if (mathEquation == string.Empty)
            {
                return string.Empty;
            }
    
            // Get next operator or numeric value in equation and return it
            string tmp = "";
            foreach (char c in mathEquation)
            {
                if (_allOperators.Contains(c))
                {
                    return (tmp == "" ? c.ToString() : tmp);
                }
                else
                {
                    tmp += c;
                }
            }
    
            return tmp;
        }
    }
    

    但老实说,如果这些值仅以单一形式使用,那么我只需在 View 后面的代码中设置 Loaded 事件中的值 :)

    【讨论】:

    • 注意,应该是(nextToken == "(" || double.Parse(nextToken) == numbers[index + 1])而不是(double.Parse(nextToken) == numbers[index + 1] || nextToken == "("),以免解析时崩溃。
    • @herohuyongtao 哦,是的,应该是。实际上,我在此之前几行检查了if (nextToken == "("),以便可以删除检查。我写这个假设ConverterParameter是一个有效的数学公式,目的是如果公式无效,它应该崩溃,这样开发人员就会知道并修复它:)
    【解决方案2】:

    将那些静态的放在 app.config 中,它们会更干净。

    使用 app.config,您首先必须引用 System.Configuration

    然后你可以这样做(可能涉及一些类型转换):

     ConfigurationManager.AppSettings["MarginInner"];
    

    检索:

    <configuration>
        <appsettings>
            <add key="MarginInner" value="6D" />
        </appsettings>
    </configuration>
    

    然后可能有一个静态类来保存动态计算,例如:

    public class CalculationHelper
    {
        //your dynamic properties in here
    }
    

    【讨论】:

      【解决方案3】:

      mattytommo 的回答仅针对您拥有的常量值(边距和笔划粗细),而不是计算字段。

      结合 Matty 所说,我将添加一个设置类,该类从 app.config 文件中检索常量值并进行所需的适当计算,然后我可以在 XAML 中引用适当的属性

      { Settings.MainWindow.LayoutWidth }
      

      编辑:

      看起来马蒂和他在我发帖之间编辑的一样;)

      【讨论】:

      • 大声笑是的,我刚想说我已经考虑到了,我在发布后自己考虑了一下:)
      猜你喜欢
      • 2018-04-28
      • 1970-01-01
      • 2011-03-31
      • 2011-02-13
      • 1970-01-01
      • 2023-03-19
      • 1970-01-01
      • 2010-10-06
      • 1970-01-01
      相关资源
      最近更新 更多