Compare commits

..

12 Commits

51 changed files with 1465 additions and 1237 deletions

18
.config/dotnet-tools.json Normal file
View File

@ -0,0 +1,18 @@
{
"version": 1,
"isRoot": true,
"tools": {
"cake.tool": {
"version": "2.0.0-rc0002",
"commands": [
"dotnet-cake"
]
},
"gitversion.tool": {
"version": "5.8.1",
"commands": [
"dotnet-gitversion"
]
}
}
}

6
.csharpierrc Normal file
View File

@ -0,0 +1,6 @@
{
"printWidth": 200,
"useTabs": false,
"tabWidth": 4,
"preprocessorSymbolSets": ["", "DEBUG", "DEBUG,CODE_STYLE"]
}

247
.editorconfig Normal file
View File

@ -0,0 +1,247 @@
# Entfernen Sie die folgende Zeile, wenn Sie EDITORCONFIG-Einstellungen von höheren Verzeichnissen vererben möchten.
root = true
# C#-Dateien
[*.cs]
#### Wichtige EditorConfig-Optionen ####
# Einzüge und Abstände
indent_size = 4
indent_style = space
tab_width = 4
# Einstellungen für neue Zeilen
end_of_line = crlf
insert_final_newline = true
#### .NET-Codierungskonventionen ####
# Using-Direktiven organisieren
dotnet_separate_import_directive_groups = false
dotnet_sort_system_directives_first = false
file_header_template = unset
# this.- und Me.-Einstellungen
dotnet_style_qualification_for_event = false
dotnet_style_qualification_for_field = false
dotnet_style_qualification_for_method = false
dotnet_style_qualification_for_property = false
# Einstellungen für Sprachschlüsselwörter und BCL-Typen
dotnet_style_predefined_type_for_locals_parameters_members = true
dotnet_style_predefined_type_for_member_access = true
# Einstellungen für Klammern
dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity
dotnet_style_parentheses_in_other_binary_operators = always_for_clarity
dotnet_style_parentheses_in_other_operators = never_if_unnecessary
dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity
# Einstellungen für Modifizierer
dotnet_style_require_accessibility_modifiers = for_non_interface_members
# Einstellungen für Ausdrucksebene
dotnet_style_coalesce_expression = true
dotnet_style_collection_initializer = true
dotnet_style_explicit_tuple_names = true
dotnet_style_namespace_match_folder = true
dotnet_style_null_propagation = true
dotnet_style_object_initializer = true
dotnet_style_operator_placement_when_wrapping = beginning_of_line
dotnet_style_prefer_auto_properties = true
dotnet_style_prefer_compound_assignment = true
dotnet_style_prefer_conditional_expression_over_assignment = true
dotnet_style_prefer_conditional_expression_over_return = true
dotnet_style_prefer_foreach_explicit_cast_in_source = when_strongly_typed
dotnet_style_prefer_inferred_anonymous_type_member_names = true
dotnet_style_prefer_inferred_tuple_names = true
dotnet_style_prefer_is_null_check_over_reference_equality_method = true
dotnet_style_prefer_simplified_boolean_expressions = true
dotnet_style_prefer_simplified_interpolation = true
# Einstellungen für Felder
dotnet_style_readonly_field = true
# Einstellungen für Parameter
dotnet_code_quality_unused_parameters = all
# Unterdrückungseinstellungen
dotnet_remove_unnecessary_suppression_exclusions = none
# Einstellungen für neue Zeilen
dotnet_style_allow_multiple_blank_lines_experimental = true
dotnet_style_allow_statement_immediately_after_block_experimental = true
#### C#-Codierungskonventionen ####
# Var-Einstellungen
csharp_style_var_elsewhere = false
csharp_style_var_for_built_in_types = false
csharp_style_var_when_type_is_apparent = false
# Ausdruckskörpermember
csharp_style_expression_bodied_accessors = true:silent
csharp_style_expression_bodied_constructors = false:silent
csharp_style_expression_bodied_indexers = true:silent
csharp_style_expression_bodied_lambdas = true:silent
csharp_style_expression_bodied_local_functions = false:silent
csharp_style_expression_bodied_methods = false:silent
csharp_style_expression_bodied_operators = false:silent
csharp_style_expression_bodied_properties = true:silent
# Einstellungen für den Musterabgleich
csharp_style_pattern_matching_over_as_with_null_check = true
csharp_style_pattern_matching_over_is_with_cast_check = true
csharp_style_prefer_extended_property_pattern = true
csharp_style_prefer_not_pattern = true
csharp_style_prefer_pattern_matching = true
csharp_style_prefer_switch_expression = true:suggestion
# Einstellungen für NULL-Überprüfung
csharp_style_conditional_delegate_call = true
# Einstellungen für Modifizierer
csharp_prefer_static_local_function = true
csharp_preferred_modifier_order = public,private,protected,internal,file,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,required,volatile,async
csharp_style_prefer_readonly_struct = true
# Einstellungen für Codeblöcke
csharp_prefer_braces = true:silent
csharp_prefer_simple_using_statement = true:suggestion
csharp_style_namespace_declarations = block_scoped:silent
csharp_style_prefer_method_group_conversion = true:silent
csharp_style_prefer_top_level_statements = true:silent
# Einstellungen für Ausdrucksebene
csharp_prefer_simple_default_expression = true:suggestion
csharp_style_deconstructed_variable_declaration = true:suggestion
csharp_style_implicit_object_creation_when_type_is_apparent = true:suggestion
csharp_style_inlined_variable_declaration = true:suggestion
csharp_style_prefer_index_operator = true:suggestion
csharp_style_prefer_local_over_anonymous_function = true:suggestion
csharp_style_prefer_null_check_over_type_check = true:suggestion
csharp_style_prefer_range_operator = true:suggestion
csharp_style_prefer_tuple_swap = true:suggestion
csharp_style_prefer_utf8_string_literals = true:suggestion
csharp_style_throw_expression = true:suggestion
csharp_style_unused_value_assignment_preference = discard_variable:suggestion
csharp_style_unused_value_expression_statement_preference = discard_variable:silent
# Einstellungen für using-Anweisungen
csharp_using_directive_placement = outside_namespace
# Einstellungen für neue Zeilen
csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true
csharp_style_allow_blank_lines_between_consecutive_braces_experimental = true
csharp_style_allow_embedded_statements_on_same_line_experimental = true
#### C#-Formatierungsregeln ####
# Einstellungen für neue Zeilen
csharp_new_line_before_catch = true
csharp_new_line_before_else = true
csharp_new_line_before_finally = true
csharp_new_line_before_members_in_anonymous_types = true
csharp_new_line_before_members_in_object_initializers = true
csharp_new_line_before_open_brace = all
csharp_new_line_between_query_expression_clauses = true
# Einstellungen für Einrückung
csharp_indent_block_contents = true
csharp_indent_braces = false
csharp_indent_case_contents = true
csharp_indent_case_contents_when_block = true
csharp_indent_labels = one_less_than_current
csharp_indent_switch_labels = true
# Einstellungen für Abstände
csharp_space_after_cast = false
csharp_space_after_colon_in_inheritance_clause = true
csharp_space_after_comma = true
csharp_space_after_dot = false
csharp_space_after_keywords_in_control_flow_statements = true
csharp_space_after_semicolon_in_for_statement = true
csharp_space_around_binary_operators = before_and_after
csharp_space_around_declaration_statements = false
csharp_space_before_colon_in_inheritance_clause = true
csharp_space_before_comma = false
csharp_space_before_dot = false
csharp_space_before_open_square_brackets = false
csharp_space_before_semicolon_in_for_statement = false
csharp_space_between_empty_square_brackets = false
csharp_space_between_method_call_empty_parameter_list_parentheses = false
csharp_space_between_method_call_name_and_opening_parenthesis = false
csharp_space_between_method_call_parameter_list_parentheses = false
csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
csharp_space_between_method_declaration_name_and_open_parenthesis = false
csharp_space_between_method_declaration_parameter_list_parentheses = false
csharp_space_between_parentheses = false
csharp_space_between_square_brackets = false
# Umbrucheinstellungen
csharp_preserve_single_line_blocks = true
csharp_preserve_single_line_statements = true
#### Benennungsstile ####
# Benennungsregeln
dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion
dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface
dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i
dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.types_should_be_pascal_case.symbols = types
dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case
dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members
dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case
# Symbolspezifikationen
dotnet_naming_symbols.interface.applicable_kinds = interface
dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.interface.required_modifiers =
dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum
dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.types.required_modifiers =
dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.non_field_members.required_modifiers =
# Benennungsstile
dotnet_naming_style.pascal_case.required_prefix =
dotnet_naming_style.pascal_case.required_suffix =
dotnet_naming_style.pascal_case.word_separator =
dotnet_naming_style.pascal_case.capitalization = pascal_case
dotnet_naming_style.begins_with_i.required_prefix = I
dotnet_naming_style.begins_with_i.required_suffix =
dotnet_naming_style.begins_with_i.word_separator =
dotnet_naming_style.begins_with_i.capitalization = pascal_case
[*.{cs,vb}]
dotnet_style_operator_placement_when_wrapping = beginning_of_line
end_of_line = crlf
tab_width = 4
indent_size = 4
dotnet_style_coalesce_expression = true:suggestion
dotnet_style_null_propagation = true:suggestion
dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion
dotnet_style_prefer_auto_properties = true:silent
dotnet_style_object_initializer = true:suggestion
dotnet_style_collection_initializer = true:suggestion
dotnet_style_prefer_simplified_boolean_expressions = true:suggestion
dotnet_style_prefer_conditional_expression_over_assignment = true:silent
dotnet_style_prefer_conditional_expression_over_return = true:silent
dotnet_style_explicit_tuple_names = true:suggestion
dotnet_style_prefer_inferred_tuple_names = true:suggestion
dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
dotnet_style_prefer_compound_assignment = true:suggestion
dotnet_style_prefer_simplified_interpolation = true:suggestion
dotnet_style_namespace_match_folder = true:suggestion

View File

@ -15,8 +15,6 @@ jobs:
- uses: actions/checkout@v2
- name: Setup .NET Core
uses: actions/setup-dotnet@v1
with:
dotnet-version: 3.1.101
- name: Install dependencies
run: dotnet restore
- name: Build

2
.gitignore vendored
View File

@ -338,3 +338,5 @@ ASALocalRun/
# BeatPulse healthcheck temp database
healthchecksdb
.ionide
/tools

2
.vscode/launch.json vendored
View File

@ -10,7 +10,7 @@
"request": "launch",
"preLaunchTask": "build",
// If you have changed target frameworks, make sure to update the program path.
"program": "${workspaceFolder}/bin/Debug/netcoreapp3.1/ModernWpfPlayground.dll",
"program": "${workspaceFolder}/App/bin/Debug/net5.0-windows/win-x64/ModernWpfPlayground.dll",
"args": [],
"cwd": "${workspaceFolder}",
// For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console

