Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<Application xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="Converter.Avalonia.App"
RequestedThemeVariant="Default">
<Application.Styles>
<FluentTheme />
</Application.Styles>
</Application>
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;

namespace Converter.Avalonia;

public partial class App : Application
{
public override void Initialize() => AvaloniaXamlLoader.Load(this);

public override void OnFrameworkInitializationCompleted()
{
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
desktop.MainWindow = new MainWindow();
}

base.OnFrameworkInitializationCompleted();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Avalonia" Version="12.1.3" />
<PackageReference Include="Avalonia.Desktop" Version="12.1.3" />
<PackageReference Include="Avalonia.Themes.Fluent" Version="12.1.3" />
<PackageReference Include="Avalonia.Fonts.Inter" Version="12.1.3" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Converter.Core\Converter.Core.csproj" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:core="using:Converter.Core"
x:Class="Converter.Avalonia.MainWindow"
x:DataType="core:ConverterViewModel"
Title="Converter (Avalonia)" Width="320" SizeToContent="Height">
<Window.DataContext>
<core:ConverterViewModel />
</Window.DataContext>
<StackPanel Margin="12">
<TextBox Text="{Binding Celsius}" />
<Button Content="Convert" Command="{Binding ConvertCommand}" Margin="0,8" />
<TextBlock Text="{Binding Result}" />
</StackPanel>
</Window>
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using Avalonia.Controls;

namespace Converter.Avalonia;

public partial class MainWindow : Window
{
public MainWindow() => InitializeComponent();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using Avalonia;

namespace Converter.Avalonia;

internal static class Program
{
[STAThread]
public static void Main(string[] args) =>
BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);

public static AppBuilder BuildAvaloniaApp() =>
AppBuilder.Configure<App>()
.UsePlatformDetect()
.WithInterFont();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
using System.ComponentModel;
using System.Runtime.CompilerServices;

namespace Converter.Core;

public sealed class ConverterViewModel : INotifyPropertyChanged
{
public ConverterViewModel() => ConvertCommand = new RelayCommand(Convert, CanConvert);

public event PropertyChangedEventHandler? PropertyChanged;

public RelayCommand ConvertCommand { get; }

public string Celsius
{
get;
set
{
if (field == value) return;
field = value;
OnPropertyChanged();
ConvertCommand.RaiseCanExecuteChanged();
}
} = "";

public string Result
{
get;
private set
{
field = value;
OnPropertyChanged();
}
} = "";

private bool CanConvert() => double.TryParse(Celsius, out _);

private void Convert()
{
if (double.TryParse(Celsius, out var celsius))
{
Result = $"{celsius * 9 / 5 + 32:0.#} °F";
}
}

private void OnPropertyChanged([CallerMemberName] string? name = null) =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using System.Windows.Input;

namespace Converter.Core;

public sealed class RelayCommand(Action execute, Func<bool> canExecute) : ICommand
{
public event EventHandler? CanExecuteChanged;

public bool CanExecute(object? parameter) => canExecute();

public void Execute(object? parameter) => execute();

public void RaiseCanExecuteChanged() => CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Headless;
using Avalonia.Input;
using Converter.Avalonia;

[assembly: AvaloniaTestApplication(typeof(App))]

namespace Converter.Tests;

public class AvaloniaWindowTests
{
[Fact]
public async Task TypingAndClickingConvert_ShowsFahrenheit()
{
var session = HeadlessUnitTestSession.GetOrStartForAssembly(typeof(AvaloniaWindowTests).Assembly);

var (enabledBefore, enabledAfterTyping, result) = await session.Dispatch(() =>
{
var window = new MainWindow();
window.Show();
var panel = (StackPanel)window.Content!;
var celsiusBox = (TextBox)panel.Children[0];
var convertButton = (Button)panel.Children[1];
var resultText = (TextBlock)panel.Children[2];

var before = convertButton.IsEffectivelyEnabled;
celsiusBox.Focus();
window.KeyTextInput("100");
var afterTyping = convertButton.IsEffectivelyEnabled;

var center = convertButton.TranslatePoint(
new Point(convertButton.Bounds.Width / 2, convertButton.Bounds.Height / 2), window)!.Value;
window.MouseDown(center, MouseButton.Left);
window.MouseUp(center, MouseButton.Left);

return (before, afterTyping, resultText.Text);
}, TestContext.Current.CancellationToken);

Assert.False(enabledBefore);
Assert.True(enabledAfterTyping);
Assert.Equal("212 °F", result);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<OutputType>Exe</OutputType>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
</PropertyGroup>

<ItemGroup>
<Using Include="Xunit" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.10.1" />
<PackageReference Include="xunit.v3.mtp-off" Version="4.0.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="4.0.0" />
<PackageReference Include="Avalonia.Headless" Version="12.1.3" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Converter.Core\Converter.Core.csproj" />
<ProjectReference Include="..\Converter.Avalonia\Converter.Avalonia.csproj" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
using Converter.Core;

namespace Converter.Tests;

public class ConverterViewModelTests
{
[Fact]
public void ConvertCommand_IsDisabled_UntilCelsiusParses()
{
var viewModel = new ConverterViewModel();
Assert.False(viewModel.ConvertCommand.CanExecute(null));

viewModel.Celsius = "warm";
Assert.False(viewModel.ConvertCommand.CanExecute(null));

viewModel.Celsius = "100";
Assert.True(viewModel.ConvertCommand.CanExecute(null));
}

[Theory]
[InlineData("100", "212 °F")]
[InlineData("0", "32 °F")]
public void ConvertCommand_WritesFahrenheitToResult(string celsius, string expected)
{
var viewModel = new ConverterViewModel { Celsius = celsius };

viewModel.ConvertCommand.Execute(null);

Assert.Equal(expected, viewModel.Result);
}

[Fact]
public void SettingCelsius_NotifiesTheView()
{
var viewModel = new ConverterViewModel();
var changedProperties = new List<string?>();
var canExecuteChanges = 0;
viewModel.PropertyChanged += (_, e) => changedProperties.Add(e.PropertyName);
viewModel.ConvertCommand.CanExecuteChanged += (_, _) => canExecuteChanges++;

viewModel.Celsius = "25";

Assert.Equal([nameof(ConverterViewModel.Celsius)], changedProperties);
Assert.Equal(1, canExecuteChanges);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using Converter.Core;

namespace Converter.WinForms;

public sealed class BoundForm : Form
{
public BoundForm(ConverterViewModel viewModel)
{
Text = "Converter (WinForms, bound)";

var celsiusBox = new TextBox { Width = 200 };
celsiusBox.DataBindings.Add(nameof(TextBox.Text), viewModel,
nameof(ConverterViewModel.Celsius), false, DataSourceUpdateMode.OnPropertyChanged);

var convertButton = new Button { Text = "Convert", Command = viewModel.ConvertCommand };

var resultLabel = new Label { AutoSize = true };
resultLabel.DataBindings.Add(nameof(Label.Text), viewModel, nameof(ConverterViewModel.Result));

Controls.Add(new FlowLayoutPanel
{
Dock = DockStyle.Fill,
FlowDirection = FlowDirection.TopDown,
Controls = { celsiusBox, convertButton, resultLabel }
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
namespace Converter.WinForms;

public sealed class ClassicForm : Form
{
private readonly TextBox _celsiusBox = new() { Width = 200 };
private readonly Button _convertButton = new() { Text = "Convert", Enabled = false };
private readonly Label _resultLabel = new() { AutoSize = true };

public ClassicForm()
{
Text = "Converter (WinForms, classic)";
Controls.Add(new FlowLayoutPanel
{
Dock = DockStyle.Fill,
FlowDirection = FlowDirection.TopDown,
Controls = { _celsiusBox, _convertButton, _resultLabel }
});

_celsiusBox.TextChanged += (_, _) =>
_convertButton.Enabled = double.TryParse(_celsiusBox.Text, out _);

_convertButton.Click += (_, _) =>
{
var celsius = double.Parse(_celsiusBox.Text);
_resultLabel.Text = $"{celsius * 9 / 5 + 32:0.#} °F";
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net10.0-windows</TargetFramework>
<UseWindowsForms>true</UseWindowsForms>
<EnableWindowsTargeting>true</EnableWindowsTargeting>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\Converter.Core\Converter.Core.csproj" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using Converter.Core;

namespace Converter.WinForms;

internal static class Program
{
[STAThread]
private static void Main(string[] args)
{
ApplicationConfiguration.Initialize();
Application.SetColorMode(SystemColorMode.System);

Form form = args.Contains("--classic")
? new ClassicForm()
: new BoundForm(new ConverterViewModel());

Application.Run(form);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<Application x:Class="Converter.Wpf.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="MainWindow.xaml"
ThemeMode="System">
</Application>
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
using System.Windows;

namespace Converter.Wpf;

public partial class App : Application
{
}
Loading
Loading