manually implemented reading method to get enum value by matching description, removing dependency Humanizer,

fixed various warnings
master
Jonas Arnold 3 years ago
parent 1d8b9148df
commit 637eb20386
  1. 89
      Common/Helpers/EnumHelpers.cs
  2. 2
      MultiTerm.Core/ViewModel/TerminalViewModel.cs
  3. 3
      MultiTerm.Wpf/Behaviours/ProgressBarSmoother.cs
  4. 19
      MultiTerm.Wpf/Behaviours/TabContent.cs
  5. 1
      MultiTerm.Wpf/MultiTerm.Wpf.csproj
  6. 77
      MultiTerm.Wpf/ValueConverters/EnumDescriptionToMenuItemConverter.cs
  7. 20
      MultiTerm.Wpf/ValueConverters/EnumValueToEnumDescriptionConverter.cs
  8. 32
      MultiTerm.Wpf/View/SendReceiveView.xaml

@ -6,9 +6,11 @@ namespace Common.Helpers;
public static class EnumHelpers public static class EnumHelpers
{ {
/// <summary> /// <summary>
/// Parse enum from string to enum type. /// Parse enum from string to enum enumType.
/// String must be exactly the same as generated by the default Enum.ToString()
/// method and cannot be a searchedDescription of the Enum value.
/// </summary> /// </summary>
/// <typeparam name="T">type of the enum</typeparam> /// <typeparam name="T">enumType of the enum</typeparam>
/// <param name="value">value to parse to enum</param> /// <param name="value">value to parse to enum</param>
/// <returns>returns enum object with value</returns> /// <returns>returns enum object with value</returns>
public static T ParseEnum<T>(string value) public static T ParseEnum<T>(string value)
@ -17,7 +19,24 @@ public static class EnumHelpers
} }
/// <summary> /// <summary>
/// Gets the description of an Enum value. /// Creates an instance of the given <paramref name="enumType"/> if it is an enum type.
/// Throws an exception if it is not an enum type.
/// </summary>
/// <param name="enumType">type of target enum</param>
/// <returns>instance of enum tye with default value</returns>
public static Enum CreateInstanceOfEnumType(Type enumType)
{
// generate instance of enum using target enumType
if (Activator.CreateInstance(enumType) is not Enum enumObj)
{
throw new Exception($"'{nameof(CreateInstanceOfEnumType)}()' could not instanciate Enum of type {enumType}.");
}
return enumObj;
}
/// <summary>
/// Gets the searchedDescription of an Enum value.
/// If there is no Description set, the Enum Value will be converted to string. /// If there is no Description set, the Enum Value will be converted to string.
/// </summary> /// </summary>
/// <param name="enumObject">Enum Object to get Description of.</param> /// <param name="enumObject">Enum Object to get Description of.</param>
@ -27,7 +46,7 @@ public static class EnumHelpers
// guard argument null // guard argument null
if (enumObject == null) { throw new ArgumentNullException(nameof(enumObject)); } if (enumObject == null) { throw new ArgumentNullException(nameof(enumObject)); }
// get field info from enum type // get field info from enum enumType
FieldInfo? fieldInfo = enumObject.GetType().GetField(enumObject.ToString()); FieldInfo? fieldInfo = enumObject.GetType().GetField(enumObject.ToString());
// return string of enum value if there is no field info // return string of enum value if there is no field info
if (fieldInfo == null) if (fieldInfo == null)
@ -35,14 +54,72 @@ public static class EnumHelpers
return enumObject.ToString(); return enumObject.ToString();
} }
// get description attribute and return if it is present // get searchedDescription attribute and return if it is present
DescriptionAttribute? descAttrib = (DescriptionAttribute?)fieldInfo.GetCustomAttribute(typeof(DescriptionAttribute), true); DescriptionAttribute? descAttrib = (DescriptionAttribute?)fieldInfo.GetCustomAttribute(typeof(DescriptionAttribute), true);
if (descAttrib != null) if (descAttrib != null)
{ {
return descAttrib.Description; return descAttrib.Description;
} }
// if no description attribute was found => return string of enum value // if no searchedDescription attribute was found => return string of enum value
return enumObject.ToString(); return enumObject.ToString();
} }
/// <summary>
/// Gets all Description Attributes of a Enum enumType.
/// Descriptions must be unique!
/// </summary>
/// <param name="enumObject">An object of the Enum. Will be searched for other Descriptions</param>
/// <returns>Key Value Pair of Description and respective Enum Value</returns>
public static Dictionary<string, Enum> GetAllEnumDescriptions(Enum enumObject)
{
// guard argument null
if (enumObject == null) { throw new ArgumentNullException(nameof(enumObject)); }
Dictionary<string, Enum> descriptionsToEnumValues = new();
// get members of enum enumType
var members = enumObject.GetType().GetMembers();
foreach (var member in members)
{
// get searchedDescription attributes of all members
DescriptionAttribute? descAttrib = (DescriptionAttribute?)member.GetCustomAttribute(typeof(DescriptionAttribute), true);
if (descAttrib != null)
{
// if a searchedDescription exists, add the searchedDescription and the enum value to the dictionary
descriptionsToEnumValues.Add(descAttrib.Description, (Enum)Enum.Parse(enumObject.GetType(), member.Name));
}
}
return descriptionsToEnumValues;
}
/// <summary>
/// Searches and returns the Enum Value of which the <see cref="DescriptionAttribute"/> equals the <paramref name="searchedDescription"/>.
/// Descriptions of enumType of <paramref name="enumObj"/> must be uniqe!
/// </summary>
/// <param name="enumObj">any instance of the enum enumType</param>
/// <param name="searchedDescription">description that is compared to the <see cref="DescriptionAttribute"/></param>
/// <returns>enum object of enumType of <paramref name="enumObj"/> or null if no enum with matching description found</returns>
public static Enum? GetEnumValueByDescription(Enum enumObj, string searchedDescription)
{
// guard null
if(enumObj == null) { return null; }
// get all descriptions of the enum
var descriptions = GetAllEnumDescriptions(enumObj);
// return if equal
foreach (var desc in descriptions)
{
if (desc.Key.Equals(searchedDescription))
{
return desc.Value;
}
}
// nothing found
return null;
//throw new Exception($"{nameof(GetEnumValueByDescription)} could not find an enum value in enumType {typeof(enumObj)} that has the Description {searchedDescription}.");
}
} }