6
.vscode/tasks.json vendored
View File

@ -7,7 +7,7 @@
"type": "process",
"args": [
"build",
"${workspaceFolder}/ModernWpfPlayground.csproj",
"${workspaceFolder}/App/ModernWpfPlayground.csproj",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary"
],
@ -19,7 +19,7 @@
"type": "process",
"args": [
"publish",
"${workspaceFolder}/ModernWpfPlayground.csproj",
"${workspaceFolder}/App/ModernWpfPlayground.csproj",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary"
],
@ -32,7 +32,7 @@
"args": [
"watch",
"run",
"${workspaceFolder}/ModernWpfPlayground.csproj",
"${workspaceFolder}/App/ModernWpfPlayground.csproj",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary"
],

View File

@ -1,10 +0,0 @@
using System.Windows;
[assembly:ThemeInfo(
ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
//(used if a resource is not found in the page,
// or application resource dictionaries)
ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
//(used if a resource is not found in the page,
// app, or any theme specific resource dictionaries)
)]

View File

@ -1,13 +0,0 @@
namespace ModernWpfPlayground
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow
{
public MainWindow()
{
InitializeComponent();
}
}
}

View File

@ -1,166 +0,0 @@
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;
using Microsoft.Win32;
using ModernWpfPlayground.MvvmStuff;
using ModernWpfPlayground.Types;
using Prism.Commands;
using YamlDotNet.Serialization;
using static ModernWpf.ThemeManager;
namespace ModernWpfPlayground
{
// ReSharper disable once ClassNeverInstantiated.Global
public class MainWindowViewModel : BaseViewModel
{
private const string AppName = "TaBEA 3.0.0";
private string? _path;
private string _title = AppName;
private readonly ISerializer _serializer;
private readonly IDeserializer _deserializer;
public MainWindowViewModel()
{
ShowDialogCommand = new DelegateCommand(ShowDialog);
CloseCommand = new DelegateCommand(() => Application.Current.Shutdown());
OpenViewModelCommand = new DelegateCommand(LoadViewModel);
SaveViewModelCommand = new DelegateCommand(SaveViewModel);
ResetViewModelCommand = new DelegateCommand(() =>
{
ResetViewModel();
Path = null;
});
_serializer = new SerializerBuilder().Build();
_deserializer = new DeserializerBuilder().Build();
}
private string? Path
{
get => _path;
set => SetProperty(ref _path, value,
() => Title = value != null ? $"{System.IO.Path.GetFileName(value)} - {AppName}" : AppName);
}
public string Title
{
get => _title;
set => SetProperty(ref _title, value);
}
public bool BooleanValue
{
get => GetProperty(true);
set => SetProperty(value, BooleanValue_OnChanged);
}
public Visibility VisibilityEnumTest
{
get => GetProperty(Visibility.Visible);
set => SetProperty(value);
}
public double SliderTest
{
get => GetProperty(100D);
set => SetProperty(value);
}
public double ValidationTest
{
get => GetProperty(0D);
set => SetProperty(value);
}
public ICommand ShowDialogCommand { get; }
public string WelcomeMessage
{
get => GetProperty("Shadow of the empire");
set => SetProperty(value);
}
public ICommand CloseCommand { get; }
public ICommand OpenViewModelCommand { get; }
public ICommand SaveViewModelCommand { get; }
public ICommand ResetViewModelCommand { get; }
public ThemeMode ThemeMode
{
get => GetProperty(ThemeMode.UseSystemSetting);
set => SetProperty(value, SetTheme);
}
public AccentColors AccentColors
{
get => GetProperty(AccentColors.Green);
set => SetProperty(value, SetAccentColor);
}
private static void SetAccentColor(AccentColors accentColors) => Current.AccentColor = accentColors.ToWindowsColor();
public int WindowWidth
{
get => GetProperty(1200);
set => SetProperty(value);
}
public int WindowHeight
{
get => GetProperty(600);
set => SetProperty(value);
}
public bool IsPaneOpen
{
get => GetProperty(true);
set => SetProperty(value);
}
private static void SetTheme(ThemeMode themeMode) => Current.ApplicationTheme = themeMode.ToApplicationTheme();
private void ShowDialog()
{
var dialog = new ContentDialogExample {Message = WelcomeMessage};
dialog.ShowAsync().Await(completedCallback: x => WelcomeMessage = x.ToString());
}
private void BooleanValue_OnChanged(bool obj)
{
VisibilityEnumTest = obj ? Visibility.Visible : Visibility.Collapsed;
}
private void SaveViewModel()
{
var contents = _serializer.Serialize(Values);
if (Path == null)
{
var saveFileDialog = new SaveFileDialog {AddExtension = true, DefaultExt = "*.yaml"};
var result = saveFileDialog.ShowDialog(Application.Current.MainWindow?.Owner);
if (result != true) return;
Path = saveFileDialog.FileName;
}
File.WriteAllText(Path, contents);
}
protected override IEnumerable<(string key, object? value)> GetViewModelItems()
{
var openFileDialog = new OpenFileDialog {AddExtension = true, DefaultExt = "*.yaml"};
var result = openFileDialog.ShowDialog(Application.Current.MainWindow?.Owner);
if (result != true) yield break;
var contents = File.ReadAllText(Path = openFileDialog.FileName);
var obj = _deserializer.Deserialize<Dictionary<string, object>>(contents);
foreach (var (key, value) in obj)
{
yield return (key, DeserializationExtension.Convert(value, ObjectAccessor[key].GetType()));
}
}
}
}

View File

@ -1,26 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net5.0-windows10.0.18362.0</TargetFramework>
<UseWPF>true</UseWPF>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="FastMember" Version="1.5.0" />
<PackageReference Include="ModernWpfUI" Version="0.9.3-preview.201204.0" />
<PackageReference Include="Prism.DryIoc" Version="8.0.0.1909" />
<PackageReference Include="Prism.Wpf" Version="8.0.0.1909" />
<PackageReference Include="YamlDotNet" Version="9.1.0" />
<PackageReference Include="System.Drawing.Common" Version="5.0.0" />
<PackageReference Include="MahApps.Metro.IconPacks.FontAwesome" Version="4.8.0" />
<PackageReference Include="Microsoft.Windows.CsWinRT" Version="1.1.0" />
<PackageReference Include="Microsoft.Windows.SDK.Contracts" Version="10.0.18362.2005" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Controls\Controls.csproj" />
</ItemGroup>
</Project>

View File

@ -1,67 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using FastMember;
using Prism.Mvvm;
namespace ModernWpfPlayground.MvvmStuff
{
public abstract class BaseViewModel : BindableBase
{
protected BaseViewModel()
{
ObjectAccessor = ObjectAccessor.Create(this);
}
private readonly Dictionary<string, object?> _values = new Dictionary<string, object?>();
protected readonly ObjectAccessor ObjectAccessor;
private readonly Dictionary<string, object?> _defaultValues = new Dictionary<string, object?>();
protected IReadOnlyDictionary<string, object?> Values => _values;
protected bool SetProperty<T>(T value, Action<T>? onChanged = null,
[CallerMemberName] string? propertyName = null)
{
if (propertyName == null) throw new ArgumentNullException(nameof(propertyName));
if (_values.TryGetValue(propertyName, out var obj) && Equals(value, obj)) return false;
_values[propertyName] = value;
RaisePropertyChanged(propertyName);
onChanged?.Invoke(value);
return true;
}
protected T GetProperty<T>(T defaultValue = default, [CallerMemberName] string? propertyName = null)
{
if (propertyName == null) throw new ArgumentNullException(nameof(propertyName));
if (Values.TryGetValue(propertyName, out var obj))
{
return (T) obj!;
}
_defaultValues[propertyName] =defaultValue;
return defaultValue;
}
protected void ResetViewModel()
{
foreach (var (key, value) in _values.ToArray())
{
if (_defaultValues.TryGetValue(key, out var defaultValue) && Equals(value, defaultValue)) continue;
ObjectAccessor[key] = defaultValue;
}
}
protected abstract IEnumerable<(string key, object? value)> GetViewModelItems();
protected void LoadViewModel()
{
ResetViewModel();
foreach (var (key, value) in GetViewModelItems())
{
ObjectAccessor[key] = value;
}
}
}
}

View File

@ -1,20 +0,0 @@
using System;
using System.Globalization;
namespace ModernWpfPlayground.MvvmStuff
{
public static class DeserializationExtension
{
public static object? Convert(object? value, Type propertyType)
{
if (value is null) return Activator.CreateInstance(propertyType);
if (propertyType.IsEnum && value is string s)
{
return Enum.Parse(propertyType, s);
}
return System.Convert.ChangeType(value, propertyType, CultureInfo.InvariantCulture);
}
}
}

View File

@ -1,30 +0,0 @@
using System;
using System.Windows.Media;
namespace ModernWpfPlayground.Types
{
public enum AccentColors
{
Green,
Yellow,
Blue,
Purple,
Red
}
public static class AccentColorExtension
{
public static Color ToWindowsColor(this AccentColors accentColor)
{
return accentColor switch
{
AccentColors.Green => Color.FromRgb(0, 86, 76),
AccentColors.Yellow => Color.FromRgb(164, 144, 0),
AccentColors.Blue => Color.FromRgb(0, 120, 215),
AccentColors.Purple => Color.FromRgb(104, 33, 122),
AccentColors.Red => Color.FromRgb(183, 71, 42),
_ => throw new ArgumentOutOfRangeException(nameof(accentColor), accentColor, null)
};
}
}
}

View File

@ -1,16 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0-windows10.0.18362.0</TargetFramework>
<UseWPF>true</UseWPF>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Windows.CsWinRT" Version="1.1.0" />
<PackageReference Include="ModernWpfUI" Version="0.9.3-preview.201204.0" />
<PackageReference Include="System.Drawing.Common" Version="5.0.0" />
<PackageReference Include="Microsoft.Windows.SDK.Contracts" Version="10.0.18362.2005" />
</ItemGroup>
</Project>

View File

@ -1,9 +0,0 @@
using System;
using System.Runtime.InteropServices;
using System.Windows.Markup;
[assembly: ComVisible(false)]
[assembly: CLSCompliant(true)]
[assembly: XmlnsDefinition("http://wpflib.de/", "Controls")]

View File

