mirror of
https://github.com/holgerb83/ModernWpfPlayground.git
synced 2025-07-01 10:00:52 +02:00
refactoring and working with cake
This commit is contained in:
25
src/App/App.xaml
Normal file
25
src/App/App.xaml
Normal file
@ -0,0 +1,25 @@
|
||||
<Application
|
||||
x:Class="ModernWpfPlayground.App"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:ui="http://schemas.modernwpf.com/2019"
|
||||
StartupUri="MainWindow.xaml">
|
||||
<Application.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ui:ThemeResources />
|
||||
<ui:IntellisenseResources Source="/ModernWpf;component/DesignTime/DesignTimeResources.xaml" />
|
||||
<ui:XamlControlsResources />
|
||||
<ResourceDictionary>
|
||||
<Style BasedOn="{StaticResource {x:Type TextBox}}" TargetType="TextBox">
|
||||
<Style.Triggers>
|
||||
<Trigger Property="IsReadOnly" Value="True">
|
||||
<Setter Property="Background" Value="{DynamicResource SystemControlDisabledChromeMediumLowBrush}" />
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</ResourceDictionary>
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</Application.Resources>
|
||||
</Application>
|
19
src/App/App.xaml.cs
Normal file
19
src/App/App.xaml.cs
Normal file
@ -0,0 +1,19 @@
|
||||
using System.Windows;
|
||||
using ModernWpf;
|
||||
using ModernWpfPlayground.Types;
|
||||
|
||||
namespace ModernWpfPlayground
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for App.xaml
|
||||
/// </summary>
|
||||
public partial class App
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void OnStartup(StartupEventArgs e)
|
||||
{
|
||||
base.OnStartup(e);
|
||||
ThemeManager.Current.AccentColor = AccentColors.Green.ToWindowsColor();
|
||||
}
|
||||
}
|
||||
}
|
10
src/App/AssemblyInfo.cs
Normal file
10
src/App/AssemblyInfo.cs
Normal 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)
|
||||
)]
|
21
src/App/ContentDialogExample.xaml
Normal file
21
src/App/ContentDialogExample.xaml
Normal file
@ -0,0 +1,21 @@
|
||||
<ui:ContentDialog
|
||||
x:Class="ModernWpfPlayground.ContentDialogExample"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:ui="http://schemas.modernwpf.com/2019"
|
||||
x:Name="LayoutRoot"
|
||||
Title="Delete your work?"
|
||||
CloseButtonText="Cancel"
|
||||
DefaultButton="Close"
|
||||
IsShadowEnabled="True"
|
||||
PrimaryButtonText="Yes"
|
||||
SecondaryButtonText="No">
|
||||
<ui:SimpleStackPanel
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
Spacing="10">
|
||||
<!-- Content body -->
|
||||
<TextBlock Text="Delete message?" />
|
||||
<TextBlock TextWrapping="Wrap"><Run Text=""" /><Run Text="{Binding Message, ElementName=LayoutRoot}" /><Run Text=""" /></TextBlock>
|
||||
</ui:SimpleStackPanel>
|
||||
</ui:ContentDialog>
|
21
src/App/ContentDialogExample.xaml.cs
Normal file
21
src/App/ContentDialogExample.xaml.cs
Normal file
@ -0,0 +1,21 @@
|
||||
using System.Windows;
|
||||
|
||||
namespace ModernWpfPlayground
|
||||
{
|
||||
public partial class ContentDialogExample
|
||||
{
|
||||
public static readonly DependencyProperty MessageProperty = DependencyProperty.Register(
|
||||
"Message", typeof(string), typeof(ContentDialogExample), new PropertyMetadata(default(string)));
|
||||
|
||||
public string? Message
|
||||
{
|
||||
get => (string?) GetValue(MessageProperty);
|
||||
set => SetValue(MessageProperty, value);
|
||||
}
|
||||
|
||||
public ContentDialogExample()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
312
src/App/MainWindow.xaml
Normal file
312
src/App/MainWindow.xaml
Normal file
@ -0,0 +1,312 @@
|
||||
<Window
|
||||
x:Class="ModernWpfPlayground.MainWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:iconPacks="http://metro.mahapps.com/winfx/xaml/iconpacks"
|
||||
xmlns:local="clr-namespace:ModernWpfPlayground"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:ui="http://schemas.modernwpf.com/2019"
|
||||
xmlns:controls="clr-namespace:Controls;assembly=Controls"
|
||||
x:Name="Window"
|
||||
Title="{Binding Title}"
|
||||
Width="{Binding WindowWidth, Mode=TwoWay}"
|
||||
Height="{Binding WindowHeight, Mode=TwoWay}"
|
||||
d:DataContext="{d:DesignInstance local:MainWindowViewModel}"
|
||||
ui:ThemeManager.IsThemeAware="True"
|
||||
ui:TitleBar.ExtendViewIntoTitleBar="True"
|
||||
ui:WindowHelper.UseModernWindowStyle="True"
|
||||
TextOptions.TextFormattingMode="Display"
|
||||
UseLayoutRounding="True"
|
||||
mc:Ignorable="d">
|
||||
<Window.DataContext>
|
||||
<local:MainWindowViewModel />
|
||||
</Window.DataContext>
|
||||
<Window.InputBindings>
|
||||
<KeyBinding Key="O" Modifiers="Control" />
|
||||
<KeyBinding Key="S" Modifiers="Control" />
|
||||
<KeyBinding Key="N" Modifiers="Control" />
|
||||
</Window.InputBindings>
|
||||
|
||||
<DockPanel>
|
||||
<!-- TitleBar -->
|
||||
<Grid
|
||||
Height="{Binding ElementName=Window, Path=(ui:TitleBar.Height)}"
|
||||
Background="{DynamicResource SystemControlBackgroundChromeMediumBrush}"
|
||||
DockPanel.Dock="Top">
|
||||
<Grid.Style>
|
||||
<Style TargetType="Grid">
|
||||
<Setter Property="TextElement.Foreground" Value="{DynamicResource SystemControlForegroundBaseHighBrush}" />
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding IsActive, ElementName=Window}" Value="False">
|
||||
<Setter Property="TextElement.Foreground" Value="{DynamicResource SystemControlDisabledBaseMediumLowBrush}" />
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</Grid.Style>
|
||||
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="{Binding ElementName=Window, Path=(ui:TitleBar.SystemOverlayLeftInset), Converter={local:PixelsToGridLengthConverter}}" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Menu
|
||||
Grid.Column="1"
|
||||
Height="{Binding ElementName=Window, Path=(ui:TitleBar.Height)}"
|
||||
Margin="0"
|
||||
Padding="0"
|
||||
WindowChrome.IsHitTestVisibleInChrome="True">
|
||||
<Menu.Resources>
|
||||
<Style BasedOn="{StaticResource {x:Type MenuItem}}" TargetType="MenuItem">
|
||||
<Style.Triggers>
|
||||
<Trigger Property="Role" Value="TopLevelHeader">
|
||||
<Setter Property="Height" Value="{Binding ElementName=Window, Path=(ui:TitleBar.Height)}" />
|
||||
</Trigger>
|
||||
<DataTrigger Binding="{Binding IsActive, ElementName=Window}" Value="False">
|
||||
<Setter Property="TextElement.Foreground" Value="{DynamicResource SystemControlDisabledBaseMediumLowBrush}" />
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</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"
|
||||
Icon="{iconPacks:FontAwesome Kind=SaveRegular}"
|
||||
InputGestureText="Ctrl+S" />
|
||||
<Separator />
|
||||
<MenuItem
|
||||
Command="{Binding CloseCommand}"
|
||||
Header="Close"
|
||||
Icon="{iconPacks:FontAwesome Kind=WindowCloseRegular}"
|
||||
InputGestureText="Alt+F4" />
|
||||
</MenuItem>
|
||||
<MenuItem Header="Edit">
|
||||
<MenuItem Header="Copy" Icon="{iconPacks:FontAwesome Kind=CopyRegular}" />
|
||||
<MenuItem Header="Cut" Icon="{iconPacks:FontAwesome Kind=CutSolid}" />
|
||||
<MenuItem Header="Paste" Icon="{iconPacks:FontAwesome Kind=ClipboardRegular}" />
|
||||
</MenuItem>
|
||||
<MenuItem Header="Help">
|
||||
<MenuItem Header="?" Icon="{iconPacks:FontAwesome Kind=QuestionCircleRegular}" />
|
||||
<Separator />
|
||||
<MenuItem Header="Info" Icon="{iconPacks:FontAwesome Kind=InfoSolid}" />
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
<!-- Horizontally centered title -->
|
||||
<TextBlock
|
||||
Grid.Column="0"
|
||||
Grid.ColumnSpan="3"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
FontSize="13"
|
||||
Text="{Binding ElementName=Window, Path=Title}" />
|
||||
</Grid>
|
||||
<!-- Footer -->
|
||||
<Grid
|
||||
Height="24"
|
||||
Background="{DynamicResource SystemControlBackgroundAccentBrush}"
|
||||
DockPanel.Dock="Bottom">
|
||||
<Grid.Resources>
|
||||
<Style TargetType="TextBlock">
|
||||
<Setter Property="Foreground" Value="White" />
|
||||
<Setter Property="Margin" Value="10,0" />
|
||||
<Setter Property="VerticalAlignment" Value="Center" />
|
||||
</Style>
|
||||
</Grid.Resources>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Grid.Column="0" Text="Holger Börchers" />
|
||||
<TextBlock Grid.Column="1" Text="|" />
|
||||
<TextBlock Grid.Column="2" Text="SQL-SRV\SQLEXPRESS" />
|
||||
<TextBlock
|
||||
Grid.Column="3"
|
||||
HorizontalAlignment="Right"
|
||||
Text="BlaBlaBla" />
|
||||
</Grid>
|
||||
<Grid Row="1">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="40" />
|
||||
<ColumnDefinition />
|
||||
</Grid.ColumnDefinitions>
|
||||
<!-- Workspace -->
|
||||
<Border
|
||||
Grid.Column="0"
|
||||
Panel.ZIndex="1"
|
||||
Background="#2C2C2C"
|
||||
BorderThickness="0">
|
||||
<ui:SimpleStackPanel HorizontalAlignment="Center" Spacing="10">
|
||||
<ui:SimpleStackPanel.Resources>
|
||||
<Style TargetType="TextBlock">
|
||||
<Setter Property="LayoutTransform">
|
||||
<Setter.Value>
|
||||
<RotateTransform Angle="-90" />
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
<Style TargetType="ui:ToggleSwitch">
|
||||
<Setter Property="LayoutTransform">
|
||||
<Setter.Value>
|
||||
<RotateTransform Angle="-90" />
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</ui:SimpleStackPanel.Resources>
|
||||
<ui:ToggleSwitch
|
||||
x:Name="SplitViewSwitch"
|
||||
Margin="0,5,0,0"
|
||||
IsOn="{Binding IsPaneOpen}"
|
||||
ToolTip="Show/hide navigation" />
|
||||
<TextBlock
|
||||
Cursor="Hand"
|
||||
FontSize="20"
|
||||
Foreground="#F4F4F4"
|
||||
Text="Dummy active" />
|
||||
<TextBlock
|
||||
Cursor="Hand"
|
||||
FontSize="20"
|
||||
Foreground="#808080"
|
||||
Text="Dummy inactive" />
|
||||
</ui:SimpleStackPanel>
|
||||
</Border>
|
||||
<!-- Navigation -->
|
||||
<ui:SplitView
|
||||
Grid.Column="1"
|
||||
BorderThickness="0"
|
||||
DisplayMode="Inline"
|
||||
IsPaneOpen="{Binding IsOn, ElementName=SplitViewSwitch}"
|
||||
OpenPaneLength="200"
|
||||
PanePlacement="Left">
|
||||
<ui:SplitView.Pane>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition />
|
||||
</Grid.RowDefinitions>
|
||||
<TextBlock Margin="10,10,10,0" Text="NAVIGATION" />
|
||||
<TreeView Grid.Row="1">
|
||||
<TreeViewItem Header="Root" IsExpanded="True">
|
||||
<TreeViewItem Header="Child1" />
|
||||
<TreeViewItem Header="Child2" />
|
||||
<TreeViewItem Header="Child3" />
|
||||
</TreeViewItem>
|
||||
</TreeView>
|
||||
</Grid>
|
||||
</ui:SplitView.Pane>
|
||||
<!-- Content -->
|
||||
<TabControl>
|
||||
<TabItem Header="Bolt">
|
||||
<ScrollViewer ui:ScrollViewerHelper.AutoHideScrollBars="True">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition />
|
||||
<ColumnDefinition />
|
||||
<ColumnDefinition />
|
||||
</Grid.ColumnDefinitions>
|
||||
<ui:SimpleStackPanel
|
||||
Grid.Column="0"
|
||||
Margin="5"
|
||||
Orientation="Vertical"
|
||||
Spacing="5">
|
||||
<Button
|
||||
HorizontalAlignment="Stretch"
|
||||
Command="{Binding OpenViewModelCommand}"
|
||||
Content="Open" />
|
||||
<Button
|
||||
HorizontalAlignment="Stretch"
|
||||
Command="{Binding SaveViewModelCommand}"
|
||||
Content="Save" />
|
||||
</ui:SimpleStackPanel>
|
||||
</Grid>
|
||||
</ScrollViewer>
|
||||
</TabItem>
|
||||
<TabItem
|
||||
Header="General"
|
||||
IsSelected="True">
|
||||
<ScrollViewer ui:ScrollViewerHelper.AutoHideScrollBars="True">
|
||||
<ui:SimpleStackPanel Margin="5" Spacing="10">
|
||||
<ComboBox
|
||||
DisplayMemberPath="Key"
|
||||
ItemsSource="{Binding ThemeMode, Converter={controls:EnumToItemSourceConverter}}"
|
||||
SelectedValue="{Binding ThemeMode}"
|
||||
SelectedValuePath="Value" />
|
||||
<controls:PropertyPresenter Label="Theme Mode" Value="{Binding ThemeMode}" />
|
||||
<controls:PropertyPresenter Label="Accent color" Value="{Binding AccentColors}" />
|
||||
<controls:PropertyPresenter
|
||||
Command="{Binding ShowDialogCommand}"
|
||||
Label="Hello"
|
||||
Symbol="x³"
|
||||
Value="{Binding WelcomeMessage}" />
|
||||
<controls:PropertyPresenter
|
||||
IsReadOnly="True"
|
||||
Label="Hallo"
|
||||
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" />
|
||||
<ColumnDefinition />
|
||||
</Grid.ColumnDefinitions>
|
||||
<ToggleButton
|
||||
x:Name="TextBoxSwitch"
|
||||
Grid.Column="0"
|
||||
Width="145"
|
||||
Margin="0,0,5,0">
|
||||
<ToggleButton.Style>
|
||||
<Style BasedOn="{StaticResource {x:Type ToggleButton}}" TargetType="ToggleButton">
|
||||
<Setter Property="Content" Value="Read/Write" />
|
||||
<Style.Triggers>
|
||||
<Trigger Property="IsChecked" Value="True">
|
||||
<Setter Property="Content" Value="Read only" />
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</ToggleButton.Style>
|
||||
</ToggleButton>
|
||||
<TextBox
|
||||
Grid.Column="1"
|
||||
IsReadOnly="{Binding IsChecked, ElementName=TextBoxSwitch}"
|
||||
Text="Eine einfache Textbox" />
|
||||
</Grid>
|
||||
<controls:PropertyPresenter Label="Hello" Value="{Binding BooleanValue}" />
|
||||
<controls:PropertyPresenter Label="Hello">
|
||||
<Slider
|
||||
AutoToolTipPlacement="TopLeft"
|
||||
Interval="1"
|
||||
IsSnapToTickEnabled="True"
|
||||
Maximum="150"
|
||||
Minimum="50"
|
||||
TickFrequency="10"
|
||||
TickPlacement="BottomRight"
|
||||
Value="{Binding SliderTest}" />
|
||||
</controls:PropertyPresenter>
|
||||
<ui:ProgressRing
|
||||
Width="{Binding SliderTest}"
|
||||
Height="{Binding SliderTest}"
|
||||
IsActive="{Binding BooleanValue}"
|
||||
Visibility="{Binding VisibilityEnumTest}" />
|
||||
<controls:PropertyPresenter Label="Visi" Value="{Binding VisibilityEnumTest}" />
|
||||
</ui:SimpleStackPanel>
|
||||
</ScrollViewer>
|
||||
</TabItem>
|
||||
</TabControl>
|
||||
</ui:SplitView>
|
||||
</Grid>
|
||||
</DockPanel>
|
||||
</Window>
|
13
src/App/MainWindow.xaml.cs
Normal file
13
src/App/MainWindow.xaml.cs
Normal file
@ -0,0 +1,13 @@
|
||||
namespace ModernWpfPlayground
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for MainWindow.xaml
|
||||
/// </summary>
|
||||
public partial class MainWindow
|
||||
{
|
||||
public MainWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
87
src/App/MainWindowViewModel.cs
Normal file
87
src/App/MainWindowViewModel.cs
Normal file
@ -0,0 +1,87 @@
|
||||
using System.Windows;
|
||||
using ModernWpfPlayground.Types;
|
||||
using MvvmGen;
|
||||
using static ModernWpf.ThemeManager;
|
||||
|
||||
namespace ModernWpfPlayground
|
||||
{
|
||||
// ReSharper disable once ClassNeverInstantiated.Global
|
||||
[ViewModel]
|
||||
public partial class MainWindowViewModel
|
||||
{
|
||||
private const string AppName = "TaBEA 3.0.0";
|
||||
|
||||
[Property, PropertyCallMethod(nameof(SetTitle))]
|
||||
private string? _path;
|
||||
|
||||
[Property] private string _title = AppName;
|
||||
|
||||
[Property, PropertyCallMethod(nameof(BooleanValue_OnChanged))]
|
||||
private bool _booleanValue = true;
|
||||
|
||||
[Property] private Visibility _visibilityEnumTest = Visibility.Visible;
|
||||
[Property] private double _sliderTest = 100;
|
||||
[Property] private double _validationTest;
|
||||
[Property] private string? _welcomeMessage = "Shadow of the empire";
|
||||
|
||||
[Property, PropertyCallMethod(nameof(SetTheme))]
|
||||
private ThemeMode _themeMode = ThemeMode.UseSystemSetting;
|
||||
|
||||
[Property, PropertyCallMethod(nameof(SetAccentColor))]
|
||||
private AccentColors _accentColors = AccentColors.Green;
|
||||
|
||||
[Property] private int _windowWidth = 1200;
|
||||
[Property] private int _windowHeight = 600;
|
||||
[Property] private bool _isPaneOpen = true;
|
||||
|
||||
|
||||
[Command]
|
||||
private void ShowNotification()
|
||||
{
|
||||
}
|
||||
|
||||
[Command]
|
||||
private void Close()
|
||||
{
|
||||
Application.Current.MainWindow?.Close();
|
||||
}
|
||||
|
||||
private void SetTitle()
|
||||
{
|
||||
Title = Path != null ? $"{System.IO.Path.GetFileName(Path)} - {AppName}" : AppName;
|
||||
}
|
||||
|
||||
private void SetAccentColor() => Current.AccentColor = AccentColors.ToWindowsColor();
|
||||
|
||||
|
||||
private void SetTheme() => Current.ApplicationTheme = ThemeMode.ToApplicationTheme();
|
||||
|
||||
[Command]
|
||||
private async void ShowDialog()
|
||||
{
|
||||
var dialog = new ContentDialogExample { Message = WelcomeMessage };
|
||||
var result = await dialog.ShowAsync();
|
||||
WelcomeMessage = result.ToString();
|
||||
}
|
||||
|
||||
private void BooleanValue_OnChanged()
|
||||
{
|
||||
VisibilityEnumTest = BooleanValue ? Visibility.Visible : Visibility.Collapsed;
|
||||
}
|
||||
|
||||
[Command]
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
39
src/App/ModernWpfPlayground.csproj
Normal file
39
src/App/ModernWpfPlayground.csproj
Normal file
@ -0,0 +1,39 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net6.0-windows</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<UseWPF>true</UseWPF>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Controls\Controls.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="FastMember" Version="1.5.0" />
|
||||
<PackageReference Include="Hardcodet.NotifyIcon.Wpf" Version="1.1.0" />
|
||||
<PackageReference Include="ModernWpfUis" Version="1.2.0" />
|
||||
<PackageReference Include="MvvmGen" Version="1.1.2" />
|
||||
<PackageReference Include="YamlDotNet" Version="11.2.1" />
|
||||
<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>
|
26
src/App/PixelsToGridLengthConverter.cs
Normal file
26
src/App/PixelsToGridLengthConverter.cs
Normal file
@ -0,0 +1,26 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Windows;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Markup;
|
||||
|
||||
namespace ModernWpfPlayground
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override object ProvideValue(IServiceProvider serviceProvider) =>
|
||||
_converter ??= new PixelsToGridLengthConverter();
|
||||
}
|
||||
}
|
30
src/App/Types/AccentColors.cs
Normal file
30
src/App/Types/AccentColors.cs
Normal file
@ -0,0 +1,30 @@
|
||||
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)
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
27
src/App/Types/ThemeMode.cs
Normal file
27
src/App/Types/ThemeMode.cs
Normal file
@ -0,0 +1,27 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using ModernWpf;
|
||||
|
||||
namespace ModernWpfPlayground.Types
|
||||
{
|
||||
public enum ThemeMode
|
||||
{
|
||||
[Description("Light")] Light,
|
||||
[Description("Dark")] Dark,
|
||||
[Description("Use system setting")] UseSystemSetting
|
||||
}
|
||||
|
||||
public static class ThemeModeExtension
|
||||
{
|
||||
public static ApplicationTheme? ToApplicationTheme(this ThemeMode themeMode)
|
||||
{
|
||||
return themeMode switch
|
||||
{
|
||||
ThemeMode.Light => ApplicationTheme.Light,
|
||||
ThemeMode.Dark => ApplicationTheme.Dark,
|
||||
ThemeMode.UseSystemSetting => default,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(themeMode), themeMode, null)
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
10
src/Controls/AssemblyInfo.cs
Normal file
10
src/Controls/AssemblyInfo.cs
Normal 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)
|
||||
)]
|
20
src/Controls/Controls.csproj
Normal file
20
src/Controls/Controls.csproj
Normal file
@ -0,0 +1,20 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0-windows</TargetFramework>
|
||||
<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="6.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
</Project>
|
49
src/Controls/PropertyPresenter/EnumToItemSourceConverter.cs
Normal file
49
src/Controls/PropertyPresenter/EnumToItemSourceConverter.cs
Normal file
@ -0,0 +1,49 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
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();
|
||||
}
|
||||
}
|
43
src/Controls/PropertyPresenter/MagicSymbolControl.cs
Normal file
43
src/Controls/PropertyPresenter/MagicSymbolControl.cs
Normal file
@ -0,0 +1,43 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
152
src/Controls/PropertyPresenter/MagicSymbolConverter.cs
Normal file
152
src/Controls/PropertyPresenter/MagicSymbolConverter.cs
Normal file
@ -0,0 +1,152 @@
|
||||
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[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;
|
||||
}
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
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
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
207
src/Controls/PropertyPresenter/PropertyPresenter.xaml
Normal file
207
src/Controls/PropertyPresenter/PropertyPresenter.xaml
Normal file
@ -0,0 +1,207 @@
|
||||
<ContentControl
|
||||
x:Class="Controls.PropertyPresenter"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:controls="clr-namespace:Controls"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
x:Name="LayoutRoot"
|
||||
d:DesignHeight="300"
|
||||
d:DesignWidth="300"
|
||||
Focusable="False"
|
||||
mc:Ignorable="d">
|
||||
<Validation.ErrorTemplate>
|
||||
<ControlTemplate />
|
||||
</Validation.ErrorTemplate>
|
||||
<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>
|
||||
<TextBlock
|
||||
Margin="6,0,0,0"
|
||||
Padding="2,2,2,2"
|
||||
VerticalAlignment="Center"
|
||||
IsHitTestVisible="False"
|
||||
Text="{Binding Watermark, ElementName=LayoutRoot}">
|
||||
<TextBlock.Style>
|
||||
<Style TargetType="TextBlock">
|
||||
<Setter Property="Visibility" Value="Collapsed" />
|
||||
<Setter Property="Opacity" Value="0.6" />
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding Text, ElementName=InputTextBox}" Value="">
|
||||
<Setter Property="Visibility" Value="Visible" />
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding IsFocused, ElementName=InputTextBox}" Value="True">
|
||||
<Setter Property="Opacity" Value="0.3" />
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</TextBlock.Style>
|
||||
</TextBlock>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
<DataTemplate x:Key="BooleanDataTemplate">
|
||||
<CheckBox
|
||||
Margin="0,4"
|
||||
VerticalAlignment="Center"
|
||||
IsChecked="{Binding Value, ElementName=LayoutRoot}"
|
||||
Validation.ErrorTemplate="{DynamicResource ValidationErrorTemplate}"
|
||||
Validation.ValidationAdornerSiteFor="{Binding ElementName=LayoutRoot}">
|
||||
<CheckBox.Style>
|
||||
<Style BasedOn="{StaticResource {x:Type CheckBox}}" TargetType="{x:Type CheckBox}">
|
||||
<Setter Property="IsEnabled" 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="IsEnabled" Value="True" />
|
||||
</MultiDataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</CheckBox.Style>
|
||||
</CheckBox>
|
||||
</DataTemplate>
|
||||
<DataTemplate x:Key="EnumComboBoxDataTemplate">
|
||||
<ComboBox
|
||||
HorizontalAlignment="Stretch"
|
||||
DisplayMemberPath="Key"
|
||||
ItemsSource="{Binding Value, ElementName=LayoutRoot, Converter={controls:EnumToItemSourceConverter}}"
|
||||
SelectedValue="{Binding Value, ElementName=LayoutRoot}"
|
||||
SelectedValuePath="Value"
|
||||
Validation.ErrorTemplate="{DynamicResource ValidationErrorTemplate}"
|
||||
Validation.ValidationAdornerSiteFor="{Binding ElementName=LayoutRoot}">
|
||||
<ComboBox.Style>
|
||||
<Style BasedOn="{StaticResource {x:Type ComboBox}}" TargetType="{x:Type ComboBox}">
|
||||
<Setter Property="IsEnabled" 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="IsEnabled" Value="True" />
|
||||
</MultiDataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</ComboBox.Style>
|
||||
</ComboBox>
|
||||
</DataTemplate>
|
||||
<controls:PropertyDataTemplateSelector
|
||||
x:Key="DataTemplateSelector"
|
||||
BooleanDataTemplate="{StaticResource BooleanDataTemplate}"
|
||||
DefaultDataTemplate="{StaticResource DefaultDataTemplate}"
|
||||
EnumComboBoxDataTemplate="{StaticResource EnumComboBoxDataTemplate}" />
|
||||
</ContentControl.Resources>
|
||||
<Grid Margin="2">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="{Binding LabelWidth, ElementName=LayoutRoot, FallbackValue=150}" />
|
||||
<ColumnDefinition />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid Grid.Column="0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<CheckBox
|
||||
Grid.Column="0"
|
||||
VerticalAlignment="Center"
|
||||
IsChecked="{Binding IsChecked, ElementName=LayoutRoot}">
|
||||
<CheckBox.Style>
|
||||
<Style BasedOn="{StaticResource {x:Type CheckBox}}" TargetType="CheckBox">
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding IsChecked, ElementName=LayoutRoot}" Value="{x:Null}">
|
||||
<Setter Property="Visibility" Value="Collapsed" />
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding IsReadOnly, ElementName=LayoutRoot}" Value="True">
|
||||
<Setter Property="IsEnabled" Value="False" />
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</CheckBox.Style>
|
||||
</CheckBox>
|
||||
<TextBlock
|
||||
x:Name="PartLabel"
|
||||
Grid.Column="1"
|
||||
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
|
||||
x:Name="PartButton"
|
||||
Command="{Binding Command, ElementName=LayoutRoot}"
|
||||
CommandParameter="{Binding CommandParameter, ElementName=LayoutRoot}"
|
||||
Content="{Binding CommandContent, ElementName=LayoutRoot}"
|
||||
DockPanel.Dock="{Binding ButtonAlignment, ElementName=LayoutRoot}">
|
||||
<Button.Style>
|
||||
<Style BasedOn="{StaticResource {x:Type Button}}" TargetType="Button">
|
||||
<Setter Property="IsEnabled" Value="False" />
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding ButtonAlignment, ElementName=LayoutRoot}" Value="Right">
|
||||
<Setter Property="Margin" Value="5,0,0,0" />
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding ButtonAlignment, ElementName=LayoutRoot}" Value="Bottom">
|
||||
<Setter Property="Margin" Value="0,5,0,0" />
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding ButtonAlignment, ElementName=LayoutRoot}" Value="Left">
|
||||
<Setter Property="Margin" Value="0,0,5,0" />
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding ButtonAlignment, ElementName=LayoutRoot}" Value="Top">
|
||||
<Setter Property="Margin" Value="0,0,0,5" />
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding Command, ElementName=LayoutRoot}" Value="{x:Null}">
|
||||
<Setter Property="Visibility" Value="Collapsed" />
|
||||
</DataTrigger>
|
||||
<MultiDataTrigger>
|
||||
<MultiDataTrigger.Conditions>
|
||||
<Condition Binding="{Binding IsReadOnly, ElementName=LayoutRoot}" Value="False" />
|
||||
<Condition Binding="{Binding IsChecked, ElementName=LayoutRoot, TargetNullValue=True}" Value="True" />
|
||||
</MultiDataTrigger.Conditions>
|
||||
<Setter Property="IsEnabled" Value="True" />
|
||||
</MultiDataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</Button.Style>
|
||||
</Button>
|
||||
<ContentPresenter
|
||||
x:Name="PartContent"
|
||||
Content="{Binding Value, ElementName=LayoutRoot}"
|
||||
ContentTemplateSelector="{StaticResource DataTemplateSelector}"
|
||||
Focusable="False" />
|
||||
</DockPanel>
|
||||
</Grid>
|
||||
</ContentControl>
|
181
src/Controls/PropertyPresenter/PropertyPresenter.xaml.cs
Normal file
181
src/Controls/PropertyPresenter/PropertyPresenter.xaml.cs
Normal file
@ -0,0 +1,181 @@
|
||||
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}";
|
||||
}
|
||||
}
|
||||
}
|
165
src/Controls/PropertyPresenter/TextBoxEx.cs
Normal file
165
src/Controls/PropertyPresenter/TextBoxEx.cs
Normal file
@ -0,0 +1,165 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
31
src/Controls/PropertyPresenter/TextComponent.cs
Normal file
31
src/Controls/PropertyPresenter/TextComponent.cs
Normal file
@ -0,0 +1,31 @@
|
||||
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;
|
||||
}
|
||||
}
|
100
src/Controls/PropertyPresenter/ValueConverter.cs
Normal file
100
src/Controls/PropertyPresenter/ValueConverter.cs
Normal file
@ -0,0 +1,100 @@
|
||||
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 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;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user