Elegant Solution for Readonly Values

I am developing a WPF application, the size and location of which in terms of size and components of Windows should be dynamically calculated during initialization, since they are based on the main size of the UserControl that I use, and some other small parameters. So, for now, I have put these constants in my window code as follows:

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; 

Then I just use them inside my XAML as follows:

 <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}"> 

Well ... nothing to say. It works ... but it is soooo damn ugly to see, and I was wondering if there is a better solution for this. I don’t know ... maybe a settings file, bindings, built-in XAML calculations or something else ... something that would make it just better.

+4
source share
3 answers

I usually set all the static parameters of my application in one static or singleton class, called something common, for example ApplicationSettings (or MainWindowSettings , if the values ​​are used only by MainWindow )

If the values ​​are for customization, they are included in app.config and loaded into the constructor of the static class. If not, I will just copy them in my static class so that they are easy to find / modify later.

 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; } } 

For calculated values ​​in your XAML, I usually use the MathConverter , which I wrote, which allows me to write a binding with a mathematical expression and pass its used values.

The version that I posted on my blog is just an IValueConverter , but it’s pretty easy to deploy it to IMultiValueConverter so that it can take several related values.

 <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> 

Normally, I would hide all this messy XAML in style somewhere, so it does not clutter up my main XAML code and just apply the style where necessary.

Here is a copy of the converter code that I use for 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; } } 

But honestly, if these values ​​are used in only one form, I just set the values ​​in the Loaded event in the code behind the View :)

+4
source

Put these static in app.config, they will be much cleaner there.

Using app.config, you first need to have a link to System.Configuration .

Then you can do ( some type may be involved ):

  ConfigurationManager.AppSettings["MarginInner"]; 

To obtain:

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

Then, perhaps you have a static class for storing dynamic calculations, for example:

 public class CalculationHelper { //your dynamic properties in here } 
+2
source

Mattytommo's answer only answers the constant values ​​that you have (fields and stroke thickness) but not calculated fields.

I would, in combination with what Matti said, add a settings class that extracts constant values ​​from the app.config file and also does the corresponding calculations, and then I could refer to the corresponding property in XAML

i.e.

 { Settings.MainWindow.LayoutWidth } 

EDIT:

it looks like Matty had the same thing, although he edited between me, posting;)

+1
source

All Articles