@ -1,42 +0,0 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Windows.Data;
namespace Controls
{
/// <summary>
/// Converts enums to a List with KeyValuePairs.
/// </summary>
public class EnumToKeyValueListConverter : IValueConverter
{
object IValueConverter.Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (!(value is Enum)) return Binding.DoNothing;
return (from object enumValue in Enum.GetValues(value.GetType())
select new KeyValuePair<string, object>(GetDescription(enumValue), enumValue)).ToList();
}
/// <summary>
/// Returns the content of a description attribute of an enum.
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
private static string GetDescription(object value)
{
if (!(value is Enum enumValue)) return string.Empty;
var descriptionAttribute = enumValue.GetType()
.GetField(enumValue.ToString())?.GetCustomAttributes(false)
.OfType<DescriptionAttribute>().FirstOrDefault();
return descriptionAttribute?.Description ?? value.ToString() ?? string.Empty;
}
object IValueConverter.ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return Binding.DoNothing;
}
}
}

View File

@ -1,43 +0,0 @@
using System.Windows;
using System.Windows.Controls;
using System.Windows.Markup;
namespace Controls
{
/// <summary>
/// Interaction logic for <see cref="MagicSymbolControl"/>
/// </summary>
[ContentProperty(nameof(Symbol))]
public class MagicSymbolControl : ContentControl
{
/// <summary>
/// Dependency property for <see cref="Symbol"/> property
/// </summary>
public static readonly DependencyProperty SymbolProperty = DependencyProperty.Register(nameof(Symbol), typeof(object), typeof(MagicSymbolControl), new PropertyMetadata(default, PropertyChangedCallback));
private static void PropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is MagicSymbolControl magic)
{
magic.Content = MagicSymbolConverter.ConvertToFrameworkElement(e.NewValue);
}
}
/// <summary>
/// Symbol to show
/// </summary>
public object Symbol
{
get => GetValue(SymbolProperty);
set => SetValue(SymbolProperty, value);
}
/// <summary>
/// Creates a new instance of <see cref="MagicSymbolControl"/>
/// </summary>
public MagicSymbolControl()
{
Focusable = false;
}
}
}

View File

@ -1,152 +0,0 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Media;
namespace Controls
{
/// <summary>
/// Magically converts a text to
/// </summary>
[ValueConversion(typeof(string), typeof(FrameworkElement))]
public class MagicSymbolConverter : IValueConverter
{
private const string NoParseKeyword = "noParse:";
private const string PathKeyword = "path:";
private const string DynResKeyword = "dynRes:";
/// <inheritdoc />
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return ConvertToFrameworkElement(value);
}
/// <summary>
/// Convert string to Framework element.
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
public static object ConvertToFrameworkElement(object value)
{
var data = value as string;
if (string.IsNullOrWhiteSpace(data)) return value; //maybe not a string. eventually something else.
if (data.StartsWith(NoParseKeyword, StringComparison.Ordinal)) return data.Substring(NoParseKeyword.Length);
if (data.StartsWith(PathKeyword, StringComparison.Ordinal))
{
var path = data.Substring(PathKeyword.Length);
var icon = ObjectImageConverter.GetIcon(Geometry.Parse(path), Brushes.Black);
return new Image
{
Source = icon,
HorizontalAlignment = HorizontalAlignment.Right,
VerticalAlignment = VerticalAlignment.Center,
Height = 16
};
}
if (data.StartsWith(DynResKeyword, StringComparison.Ordinal))
{
var resourceKey = data.Substring(DynResKeyword.Length);
//get icon from resource dictionary
return new Image
{
Source = Application.Current.FindResource(resourceKey) as ImageSource,
HorizontalAlignment = HorizontalAlignment.Right,
VerticalAlignment = VerticalAlignment.Center,
Height = 16
};
}
var textComponents = ParseText(data);
return CreateTextBlock(textComponents);
}
private static TextBlock CreateTextBlock(IEnumerable<TextComponent> textComponents)
{
//create text block
var block = new TextBlock();
foreach (var tc in textComponents)
{
var run = new Run(tc.Text) {FontFamily = new FontFamily("Palatino Linotype"), FontSize = 16};
switch (tc.Style)
{
case BaselineAlignment.Subscript:
run.BaselineAlignment = BaselineAlignment.Subscript;
run.FontSize = 12;
break;
case BaselineAlignment.Superscript:
run.BaselineAlignment = BaselineAlignment.Superscript;
run.FontSize = 12;
break;
}
block.Inlines.Add(run);
}
block.HorizontalAlignment = HorizontalAlignment.Right;
block.VerticalAlignment = VerticalAlignment.Center;
return block;
}
private static IEnumerable<TextComponent> ParseText(string data)
{
//parse text
var textComponents = new List<TextComponent>();
var alignment = BaselineAlignment.Baseline;
var snippet = new StringBuilder();
foreach (var c in data)
{
switch (c)
{
case '~':
if (snippet.Length > 0)
{
var comp = new TextComponent(snippet.ToString(), alignment);
textComponents.Add(comp);
snippet.Clear();
}
alignment = alignment == BaselineAlignment.Subscript
? BaselineAlignment.Baseline
: BaselineAlignment.Subscript;
break;
case '^':
if (snippet.Length > 0)
{
var comp = new TextComponent(snippet.ToString(), alignment);
textComponents.Add(comp);
snippet.Clear();
}
alignment = alignment == BaselineAlignment.Superscript
? BaselineAlignment.Baseline
: BaselineAlignment.Superscript;
break;
default:
snippet.Append(c);
break;
}
}
if (snippet.Length > 0)
{
var comp = new TextComponent(snippet.ToString(), alignment);
textComponents.Add(comp);
snippet.Clear();
}
return textComponents;
}
/// <inheritdoc />
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
=> Binding.DoNothing;
}
}

View File

@ -1,39 +0,0 @@
using System;
using System.Windows;
using System.Windows.Controls;
namespace Controls
{
/// <summary>
/// Selects the right template on base of value-type.
/// </summary>
public class PropertyDataTemplateSelector : DataTemplateSelector
{
/// <summary>
/// Default data template. (currently Textbox)
/// </summary>
public DataTemplate? DefaultDataTemplate { private get; set; }
/// <summary>
/// Data template for boolean. (currently Checkbox)
/// </summary>
public DataTemplate? BooleanDataTemplate { private get; set; }
/// <summary>
/// Data template for enums. (currently Combobox)
/// </summary>
public DataTemplate? EnumComboBoxDataTemplate { private get; set; }
/// <inheritdoc />
public override DataTemplate? SelectTemplate(object item, DependencyObject container)
{
return item switch
{
bool _ => BooleanDataTemplate,
Enum _ => EnumComboBoxDataTemplate,
UIElement _ => null,
_ => DefaultDataTemplate
};
}
}
}

View File

