-
Notifications
You must be signed in to change notification settings - Fork 393
WPF MVVM Sample App #353
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
WPF MVVM Sample App #353
Changes from all commits
265d0f7
0f52ce9
da11510
0b19788
08e300d
61d7b53
0cc0d69
a02fa39
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
## WPF MVVM Sample | ||
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. | ||
|
||
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. | ||
|
||
A key feature enabling this sample is the [UnitToStringConverter](https://github.com/dayewah/UnitsNet/blob/master/Samples/WpfMVVMSample/WpfMVVMSample/Converters/UnitToStringConverter.cs) class | ||
- If a parameter is entered as a number the unit is assigned automatically | ||
- If a parameter is entered as a unit other than the default it is converted automatically | ||
- If a non-compatible unit is used a validation error is triggered | ||
|
||
The default unit for each parameter and the result can be changed from the settings pull down. | ||
|
||
The number of significant digits displayed can also be changed from settings. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
| ||
Microsoft Visual Studio Solution File, Format Version 12.00 | ||
# Visual Studio 15 | ||
VisualStudioVersion = 15.0.27130.2010 | ||
MinimumVisualStudioVersion = 10.0.40219.1 | ||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WpfMVVMSample", "WpfMVVMSample\WpfMVVMSample.csproj", "{B72F9215-70FF-4155-89BC-9A02CC550447}" | ||
EndProject | ||
Global | ||
GlobalSection(SolutionConfigurationPlatforms) = preSolution | ||
Debug|Any CPU = Debug|Any CPU | ||
Release|Any CPU = Release|Any CPU | ||
EndGlobalSection | ||
GlobalSection(ProjectConfigurationPlatforms) = postSolution | ||
{B72F9215-70FF-4155-89BC-9A02CC550447}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||
{B72F9215-70FF-4155-89BC-9A02CC550447}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||
{B72F9215-70FF-4155-89BC-9A02CC550447}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||
{B72F9215-70FF-4155-89BC-9A02CC550447}.Release|Any CPU.Build.0 = Release|Any CPU | ||
EndGlobalSection | ||
GlobalSection(SolutionProperties) = preSolution | ||
HideSolutionNode = FALSE | ||
EndGlobalSection | ||
GlobalSection(ExtensibilityGlobals) = postSolution | ||
SolutionGuid = {D5FA6C7A-26EC-4F45-B539-F20AD40CE0A4} | ||
EndGlobalSection | ||
EndGlobal |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
<?xml version="1.0" encoding="utf-8" ?> | ||
<configuration> | ||
<startup> | ||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1" /> | ||
</startup> | ||
</configuration> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
<Application x:Class="WpfMVVMSample.App" | ||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" | ||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" | ||
xmlns:local="clr-namespace:WpfMVVMSample"> | ||
<Application.Resources> | ||
|
||
</Application.Resources> | ||
</Application> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Configuration; | ||
using System.Data; | ||
using System.Linq; | ||
using System.Threading.Tasks; | ||
using System.Windows; | ||
|
||
namespace WpfMVVMSample | ||
{ | ||
/// <summary> | ||
/// Interaction logic for App.xaml | ||
/// </summary> | ||
public partial class App : Application | ||
{ | ||
protected override void OnStartup(StartupEventArgs e) | ||
{ | ||
base.OnStartup(e); | ||
|
||
var bootstrapper = new Bootstrapper(); | ||
bootstrapper.Run(); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
using Microsoft.Practices.ServiceLocation; | ||
using Microsoft.Practices.Unity; | ||
using Prism.Unity; | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Text; | ||
using System.Threading.Tasks; | ||
using System.Windows; | ||
using WpfMVVMSample.Converters; | ||
using WpfMVVMSample.Settings; | ||
|
||
namespace WpfMVVMSample | ||
{ | ||
public class Bootstrapper : UnityBootstrapper | ||
{ | ||
protected override DependencyObject CreateShell() | ||
{ | ||
return Container.TryResolve<MainWindow>(); | ||
} | ||
|
||
protected override void InitializeShell() | ||
{ | ||
Application.Current.MainWindow.Show(); | ||
} | ||
|
||
protected override void ConfigureContainer() | ||
{ | ||
base.ConfigureContainer(); | ||
|
||
Container.RegisterType<SettingsManager>(new ContainerControlledLifetimeManager());//singleton | ||
} | ||
|
||
protected override void ConfigureServiceLocator() | ||
{ | ||
base.ConfigureServiceLocator(); | ||
|
||
var serviceLocator = new UnityServiceLocator(Container); | ||
ServiceLocator.SetLocatorProvider(() => serviceLocator); | ||
|
||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Collections.ObjectModel; | ||
using System.Linq; | ||
using System.Text; | ||
using System.Threading.Tasks; | ||
using System.Windows.Markup; | ||
|
||
namespace WpfMVVMSample.Converters | ||
{ | ||
public class EnumBindingSourceExtension : MarkupExtension | ||
{ | ||
private Type _enumType; | ||
private Type EnumType | ||
{ | ||
get { return this._enumType; } | ||
set | ||
{ | ||
if (value != this._enumType) | ||
{ | ||
if (value != null) | ||
{ | ||
Type enumType = Nullable.GetUnderlyingType(value) ?? value; | ||
|
||
if (!enumType.IsEnum) | ||
throw new ArgumentException("Type must be for an Enum."); | ||
} | ||
|
||
this._enumType = value; | ||
} | ||
} | ||
} | ||
|
||
public EnumBindingSourceExtension() { } | ||
|
||
public EnumBindingSourceExtension(Type enumType) | ||
{ | ||
this.EnumType = enumType; | ||
} | ||
|
||
public override object ProvideValue(IServiceProvider serviceProvider) | ||
{ | ||
if (this._enumType== null) | ||
throw new InvalidOperationException("The EnumType must be specified."); | ||
|
||
Type actualEnumType = Nullable.GetUnderlyingType(this._enumType) ?? this._enumType; | ||
|
||
//omits the first enum element, typically "undefined" | ||
var enumValues = Enum.GetValues(actualEnumType).Cast<object>().Skip(1); | ||
|
||
return enumValues; | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
using Microsoft.Practices.ServiceLocation; | ||
using System; | ||
using System.Collections.Generic; | ||
using System.ComponentModel; | ||
using System.Globalization; | ||
using System.Linq; | ||
using System.Text; | ||
using System.Threading.Tasks; | ||
using System.Windows; | ||
using System.Windows.Controls; | ||
using System.Windows.Data; | ||
using System.Windows.Markup; | ||
using UnitsNet; | ||
using WpfMVVMSample.Settings; | ||
|
||
namespace WpfMVVMSample.Converters | ||
{ | ||
public class UnitToStringConverter :MarkupExtension, IValueConverter | ||
{ | ||
//http://www.thejoyofcode.com/WPF_Quick_Tip_Converters_as_MarkupExtensions.aspx | ||
private SettingsManager _settings; | ||
private static UnitToStringConverter _instance; | ||
|
||
public UnitToStringConverter() | ||
{ | ||
if (!DesignerProperties.GetIsInDesignMode(new DependencyObject())) | ||
{ | ||
_settings = ServiceLocator.Current.GetInstance<SettingsManager>(); | ||
} | ||
} | ||
|
||
|
||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) | ||
{ | ||
var unitType = value.GetType(); | ||
var unitEnumType = unitType.GetProperty("BaseUnit").PropertyType; | ||
var unitEnumValue = _settings.GetDefaultUnit(unitEnumType); | ||
var significantDigits = _settings.SignificantDigits; | ||
|
||
var result = unitType | ||
.GetMethod("ToString", new[] { unitEnumType, typeof(IFormatProvider), typeof(int) }) | ||
.Invoke(value, new object[] { unitEnumValue, null, significantDigits }); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My only concern is that this is quite heavy on reflection and might easily break at runtime when subtle changes to the library are made that are backwards compatible on calling code, but incompatible with the reflection method signatures expected here. I think maybe the library could be adapted a bit to facilitate this better. I have only given this a brief thinking, but I I think it would help to make https://github.com/angularsen/UnitsNet/blob/master/UnitsNet/CustomCode/QuantityParser.cs @JKSnd @eriove @ferittuncer I'm seeing some potential usecases for exposing There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree that the reflection is a bit clunky. Any library enhancements that better enable this strategy would be welcome. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Well, we have experienced these problems too when we used this library in our GUI. Lacking base classes for units and quantities forces implementation to reflection as presenter layer doesn't know which unit or quantity it's dealing with at the runtime. Perhaps there are other ways we couldn't see, I'm not sure. I just have an instinct that we should have base classes. You know better, your call @angularsen There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A common interface for all quantities would be a simple addition (at least if it contained members that are already there) and wouldn't incur any performance hit (boxing) as long as it isn't used. That's of course not part of this pull request but it is much easier to see in this context. |
||
|
||
return result; | ||
} | ||
|
||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) | ||
{ | ||
var unitEnumType = targetType.GetProperty("BaseUnit").PropertyType; | ||
var unitEnumValue = _settings.GetDefaultUnit(unitEnumType); | ||
|
||
if ((string)value == "") return 0.0; | ||
|
||
double number; | ||
if (double.TryParse((string)value, out number)) | ||
return ParseDouble(targetType, number, unitEnumType, unitEnumValue); | ||
|
||
try | ||
{ | ||
return ParseUnit(value, targetType); | ||
} | ||
catch (Exception e) | ||
{ | ||
return new ValidationResult(false, e.InnerException.Message); | ||
} | ||
} | ||
|
||
|
||
private static object ParseDouble(Type targetType, double number, Type unitEnumType, object unitEnumValue) | ||
{ | ||
return targetType | ||
.GetMethod("From", new[] { typeof(QuantityValue), unitEnumType }) | ||
.Invoke(null, new object[] { (QuantityValue)number, unitEnumValue }); | ||
} | ||
private static object ParseUnit(object value, Type targetType) | ||
{ | ||
return targetType | ||
.GetMethod("Parse", new[] { typeof(string) }) | ||
.Invoke(null, new object[] { value }); | ||
} | ||
|
||
public override object ProvideValue(IServiceProvider serviceProvider) | ||
{ | ||
return _instance ?? (_instance = new UnitToStringConverter()); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
<Window x:Class="WpfMVVMSample.MainWindow" | ||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" | ||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" | ||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" | ||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" | ||
xmlns:prism="http://prismlibrary.com/" | ||
xmlns:conv="clr-namespace:WpfMVVMSample.Converters" | ||
xmlns:local="clr-namespace:WpfMVVMSample" | ||
xmlns:units="clr-namespace:UnitsNet.Units;assembly=UnitsNet" | ||
prism:ViewModelLocator.AutoWireViewModel="True" | ||
|
||
mc:Ignorable="d" | ||
Title="MainWindow"> | ||
<Window.Resources> | ||
<Style TargetType="TextBox"> | ||
<Setter Property="HorizontalAlignment" Value="Stretch"/> | ||
<Setter Property="HorizontalContentAlignment" Value="Center"/> | ||
<Setter Property="VerticalContentAlignment" Value="Center"/> | ||
<Setter Property="Height" Value="30"/> | ||
<Setter Property="FontSize" Value="16"/> | ||
<Setter Property="Margin" Value="10"/> | ||
</Style> | ||
<Style TargetType="ComboBox"> | ||
<Setter Property="HorizontalAlignment" Value="Stretch"/> | ||
<Setter Property="HorizontalContentAlignment" Value="Center"/> | ||
<Setter Property="VerticalContentAlignment" Value="Center"/> | ||
<Setter Property="Height" Value="30"/> | ||
<Setter Property="FontSize" Value="16"/> | ||
<Setter Property="Margin" Value="10"/> | ||
</Style> | ||
<Style TargetType="Label" x:Key="ResultLabel"> | ||
<Setter Property="HorizontalAlignment" Value="Center"/> | ||
<Setter Property="HorizontalContentAlignment" Value="Center"/> | ||
<Setter Property="VerticalContentAlignment" Value="Center"/> | ||
<Setter Property="Height" Value="30"/> | ||
<Setter Property="FontSize" Value="16"/> | ||
<Setter Property="Width" Value="150"/> | ||
<Setter Property="Margin" Value="10"/> | ||
</Style> | ||
<Style TargetType="Label"> | ||
<Setter Property="HorizontalAlignment" Value="Right"/> | ||
<Setter Property="VerticalContentAlignment" Value="Center"/> | ||
<Setter Property="Margin" Value="10,0"/> | ||
<Setter Property="FontSize" Value="16"/> | ||
</Style> | ||
</Window.Resources> | ||
<StackPanel> | ||
<Expander Grid.Column="0" Grid.Row="3" Grid.ColumnSpan="3" FontSize="16" Background="WhiteSmoke"> | ||
<Expander.Header> | ||
<Border> | ||
<Label Content="Settings"></Label> | ||
</Border> | ||
</Expander.Header> | ||
<Grid> | ||
<Grid.ColumnDefinitions> | ||
<ColumnDefinition Width="1*"/> | ||
<ColumnDefinition Width="2*"/> | ||
<ColumnDefinition Width="1*"/> | ||
</Grid.ColumnDefinitions> | ||
<Grid.RowDefinitions> | ||
<RowDefinition Height="Auto"/> | ||
<RowDefinition Height="Auto"/> | ||
<RowDefinition Height="Auto"/> | ||
<RowDefinition Height="Auto"/> | ||
<RowDefinition Height="Auto"/> | ||
</Grid.RowDefinitions> | ||
|
||
<Label Grid.Column="0" Grid.Row="0" Content="Mass"/> | ||
<ComboBox Grid.Column="1" | ||
ItemsSource="{Binding Source={conv:EnumBindingSource {x:Type units:MassUnit}}}" | ||
SelectedItem="{Binding Settings.DefaultMassUnit}"/> | ||
|
||
<Label Grid.Column="0" Grid.Row="1" Content="G" /> | ||
<ComboBox Grid.Column="1" Grid.Row="1" | ||
ItemsSource="{Binding Source={conv:EnumBindingSource {x:Type units:AccelerationUnit}}}" | ||
SelectedItem="{Binding Settings.DefaultAccelerationUnit}"/> | ||
|
||
<Label Grid.Column="0" Grid.Row="2" Content="Weight" /> | ||
<ComboBox Grid.Column="1" Grid.Row="2" | ||
ItemsSource="{Binding Source={conv:EnumBindingSource {x:Type units:ForceUnit}}}" | ||
SelectedItem="{Binding Settings.DefaultForceUnit}"/> | ||
|
||
<Label Grid.Column="0" Grid.Row="3" Content="No. Digits" /> | ||
<TextBox Grid.Column="1" Grid.Row="3" Text="{Binding Settings.SignificantDigits}"/> | ||
</Grid> | ||
</Expander> | ||
<Grid> | ||
<Grid.ColumnDefinitions> | ||
<ColumnDefinition Width="1*"/> | ||
<ColumnDefinition Width="2*"/> | ||
<ColumnDefinition Width="1*"/> | ||
</Grid.ColumnDefinitions> | ||
<Grid.RowDefinitions> | ||
<RowDefinition Height="Auto"/> | ||
<RowDefinition Height="Auto"/> | ||
<RowDefinition Height="Auto"/> | ||
<RowDefinition Height="Auto"/> | ||
</Grid.RowDefinitions> | ||
|
||
<Label Grid.Column="0" Grid.Row="0" Content="Mass"/> | ||
<TextBox Grid.Column="1" Grid.Row="0" Text="{Binding ObjectMass, Converter={conv:UnitToStringConverter}}"/> | ||
|
||
<Label Grid.Column="0" Grid.Row="1" Content="G" /> | ||
<TextBox Grid.Column="1" Grid.Row="1" Text="{Binding G, Converter={conv:UnitToStringConverter}}"/> | ||
|
||
<Label Grid.Column="0" Grid.Row="2" Content="Weight" /> | ||
<Label Grid.Column="1" Grid.Row="2" Content="{Binding Weight, Converter={conv:UnitToStringConverter}}" Style="{StaticResource ResultLabel}"/> | ||
|
||
</Grid> | ||
</StackPanel> | ||
</Window> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This readme is great!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cool