file dialog

This commit is contained in:
Matthew Parker
2023-08-30 00:07:55 +10:00
parent 7be89cf2e0
commit aefb12e688
14 changed files with 190 additions and 62 deletions

View File

@@ -2,8 +2,6 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="SolutionParityChecker.App.App"
RequestedThemeVariant="Default">
<!-- "Default" ThemeVariant follows system theme variant. "Dark" or "Light" are other available options. -->
<Application.Styles>
<FluentTheme />
</Application.Styles>

View File

@@ -1,6 +1,8 @@
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;
using SolutionParityChecker.App.ViewModels;
using SolutionParityChecker.App.Views;
namespace SolutionParityChecker.App;

Binary file not shown.

After

Width:  |  Height:  |  Size: 172 KiB

View File

@@ -1,24 +0,0 @@
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:app="clr-namespace:SolutionParityChecker.App"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="SolutionParityChecker.App.MainWindow"
Title="SolutionParityChecker.App"
x:DataType="app:MainWindowViewModel">
<Window.DataContext>
<app:MainWindowViewModel />
</Window.DataContext>
<Panel>
<StackPanel>
<TextBlock>Welcome to Avalonia!</TextBlock>
<Button Click="LoadSolutionFolder">Select Solution Folder</Button>
<Button Click="LoadSolutionFile">Select Solution File</Button>
<TextBlock Name="SolutionFilePath" Text="{Binding SolutionFilePath}" />
<TextBlock Name="SolutionFolderPath" Text="{Binding SolutionFolderPath}" />
</StackPanel>
</Panel>
</Window>

View File

@@ -1,7 +0,0 @@
namespace SolutionParityChecker.App;
public class MainWindowViewModel
{
public string SolutionFolderPath { get; set; } = string.Empty;
public string SolutionFilePath { get; set; } = string.Empty;
}

View File

@@ -1,6 +0,0 @@
namespace SolutionParityChecker.App.Models;
public class AppState
{
}

View File

@@ -1,5 +1,5 @@
using Avalonia;
using System;
using System;
using Avalonia;
namespace SolutionParityChecker.App;
@@ -9,13 +9,10 @@ class Program
// SynchronizationContext-reliant code before AppMain is called: things aren't initialized
// yet and stuff might break.
[STAThread]
public static void Main(string[] args) => BuildAvaloniaApp()
.StartWithClassicDesktopLifetime(args);
public static void Main(string[] args) =>
BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
// Avalonia configuration, don't remove; also used by visual designer.
public static AppBuilder BuildAvaloniaApp()
=> AppBuilder.Configure<App>()
.UsePlatformDetect()
.WithInterFont()
.LogToTrace();
}
public static AppBuilder BuildAvaloniaApp() =>
AppBuilder.Configure<App>().UsePlatformDetect().WithInterFont().LogToTrace();
}

View File

@@ -1,6 +0,0 @@
namespace SolutionParityChecker.App.Services;
public class DialogService
{
}

View File

@@ -8,13 +8,22 @@
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
</PropertyGroup>
<ItemGroup>
<AvaloniaResource Include="Assets\**"/>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Avalonia" Version="11.0.4" />
<PackageReference Include="Avalonia.Desktop" Version="11.0.4" />
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.0.4" />
<PackageReference Include="Avalonia.Fonts.Inter" Version="11.0.4" />
<ProjectCapability Include="Avalonia"/>
<TrimmerRootAssembly Include="Avalonia.Themes.Fluent"/>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Avalonia" Version="11.0.0-preview8"/>
<PackageReference Include="Avalonia.Desktop" Version="11.0.0-preview8"/>
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.0.0-preview8"/>
<PackageReference Include="Avalonia.Fonts.Inter" Version="11.0.0-preview8"/>
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.0.4" />
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.0.0-preview8"/>
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.0"/>
</ItemGroup>
</Project>

View File