@ -1,181 +0,0 @@
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Markup;
namespace Controls
{
/// <inheritdoc cref="ContentControl" />
/// <summary>
/// Interaction logic for PropertyPresenter.xaml
/// </summary>
[ContentProperty(nameof(Value))]
public sealed partial class PropertyPresenter
{
/// <summary>
/// Button alignment property.
/// </summary>
public static readonly DependencyProperty ButtonAlignmentProperty = DependencyProperty.Register(nameof(ButtonAlignment), typeof(Dock), typeof(PropertyPresenter), new PropertyMetadata(Dock.Right));
/// <summary>
/// Content of the command property.
/// </summary>
public static readonly DependencyProperty CommandContentProperty = DependencyProperty.Register(nameof(CommandContent), typeof(object), typeof(PropertyPresenter), new PropertyMetadata("..."));
/// <summary>
/// Command Parameter property
/// </summary>
public static readonly DependencyProperty CommandParameterProperty = DependencyProperty.Register(nameof(CommandParameter), typeof(object), typeof(PropertyPresenter), new PropertyMetadata(default(object)));
/// <summary>
/// Command property
/// </summary>
public static readonly DependencyProperty CommandProperty = DependencyProperty.Register(nameof(Command), typeof(ICommand), typeof(PropertyPresenter), new PropertyMetadata(default(ICommand)));
/// <summary>
/// is checked property.
/// </summary>
public static readonly DependencyProperty IsCheckedProperty = DependencyProperty.Register(nameof(IsChecked), typeof(bool?), typeof(PropertyPresenter), new FrameworkPropertyMetadata(default(bool?), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
/// <summary>
/// Is readonly property
/// </summary>
public static readonly DependencyProperty IsReadOnlyProperty = DependencyProperty.Register(nameof(IsReadOnly), typeof(bool), typeof(PropertyPresenter), new PropertyMetadata(default(bool)));
/// <summary>
/// Label property
/// </summary>
public static readonly DependencyProperty LabelProperty = DependencyProperty.Register(nameof(Label), typeof(string), typeof(PropertyPresenter), new PropertyMetadata(default(string)));
/// <summary>
/// label width property.
/// </summary>
public static readonly DependencyProperty LabelWidthProperty = DependencyProperty.Register(nameof(LabelWidth), typeof(double), typeof(PropertyPresenter), new PropertyMetadata(150.0));
/// <summary>
/// Symbol Property
/// </summary>
public static readonly DependencyProperty SymbolProperty = DependencyProperty.Register(nameof(Symbol), typeof(object), typeof(PropertyPresenter), new PropertyMetadata(default(object)));
/// <summary>
/// Value Property
/// </summary>
public static readonly DependencyProperty ValueProperty = DependencyProperty.Register(nameof(Value), typeof(object), typeof(PropertyPresenter), new FrameworkPropertyMetadata(default(string), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
/// <summary>
/// Watermark Property
/// </summary>
public static readonly DependencyProperty WatermarkProperty = DependencyProperty.Register(nameof(Watermark), typeof(string), typeof(PropertyPresenter), new FrameworkPropertyMetadata(default(string)));
/// <inheritdoc />
public PropertyPresenter()
{
InitializeComponent();
}
/// <summary>
/// Button alignment.
/// </summary>
public Dock ButtonAlignment
{
get => (Dock)GetValue(ButtonAlignmentProperty);
set => SetValue(ButtonAlignmentProperty, value);
}
/// <summary>
/// Command.
/// </summary>
public ICommand? Command
{
get => (ICommand)GetValue(CommandProperty);
set => SetValue(CommandProperty, value);
}
/// <summary>
/// Command content.
/// </summary>
public object? CommandContent
{
get => GetValue(CommandContentProperty);
set => SetValue(CommandContentProperty, value);
}
/// <summary>
/// Command parameter.
/// </summary>
public object? CommandParameter
{
get => GetValue(CommandParameterProperty);
set => SetValue(CommandParameterProperty, value);
}
/// <summary>
/// IsChecked.
/// </summary>
public bool? IsChecked
{
get => (bool?)GetValue(IsCheckedProperty);
set => SetValue(IsCheckedProperty, value);
}
/// <summary>
/// IsReadOnly
/// </summary>
public bool IsReadOnly
{
get => (bool)GetValue(IsReadOnlyProperty);
set => SetValue(IsReadOnlyProperty, value);
}
/// <summary>
/// Label.
/// </summary>
public string? Label
{
get => (string)GetValue(LabelProperty);
set => SetValue(LabelProperty, value);
}
/// <summary>
/// Label width.
/// </summary>
public double LabelWidth
{
get => (double)GetValue(LabelWidthProperty);
set => SetValue(LabelWidthProperty, value);
}
/// <summary>
/// Symbol.
/// </summary>
public object? Symbol
{
get => GetValue(SymbolProperty);
set => SetValue(SymbolProperty, value);
}
/// <summary>
/// Value.
/// </summary>
public object? Value
{
get => GetValue(ValueProperty);
set => SetValue(ValueProperty, value);
}
/// <summary>
/// Watermark.
/// </summary>
public string Watermark
{
get => (string)GetValue(WatermarkProperty);
set => SetValue(WatermarkProperty, value);
}
/// <inheritdoc />
public override string ToString()
{
return $"{base.ToString()} {Value}";
}
}
}

View File

@ -1,165 +0,0 @@
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace Controls
{
/// <inheritdoc />
/// <summary>
/// Represents a TextBox that can update the binding on enter.
/// </summary>
public class TextBoxEx : TextBox
{
/// <summary>
/// Identifies the <see cref="MoveFocusOnEnter"/> dependency property.
/// </summary>
public static readonly DependencyProperty MoveFocusOnEnterProperty =
DependencyProperty.Register(
"MoveFocusOnEnter", typeof(bool), typeof(TextBoxEx), new UIPropertyMetadata(true));
/// <summary>
/// Identifies the <see cref="UpdateBindingOnEnter"/> dependency property.
/// </summary>
public static readonly DependencyProperty UpdateBindingOnEnterProperty =
DependencyProperty.Register(
"UpdateBindingOnEnter", typeof(bool), typeof(TextBoxEx), new UIPropertyMetadata(true));
/// <summary>
/// Identifies the <see cref="ScrollToHomeOnFocus"/> dependency property.
/// </summary>
public static readonly DependencyProperty ScrollToHomeOnFocusProperty =
DependencyProperty.Register("ScrollToHomeOnFocus", typeof(bool), typeof(TextBoxEx), new PropertyMetadata(true));
/// <summary>
/// Identifies the <see cref="SelectAllOnFocus"/> dependency property.
/// </summary>
public static readonly DependencyProperty SelectAllOnFocusProperty =
DependencyProperty.Register("SelectAllOnFocus", typeof(bool), typeof(TextBoxEx), new PropertyMetadata(true));
/// <inheritdoc />
/// <summary>
/// Initializes a new instance of the <see cref="T:Controls.TextBoxEx" /> class.
/// </summary>
public TextBoxEx()
{
GotKeyboardFocus += HandleGotKeyboardFocus;
}
/// <summary>
/// Gets or sets a value indicating whether to select all on focus.
/// </summary>
/// <value>
/// <c>true</c> if all should be selected; otherwise, <c>false</c>.
/// </value>
public bool SelectAllOnFocus
{
get => (bool)GetValue(SelectAllOnFocusProperty);
set => SetValue(SelectAllOnFocusProperty, value);
}
/// <summary>
/// Gets or sets a value indicating whether to scroll to home on focus.
/// </summary>
/// <value>
/// <c>true</c> if scroll is enabled; otherwise, <c>false</c>.
/// </value>
public bool ScrollToHomeOnFocus
{
get => (bool)GetValue(ScrollToHomeOnFocusProperty);
set => SetValue(ScrollToHomeOnFocusProperty, value);
}
/// <summary>
/// Gets or sets a value indicating whether MoveFocusOnEnter.
/// </summary>
public bool MoveFocusOnEnter
{
get => (bool)GetValue(MoveFocusOnEnterProperty);
set => SetValue(MoveFocusOnEnterProperty, value);
}
/// <summary>
/// Gets or sets a value indicating whether UpdateBindingOnEnter.
/// </summary>
public bool UpdateBindingOnEnter
{
get => (bool)GetValue(UpdateBindingOnEnterProperty);
set => SetValue(UpdateBindingOnEnterProperty, value);
}
/// <inheritdoc />
protected override void OnPreviewKeyDown(KeyEventArgs e)
{
base.OnPreviewKeyDown(e);
switch (e.Key)
{
case Key.Enter:
if (!AcceptsReturn)
{
if (UpdateBindingOnEnter)
{
// get the binding to the Text property
var bindingExpression = GetBindingExpression(TextProperty);
// update the source (do not update the target)
bindingExpression?.UpdateSource();
}
if (MoveFocusOnEnter)
{
// Move focus to next element
// http://madprops.org/blog/enter-to-tab-in-wpf/
if (e.OriginalSource is UIElement uiElement)
{
var shift = (Keyboard.Modifiers & ModifierKeys.Shift) == ModifierKeys.Shift;
uiElement.MoveFocus(new TraversalRequest(shift ? FocusNavigationDirection.Previous : FocusNavigationDirection.Next));
}
}
e.Handled = true;
}
break;
case Key.Escape:
Undo();
SelectAll();
e.Handled = true;
break;
}
}
/// <inheritdoc />
protected override void OnPreviewMouseLeftButtonDown(MouseButtonEventArgs e)
{
base.OnPreviewMouseLeftButtonDown(e);
if (!IsKeyboardFocusWithin)
{
SelectAll();
Focus();
e.Handled = true;
}
}
/// <summary>
/// Handles the got keyboard focus event.
/// </summary>
/// <param name="sender">The sender.</param>
/// <param name="e">The <see cref="KeyboardFocusChangedEventArgs" /> instance containing the event data.</param>
private void HandleGotKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
{
if (SelectAllOnFocus)
{
SelectAll();
}
if (ScrollToHomeOnFocus)
{
ScrollToHome();
}
e.Handled = true;
}
}
}

View File

@ -1,31 +0,0 @@
using System.Windows;
namespace Controls
{
/// <summary>
/// A component of the symbol
/// </summary>
public readonly struct TextComponent
{
/// <summary>
/// Constructor
/// </summary>
/// <param name="text"></param>
/// <param name="style"></param>
public TextComponent(string text, BaselineAlignment style = BaselineAlignment.Baseline)
{
Text = text;
Style = style;
}
/// <summary>
/// Text of the symbol component
/// </summary>
public readonly string Text;
/// <summary>
/// Style of the symbol component
/// </summary>
public readonly BaselineAlignment Style;
}
}

View File

@ -1,100 +0,0 @@
using System;
using System.Drawing;
using System.Globalization;
using System.Net;
using System.Windows;
using System.Windows.Data;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using Brush = System.Windows.Media.Brush;
using Brushes = System.Windows.Media.Brushes;
using FontFamily = System.Windows.Media.FontFamily;
using Point = System.Windows.Point;
namespace Controls
{
/// <summary>
/// Makes an Bitmap from every Imageformat.
/// </summary>
public sealed class ObjectImageConverter : IValueConverter
{
object IValueConverter.Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
const string dynResPrefix = "dynRes:";
if (value is Bitmap bitmap)
{
return System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(bitmap.GetHbitmap(), IntPtr.Zero, Int32Rect.Empty, BitmapSizeOptions.FromWidthAndHeight(16, 16));
}
if (!(value is string strValue)) return Binding.DoNothing;
if (strValue.StartsWith(dynResPrefix, StringComparison.Ordinal))
{
var resource = Application.Current.TryFindResource(strValue.Replace(dynResPrefix, string.Empty , StringComparison.InvariantCulture));
return resource is ImageSource source ? source : Binding.DoNothing;
}
if (strValue.StartsWith("text:", StringComparison.Ordinal))
{
var parts = strValue.Split(':');
return parts.Length == 3 ? DrawText(WebUtility.HtmlDecode(parts[2]), parts[1], Brushes.Black) : Binding.DoNothing;
}
return GetIcon(Geometry.Parse(strValue), null);
}
object IValueConverter.ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) =>
Binding.DoNothing;
/// <summary>
/// Get icon a ImageSource from geometry.
/// </summary>
/// <param name="geometry">geometry</param>
/// <param name="brush">color of the icon</param>
/// <returns></returns>
public static ImageSource GetIcon(Geometry geometry, Brush? brush)
{
if (brush == null)
brush = Brushes.Black;
var drawing = new GeometryDrawing(brush, null, geometry);
return new DrawingImage(drawing);
}
/// <summary>
/// Draw text as ImageSource from string.
/// </summary>
/// <param name="text">the text</param>
/// <param name="strFontFamily">font of the string</param>
/// <param name="brush">color of the string</param>
/// <returns></returns>
public static ImageSource DrawText(string text, string strFontFamily, Brush brush)
{
var fontFamily = new FontFamily(strFontFamily);
var formattedText = new FormattedText(text,
CultureInfo.GetCultureInfo("en-us"),
FlowDirection.LeftToRight,
new Typeface(
fontFamily,
FontStyles.Normal,
FontWeights.Normal,
FontStretches.Normal),
64, brush, 1.5);
var geometry = formattedText.BuildGeometry(new Point(0, 0));
return GetIcon(geometry, null);
}
}
/// <summary>
/// Invert boolean converter
/// </summary>
[ValueConversion(typeof(bool), typeof(bool))]
public class InverseBooleanConverter : IValueConverter
{
/// <inheritdoc />
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) =>
value is bool b ? !b : throw new InvalidOperationException("The target must be a boolean");
/// <inheritdoc />
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) => Binding.DoNothing;
}
}

View File

