Skip to content

Commit 5df0b5f

Browse files
dayewahangularsen
authored andcommitted
Add WPF MVVM Sample App (#353)
Using XAML value converters and some reflection to populate the lists of units.
1 parent 1d719a8 commit 5df0b5f

19 files changed

+958
-0
lines changed

Samples/WpfMVVMSample/Readme.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
## WPF MVVM Sample
2+
This is a simple sample showing how UnitsNet can be used to create a WPF MVVM application. I have used this strategy in a few simple engineering apps and thought I would share it as a sample to see if others might offer improvements.
3+
4+
It performs a simple calculation allowing flexibility in the units for parameters and results. Default units for each are specified in the settings drop down.
5+
6+
A key feature enabling this sample is the [UnitToStringConverter](https://github.com/dayewah/UnitsNet/blob/master/Samples/WpfMVVMSample/WpfMVVMSample/Converters/UnitToStringConverter.cs) class
7+
- If a parameter is entered as a number the unit is assigned automatically
8+
- If a parameter is entered as a unit other than the default it is converted automatically
9+
- If a non-compatible unit is used a validation error is triggered
10+
11+
The default unit for each parameter and the result can be changed from the settings pull down.
12+
13+
The number of significant digits displayed can also be changed from settings.
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
2+
Microsoft Visual Studio Solution File, Format Version 12.00
3+
# Visual Studio 15
4+
VisualStudioVersion = 15.0.27130.2010
5+
MinimumVisualStudioVersion = 10.0.40219.1
6+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WpfMVVMSample", "WpfMVVMSample\WpfMVVMSample.csproj", "{B72F9215-70FF-4155-89BC-9A02CC550447}"
7+
EndProject
8+
Global
9+
GlobalSection(SolutionConfigurationPlatforms) = preSolution
10+
Debug|Any CPU = Debug|Any CPU
11+
Release|Any CPU = Release|Any CPU
12+
EndGlobalSection
13+
GlobalSection(ProjectConfigurationPlatforms) = postSolution
14+
{B72F9215-70FF-4155-89BC-9A02CC550447}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
15+
{B72F9215-70FF-4155-89BC-9A02CC550447}.Debug|Any CPU.Build.0 = Debug|Any CPU
16+
{B72F9215-70FF-4155-89BC-9A02CC550447}.Release|Any CPU.ActiveCfg = Release|Any CPU
17+
{B72F9215-70FF-4155-89BC-9A02CC550447}.Release|Any CPU.Build.0 = Release|Any CPU
18+
EndGlobalSection
19+
GlobalSection(SolutionProperties) = preSolution
20+
HideSolutionNode = FALSE
21+
EndGlobalSection
22+
GlobalSection(ExtensibilityGlobals) = postSolution
23+
SolutionGuid = {D5FA6C7A-26EC-4F45-B539-F20AD40CE0A4}
24+
EndGlobalSection
25+
EndGlobal
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<?xml version="1.0" encoding="utf-8" ?>
2+
<configuration>
3+
<startup>
4+
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1" />
5+
</startup>
6+
</configuration>
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<Application x:Class="WpfMVVMSample.App"
2+
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
3+
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
4+
xmlns:local="clr-namespace:WpfMVVMSample">
5+
<Application.Resources>
6+
7+
</Application.Resources>
8+
</Application>
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Configuration;
4+
using System.Data;
5+
using System.Linq;
6+
using System.Threading.Tasks;
7+
using System.Windows;
8+
9+
namespace WpfMVVMSample
10+
{
11+
/// <summary>
12+
/// Interaction logic for App.xaml
13+
/// </summary>
14+
public partial class App : Application
15+
{
16+
protected override void OnStartup(StartupEventArgs e)
17+
{
18+
base.OnStartup(e);
19+
20+
var bootstrapper = new Bootstrapper();
21+
bootstrapper.Run();
22+
}
23+
}
24+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
using Microsoft.Practices.ServiceLocation;
2+
using Microsoft.Practices.Unity;
3+
using Prism.Unity;
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Linq;
7+
using System.Text;
8+
using System.Threading.Tasks;
9+
using System.Windows;
10+
using WpfMVVMSample.Converters;
11+
using WpfMVVMSample.Settings;
12+
13+
namespace WpfMVVMSample
14+
{
15+
public class Bootstrapper : UnityBootstrapper
16+
{
17+
protected override DependencyObject CreateShell()
18+
{
19+
return Container.TryResolve<MainWindow>();
20+
}
21+
22+
protected override void InitializeShell()
23+
{
24+
Application.Current.MainWindow.Show();
25+
}
26+
27+
protected override void ConfigureContainer()
28+
{
29+
base.ConfigureContainer();
30+
31+
Container.RegisterType<SettingsManager>(new ContainerControlledLifetimeManager());//singleton
32+
}
33+
34+
protected override void ConfigureServiceLocator()
35+
{
36+
base.ConfigureServiceLocator();
37+
38+
var serviceLocator = new UnityServiceLocator(Container);
39+
ServiceLocator.SetLocatorProvider(() => serviceLocator);
40+
41+
}
42+
}
43+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Collections.ObjectModel;
4+
using System.Linq;
5+
using System.Text;
6+
using System.Threading.Tasks;
7+
using System.Windows.Markup;
8+
9+
namespace WpfMVVMSample.Converters
10+
{
11+
public class EnumBindingSourceExtension : MarkupExtension
12+
{
13+
private Type _enumType;
14+
private Type EnumType
15+
{
16+
get { return this._enumType; }
17+
set
18+
{
19+
if (value != this._enumType)
20+
{
21+
if (value != null)
22+
{
23+
Type enumType = Nullable.GetUnderlyingType(value) ?? value;
24+
25+
if (!enumType.IsEnum)
26+
throw new ArgumentException("Type must be for an Enum.");
27+
}
28+
29+
this._enumType = value;
30+
}
31+
}
32+
}
33+
34+
public EnumBindingSourceExtension() { }
35+
36+
public EnumBindingSourceExtension(Type enumType)
37+
{
38+
this.EnumType = enumType;
39+
}
40+
41+
public override object ProvideValue(IServiceProvider serviceProvider)
42+
{
43+
if (this._enumType== null)
44+
throw new InvalidOperationException("The EnumType must be specified.");
45+
46+
Type actualEnumType = Nullable.GetUnderlyingType(this._enumType) ?? this._enumType;
47+
48+
//omits the first enum element, typically "undefined"
49+
var enumValues = Enum.GetValues(actualEnumType).Cast<object>().Skip(1);
50+
51+
return enumValues;
52+
}
53+
}
54+
}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
using Microsoft.Practices.ServiceLocation;
2+
using System;
3+
using System.Collections.Generic;
4+
using System.ComponentModel;
5+
using System.Globalization;
6+
using System.Linq;
7+
using System.Text;
8+
using System.Threading.Tasks;
9+
using System.Windows;
10+
using System.Windows.Controls;
11+
using System.Windows.Data;
12+
using System.Windows.Markup;
13+
using UnitsNet;
14+
using WpfMVVMSample.Settings;
15+
16+
namespace WpfMVVMSample.Converters
17+
{
18+
public class UnitToStringConverter :MarkupExtension, IValueConverter
19+
{
20+
//http://www.thejoyofcode.com/WPF_Quick_Tip_Converters_as_MarkupExtensions.aspx
21+
private SettingsManager _settings;
22+
private static UnitToStringConverter _instance;
23+
24+
public UnitToStringConverter()
25+
{
26+
if (!DesignerProperties.GetIsInDesignMode(new DependencyObject()))
27+
{
28+
_settings = ServiceLocator.Current.GetInstance<SettingsManager>();
29+
}
30+
}
31+
32+
33+
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
34+
{
35+
var unitType = value.GetType();
36+
var unitEnumType = unitType.GetProperty("BaseUnit").PropertyType;
37+
var unitEnumValue = _settings.GetDefaultUnit(unitEnumType);
38+
var significantDigits = _settings.SignificantDigits;
39+
40+
var result = unitType
41+
.GetMethod("ToString", new[] { unitEnumType, typeof(IFormatProvider), typeof(int) })
42+
.Invoke(value, new object[] { unitEnumValue, null, significantDigits });
43+
44+
return result;
45+
}
46+
47+
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
48+
{
49+
var unitEnumType = targetType.GetProperty("BaseUnit").PropertyType;
50+
var unitEnumValue = _settings.GetDefaultUnit(unitEnumType);
51+
52+
if ((string)value == "") return 0.0;
53+
54+
double number;
55+
if (double.TryParse((string)value, out number))
56+
return ParseDouble(targetType, number, unitEnumType, unitEnumValue);
57+
58+
try
59+
{
60+
return ParseUnit(value, targetType);
61+
}
62+
catch (Exception e)
63+
{
64+
return new ValidationResult(false, e.InnerException.Message);
65+
}
66+
}
67+
68+
69+
private static object ParseDouble(Type targetType, double number, Type unitEnumType, object unitEnumValue)
70+
{
71+
return targetType
72+
.GetMethod("From", new[] { typeof(QuantityValue), unitEnumType })
73+
.Invoke(null, new object[] { (QuantityValue)number, unitEnumValue });
74+
}
75+
private static object ParseUnit(object value, Type targetType)
76+
{
77+
return targetType
78+
.GetMethod("Parse", new[] { typeof(string) })
79+
.Invoke(null, new object[] { value });
80+
}
81+
82+
public override object ProvideValue(IServiceProvider serviceProvider)
83+
{
84+
return _instance ?? (_instance = new UnitToStringConverter());
85+
}
86+
}
87+
}
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
<Window x:Class="WpfMVVMSample.MainWindow"
2+
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
3+
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
4+
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
5+
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
6+
xmlns:prism="http://prismlibrary.com/"
7+
xmlns:conv="clr-namespace:WpfMVVMSample.Converters"
8+
xmlns:local="clr-namespace:WpfMVVMSample"
9+
xmlns:units="clr-namespace:UnitsNet.Units;assembly=UnitsNet"
10+
prism:ViewModelLocator.AutoWireViewModel="True"
11+
12+
mc:Ignorable="d"
13+
Title="MainWindow">
14+
<Window.Resources>
15+
<Style TargetType="TextBox">
16+
<Setter Property="HorizontalAlignment" Value="Stretch"/>
17+
<Setter Property="HorizontalContentAlignment" Value="Center"/>
18+
<Setter Property="VerticalContentAlignment" Value="Center"/>
19+
<Setter Property="Height" Value="30"/>
20+
<Setter Property="FontSize" Value="16"/>
21+
<Setter Property="Margin" Value="10"/>
22+
</Style>
23+
<Style TargetType="ComboBox">
24+
<Setter Property="HorizontalAlignment" Value="Stretch"/>
25+
<Setter Property="HorizontalContentAlignment" Value="Center"/>
26+
<Setter Property="VerticalContentAlignment" Value="Center"/>
27+
<Setter Property="Height" Value="30"/>
28+
<Setter Property="FontSize" Value="16"/>
29+
<Setter Property="Margin" Value="10"/>
30+
</Style>
31+
<Style TargetType="Label" x:Key="ResultLabel">
32+
<Setter Property="HorizontalAlignment" Value="Center"/>
33+
<Setter Property="HorizontalContentAlignment" Value="Center"/>
34+
<Setter Property="VerticalContentAlignment" Value="Center"/>
35+
<Setter Property="Height" Value="30"/>
36+
<Setter Property="FontSize" Value="16"/>
37+
<Setter Property="Width" Value="150"/>
38+
<Setter Property="Margin" Value="10"/>
39+
</Style>
40+
<Style TargetType="Label">
41+
<Setter Property="HorizontalAlignment" Value="Right"/>
42+
<Setter Property="VerticalContentAlignment" Value="Center"/>
43+
<Setter Property="Margin" Value="10,0"/>
44+
<Setter Property="FontSize" Value="16"/>
45+
</Style>
46+
</Window.Resources>
47+
<StackPanel>
48+
<Expander Grid.Column="0" Grid.Row="3" Grid.ColumnSpan="3" FontSize="16" Background="WhiteSmoke">
49+
<Expander.Header>
50+
<Border>
51+
<Label Content="Settings"></Label>
52+
</Border>
53+
</Expander.Header>
54+
<Grid>
55+
<Grid.ColumnDefinitions>
56+
<ColumnDefinition Width="1*"/>
57+
<ColumnDefinition Width="2*"/>
58+
<ColumnDefinition Width="1*"/>
59+
</Grid.ColumnDefinitions>
60+
<Grid.RowDefinitions>
61+
<RowDefinition Height="Auto"/>
62+
<RowDefinition Height="Auto"/>
63+
<RowDefinition Height="Auto"/>
64+
<RowDefinition Height="Auto"/>
65+
<RowDefinition Height="Auto"/>
66+
</Grid.RowDefinitions>
67+
68+
<Label Grid.Column="0" Grid.Row="0" Content="Mass"/>
69+
<ComboBox Grid.Column="1"
70+
ItemsSource="{Binding Source={conv:EnumBindingSource {x:Type units:MassUnit}}}"
71+
SelectedItem="{Binding Settings.DefaultMassUnit}"/>
72+
73+
<Label Grid.Column="0" Grid.Row="1" Content="G" />
74+
<ComboBox Grid.Column="1" Grid.Row="1"
75+
ItemsSource="{Binding Source={conv:EnumBindingSource {x:Type units:AccelerationUnit}}}"
76+
SelectedItem="{Binding Settings.DefaultAccelerationUnit}"/>
77+
78+
<Label Grid.Column="0" Grid.Row="2" Content="Weight" />
79+
<ComboBox Grid.Column="1" Grid.Row="2"
80+
ItemsSource="{Binding Source={conv:EnumBindingSource {x:Type units:ForceUnit}}}"
81+
SelectedItem="{Binding Settings.DefaultForceUnit}"/>
82+
83+
<Label Grid.Column="0" Grid.Row="3" Content="No. Digits" />
84+
<TextBox Grid.Column="1" Grid.Row="3" Text="{Binding Settings.SignificantDigits}"/>
85+
</Grid>
86+
</Expander>
87+
<Grid>
88+
<Grid.ColumnDefinitions>
89+
<ColumnDefinition Width="1*"/>
90+
<ColumnDefinition Width="2*"/>
91+
<ColumnDefinition Width="1*"/>
92+
</Grid.ColumnDefinitions>
93+
<Grid.RowDefinitions>
94+
<RowDefinition Height="Auto"/>
95+
<RowDefinition Height="Auto"/>
96+
<RowDefinition Height="Auto"/>
97+
<RowDefinition Height="Auto"/>
98+
</Grid.RowDefinitions>
99+
100+
<Label Grid.Column="0" Grid.Row="0" Content="Mass"/>
101+
<TextBox Grid.Column="1" Grid.Row="0" Text="{Binding ObjectMass, Converter={conv:UnitToStringConverter}}"/>
102+
103+
<Label Grid.Column="0" Grid.Row="1" Content="G" />
104+
<TextBox Grid.Column="1" Grid.Row="1" Text="{Binding G, Converter={conv:UnitToStringConverter}}"/>
105+
106+
<Label Grid.Column="0" Grid.Row="2" Content="Weight" />
107+
<Label Grid.Column="1" Grid.Row="2" Content="{Binding Weight, Converter={conv:UnitToStringConverter}}" Style="{StaticResource ResultLabel}"/>
108+
109+
</Grid>
110+
</StackPanel>
111+
</Window>

0 commit comments

Comments
 (0)