@@ -0,0 +1,121 @@
using System;
using System.IO;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Platform.Storage;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
namespace SolutionParityChecker.App.ViewModels;
public partial class MainWindowViewModel : ViewModelBase
{
public string SolutionFolderPath { get; set; } = string.Empty;
public string SolutionFilePath { get; set; } = string.Empty;
[ObservableProperty]
private string? _fileText;
[RelayCommand]
private async Task OpenFile(CancellationToken token)
{
ErrorMessages?.Clear();
try
{
var file = await DoOpenFilePickerAsync();
if (file is null)
return;
// Limit the text file to 1MB so that the demo won't lag.
if ((await file.GetBasicPropertiesAsync()).Size <= 1024 * 1024 * 1)
{
await using var readStream = await file.OpenReadAsync();
using var reader = new StreamReader(readStream);
FileText = await reader.ReadToEndAsync(token);
}
else
{
throw new Exception("File exceeded 1MB limit.");
}
}
catch (Exception e)
{
ErrorMessages?.Add(e.Message);
}
}
[RelayCommand]
private async Task SaveFile()
{
ErrorMessages?.Clear();
try
{
var file = await DoSaveFilePickerAsync();
if (file is null)
return;
// Limit the text file to 1MB so that the demo won't lag.
if (FileText?.Length <= 1024 * 1024 * 1)
{
var stream = new MemoryStream(Encoding.Default.GetBytes((string)FileText));
await using var writeStream = await file.OpenWriteAsync();
await stream.CopyToAsync(writeStream);
}
else
{
throw new Exception("File exceeded 1MB limit.");
}
}
catch (Exception e)
{
ErrorMessages?.Add(e.Message);
}
}
private async Task<IStorageFile?> DoOpenFilePickerAsync()
{
// For learning purposes, we opted to directly get the reference
// for StorageProvider APIs here inside the ViewModel.
// For your real-world apps, you should follow the MVVM principles
// by making service classes and locating them with DI/IoC.
// See IoCFileOps project for an example of how to accomplish this.
if (
Application.Current?.ApplicationLifetime
is not IClassicDesktopStyleApplicationLifetime desktop
|| desktop.MainWindow?.StorageProvider is not { } provider
)
throw new NullReferenceException("Missing StorageProvider instance.");
var files = await provider.OpenFilePickerAsync(
new FilePickerOpenOptions() { Title = "Open Text File", AllowMultiple = false }
);
return files?.Count >= 1 ? files[0] : null;
}
private async Task<IStorageFile?> DoSaveFilePickerAsync()
{
// For learning purposes, we opted to directly get the reference
// for StorageProvider APIs here inside the ViewModel.
// For your real-world apps, you should follow the MVVM principles
// by making service classes and locating them with DI/IoC.
// See DepInject project for a sample of how to accomplish this.
if (
Application.Current?.ApplicationLifetime
is not IClassicDesktopStyleApplicationLifetime desktop
|| desktop.MainWindow?.StorageProvider is not { } provider
)
throw new NullReferenceException("Missing StorageProvider instance.");
return await provider.SaveFilePickerAsync(
new FilePickerSaveOptions() { Title = "Save Text File" }
);
}
}

View File

@@ -0,0 +1,15 @@
using System.Collections.ObjectModel;
using CommunityToolkit.Mvvm.ComponentModel;
namespace SolutionParityChecker.App.ViewModels;
public partial class ViewModelBase : ObservableObject
{
protected ViewModelBase()
{
ErrorMessages = new ObservableCollection<string>();
}
[ObservableProperty]
private ObservableCollection<string>? _errorMessages;
}

View File

@@ -0,0 +1,29 @@
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:viewModels="clr-namespace:SolutionParityChecker.App.ViewModels"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="SolutionParityChecker.App.Views.MainWindow"
x:DataType="viewModels:MainWindowViewModel"
Icon="/Assets/avalonia-logo.ico"
Title="FileOps">
<Design.DataContext>
<!-- This only sets the DataContext for the previewer in an IDE,
to set the actual DataContext for runtime, set the DataContext property in code (look at App.axaml.cs) -->
<viewModels:MainWindowViewModel/>
</Design.DataContext>
<Panel>
<StackPanel>
<TextBlock>Welcome to Avalonia!</TextBlock>
<Button Click="LoadSolutionFolder">Select Solution Folder</Button>
<Button Click="LoadSolutionFile">Select Solution File</Button>
<Button Command="{Binding OpenFileCommand}">Open File</Button>
<Button Command="{Binding SaveFileCommand}">Save File</Button>
<TextBlock Name="SolutionFilePath" Text="{Binding SolutionFilePath}" />
<TextBlock Name="SolutionFolderPath" Text="{Binding SolutionFolderPath}" />
<ListBox DockPanel.Dock="Bottom" ItemsSource="{Binding ErrorMessages}"/>
</StackPanel>
</Panel>
</Window>

View File

@@ -1,7 +1,7 @@
using Avalonia.Controls;
using Avalonia.Interactivity;
namespace SolutionParityChecker.App;
namespace SolutionParityChecker.App.Views;
public partial class MainWindow : Window
{

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<!-- This manifest is used on Windows only.
Don't remove it as it might cause problems with window transparency and embeded controls.
Don't remove it as it might cause problems with window transparency and embedded controls.
For more details visit https://learn.microsoft.com/en-us/windows/win32/sbscs/application-manifests -->
<assemblyIdentity version="1.0.0.0" name="SolutionParityChecker.App.Desktop"/>