@ -1,11 +1,13 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.30002.166
# Visual Studio Version 17
VisualStudioVersion = 17.0.31912.275
MinimumVisualStudioVersion = 15.0.26124.0
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ModernWpfPlayground", "App\ModernWpfPlayground.csproj", "{E090F4BD-69CD-4D8B-A3A2-620DCE22AE29}"
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{216EA1DF-5F39-4D23-9E1F-887785D1FF6C}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Controls", "Controls\Controls.csproj", "{3884FD41-5A46-4A67-A147-57C36F0059DC}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ModernWpfPlayground", "src\App\ModernWpfPlayground.csproj", "{D7364CD3-5150-4FA8-90D9-07093354D7A0}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Controls", "src\Controls\Controls.csproj", "{3AAA0309-C1C0-44C8-A53F-E3CE8556A4D4}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@ -13,18 +15,22 @@ Global
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{E090F4BD-69CD-4D8B-A3A2-620DCE22AE29}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E090F4BD-69CD-4D8B-A3A2-620DCE22AE29}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E090F4BD-69CD-4D8B-A3A2-620DCE22AE29}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E090F4BD-69CD-4D8B-A3A2-620DCE22AE29}.Release|Any CPU.Build.0 = Release|Any CPU
{3884FD41-5A46-4A67-A147-57C36F0059DC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3884FD41-5A46-4A67-A147-57C36F0059DC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3884FD41-5A46-4A67-A147-57C36F0059DC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3884FD41-5A46-4A67-A147-57C36F0059DC}.Release|Any CPU.Build.0 = Release|Any CPU
{D7364CD3-5150-4FA8-90D9-07093354D7A0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D7364CD3-5150-4FA8-90D9-07093354D7A0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D7364CD3-5150-4FA8-90D9-07093354D7A0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D7364CD3-5150-4FA8-90D9-07093354D7A0}.Release|Any CPU.Build.0 = Release|Any CPU
{3AAA0309-C1C0-44C8-A53F-E3CE8556A4D4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3AAA0309-C1C0-44C8-A53F-E3CE8556A4D4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3AAA0309-C1C0-44C8-A53F-E3CE8556A4D4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3AAA0309-C1C0-44C8-A53F-E3CE8556A4D4}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{D7364CD3-5150-4FA8-90D9-07093354D7A0} = {216EA1DF-5F39-4D23-9E1F-887785D1FF6C}
{3AAA0309-C1C0-44C8-A53F-E3CE8556A4D4} = {216EA1DF-5F39-4D23-9E1F-887785D1FF6C}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {21075E5E-39D9-4681-91A0-D9E7E0BDBB85}
EndGlobalSection

60
build.cake Normal file
View File

@ -0,0 +1,60 @@
#tool "dotnet:?package=GitVersion.Tool"
using System;
var target = Argument("target", "Publish");
var configuration = Argument("configuration", "Release");
var gitVersion = GitVersion(new GitVersionSettings());
//////////////////////////////////////////////////////////////////////
// TASKS
//////////////////////////////////////////////////////////////////////
Task("Clean")
.WithCriteria(c => HasArgument("rebuild"))
.Does(() =>
{
CleanDirectory($"./src/App/bin/{configuration}");
});
Task("Build")
.IsDependentOn("Clean")
.Does(() =>
{
DotNetBuild("./ModernWpfPlayground.sln", new DotNetBuildSettings
{
Configuration = configuration, ArgumentCustomization = c=> c.Append($"/p:Version={gitVersion.AssemblySemVer}")
});
});
Task("Publish")
.IsDependentOn("Build")
.Does(() =>
{
DotNetPublish("./src/App/ModernWpfPlayground.csproj", new DotNetPublishSettings{
Configuration = configuration,
EnableCompressionInSingleFile = true,
PublishReadyToRun = true,
PublishSingleFile = true,
Runtime = "win-x64",
SelfContained = true,
});
});
// Task("Test")
// .IsDependentOn("Build")
// .Does(() =>
// {
// DotNetCoreTest("./src/Example.sln", new DotNetCoreTestSettings
// {
// Configuration = configuration,
// NoBuild = true,
// });
// });
//////////////////////////////////////////////////////////////////////
// EXECUTION
//////////////////////////////////////////////////////////////////////
RunTarget(target);

View File

@ -1,9 +1,9 @@
<dryIoc:PrismApplication
<Application
x:Class="ModernWpfPlayground.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:dryIoc="http://prismlibrary.com/"
xmlns:ui="http://schemas.modernwpf.com/2019">
xmlns:ui="http://schemas.modernwpf.com/2019"
StartupUri="MainWindow.xaml">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
@ -22,4 +22,4 @@
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</dryIoc:PrismApplication>
</Application>

View File

@ -1,7 +1,6 @@
using System.Windows;
using ModernWpf;
using ModernWpfPlayground.Types;
using Prism.Ioc;
namespace ModernWpfPlayground
{
@ -16,17 +15,5 @@ namespace ModernWpfPlayground
base.OnStartup(e);
ThemeManager.Current.AccentColor = AccentColors.Green.ToWindowsColor();
}
protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
containerRegistry.Register<MainWindow>();
containerRegistry.Register<MainWindowViewModel>();
}
protected override Window CreateShell()
{
return Container.Resolve<MainWindow>();
}
}
}

10
src/App/AssemblyInfo.cs Normal file
View File

@ -0,0 +1,10 @@
using System.Windows;
[assembly: ThemeInfo(
ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
//(used if a resource is not found in the page,
// or application resource dictionaries)
ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
//(used if a resource is not found in the page,
// app, or any theme specific resource dictionaries)
)]

View File

@ -4,12 +4,11 @@ namespace ModernWpfPlayground
{
public partial class ContentDialogExample
{
public static readonly DependencyProperty MessageProperty = DependencyProperty.Register(
"Message", typeof(string), typeof(ContentDialogExample), new PropertyMetadata(default(string)));
public static readonly DependencyProperty MessageProperty = DependencyProperty.Register("Message", typeof(string), typeof(ContentDialogExample), new PropertyMetadata(default(string)));
public string Message
public string? Message
{
get => (string) GetValue(MessageProperty);
get => (string?)GetValue(MessageProperty);
set => SetValue(MessageProperty, value);
}

View File

@ -0,0 +1,23 @@
using System.Globalization;
using System.Windows.Data;
using System.Windows.Markup;
namespace ModernWpfPlayground;
public class DivideByHundredConverter : MarkupExtension, IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return (double)value / 100d;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
return this;
}
}

View File

@ -2,43 +2,36 @@
x:Class="ModernWpfPlayground.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="http://wpflib.de/"
xmlns:controls="clr-namespace:Controls;assembly=Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:iconPacks="http://metro.mahapps.com/winfx/xaml/iconpacks"
xmlns:local="clr-namespace:ModernWpfPlayground"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:prism="http://prismlibrary.com/"
xmlns:ui="http://schemas.modernwpf.com/2019"
x:Name="Window"
Title="{Binding Title}"
Width="{Binding WindowWidth, Mode=TwoWay}"
Height="{Binding WindowHeight, Mode=TwoWay}"
d:DataContext="{d:DesignInstance local:MainWindowViewModel}"
prism:ViewModelLocator.AutoWireViewModel="True"
ui:ThemeManager.IsThemeAware="True"
ui:TitleBar.ExtendViewIntoTitleBar="True"
ui:WindowHelper.UseModernWindowStyle="True"
TextOptions.TextFormattingMode="Display"
UseLayoutRounding="True"
mc:Ignorable="d">
<Window.Resources>
<local:PixelsToGridLengthConverter x:Key="PixelsToGridLength" />
</Window.Resources>
<Window.DataContext>
<local:MainWindowViewModel />
</Window.DataContext>
<Window.InputBindings>
<KeyBinding
Key="O"
Command="{Binding OpenViewModelCommand}"
Modifiers="Control" />
<KeyBinding
Key="S"
Command="{Binding SaveViewModelCommand}"
Modifiers="Control" />
<KeyBinding
Key="N"
Command="{Binding ResetViewModelCommand}"
Modifiers="Control" />
<KeyBinding Key="O" Modifiers="Control" />
<KeyBinding Key="S" Modifiers="Control" />
<KeyBinding Key="N" Modifiers="Control" />
</Window.InputBindings>
<Window.Resources>
<ResourceDictionary>
<ScaleTransform x:Key="ZoomSliderScaleTransform" ScaleX="{Binding Value, ElementName=ZoomSlider, Converter={local:DivideByHundredConverter}}" ScaleY="{Binding Value, ElementName=ZoomSlider, Converter={local:DivideByHundredConverter}}" />
</ResourceDictionary>
</Window.Resources>
<DockPanel>
<!-- TitleBar -->
<Grid
@ -57,9 +50,12 @@
</Grid.Style>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{Binding ElementName=Window, Path=(ui:TitleBar.SystemOverlayLeftInset), Converter={StaticResource PixelsToGridLength}}" />
<ColumnDefinition Width="{Binding ElementName=Window, Path=(ui:TitleBar.SystemOverlayLeftInset), Converter={local:PixelsToGridLengthConverter}}" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="{Binding ElementName=Window, Path=(ui:TitleBar.SystemOverlayRightInset), Converter={local:PixelsToGridLengthConverter}}" />
</Grid.ColumnDefinitions>
<Menu
Grid.Column="1"
@ -80,16 +76,6 @@
</Style>
</Menu.Resources>
<MenuItem Header="File">
<MenuItem
Command="{Binding ResetViewModelCommand}"
Header="New"
Icon="{iconPacks:FontAwesome Kind=FileRegular}"
InputGestureText="Ctrl+N" />
<MenuItem
Command="{Binding OpenViewModelCommand}"
Header="Open"
Icon="{iconPacks:FontAwesome Kind=FolderOpenRegular}"
InputGestureText="Ctrl+O" />
<MenuItem
Command="{Binding SaveViewModelCommand}"
Header="Save"
@ -121,12 +107,31 @@
VerticalAlignment="Center"
FontSize="13"
Text="{Binding ElementName=Window, Path=Title}" />
<Slider
x:Name="ZoomSlider"
Grid.Column="3"
Width="150"
AutoToolTipPlacement="TopLeft"
Interval="1"
IsSnapToTickEnabled="True"
Maximum="200"
Minimum="50"
TickFrequency="10"
TickPlacement="BottomRight"
WindowChrome.IsHitTestVisibleInChrome="True"
Value="100" />
<MenuItem
Grid.Column="4"
Click="MenuItem_OnClick"
Header="Reset"
WindowChrome.IsHitTestVisibleInChrome="True" />
</Grid>
<!-- Footer -->
<Grid
Height="24"
Background="{DynamicResource SystemControlBackgroundAccentBrush}"
DockPanel.Dock="Bottom">
DockPanel.Dock="Bottom"
LayoutTransform="{StaticResource ZoomSliderScaleTransform}">
<Grid.Resources>
<Style TargetType="TextBlock">
<Setter Property="Foreground" Value="White" />
@ -148,7 +153,7 @@
HorizontalAlignment="Right"
Text="BlaBlaBla" />
</Grid>
<Grid Row="1">
<Grid LayoutTransform="{StaticResource ZoomSliderScaleTransform}" Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="40" />
<ColumnDefinition />
@ -218,11 +223,9 @@
</Grid>
</ui:SplitView.Pane>
<!-- Content -->
<TabControl>
<TabControl LayoutTransform="{StaticResource ZoomSliderScaleTransform}">
<TabItem Header="Bolt">
<ScrollViewer ui:ScrollViewerHelper.AutoHideScrollBars="True">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
@ -234,10 +237,6 @@
Margin="5"
Orientation="Vertical"
Spacing="5">
<Button
HorizontalAlignment="Stretch"
Command="{Binding OpenViewModelCommand}"
Content="Open" />
<Button
HorizontalAlignment="Stretch"
Command="{Binding SaveViewModelCommand}"
@ -248,7 +247,6 @@
</TabItem>
<TabItem Header="General" IsSelected="True">
<ScrollViewer ui:ScrollViewerHelper.AutoHideScrollBars="True">
<ui:SimpleStackPanel Margin="5" Spacing="10">
<controls:PropertyPresenter Label="Theme Mode" Value="{Binding ThemeMode}" />
<controls:PropertyPresenter Label="Accent color" Value="{Binding AccentColors}" />
@ -262,10 +260,9 @@
Label="Hallo"
Symbol="x²"
Value="{Binding ValidationTest, UpdateSourceTrigger=PropertyChanged}" />
<controls:PropertyPresenter
Label="Good bye"
Symbol="x²"
Value="{Binding ValidationTest, UpdateSourceTrigger=PropertyChanged}" />
<controls:PropertyPresenter Label="Good bye" Symbol="x²">
<ui:NumberBox SpinButtonPlacementMode="Compact" Value="{Binding ValidationTest, UpdateSourceTrigger=PropertyChanged}" />
</controls:PropertyPresenter>
<Grid Margin="0,5">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />

View File

@ -0,0 +1,21 @@
using System.Windows;
using System.Windows.Controls.Primitives;
namespace ModernWpfPlayground
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow
{
public MainWindow()
{
InitializeComponent();
}
private void MenuItem_OnClick(object sender, RoutedEventArgs e)
{
ZoomSlider.Value = 100;
}
}
}

View File

@ -0,0 +1,102 @@
using System.Windows;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using ModernWpfPlayground.Types;
using static ModernWpf.ThemeManager;
namespace ModernWpfPlayground
{
// ReSharper disable once ClassNeverInstantiated.Global
public partial class MainWindowViewModel : ObservableObject
{
private const string AppName = "TaBEA 3.0.0";
[ObservableProperty]
private string? _path;
[ObservableProperty]
private string _title = AppName;
[ObservableProperty]
private bool _booleanValue = true;
[ObservableProperty]
private Visibility _visibilityEnumTest = Visibility.Visible;
[ObservableProperty]
private double _sliderTest = 100;
[ObservableProperty]
private double _validationTest;
[ObservableProperty]
private string? _welcomeMessage = "Shadow of the empire";
[ObservableProperty]
private ThemeMode _themeMode = ThemeMode.UseSystemSetting;
[ObservableProperty]
private AccentColors _accentColors = AccentColors.Green;
[ObservableProperty]
private int _windowWidth = 1200;
[ObservableProperty]
private int _windowHeight = 600;
[ObservableProperty]
private bool _isPaneOpen = true;
partial void OnBooleanValueChanged(bool value)
{
VisibilityEnumTest = value ? Visibility.Visible : Visibility.Collapsed;
}
partial void OnPathChanged(string? value)
{
Title = value != null ? $"{System.IO.Path.GetFileName(value)} - {AppName}" : AppName;
}
[RelayCommand]
private void ShowNotification() { }
[RelayCommand]
private void Close()
{
Application.Current.MainWindow?.Close();
}
partial void OnThemeModeChanged(ThemeMode value)
{
Current.ApplicationTheme = value.ToApplicationTheme();
}
partial void OnAccentColorsChanged(AccentColors value)
{
Current.AccentColor = value.ToWindowsColor();
}
[RelayCommand]
private async void ShowDialog()
{
var dialog = new ContentDialogExample { Message = WelcomeMessage };
var result = await dialog.ShowAsync();
WelcomeMessage = result.ToString();
}
[RelayCommand]
private void SaveViewModel()
{
// var contents = _serializer.Serialize(Values);
// if (Path is null)
// {
// var saveFileDialog = new SaveFileDialog {AddExtension = true, DefaultExt = "*.yaml"};
// var result = saveFileDialog.ShowDialog(Application.Current.MainWindow?.Owner);
// if (result != true) return;
// Path = saveFileDialog.FileName;
// }
//
// File.WriteAllText(Path, contents);
}
}
}

View File

@ -0,0 +1,40 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net7.0-windows</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<UseWPF>true</UseWPF>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Controls\Controls.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.1.0-preview1" />
<PackageReference Include="FastMember" Version="1.5.0" />
<PackageReference Include="Hardcodet.NotifyIcon.Wpf" Version="1.1.0" />
<PackageReference Include="ModernWpfUI" Version="0.9.7-preview.2" />
<PackageReference Include="YamlDotNet" Version="12.0.2" />
<PackageReference Include="MahApps.Metro.IconPacks.FontAwesome" Version="4.11.0" />
</ItemGroup>
<ItemGroup>
<ApplicationDefinition Update="App.xaml">
<XamlRuntime>$(DefaultXamlRuntime)</XamlRuntime>
<Generator>MSBuild:Compile</Generator>
</ApplicationDefinition>
</ItemGroup>
<ItemGroup>
<Page Update="ContentDialogExample.xaml">
<XamlRuntime>$(DefaultXamlRuntime)</XamlRuntime>
</Page>
<Page Update="MainWindow.xaml">
<XamlRuntime>$(DefaultXamlRuntime)</XamlRuntime>
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
</Project>

View File

@ -1,12 +1,14 @@
using System;
using System.Globalization;
using System.Globalization;
using System.Windows;
using System.Windows.Data;
using System.Windows.Markup;
namespace ModernWpfPlayground
{
public class PixelsToGridLengthConverter : IValueConverter
public class PixelsToGridLengthConverter : MarkupExtension, IValueConverter
{
private static PixelsToGridLengthConverter? _converter;
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return value is double d ? new GridLength(d) : new GridLength(1.0, GridUnitType.Auto);
@ -16,5 +18,7 @@ namespace ModernWpfPlayground
{
throw new NotImplementedException();
}
public override object ProvideValue(IServiceProvider serviceProvider) => _converter ??= new PixelsToGridLengthConverter();
}
}

View File

@ -0,0 +1,28 @@
using System.Windows.Media;
namespace ModernWpfPlayground.Types;
public enum AccentColors
{
Green,
Yellow,
Blue,
Purple,
Red
}
public static class AccentColorExtension
{
public static Color ToWindowsColor(this AccentColors accentColor)
{
return accentColor switch
{
AccentColors.Green => Color.FromRgb(0, 86, 76),
AccentColors.Yellow => Color.FromRgb(164, 144, 0),
AccentColors.Blue => Color.FromRgb(0, 120, 215),
AccentColors.Purple => Color.FromRgb(104, 33, 122),
AccentColors.Red => Color.FromRgb(183, 71, 42),
_ => throw new ArgumentOutOfRangeException(nameof(accentColor), accentColor, null)
};
}
}

View File

@ -1,14 +1,18 @@
using System;
using System.ComponentModel;
using System.ComponentModel;
using ModernWpf;
namespace ModernWpfPlayground.Types
{
public enum ThemeMode
{
[Description("Light")] Light,
[Description("Dark")] Dark,
[Description("Use system setting")] UseSystemSetting
[Description("Light")]
Light,
[Description("Dark")]
Dark,
[Description("Use system setting")]
UseSystemSetting
}
public static class ThemeModeExtension

View File

@ -0,0 +1,10 @@
using System.Windows;
[assembly: ThemeInfo(
ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
//(used if a resource is not found in the page,
// or application resource dictionaries)
ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
//(used if a resource is not found in the page,
// app, or any theme specific resource dictionaries)
)]

View File

@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0-windows</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<UseWPF>true</UseWPF>
</PropertyGroup>
<ItemGroup>
<Page Update="PropertyPresenter\PropertyPresenter.xaml">
<XamlRuntime>$(DefaultXamlRuntime)</XamlRuntime>
</Page>
</ItemGroup>
<ItemGroup>
<PackageReference Include="System.Drawing.Common" Version="7.0.0" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,42 @@
using System.ComponentModel;
using System.Globalization;
using System.Windows.Data;
using System.Windows.Markup;
namespace Controls;
/// <summary>
/// Converts enums to a List with KeyValuePairs.
/// </summary>
public class EnumToItemSourceConverter : MarkupExtension, IValueConverter
{
private static EnumToItemSourceConverter? _converter;
object IValueConverter.Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is not Enum)
return Binding.DoNothing;
return (from object enumValue in Enum.GetValues(value.GetType()) select new KeyValuePair<string, object>(GetDescription(enumValue), enumValue)).ToList();
}
/// <summary>
/// Returns the content of a description attribute of an enum.
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
private static string GetDescription(object value)
{
if (value is not Enum enumValue)
return string.Empty;
var descriptionAttribute = enumValue.GetType().GetField(enumValue.ToString())?.GetCustomAttributes(false).OfType<DescriptionAttribute>().FirstOrDefault();
return descriptionAttribute?.Description ?? value.ToString() ?? string.Empty;
}
object IValueConverter.ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return Binding.DoNothing;
}
public override object ProvideValue(IServiceProvider serviceProvider) => _converter ??= new EnumToItemSourceConverter();
}

View File

@ -0,0 +1,47 @@
using System.Windows;
using System.Windows.Controls;
using System.Windows.Markup;
namespace Controls;
/// <summary>
/// Interaction logic for <see cref="MagicSymbolControl"/>
/// </summary>
[ContentProperty(nameof(Symbol))]
public class MagicSymbolControl : ContentControl
{
/// <summary>
/// Dependency property for <see cref="Symbol"/> property
/// </summary>
public static readonly DependencyProperty SymbolProperty = DependencyProperty.Register(
nameof(Symbol),
typeof(object),
typeof(MagicSymbolControl),
new PropertyMetadata(default, PropertyChangedCallback)
);
private static void PropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is MagicSymbolControl magic)
{
magic.Content = MagicSymbolConverter.ConvertToFrameworkElement(e.NewValue);
}
}
/// <summary>
/// Symbol to show
/// </summary>
public object Symbol
{
get => GetValue(SymbolProperty);
set => SetValue(SymbolProperty, value);
}
/// <summary>
/// Creates a new instance of <see cref="MagicSymbolControl"/>
/// </summary>
public MagicSymbolControl()
{
Focusable = false;
}
}

View File