@ -174,7 +174,7 @@ public abstract partial class TerminalViewModel : ObservableObject, ITerminalVie
{ {
// update internal state // update internal state
this.CommunicationProtocolConnectionState = e; this.CommunicationProtocolConnectionState = e;
this.CommunicationProtocolLongIdentifier = this.CommunicationProtocol.LongInstanceIdentifier; this.CommunicationProtocolLongIdentifier = this.CommunicationProtocol!.LongInstanceIdentifier;
// If ViewModel still displays connected after unintentional disconnect => force to disconnected state // If ViewModel still displays connected after unintentional disconnect => force to disconnected state
// allows user to change settings // allows user to change settings

@ -6,7 +6,8 @@ using System.Windows;
namespace MultiTerm.Wpf.Behaviours; namespace MultiTerm.Wpf.Behaviours;
/// <summary> /// <summary>
/// From https://stackoverflow.com/questions/14485818/how-to-update-a-progress-bar-so-it-increases-smoothly /// Smoothens <see cref="ProgressBar"/> by adding an animation for each change of value.
/// Inspired by https://stackoverflow.com/questions/14485818/how-to-update-a-progress-bar-so-it-increases-smoothly
/// </summary> /// </summary>
public class ProgressBarSmoother public class ProgressBarSmoother
{ {

@ -125,8 +125,7 @@ public static class TabContent
{ {
if (obj == null) return; if (obj == null) return;
var tabControl = obj as TabControl; if (obj is not TabControl tabControl)
if (tabControl == null)
{ {
throw new InvalidOperationException("Cannot set TabContent.IsCached on object of type " + args.NewValue.GetType().Name + throw new InvalidOperationException("Cannot set TabContent.IsCached on object of type " + args.NewValue.GetType().Name +
". Only objects of type TabControl can have TabContent.IsCached property."); ". Only objects of type TabControl can have TabContent.IsCached property.");
@ -154,9 +153,10 @@ public static class TabContent
const string xaml = const string xaml =
"<DataTemplate><Border b:TabContent.InternalTabControl=\"{Binding RelativeSource={RelativeSource AncestorType=TabControl}}\" /></DataTemplate>"; "<DataTemplate><Border b:TabContent.InternalTabControl=\"{Binding RelativeSource={RelativeSource AncestorType=TabControl}}\" /></DataTemplate>";
var context = new ParserContext(); var context = new ParserContext
{
context.XamlTypeMapper = new XamlTypeMapper(new string[0]); XamlTypeMapper = new XamlTypeMapper(Array.Empty<string>())
};
context.XamlTypeMapper.AddMappingProcessingInstruction("b", typeof(TabContent).Namespace, typeof(TabContent).Assembly.FullName); context.XamlTypeMapper.AddMappingProcessingInstruction("b", typeof(TabContent).Namespace, typeof(TabContent).Assembly.FullName);
context.XmlnsDictionary.Add("", "http://schemas.microsoft.com/winfx/2006/xaml/presentation"); context.XmlnsDictionary.Add("", "http://schemas.microsoft.com/winfx/2006/xaml/presentation");
@ -169,9 +169,8 @@ public static class TabContent
private static void OnInternalTabControlChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args) private static void OnInternalTabControlChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{ {
if (obj == null) return; if (obj == null) return;
var container = obj as Decorator;
if (container == null) if (obj is not Decorator container)
{ {
var message = "Cannot set TabContent.InternalTabControl on object of type " + obj.GetType().Name + var message = "Cannot set TabContent.InternalTabControl on object of type " + obj.GetType().Name +
". Only controls that derive from Decorator, such as Border can have a TabContent.InternalTabControl."; ". Only controls that derive from Decorator, such as Border can have a TabContent.InternalTabControl.";
@ -179,7 +178,7 @@ public static class TabContent
} }
if (args.NewValue == null) return; if (args.NewValue == null) return;
if (!(args.NewValue is TabControl)) if (args.NewValue is not TabControl)
{ {
throw new InvalidOperationException("Value of TabContent.InternalTabControl cannot be of type " + args.NewValue.GetType().Name +", it must be of type TabControl"); throw new InvalidOperationException("Value of TabContent.InternalTabControl cannot be of type " + args.NewValue.GetType().Name +", it must be of type TabControl");
} }
@ -230,7 +229,7 @@ public static class TabContent
public class ContentManager public class ContentManager
{ {
TabControl _tabControl; readonly TabControl _tabControl;
Decorator _border; Decorator _border;
public ContentManager(TabControl tabControl, Decorator border) public ContentManager(TabControl tabControl, Decorator border)
@ -253,7 +252,7 @@ public static class TabContent
_border.Child = GetCurrentContent(); _border.Child = GetCurrentContent();
} }
private ContentControl GetCurrentContent() private ContentControl? GetCurrentContent()
{ {
var item = _tabControl.SelectedItem; var item = _tabControl.SelectedItem;
if (item == null) return null; if (item == null) return null;

@ -15,7 +15,6 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Humanizer.Core" Version="2.14.1" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="7.0.0" /> <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="7.0.1" /> <PackageReference Include="Microsoft.Extensions.Hosting" Version="7.0.1" />
<PackageReference Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.39" /> <PackageReference Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.39" />

@ -1,10 +1,8 @@
using System; using System;
using System.ComponentModel;
using System.Globalization; using System.Globalization;
using System.Reflection;
using System.Windows.Data; using System.Windows.Data;
using System.Collections.Generic;
using System.Windows.Controls; using System.Windows.Controls;
using Common.Helpers;
namespace MultiTerm.Wpf.ValueConverters; namespace MultiTerm.Wpf.ValueConverters;
@ -16,65 +14,6 @@ namespace MultiTerm.Wpf.ValueConverters;
[ValueConversion(typeof(Enum), typeof(MenuItem))] [ValueConversion(typeof(Enum), typeof(MenuItem))]
public class EnumDescriptionToMenuItemConverter : IValueConverter public class EnumDescriptionToMenuItemConverter : IValueConverter
{ {
/// <summary>
/// Gets the description of an Enum value.
/// If there is no Description set, the Enum Value will be converted to string.
/// </summary>
/// <param name="enumObject">Enum Object to get Description of.</param>
/// <returns>String with Content of DescriptionAttribute of Enum object.</returns>
private static string GetEnumDescription(Enum enumObject)
{
// guard argument null
if(enumObject == null) { throw new ArgumentNullException(nameof(enumObject)); }
// get field info from enum type
FieldInfo? fieldInfo = enumObject.GetType().GetField(enumObject.ToString());
// return string of enum value if there is no field info
if (fieldInfo == null)
{
return enumObject.ToString();
}
// get description attribute and return if it is present
DescriptionAttribute? descAttrib = (DescriptionAttribute?)fieldInfo.GetCustomAttribute(typeof(DescriptionAttribute), true);
if (descAttrib != null)
{
return descAttrib.Description;
}
// if no description attribute was found => return string of enum value
return enumObject.ToString();
}
/// <summary>
/// Gets all Description Attributes of a Enum type.
/// Descriptions must be unique!
/// </summary>
/// <param name="enumObject">An object of the Enum. Will be searched for other Descriptions</param>
/// <returns>Key Value Pair of Description and respective Enum Value</returns>
private static Dictionary<string, Enum> GetAllEnumDescriptions(Enum enumObject)
{
// guard argument null
if (enumObject == null) { throw new ArgumentNullException(nameof(enumObject)); }
Dictionary<string, Enum> descriptionsToEnumValues = new();
// get members of enum type
var members = enumObject.GetType().GetMembers();
foreach (var member in members)
{
// get description attributes of all members
DescriptionAttribute? descAttrib = (DescriptionAttribute?)member.GetCustomAttribute(typeof(DescriptionAttribute), true);
if(descAttrib != null)
{
// if a description exists, add the description and the enum value to the dictionary
descriptionsToEnumValues.Add(descAttrib.Description, (Enum)Enum.Parse(enumObject.GetType(), member.Name));
}
}
return descriptionsToEnumValues;
}
/// <summary> /// <summary>
/// Convert from Data Source to Dependency Object type. /// Convert from Data Source to Dependency Object type.
/// Here: Enum type to MenuItem. /// Here: Enum type to MenuItem.
@ -87,7 +26,7 @@ public class EnumDescriptionToMenuItemConverter : IValueConverter
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{ {
Enum enumValue = (Enum)value; Enum enumValue = (Enum)value;
string description = GetEnumDescription(enumValue); string description = EnumHelpers.GetEnumDescription(enumValue);
MenuItem newMenuItem = new() MenuItem newMenuItem = new()
{ {
Header = description Header = description
@ -106,8 +45,6 @@ public class EnumDescriptionToMenuItemConverter : IValueConverter
/// <returns>Enum value of the MenuItem that was converted</returns> /// <returns>Enum value of the MenuItem that was converted</returns>
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{ {
Enum? enumObj;
// guard argument null // guard argument null
if (targetType == null) { throw new ArgumentNullException(nameof(targetType)); } if (targetType == null) { throw new ArgumentNullException(nameof(targetType)); }
if (value == null) { throw new ArgumentNullException(nameof(value)); } if (value == null) { throw new ArgumentNullException(nameof(value)); }
@ -118,15 +55,9 @@ public class EnumDescriptionToMenuItemConverter : IValueConverter
throw new Exception($"Cannot convert value that is not of type {nameof(MenuItem)} with {nameof(EnumDescriptionToMenuItemConverter)}"); throw new Exception($"Cannot convert value that is not of type {nameof(MenuItem)} with {nameof(EnumDescriptionToMenuItemConverter)}");
} }
// generate instance of enum using target type
enumObj = Activator.CreateInstance(targetType) as Enum;
if (enumObj == null)
{
throw new Exception($"Could not instanciate Enum of targetType {targetType}.");
}
// get all enum descriptions and iterate // get all enum descriptions and iterate
var descriptionToEnumValue = GetAllEnumDescriptions(enumObj); var enumObj = EnumHelpers.CreateInstanceOfEnumType(targetType);
var descriptionToEnumValue = EnumHelpers.GetAllEnumDescriptions(enumObj);
foreach (var kvp in descriptionToEnumValue) foreach (var kvp in descriptionToEnumValue)
{ {
// compare key (enum description) to menu header // compare key (enum description) to menu header

@ -1,9 +1,9 @@
using Humanizer; using System;
using System;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Windows.Data; using System.Windows.Data;
using Common.Helpers;
namespace MultiTerm.Wpf.ValueConverters; namespace MultiTerm.Wpf.ValueConverters;
@ -26,13 +26,13 @@ public class EnumValueToEnumDescriptionConverter : IValueConverter
List<string> outputValues = new(); List<string> outputValues = new();
foreach (var item in arrayOfEnumValues) foreach (var item in arrayOfEnumValues)
{ {
outputValues.Add(((Enum)item).Humanize()); outputValues.Add(EnumHelpers.GetEnumDescription((Enum)item));
} }
return outputValues; return outputValues;
} }
else if(value is Enum) else if(value is Enum)
{ {
return ((Enum)value).Humanize(); return EnumHelpers.GetEnumDescription((Enum)value);
} }
else else
{ {
@ -43,13 +43,21 @@ public class EnumValueToEnumDescriptionConverter : IValueConverter
/// <summary> /// <summary>
/// Converts single string values to Enum of <paramref name="targetType"/>. /// Converts single string values to Enum of <paramref name="targetType"/>.
/// </summary> /// </summary>
/// <returns>enum value</returns> /// <returns>enum value or empty object if no matching enum value was found</returns>
/// <exception cref="ArgumentException">if value is not a string</exception> /// <exception cref="ArgumentException">if value is not a string</exception>
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{ {
// guard that it is a string, cannot be a collection // guard that it is a string, cannot be a collection
if (value is not string stringValue) { throw new ArgumentException("Can only convert string values."); } if (value is not string stringValue) { throw new ArgumentException("Can only convert string values."); }
return stringValue.DehumanizeTo(targetType); // find value by description
var objOfEnumType = EnumHelpers.CreateInstanceOfEnumType(targetType);
var enumObjectWithMatchingDescription = EnumHelpers.GetEnumValueByDescription(objOfEnumType, stringValue);
// return new object if none found
if(enumObjectWithMatchingDescription == null)
{ return new object(); }
return enumObjectWithMatchingDescription;
} }
} }

@ -13,7 +13,7 @@
xmlns:types="clr-namespace:MultiTerm.Core.Types;assembly=MultiTerm.Core" xmlns:types="clr-namespace:MultiTerm.Core.Types;assembly=MultiTerm.Core"
xmlns:settings_view="clr-namespace:MultiTerm.Wpf.View.SettingsView" xmlns:settings_view="clr-namespace:MultiTerm.Wpf.View.SettingsView"
xmlns:custom_controls="clr-namespace:MultiTerm.Wpf.CustomControl;assembly=MultiTerm.Wpf.CustomControl" xmlns:custom_controls="clr-namespace:MultiTerm.Wpf.CustomControl;assembly=MultiTerm.Wpf.CustomControl"
xmlns:behaviors="http://schemas.microsoft.com/xaml/behaviors" xmlns:XamlBehavioursWpf="http://schemas.microsoft.com/xaml/behaviors"
mc:Ignorable="d" mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"> d:DesignHeight="450" d:DesignWidth="800">
<UserControl.Resources> <UserControl.Resources>
@ -80,11 +80,11 @@
<!-- Received Data View --> <!-- Received Data View -->
<custom_controls:MultiFormatDataView x:Name="mfdvReceive" DataSource="{Binding ReceivedData}"> <custom_controls:MultiFormatDataView x:Name="mfdvReceive" DataSource="{Binding ReceivedData}">
<behaviors:Interaction.Triggers> <XamlBehavioursWpf:Interaction.Triggers>
<behaviors:EventTrigger EventName="ClearRequested" SourceObject="{Binding ElementName=mfdvReceive}"> <XamlBehavioursWpf:EventTrigger EventName="ClearRequested" SourceObject="{Binding ElementName=mfdvReceive}">
<behaviors:InvokeCommandAction Command="{Binding ReceivedData.ClearCommand}" /> <XamlBehavioursWpf:InvokeCommandAction Command="{Binding ReceivedData.ClearCommand}" />
</behaviors:EventTrigger> </XamlBehavioursWpf:EventTrigger>
</behaviors:Interaction.Triggers> </XamlBehavioursWpf:Interaction.Triggers>
</custom_controls:MultiFormatDataView> </custom_controls:MultiFormatDataView>
</GroupBox> </GroupBox>
<GroupBox Header="Send" Grid.Row="3" Padding="5"> <GroupBox Header="Send" Grid.Row="3" Padding="5">
@ -103,11 +103,11 @@
<custom_controls:MultiFormatTextBox x:Name="sendTextBox" DockPanel.Dock="Left" <custom_controls:MultiFormatTextBox x:Name="sendTextBox" DockPanel.Dock="Left"
CurrentMultiFormatString="{Binding Path=SendableData, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"> CurrentMultiFormatString="{Binding Path=SendableData, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<behaviors:Interaction.Triggers> <XamlBehavioursWpf:Interaction.Triggers>
<behaviors:EventTrigger EventName="EnterPressed" SourceObject="{Binding ElementName=sendTextBox}"> <XamlBehavioursWpf:EventTrigger EventName="EnterPressed" SourceObject="{Binding ElementName=sendTextBox}">
<behaviors:InvokeCommandAction Command="{Binding SendCommand}" /> <XamlBehavioursWpf:InvokeCommandAction Command="{Binding SendCommand}" />
</behaviors:EventTrigger> </XamlBehavioursWpf:EventTrigger>
</behaviors:Interaction.Triggers> </XamlBehavioursWpf:Interaction.Triggers>
</custom_controls:MultiFormatTextBox> </custom_controls:MultiFormatTextBox>
</DockPanel> </DockPanel>
<Separator/> <Separator/>
@ -115,11 +115,11 @@
<!-- Sent Data View --> <!-- Sent Data View -->
<custom_controls:MultiFormatDataView x:Name="mfdvSend" DataSource="{Binding SentData}"> <custom_controls:MultiFormatDataView x:Name="mfdvSend" DataSource="{Binding SentData}">
<behaviors:Interaction.Triggers> <XamlBehavioursWpf:Interaction.Triggers>
<behaviors:EventTrigger EventName="ClearRequested" SourceObject="{Binding ElementName=mfdvSend}"> <XamlBehavioursWpf:EventTrigger EventName="ClearRequested" SourceObject="{Binding ElementName=mfdvSend}">
<behaviors:InvokeCommandAction Command="{Binding SentData.ClearCommand}" /> <XamlBehavioursWpf:InvokeCommandAction Command="{Binding SentData.ClearCommand}" />
</behaviors:EventTrigger> </XamlBehavioursWpf:EventTrigger>
</behaviors:Interaction.Triggers> </XamlBehavioursWpf:Interaction.Triggers>
</custom_controls:MultiFormatDataView> </custom_controls:MultiFormatDataView>
</DockPanel> </DockPanel>

Loading…
Cancel
Save