@ -0,0 +1,146 @@
using System.Globalization;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Media;
namespace Controls;
/// <summary>
/// Magically converts a text to
/// </summary>
[ValueConversion(typeof(string), typeof(FrameworkElement))]
public class MagicSymbolConverter : IValueConverter
{
private const string NoParseKeyword = "noParse:";
private const string PathKeyword = "path:";
private const string DynResKeyword = "dynRes:";
/// <inheritdoc />
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return ConvertToFrameworkElement(value);
}
/// <summary>
/// Convert string to Framework element.
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
public static object ConvertToFrameworkElement(object value)
{
var data = value as string;
if (string.IsNullOrWhiteSpace(data))
return value; //maybe not a string. eventually something else.
if (data.StartsWith(NoParseKeyword, StringComparison.Ordinal))
return data[NoParseKeyword.Length..];
if (data.StartsWith(PathKeyword, StringComparison.Ordinal))
{
var path = data[PathKeyword.Length..];
var icon = ObjectImageConverter.GetIcon(Geometry.Parse(path), Brushes.Black);
return new Image
{
Source = icon,
HorizontalAlignment = HorizontalAlignment.Right,
VerticalAlignment = VerticalAlignment.Center,
Height = 16
};
}
if (data.StartsWith(DynResKeyword, StringComparison.Ordinal))
{
var resourceKey = data[DynResKeyword.Length..];
//get icon from resource dictionary
return new Image
{
Source = Application.Current.FindResource(resourceKey) as ImageSource,
HorizontalAlignment = HorizontalAlignment.Right,
VerticalAlignment = VerticalAlignment.Center,
Height = 16
};
}
var textComponents = ParseText(data);
return CreateTextBlock(textComponents);
}
private static TextBlock CreateTextBlock(IEnumerable<TextComponent> textComponents)
{
//create text block
var block = new TextBlock();
foreach (var tc in textComponents)
{
var run = new Run(tc.Text) { FontFamily = new FontFamily("Palatino Linotype"), FontSize = 16 };
switch (tc.Style)
{
case BaselineAlignment.Subscript:
run.BaselineAlignment = BaselineAlignment.Subscript;
run.FontSize = 12;
break;
case BaselineAlignment.Superscript:
run.BaselineAlignment = BaselineAlignment.Superscript;
run.FontSize = 12;
break;
}
block.Inlines.Add(run);
}
block.HorizontalAlignment = HorizontalAlignment.Right;
block.VerticalAlignment = VerticalAlignment.Center;
return block;
}
private static IEnumerable<TextComponent> ParseText(string data)
{
//parse text
var textComponents = new List<TextComponent>();
var alignment = BaselineAlignment.Baseline;
var snippet = new StringBuilder();
foreach (var c in data)
{
switch (c)
{
case '~':
if (snippet.Length > 0)
{
var comp = new TextComponent(snippet.ToString(), alignment);
textComponents.Add(comp);
snippet.Clear();
}
alignment = alignment == BaselineAlignment.Subscript ? BaselineAlignment.Baseline : BaselineAlignment.Subscript;
break;
case '^':
if (snippet.Length > 0)
{
var comp = new TextComponent(snippet.ToString(), alignment);
textComponents.Add(comp);
snippet.Clear();
}
alignment = alignment == BaselineAlignment.Superscript ? BaselineAlignment.Baseline : BaselineAlignment.Superscript;
break;
default:
snippet.Append(c);
break;
}
}
if (snippet.Length > 0)
{
var comp = new TextComponent(snippet.ToString(), alignment);
textComponents.Add(comp);
snippet.Clear();
}
return textComponents;
}
/// <inheritdoc />
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) => Binding.DoNothing;
}

View File

@ -0,0 +1,37 @@
using System.Windows;
using System.Windows.Controls;
namespace Controls;
/// <summary>
/// Selects the right template on base of value-type.
/// </summary>
public class PropertyDataTemplateSelector : DataTemplateSelector
{
/// <summary>
/// Default data template. (currently Textbox)
/// </summary>
public DataTemplate? DefaultDataTemplate { private get; set; }
/// <summary>
/// Data template for boolean. (currently Checkbox)
/// </summary>
public DataTemplate? BooleanDataTemplate { private get; set; }
/// <summary>
/// Data template for enums. (currently Combobox)
/// </summary>
public DataTemplate? EnumComboBoxDataTemplate { private get; set; }
/// <inheritdoc />
public override DataTemplate? SelectTemplate(object item, DependencyObject container)
{
return item switch
{
bool _ => BooleanDataTemplate,
Enum _ => EnumComboBoxDataTemplate,
UIElement _ => null,
_ => DefaultDataTemplate
};
}
}

View File

@ -16,29 +16,37 @@
<ContentControl.Resources>
<DataTemplate x:Key="DefaultDataTemplate">
<Grid>
<controls:TextBoxEx
x:Name="InputTextBox"
Text="{Binding Value, ElementName=LayoutRoot}"
Validation.ErrorTemplate="{DynamicResource ValidationErrorTemplate}"
Validation.ValidationAdornerSiteFor="{Binding ElementName=LayoutRoot}">
<TextBox.Style>
<Style BasedOn="{StaticResource {x:Type TextBox}}" TargetType="{x:Type controls:TextBoxEx}">
<Setter Property="IsReadOnly" Value="True" />
<Setter Property="UpdateBindingOnEnter" Value="False" />
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding Command, ElementName=LayoutRoot}" Value="{x:Null}" />
<Condition Binding="{Binding IsReadOnly, ElementName=LayoutRoot}" Value="False" />
<Condition Binding="{Binding IsChecked, ElementName=LayoutRoot, TargetNullValue=True}" Value="True" />
</MultiDataTrigger.Conditions>
<Setter Property="IsReadOnly" Value="False" />
<Setter Property="UpdateBindingOnEnter" Value="True" />
</MultiDataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</controls:TextBoxEx>
<Grid>
<controls:TextBoxEx
x:Name="InputTextBox"
Text="{Binding Value, ElementName=LayoutRoot}"
Validation.ErrorTemplate="{DynamicResource ValidationErrorTemplate}"
Validation.ValidationAdornerSiteFor="{Binding ElementName=LayoutRoot}">
<TextBox.Style>
<Style BasedOn="{StaticResource {x:Type TextBox}}" TargetType="{x:Type controls:TextBoxEx}">
<Setter Property="IsReadOnly" Value="True" />
<Setter Property="UpdateBindingOnEnter" Value="False" />
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding Command, ElementName=LayoutRoot}" Value="{x:Null}" />
<Condition Binding="{Binding IsReadOnly, ElementName=LayoutRoot}" Value="False" />
<Condition Binding="{Binding IsChecked, ElementName=LayoutRoot, TargetNullValue=True}" Value="True" />
</MultiDataTrigger.Conditions>
<Setter Property="IsReadOnly" Value="False" />
<Setter Property="UpdateBindingOnEnter" Value="True" />
</MultiDataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</controls:TextBoxEx>
<controls:MagicSymbolControl
x:Name="PartSymbol"
Margin="5,0"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Symbol="{Binding Symbol, ElementName=LayoutRoot}" />
</Grid>
<TextBlock
Margin="6,0,0,0"
Padding="2,2,2,2"
@ -86,12 +94,11 @@
</CheckBox.Style>
</CheckBox>
</DataTemplate>
<controls:EnumToKeyValueListConverter x:Key="EnumToKeyValuePairConverter" />
<DataTemplate x:Key="EnumComboBoxDataTemplate">
<ComboBox
HorizontalAlignment="Stretch"
DisplayMemberPath="Key"
ItemsSource="{Binding Value, ElementName=LayoutRoot, Converter={StaticResource EnumToKeyValuePairConverter}}"
ItemsSource="{Binding Value, ElementName=LayoutRoot, Converter={controls:EnumToItemSourceConverter}}"
SelectedValue="{Binding Value, ElementName=LayoutRoot}"
SelectedValuePath="Value"
Validation.ErrorTemplate="{DynamicResource ValidationErrorTemplate}"
@ -153,13 +160,6 @@
VerticalAlignment="Center"
Focusable="False"
Text="{Binding Label, ElementName=LayoutRoot}" />
<controls:MagicSymbolControl
x:Name="PartSymbol"
Grid.Column="2"
Margin="5,0"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Symbol="{Binding Symbol, ElementName=LayoutRoot}" />
</Grid>
<DockPanel Grid.Column="1">
<Button

View File

@ -0,0 +1,200 @@
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Markup;
namespace Controls;
/// <inheritdoc cref="ContentControl" />
/// <summary>
/// Interaction logic for PropertyPresenter.xaml
/// </summary>
[ContentProperty(nameof(Value))]
public sealed partial class PropertyPresenter
{
/// <summary>
/// Button alignment property.
/// </summary>
public static readonly DependencyProperty ButtonAlignmentProperty = DependencyProperty.Register(nameof(ButtonAlignment), typeof(Dock), typeof(PropertyPresenter), new PropertyMetadata(Dock.Right));
/// <summary>
/// Content of the command property.
/// </summary>
public static readonly DependencyProperty CommandContentProperty = DependencyProperty.Register(nameof(CommandContent), typeof(object), typeof(PropertyPresenter), new PropertyMetadata("..."));
/// <summary>
/// Command Parameter property
/// </summary>
public static readonly DependencyProperty CommandParameterProperty = DependencyProperty.Register(
nameof(CommandParameter),
typeof(object),
typeof(PropertyPresenter),
new PropertyMetadata(default(object))
);
/// <summary>
/// Command property
/// </summary>
public static readonly DependencyProperty CommandProperty = DependencyProperty.Register(nameof(Command), typeof(ICommand), typeof(PropertyPresenter), new PropertyMetadata(default(ICommand)));
/// <summary>
/// is checked property.
/// </summary>
public static readonly DependencyProperty IsCheckedProperty = DependencyProperty.Register(
nameof(IsChecked),
typeof(bool?),
typeof(PropertyPresenter),
new FrameworkPropertyMetadata(default(bool?), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault)
);
/// <summary>
/// Is readonly property
/// </summary>
public static readonly DependencyProperty IsReadOnlyProperty = DependencyProperty.Register(nameof(IsReadOnly), typeof(bool), typeof(PropertyPresenter), new PropertyMetadata(default(bool)));
/// <summary>
/// Label property
/// </summary>
public static readonly DependencyProperty LabelProperty = DependencyProperty.Register(nameof(Label), typeof(string), typeof(PropertyPresenter), new PropertyMetadata(default(string)));
/// <summary>
/// label width property.
/// </summary>
public static readonly DependencyProperty LabelWidthProperty = DependencyProperty.Register(nameof(LabelWidth), typeof(double), typeof(PropertyPresenter), new PropertyMetadata(150.0));
/// <summary>
/// Symbol Property
/// </summary>
public static readonly DependencyProperty SymbolProperty = DependencyProperty.Register(nameof(Symbol), typeof(object), typeof(PropertyPresenter), new PropertyMetadata(default(object)));
/// <summary>
/// Value Property
/// </summary>
public static readonly DependencyProperty ValueProperty = DependencyProperty.Register(
nameof(Value),
typeof(object),
typeof(PropertyPresenter),
new FrameworkPropertyMetadata(default(string), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault)
);
/// <summary>
/// Watermark Property
/// </summary>
public static readonly DependencyProperty WatermarkProperty = DependencyProperty.Register(
nameof(Watermark),
typeof(string),
typeof(PropertyPresenter),
new FrameworkPropertyMetadata(default(string))
);
/// <inheritdoc />
public PropertyPresenter()
{
InitializeComponent();
}
/// <summary>
/// Button alignment.
/// </summary>
public Dock ButtonAlignment
{
get => (Dock)GetValue(ButtonAlignmentProperty);
set => SetValue(ButtonAlignmentProperty, value);
}
/// <summary>
/// Command.
/// </summary>
public ICommand? Command
{
get => (ICommand)GetValue(CommandProperty);
set => SetValue(CommandProperty, value);
}
/// <summary>
/// Command content.
/// </summary>
public object? CommandContent
{
get => GetValue(CommandContentProperty);
set => SetValue(CommandContentProperty, value);
}
/// <summary>
/// Command parameter.
/// </summary>
public object? CommandParameter
{
get => GetValue(CommandParameterProperty);
set => SetValue(CommandParameterProperty, value);
}
/// <summary>
/// IsChecked.
/// </summary>
public bool? IsChecked
{
get => (bool?)GetValue(IsCheckedProperty);
set => SetValue(IsCheckedProperty, value);
}
/// <summary>
/// IsReadOnly
/// </summary>
public bool IsReadOnly
{
get => (bool)GetValue(IsReadOnlyProperty);
set => SetValue(IsReadOnlyProperty, value);
}
/// <summary>
/// Label.
/// </summary>
public string? Label
{
get => (string)GetValue(LabelProperty);
set => SetValue(LabelProperty, value);
}
/// <summary>
/// Label width.
/// </summary>
public double LabelWidth
{
get => (double)GetValue(LabelWidthProperty);
set => SetValue(LabelWidthProperty, value);
}
/// <summary>
/// Symbol.
/// </summary>
public object? Symbol
{
get => GetValue(SymbolProperty);
set => SetValue(SymbolProperty, value);
}
/// <summary>
/// Value.
/// </summary>
public object? Value
{
get => GetValue(ValueProperty);
set => SetValue(ValueProperty, value);
}
/// <summary>
/// Watermark.
/// </summary>
public string Watermark
{
get => (string)GetValue(WatermarkProperty);
set => SetValue(WatermarkProperty, value);
}
/// <inheritdoc />
public override string ToString()
{
return $"{base.ToString()} {Value}";
}
}

View File

@ -0,0 +1,156 @@
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace Controls;
/// <inheritdoc />
/// <summary>
/// Represents a TextBox that can update the binding on enter.
/// </summary>
public class TextBoxEx : TextBox
{
/// <summary>
/// Identifies the <see cref="MoveFocusOnEnter"/> dependency property.
/// </summary>
public static readonly DependencyProperty MoveFocusOnEnterProperty = DependencyProperty.Register(nameof(MoveFocusOnEnter), typeof(bool), typeof(TextBoxEx), new UIPropertyMetadata(true));
/// <summary>
/// Identifies the <see cref="UpdateBindingOnEnter"/> dependency property.
/// </summary>
public static readonly DependencyProperty UpdateBindingOnEnterProperty = DependencyProperty.Register(nameof(UpdateBindingOnEnter), typeof(bool), typeof(TextBoxEx), new UIPropertyMetadata(true));
/// <summary>
/// Identifies the <see cref="ScrollToHomeOnFocus"/> dependency property.
/// </summary>
public static readonly DependencyProperty ScrollToHomeOnFocusProperty = DependencyProperty.Register(nameof(ScrollToHomeOnFocus), typeof(bool), typeof(TextBoxEx), new PropertyMetadata(true));
/// <summary>
/// Identifies the <see cref="SelectAllOnFocus"/> dependency property.
/// </summary>
public static readonly DependencyProperty SelectAllOnFocusProperty = DependencyProperty.Register(nameof(SelectAllOnFocus), typeof(bool), typeof(TextBoxEx), new PropertyMetadata(true));
/// <inheritdoc />
/// <summary>
/// Initializes a new instance of the <see cref="T:Controls.TextBoxEx" /> class.
/// </summary>
public TextBoxEx()
{
GotKeyboardFocus += HandleGotKeyboardFocus;
}
/// <summary>
/// Gets or sets a value indicating whether to select all on focus.
/// </summary>
/// <value>
/// <c>true</c> if all should be selected; otherwise, <c>false</c>.
/// </value>
public bool SelectAllOnFocus
{
get => (bool)GetValue(SelectAllOnFocusProperty);
set => SetValue(SelectAllOnFocusProperty, value);
}
/// <summary>
/// Gets or sets a value indicating whether to scroll to home on focus.
/// </summary>
/// <value>
/// <c>true</c> if scroll is enabled; otherwise, <c>false</c>.
/// </value>
public bool ScrollToHomeOnFocus
{
get => (bool)GetValue(ScrollToHomeOnFocusProperty);
set => SetValue(ScrollToHomeOnFocusProperty, value);
}
/// <summary>
/// Gets or sets a value indicating whether MoveFocusOnEnter.
/// </summary>
public bool MoveFocusOnEnter
{
get => (bool)GetValue(MoveFocusOnEnterProperty);
set => SetValue(MoveFocusOnEnterProperty, value);
}
/// <summary>
/// Gets or sets a value indicating whether UpdateBindingOnEnter.
/// </summary>
public bool UpdateBindingOnEnter
{
get => (bool)GetValue(UpdateBindingOnEnterProperty);
set => SetValue(UpdateBindingOnEnterProperty, value);
}
/// <inheritdoc />
protected override void OnPreviewKeyDown(KeyEventArgs e)
{
base.OnPreviewKeyDown(e);
switch (e.Key)
{
case Key.Enter:
if (!AcceptsReturn)
{
if (UpdateBindingOnEnter)
{
// get the binding to the Text property
var bindingExpression = GetBindingExpression(TextProperty);
// update the source (do not update the target)
bindingExpression?.UpdateSource();
}
if (MoveFocusOnEnter)
{
// Move focus to next element
// http://madprops.org/blog/enter-to-tab-in-wpf/
if (e.OriginalSource is UIElement uiElement)
{
var shift = (Keyboard.Modifiers & ModifierKeys.Shift) == ModifierKeys.Shift;
uiElement.MoveFocus(new TraversalRequest(shift ? FocusNavigationDirection.Previous : FocusNavigationDirection.Next));
}
}
e.Handled = true;
}
break;
case Key.Escape:
Undo();
SelectAll();
e.Handled = true;
break;
}
}
/// <inheritdoc />
protected override void OnPreviewMouseLeftButtonDown(MouseButtonEventArgs e)
{
base.OnPreviewMouseLeftButtonDown(e);
if (!IsKeyboardFocusWithin)
{
SelectAll();
Focus();
e.Handled = true;
}
}
/// <summary>
/// Handles the got keyboard focus event.
/// </summary>
/// <param name="sender">The sender.</param>
/// <param name="e">The <see cref="KeyboardFocusChangedEventArgs" /> instance containing the event data.</param>
private void HandleGotKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
{
if (SelectAllOnFocus)
{
SelectAll();
}
if (ScrollToHomeOnFocus)
{
ScrollToHome();
}
e.Handled = true;
}
}

View File

@ -0,0 +1,30 @@
using System.Windows;
namespace Controls;
/// <summary>
/// A component of the symbol
/// </summary>
public readonly struct TextComponent
{
/// <summary>
/// Constructor
/// </summary>
/// <param name="text"></param>
/// <param name="style"></param>
public TextComponent(string text, BaselineAlignment style = BaselineAlignment.Baseline)
{
Text = text;
Style = style;
}
/// <summary>
/// Text of the symbol component
/// </summary>
public readonly string Text;
/// <summary>
/// Style of the symbol component
/// </summary>
public readonly BaselineAlignment Style;
}

View File

@ -0,0 +1,97 @@
using System.Drawing;
using System.Globalization;
using System.Net;
using System.Windows;
using System.Windows.Data;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using Brush = System.Windows.Media.Brush;
using Brushes = System.Windows.Media.Brushes;
using FontFamily = System.Windows.Media.FontFamily;
using Point = System.Windows.Point;
namespace Controls;
/// <summary>
/// Makes an Bitmap from every Imageformat.
/// </summary>
public sealed class ObjectImageConverter : IValueConverter
{
object IValueConverter.Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
const string dynResPrefix = "dynRes:";
if (value is Bitmap bitmap)
{
return System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(bitmap.GetHbitmap(), IntPtr.Zero, Int32Rect.Empty, BitmapSizeOptions.FromWidthAndHeight(16, 16));
}
if (value is not string strValue)
return Binding.DoNothing;
if (strValue.StartsWith(dynResPrefix, StringComparison.Ordinal))
{
var resource = Application.Current.TryFindResource(strValue.Replace(dynResPrefix, string.Empty, StringComparison.InvariantCulture));
return resource is ImageSource source ? source : Binding.DoNothing;
}
if (strValue.StartsWith("text:", StringComparison.Ordinal))
{
var parts = strValue.Split(':');
return parts.Length == 3 ? DrawText(WebUtility.HtmlDecode(parts[2]), parts[1], Brushes.Black) : Binding.DoNothing;
}
return GetIcon(Geometry.Parse(strValue), null);
}
object IValueConverter.ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) => Binding.DoNothing;
/// <summary>
/// Get icon a ImageSource from geometry.
/// </summary>
/// <param name="geometry">geometry</param>
/// <param name="brush">color of the icon</param>
/// <returns></returns>
public static ImageSource GetIcon(Geometry geometry, Brush? brush)
{
if (brush == null)
brush = Brushes.Black;
var drawing = new GeometryDrawing(brush, null, geometry);
return new DrawingImage(drawing);
}
/// <summary>
/// Draw text as ImageSource from string.
/// </summary>
/// <param name="text">the text</param>
/// <param name="strFontFamily">font of the string</param>
/// <param name="brush">color of the string</param>
/// <returns></returns>
public static ImageSource DrawText(string text, string strFontFamily, Brush brush)
{
var fontFamily = new FontFamily(strFontFamily);
var formattedText = new FormattedText(
text,
CultureInfo.GetCultureInfo("en-us"),
FlowDirection.LeftToRight,
new Typeface(fontFamily, FontStyles.Normal, FontWeights.Normal, FontStretches.Normal),
64,
brush,
1.5
);
var geometry = formattedText.BuildGeometry(new Point(0, 0));
return GetIcon(geometry, null);
}
}
/// <summary>
/// Invert boolean converter
/// </summary>
[ValueConversion(typeof(bool), typeof(bool))]
public class InverseBooleanConverter : IValueConverter
{
/// <inheritdoc />
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) => value is bool b ? !b : throw new InvalidOperationException("The target must be a boolean");
/// <inheritdoc />
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) => Binding.DoNothing;
}