Add Avalonia UI frontend alongside WinForms and WPF
Introduce Avalonia as a new cross-platform UI option, including new XAML and code-behind files for the application and main window. Update Program.cs to support a --avalonia launch argument and add corresponding launch profile. Integrate Avalonia NuGet packages and ensure DataModel supports UI-thread invocation for all frontends. All business logic and state are shared via DI, enabling consistent behavior across WinForms, WPF, and Avalonia.
This commit is contained in:
parent
3ae4da3e49
commit
311b3e76f0
8 changed files with 535 additions and 9 deletions
8
imagecatalog/AvaloniaApp.axaml
Normal file
8
imagecatalog/AvaloniaApp.axaml
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
<Application xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
x:Class="ImageCatalog_2.AvaloniaApp">
|
||||
<Application.Styles>
|
||||
<FluentTheme />
|
||||
<StyleInclude Source="avares://Avalonia.Controls.DataGrid/Themes/Fluent.xaml"/>
|
||||
</Application.Styles>
|
||||
</Application>
|
||||
20
imagecatalog/AvaloniaApp.axaml.cs
Normal file
20
imagecatalog/AvaloniaApp.axaml.cs
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
using Avalonia.Controls.ApplicationLifetimes;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace ImageCatalog_2;
|
||||
|
||||
public partial class AvaloniaApp : Avalonia.Application
|
||||
{
|
||||
public override void Initialize() => AvaloniaXamlLoader.Load(this);
|
||||
|
||||
public override void OnFrameworkInitializationCompleted()
|
||||
{
|
||||
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||
{
|
||||
var model = Program.ServiceProvider.GetRequiredService<DataModel>();
|
||||
desktop.MainWindow = new AvaloniaMainWindow(model);
|
||||
}
|
||||
base.OnFrameworkInitializationCompleted();
|
||||
}
|
||||
}
|
||||
317
imagecatalog/AvaloniaMainWindow.axaml
Normal file
317
imagecatalog/AvaloniaMainWindow.axaml
Normal file
|
|
@ -0,0 +1,317 @@
|
|||
<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"
|
||||
x:Class="ImageCatalog_2.AvaloniaMainWindow"
|
||||
mc:Ignorable="d"
|
||||
Title="Image Catalog - Avalonia" Height="540" Width="800">
|
||||
|
||||
<Grid RowDefinitions="*,Auto">
|
||||
|
||||
<Grid Grid.Row="0" Margin="10">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="3*" />
|
||||
<ColumnDefinition Width="0.8*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- Left: Tabs -->
|
||||
<TabControl Grid.Column="0" Margin="0,0,10,0">
|
||||
|
||||
<!-- Tab 1: Generale -->
|
||||
<TabItem Header="Generale">
|
||||
<ScrollViewer>
|
||||
<StackPanel Margin="8">
|
||||
<TextBlock Text="Percorsi" FontWeight="Bold" />
|
||||
<StackPanel Margin="0,6,0,0">
|
||||
<!-- Source -->
|
||||
<Grid Margin="0,0,0,6" ColumnDefinitions="Auto,*,Auto,Auto">
|
||||
<TextBlock Text="Sorgente:" VerticalAlignment="Center" Margin="0,0,8,0" Grid.Column="0" />
|
||||
<TextBox Text="{Binding SourcePath, Mode=TwoWay}" Grid.Column="1" VerticalAlignment="Center" />
|
||||
<Button Width="88" Margin="8,0,0,0" Command="{Binding SelectSourceFolderCommand}" Grid.Column="2" Content="Scegli..." />
|
||||
<Button Width="56" Margin="8,0,0,0" Grid.Column="3" Click="OpenSourceFolder_Click" Content="Apri" />
|
||||
</Grid>
|
||||
<!-- Destination -->
|
||||
<Grid ColumnDefinitions="Auto,*,Auto,Auto">
|
||||
<TextBlock Text="Destinazione:" VerticalAlignment="Center" Margin="0,0,8,0" Grid.Column="0" />
|
||||
<TextBox Text="{Binding DestinationPath, Mode=TwoWay}" Grid.Column="1" VerticalAlignment="Center" />
|
||||
<Button Width="88" Margin="8,0,0,0" Command="{Binding SelectDestinationFolderCommand}" Grid.Column="2" Content="Scegli..." />
|
||||
<Button Width="56" Margin="8,0,0,0" Grid.Column="3" Click="OpenDestinationFolder_Click" Content="Apri" />
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
|
||||
<TextBlock Text="Opzioni" FontWeight="Bold" Margin="0,12,0,0" />
|
||||
<StackPanel Margin="0,6,0,0">
|
||||
<CheckBox Content="Forza JPEG" IsChecked="{Binding ForceJpeg}" />
|
||||
<CheckBox Content="Aggiorna sottodirectory" IsChecked="{Binding UpdateSubdirectories}" />
|
||||
<CheckBox Content="Crea sottocartelle" IsChecked="{Binding CreateSubfolders}" />
|
||||
<CheckBox Content="Sovrascrivi immagini" IsChecked="{Binding OverwriteImages}" />
|
||||
</StackPanel>
|
||||
|
||||
<TextBlock Text="Elaborazione" FontWeight="Bold" Margin="0,12,0,0" />
|
||||
<StackPanel Orientation="Horizontal" Margin="0,6,0,0">
|
||||
<TextBlock Text="Threads:" VerticalAlignment="Center" />
|
||||
<TextBox Text="{Binding ThreadsCount, Mode=TwoWay}" Width="60" Margin="8,0,0,0" />
|
||||
<TextBlock Text="Chunk:" VerticalAlignment="Center" Margin="12,0,0,0" />
|
||||
<TextBox Text="{Binding ChunkSize, Mode=TwoWay}" Width="60" Margin="8,0,0,0" />
|
||||
</StackPanel>
|
||||
|
||||
<TextBlock Text="Divisione cartelle" FontWeight="Bold" Margin="0,12,0,0" />
|
||||
<StackPanel Orientation="Horizontal" Margin="0,6,0,0">
|
||||
<TextBlock Text="File per cartella:" VerticalAlignment="Center" />
|
||||
<TextBox Text="{Binding FilesPerFolder}" Width="60" Margin="8,0,0,0" />
|
||||
<TextBlock Text="Suffisso:" VerticalAlignment="Center" Margin="12,0,0,0" />
|
||||
<TextBox Text="{Binding FolderSuffix}" Width="120" Margin="8,0,0,0" />
|
||||
</StackPanel>
|
||||
|
||||
<TextBlock Text="Numerazione" FontWeight="Bold" Margin="0,12,0,0" />
|
||||
<StackPanel Orientation="Horizontal" Margin="0,6,0,0">
|
||||
<RadioButton Content="Progressiva" IsChecked="{Binding UseProgressiveNumbering}" GroupName="Num" />
|
||||
<RadioButton Content="Per file" IsChecked="{Binding UseFileNumbering}" GroupName="Num" Margin="8,0,0,0" />
|
||||
<TextBlock Text="Cifre:" VerticalAlignment="Center" Margin="12,0,0,0" />
|
||||
<TextBox Text="{Binding CounterDigits}" Width="40" Margin="8,0,0,0" />
|
||||
</StackPanel>
|
||||
|
||||
<TextBlock Text="Libreria Immagini" FontWeight="Bold" Margin="0,12,0,0" />
|
||||
<StackPanel Orientation="Horizontal" Margin="0,6,0,0">
|
||||
<RadioButton Content="System.Graphics" IsChecked="{Binding UseSystemGraphics}" GroupName="Lib" />
|
||||
<RadioButton Content="ImageSharp" IsChecked="{Binding UseImageSharp}" GroupName="Lib" Margin="8,0,0,0" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</TabItem>
|
||||
|
||||
<!-- Tab 2: Testo -->
|
||||
<TabItem Header="Testo">
|
||||
<ScrollViewer>
|
||||
<StackPanel Margin="8">
|
||||
<TextBlock Text="Testo Orizzontale" FontWeight="Bold" />
|
||||
<TextBox Text="{Binding HorizontalText, Mode=TwoWay}" />
|
||||
|
||||
<TextBlock Text="Testo Verticale" FontWeight="Bold" Margin="0,8,0,0" />
|
||||
<TextBox Text="{Binding VerticalText, Mode=TwoWay}" AcceptsReturn="True"
|
||||
TextWrapping="Wrap" MinHeight="80" />
|
||||
|
||||
<TextBlock Text="Font" FontWeight="Bold" Margin="0,8,0,0" />
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<ComboBox ItemsSource="{Binding AvailableFonts}" SelectedItem="{Binding FontName}" Width="250" />
|
||||
<TextBox Text="{Binding FontSize}" Width="60" Margin="8,0,0,0" />
|
||||
<CheckBox Content="Grassetto" IsChecked="{Binding FontBold}" Margin="8,0,0,0" VerticalAlignment="Center" />
|
||||
</StackPanel>
|
||||
|
||||
<TextBlock Text="Colore testo (hex)" FontWeight="Bold" Margin="0,8,0,0" />
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBox Text="{Binding TextColorRGB}" Width="120" />
|
||||
<Button Content="Seleziona colore" Command="{Binding SelectColorCommand}" Margin="8,0,0,0" />
|
||||
</StackPanel>
|
||||
|
||||
<TextBlock Text="Dimensioni verticale" FontWeight="Bold" Margin="0,8,0,0" />
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Text="Size:" VerticalAlignment="Center" />
|
||||
<TextBox Text="{Binding VerticalTextSize}" Width="60" Margin="8,0,0,0" />
|
||||
<TextBlock Text="Margin:" VerticalAlignment="Center" Margin="12,0,0,0" />
|
||||
<TextBox Text="{Binding VerticalTextMargin}" Width="60" Margin="8,0,0,0" />
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Orientation="Horizontal" Margin="0,6,0,0">
|
||||
<TextBlock Text="Trasparenza testo:" VerticalAlignment="Center" />
|
||||
<TextBox Text="{Binding TextTransparency}" Width="60" Margin="8,0,0,0" />
|
||||
<TextBlock Text="Margine testo:" VerticalAlignment="Center" Margin="12,0,0,0" />
|
||||
<TextBox Text="{Binding TextMargin}" Width="60" Margin="8,0,0,0" />
|
||||
</StackPanel>
|
||||
|
||||
<TextBlock Text="Tempo Gara" FontWeight="Bold" Margin="0,12,0,0" />
|
||||
<StackPanel Orientation="Horizontal" Margin="0,6,0,0">
|
||||
<CheckBox Content="Aggiungi Orario" IsChecked="{Binding AddTime}" />
|
||||
<CheckBox Content="Aggiungi tempo gara" IsChecked="{Binding AddRaceTime}" Margin="12,0,0,0" />
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal" Margin="0,6,0,0">
|
||||
<TextBlock Text="Partenza:" VerticalAlignment="Center" />
|
||||
<CalendarDatePicker SelectedDate="{Binding RaceStartDate}"
|
||||
IsEnabled="{Binding AddRaceTime}"
|
||||
Margin="8,0,0,0" Width="200" />
|
||||
<TextBox Text="{Binding TimeLabel}" Width="220" Margin="12,0,0,0" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</TabItem>
|
||||
|
||||
<!-- Tab 3: Foto -->
|
||||
<TabItem Header="Foto">
|
||||
<ScrollViewer>
|
||||
<StackPanel Margin="8">
|
||||
<TextBlock Text="Dimensioni foto grandi" FontWeight="Bold" />
|
||||
<StackPanel Orientation="Horizontal" Margin="0,6,0,0">
|
||||
<TextBox Text="{Binding PhotoBigWidth}" Width="80" />
|
||||
<TextBox Text="{Binding PhotoBigHeight}" Width="80" Margin="8,0,0,0" />
|
||||
</StackPanel>
|
||||
|
||||
<TextBlock Text="Opzioni foto" FontWeight="Bold" Margin="0,8,0,0" />
|
||||
<StackPanel Margin="0,6,0,0">
|
||||
<CheckBox Content="Mantieni dimensioni originali" IsChecked="{Binding KeepOriginalDimensions}" />
|
||||
<CheckBox Content="Rotazione automatica" IsChecked="{Binding AutomaticRotation}" />
|
||||
</StackPanel>
|
||||
|
||||
<TextBlock Text="JPEG" FontWeight="Bold" Margin="0,8,0,0" />
|
||||
<StackPanel Orientation="Horizontal" Margin="0,6,0,0">
|
||||
<TextBlock Text="Qualità:" VerticalAlignment="Center" />
|
||||
<TextBox Text="{Binding JpegQuality}" Width="60" Margin="8,0,0,0" />
|
||||
<TextBlock Text="Miniature Qualità:" VerticalAlignment="Center" Margin="12,0,0,0" />
|
||||
<TextBox Text="{Binding JpegQualityThumbnail}" Width="60" Margin="8,0,0,0" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</TabItem>
|
||||
|
||||
<!-- Tab 4: Miniature -->
|
||||
<TabItem Header="Miniature">
|
||||
<ScrollViewer>
|
||||
<StackPanel Margin="8">
|
||||
<TextBlock Text="Miniature" FontWeight="Bold" />
|
||||
<CheckBox Content="Crea miniature" IsChecked="{Binding CreateThumbnails}" Margin="0,6,0,0" />
|
||||
<StackPanel Orientation="Horizontal" Margin="0,6,0,0">
|
||||
<TextBlock Text="Prefisso:" VerticalAlignment="Center" />
|
||||
<TextBox Text="{Binding ThumbnailPrefix}" Width="120" Margin="8,0,0,0" />
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal" Margin="0,6,0,0">
|
||||
<TextBox Text="{Binding ThumbnailWidth}" Width="80" />
|
||||
<TextBox Text="{Binding ThumbnailHeight}" Width="80" Margin="8,0,0,0" />
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Margin="0,8,0,0">
|
||||
<TextBlock Text="Modalità miniature:" VerticalAlignment="Center" />
|
||||
<ComboBox SelectedIndex="{Binding ThumbnailOptionIndex, Mode=TwoWay}" Width="220" Margin="0,6,0,0">
|
||||
<ComboBoxItem>Nessuna</ComboBoxItem>
|
||||
<ComboBoxItem>Aggiungi scritta</ComboBoxItem>
|
||||
<ComboBoxItem>Nome file</ComboBoxItem>
|
||||
<ComboBoxItem>Aggiungi orario</ComboBoxItem>
|
||||
<ComboBoxItem>Nome+Orario</ComboBoxItem>
|
||||
<ComboBoxItem>Tempo gara</ComboBoxItem>
|
||||
</ComboBox>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</TabItem>
|
||||
|
||||
<!-- Tab 5: Logo -->
|
||||
<TabItem Header="Logo">
|
||||
<ScrollViewer>
|
||||
<StackPanel Margin="8">
|
||||
<TextBlock Text="Logo" FontWeight="Bold" />
|
||||
<StackPanel Orientation="Horizontal" Margin="0,6,0,0">
|
||||
<Button Command="{Binding SelectLogoFileCommand}" Content="Seleziona logo" />
|
||||
<TextBlock Text="{Binding LogoFile}" Margin="8,0,0,0" VerticalAlignment="Center"
|
||||
Width="250" TextTrimming="CharacterEllipsis" />
|
||||
</StackPanel>
|
||||
<StackPanel Margin="0,8,0,0">
|
||||
<Image Name="LogoPreview" Width="160" Height="160" Stretch="Uniform" />
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal" Margin="0,8,0,0">
|
||||
<TextBox Text="{Binding LogoWidth}" Width="80" />
|
||||
<TextBox Text="{Binding LogoHeight}" Width="80" Margin="8,0,0,0" />
|
||||
</StackPanel>
|
||||
<CheckBox Content="Aggiungi logo" IsChecked="{Binding AddLogo}" Margin="0,8,0,0" />
|
||||
<StackPanel Orientation="Horizontal" Margin="0,8,0,0">
|
||||
<TextBlock Text="Margine:" VerticalAlignment="Center" />
|
||||
<TextBox Text="{Binding LogoMargin}" Width="80" Margin="8,0,0,0" />
|
||||
<TextBlock Text="Trasparenza:" VerticalAlignment="Center" Margin="12,0,0,0" />
|
||||
<TextBox Text="{Binding LogoTransparency}" Width="60" Margin="8,0,0,0" />
|
||||
<Button Command="{Binding SelectTransparentColorCommand}" Margin="8,0,0,0"
|
||||
Content="Seleziona trasparente" />
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal" Margin="0,8,0,0">
|
||||
<TextBlock Text="Posizione:" VerticalAlignment="Center" />
|
||||
<ComboBox ItemsSource="{Binding HorizontalAlignments}"
|
||||
SelectedItem="{Binding LogoHorizontalPosition}"
|
||||
Width="120" Margin="8,0,0,0" />
|
||||
<ComboBox ItemsSource="{Binding VerticalPositions}"
|
||||
SelectedItem="{Binding LogoVerticalPosition}"
|
||||
Width="120" Margin="8,0,0,0" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</TabItem>
|
||||
|
||||
<!-- Tab 6: AI -->
|
||||
<TabItem Header="AI">
|
||||
<ScrollViewer>
|
||||
<StackPanel Margin="8">
|
||||
<TextBlock Text="AI / OCR" FontWeight="Bold" />
|
||||
<CheckBox Content="Estrai numeri dalle immagini" IsChecked="{Binding ExtractNumbers}" Margin="0,8,0,0" />
|
||||
|
||||
<TextBlock Text="Modelli" FontWeight="Bold" Margin="0,12,0,0" />
|
||||
<Grid Margin="0,6,0,0" ColumnDefinitions="Auto,*,Auto,Auto">
|
||||
<TextBlock Text="Cartella modelli:" VerticalAlignment="Center" Margin="0,0,8,0" Grid.Column="0" />
|
||||
<TextBox Text="{Binding ModelsFolderPath, Mode=TwoWay}" Grid.Column="1" VerticalAlignment="Center" />
|
||||
<Button Width="88" Margin="8,0,0,0" Command="{Binding SelectModelsFolderCommand}"
|
||||
Grid.Column="2" Content="Scegli..." />
|
||||
<Button Width="56" Margin="8,0,0,0" Grid.Column="3"
|
||||
Click="OpenModelsFolder_Click" Content="Apri" />
|
||||
</Grid>
|
||||
|
||||
<TextBlock Text="Output CSV" FontWeight="Bold" Margin="0,12,0,0" />
|
||||
<Grid Margin="0,6,0,0" ColumnDefinitions="Auto,*,Auto,Auto">
|
||||
<TextBlock Text="Percorso CSV:" VerticalAlignment="Center" Margin="0,0,8,0" Grid.Column="0" />
|
||||
<TextBox Text="{Binding CsvOutputPath, Mode=TwoWay}" Grid.Column="1" VerticalAlignment="Center" />
|
||||
<Button Width="88" Margin="8,0,0,0" Command="{Binding SelectCsvOutputCommand}"
|
||||
Grid.Column="2" Content="Scegli..." />
|
||||
<Button Width="56" Margin="8,0,0,0" Grid.Column="3"
|
||||
Click="OpenCsvOutputFolder_Click" Content="Apri" />
|
||||
</Grid>
|
||||
|
||||
<TextBlock Text="Anteprima risultati" FontWeight="Bold" Margin="0,12,0,0" />
|
||||
<DataGrid ItemsSource="{Binding PreviewResults}" IsReadOnly="True"
|
||||
AutoGenerateColumns="False" Height="200" Margin="0,6,0,0">
|
||||
<DataGrid.Columns>
|
||||
<DataGridTextColumn Header="Path" Binding="{Binding Path}" Width="*" />
|
||||
<DataGridTextColumn Header="Text" Binding="{Binding Text}" Width="2*" />
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</TabItem>
|
||||
|
||||
</TabControl>
|
||||
|
||||
<!-- Right: Controls and live info -->
|
||||
<StackPanel Grid.Column="1">
|
||||
<StackPanel HorizontalAlignment="Right" Margin="0,0,0,12">
|
||||
<Button Width="28" Height="28" Click="ToggleTheme_Click" ToolTip.Tip="Cambia tema"
|
||||
HorizontalAlignment="Right" Padding="2" Content="🌙" />
|
||||
</StackPanel>
|
||||
<Border BorderBrush="#DDD" BorderThickness="1" Padding="8" MaxWidth="280">
|
||||
<StackPanel>
|
||||
<StackPanel>
|
||||
<Button Width="120" Margin="0,0,0,8" Command="{Binding LoadSettingsCommand}" Content="Carica" />
|
||||
<Button Width="120" Margin="0,0,0,8" Command="{Binding SaveSettingsCommand}" Content="Salva" />
|
||||
<Button Width="120" Height="36" Margin="0,6,0,8"
|
||||
Command="{Binding ProcessImagesCommand}"
|
||||
IsEnabled="{Binding UiEnabled}" Content="Avvia" />
|
||||
<Button Width="120" Height="36"
|
||||
Command="{Binding AsyncCancelOperationCommand}" Content="Stop" />
|
||||
</StackPanel>
|
||||
|
||||
<Separator Margin="0,12,0,12" />
|
||||
|
||||
<TextBlock Text="Stato" FontWeight="Bold" />
|
||||
<TextBlock Text="{Binding ProcessingStatus}" TextWrapping="Wrap" />
|
||||
|
||||
<TextBlock Text="Progresso" FontWeight="Bold" Margin="0,8,0,0" />
|
||||
<ProgressBar Minimum="0" Maximum="{Binding ProgressBarMaximum}"
|
||||
Value="{Binding ProgressBarValue}" Height="20" />
|
||||
<TextBlock Margin="0,6,0,0">
|
||||
<Run Text="{Binding ProcessedImagesCount}" />
|
||||
<Run Text=" / " />
|
||||
<Run Text="{Binding TotalImagesCount}" />
|
||||
</TextBlock>
|
||||
|
||||
<TextBlock Text="Velocità" FontWeight="Bold" Margin="0,8,0,0" />
|
||||
<TextBlock Text="{Binding SpeedCounter}" TextWrapping="Wrap" />
|
||||
|
||||
<TextBlock Text="{Binding AppVersion}" Margin="0,8,0,0" Opacity="0.6" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Window>
|
||||
151
imagecatalog/AvaloniaMainWindow.axaml.cs
Normal file
151
imagecatalog/AvaloniaMainWindow.axaml.cs
Normal file
|
|
@ -0,0 +1,151 @@
|
|||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.Media.Imaging;
|
||||
using Avalonia.Platform.Storage;
|
||||
using Avalonia.Styling;
|
||||
using Avalonia.Threading;
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace ImageCatalog_2;
|
||||
|
||||
public partial class AvaloniaMainWindow : Window
|
||||
{
|
||||
private readonly DataModel _model;
|
||||
private bool _isDarkTheme = false;
|
||||
|
||||
public AvaloniaMainWindow(DataModel model)
|
||||
{
|
||||
InitializeComponent();
|
||||
_model = model;
|
||||
DataContext = _model;
|
||||
|
||||
// Provide Avalonia dispatcher so DataModel can marshal UI updates
|
||||
_model.UiInvoker = action => Dispatcher.UIThread.Invoke(action);
|
||||
|
||||
// Wire dialog events
|
||||
_model.SelectSourceFolderRequested += async (_, _) =>
|
||||
{
|
||||
var folders = await StorageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions { Title = "Seleziona cartella sorgente" });
|
||||
if (folders.Count > 0) _model.SourcePath = folders[0].Path.LocalPath + Path.DirectorySeparatorChar;
|
||||
};
|
||||
|
||||
_model.SelectDestinationFolderRequested += async (_, _) =>
|
||||
{
|
||||
var folders = await StorageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions { Title = "Seleziona cartella destinazione" });
|
||||
if (folders.Count > 0) _model.DestinationPath = folders[0].Path.LocalPath + Path.DirectorySeparatorChar;
|
||||
};
|
||||
|
||||
_model.SelectLogoFileRequested += async (_, _) =>
|
||||
{
|
||||
var files = await StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions
|
||||
{
|
||||
Title = "Seleziona logo",
|
||||
FileTypeFilter = new[] { new FilePickerFileType("Immagini") { Patterns = new[] { "*.jpg", "*.jpeg", "*.png", "*.bmp", "*.gif" } } }
|
||||
});
|
||||
if (files.Count > 0)
|
||||
{
|
||||
_model.LogoFile = files[0].Path.LocalPath;
|
||||
UpdateLogoPreview(_model.LogoFile);
|
||||
}
|
||||
};
|
||||
|
||||
_model.SelectModelsFolderRequested += async (_, _) =>
|
||||
{
|
||||
var folders = await StorageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions { Title = "Seleziona cartella modelli" });
|
||||
if (folders.Count > 0) _model.ModelsFolderPath = folders[0].Path.LocalPath + Path.DirectorySeparatorChar;
|
||||
};
|
||||
|
||||
_model.SelectCsvOutputRequested += async (_, _) =>
|
||||
{
|
||||
var file = await StorageProvider.SaveFilePickerAsync(new FilePickerSaveOptions
|
||||
{
|
||||
Title = "Salva CSV",
|
||||
DefaultExtension = "csv",
|
||||
FileTypeChoices = new[] { new FilePickerFileType("CSV") { Patterns = new[] { "*.csv" } } }
|
||||
});
|
||||
if (file != null) _model.CsvOutputPath = file.Path.LocalPath;
|
||||
};
|
||||
|
||||
_model.SaveSettingsRequested += async (_, _) =>
|
||||
{
|
||||
var file = await StorageProvider.SaveFilePickerAsync(new FilePickerSaveOptions
|
||||
{
|
||||
Title = "Salva impostazioni",
|
||||
DefaultExtension = "xml",
|
||||
FileTypeChoices = new[] { new FilePickerFileType("Setup") { Patterns = new[] { "*.xml" } } }
|
||||
});
|
||||
if (file != null) await _model.SaveSettingsToFileAsync(file.Path.LocalPath);
|
||||
};
|
||||
|
||||
_model.LoadSettingsRequested += async (_, _) =>
|
||||
{
|
||||
var files = await StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions
|
||||
{
|
||||
Title = "Carica impostazioni",
|
||||
FileTypeFilter = new[] { new FilePickerFileType("Setup") { Patterns = new[] { "*.xml" } } }
|
||||
});
|
||||
if (files.Count > 0) await _model.LoadSettingsFromFileAsync(files[0].Path.LocalPath);
|
||||
};
|
||||
|
||||
_model.SelectColorRequested += (_, _) =>
|
||||
{
|
||||
// Color is set by typing hex directly in the TextBox
|
||||
};
|
||||
|
||||
_model.SelectTransparentColorRequested += (_, _) =>
|
||||
{
|
||||
// Color is set by typing hex directly in the TextBox
|
||||
};
|
||||
|
||||
_model.PropertyChanged += (_, e) =>
|
||||
{
|
||||
if (e.PropertyName == nameof(_model.LogoFile))
|
||||
UpdateLogoPreview(_model.LogoFile);
|
||||
};
|
||||
}
|
||||
|
||||
private void ToggleTheme_Click(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
_isDarkTheme = !_isDarkTheme;
|
||||
if (Avalonia.Application.Current != null)
|
||||
Avalonia.Application.Current.RequestedThemeVariant = _isDarkTheme ? ThemeVariant.Dark : ThemeVariant.Light;
|
||||
}
|
||||
|
||||
private void OpenSourceFolder_Click(object? sender, RoutedEventArgs e) => OpenInExplorer(_model.SourcePath);
|
||||
private void OpenDestinationFolder_Click(object? sender, RoutedEventArgs e) => OpenInExplorer(_model.DestinationPath);
|
||||
private void OpenModelsFolder_Click(object? sender, RoutedEventArgs e) => OpenInExplorer(_model.ModelsFolderPath);
|
||||
private void OpenCsvOutputFolder_Click(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
var dir = Path.GetDirectoryName(_model.CsvOutputPath);
|
||||
OpenInExplorer(string.IsNullOrWhiteSpace(dir) ? _model.CsvOutputPath : dir);
|
||||
}
|
||||
|
||||
private static void OpenInExplorer(string? path)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(path)) return;
|
||||
path = path.Trim().Trim('"');
|
||||
try
|
||||
{
|
||||
if (File.Exists(path))
|
||||
System.Diagnostics.Process.Start("explorer.exe", $"/select,\"{path}\"");
|
||||
else if (Directory.Exists(path))
|
||||
System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo { FileName = path, UseShellExecute = true });
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
private void UpdateLogoPreview(string? path)
|
||||
{
|
||||
var preview = this.FindControl<Avalonia.Controls.Image>("LogoPreview");
|
||||
if (preview == null) return;
|
||||
if (string.IsNullOrWhiteSpace(path) || !File.Exists(path))
|
||||
{
|
||||
preview.Source = null;
|
||||
return;
|
||||
}
|
||||
try { preview.Source = new Avalonia.Media.Imaging.Bitmap(path); }
|
||||
catch { preview.Source = null; }
|
||||
}
|
||||
}
|
||||
|
|
@ -207,12 +207,20 @@ namespace ImageCatalog_2
|
|||
return digits;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Optional UI-thread invoker set by the active UI layer (WPF, Avalonia, etc.).
|
||||
/// When set, <see cref="InvokeOnUiThreadAsync"/> uses this delegate instead of the WPF dispatcher.
|
||||
/// </summary>
|
||||
public Action<Action>? UiInvoker { get; set; }
|
||||
|
||||
private Task InvokeOnUiThreadAsync(Action action)
|
||||
{
|
||||
// Use SynchronizationContext via Task to ensure UI thread update
|
||||
return Task.Run(() =>
|
||||
{
|
||||
System.Windows.Application.Current?.Dispatcher.Invoke(action);
|
||||
if (UiInvoker != null)
|
||||
UiInvoker(action);
|
||||
else
|
||||
System.Windows.Application.Current?.Dispatcher.Invoke(action);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -48,6 +48,10 @@
|
|||
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="10.0.3" />
|
||||
<PackageReference Include="MahApps.Metro.IconPacks" Version="6.2.1" />
|
||||
<PackageReference Include="MinVer" Version="7.0.0" PrivateAssets="all" />
|
||||
<PackageReference Include="Avalonia" Version="11.3.0" />
|
||||
<PackageReference Include="Avalonia.Desktop" Version="11.3.0" />
|
||||
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.3.0" />
|
||||
<PackageReference Include="Avalonia.Controls.DataGrid" Version="11.3.0" />
|
||||
<PackageReference Include="Microsoft.DotNet.UpgradeAssistant.Extensions.Default.Analyzers" Version="0.4.421302">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ using Microsoft.Extensions.Logging.Abstractions;
|
|||
using Microsoft.Extensions.Logging.Console;
|
||||
using System.IO;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls.ApplicationLifetimes;
|
||||
|
||||
namespace ImageCatalog_2;
|
||||
|
||||
|
|
@ -56,12 +58,18 @@ static class Program
|
|||
}
|
||||
|
||||
public static IServiceProvider ServiceProvider { get; private set; }
|
||||
|
||||
public static Avalonia.AppBuilder BuildAvaloniaApp()
|
||||
=> Avalonia.AppBuilder.Configure<AvaloniaApp>()
|
||||
.UsePlatformDetect()
|
||||
.LogToTrace();
|
||||
|
||||
[STAThread]
|
||||
static void Main(string[] args)
|
||||
{
|
||||
Application.SetHighDpiMode(HighDpiMode.SystemAware);
|
||||
Application.EnableVisualStyles();
|
||||
Application.SetCompatibleTextRenderingDefault(false);
|
||||
System.Windows.Forms.Application.SetHighDpiMode(HighDpiMode.SystemAware);
|
||||
System.Windows.Forms.Application.EnableVisualStyles();
|
||||
System.Windows.Forms.Application.SetCompatibleTextRenderingDefault(false);
|
||||
|
||||
AllocConsole();
|
||||
RedirectConsoleOutput();
|
||||
|
|
@ -74,8 +82,15 @@ static class Program
|
|||
// Resolve WPF MainWindow when available, otherwise fall back to WinForms MainForm
|
||||
var serviceProvider = ServiceProvider;
|
||||
|
||||
// Determine UI based on command line. Default: WinForms. Use --wpf to explicitly request WPF.
|
||||
// Determine UI based on command line. Default: WinForms. Use --wpf for WPF, --avalonia for Avalonia.
|
||||
bool useWpf = args is not null && Array.Exists(args, a => string.Equals(a, "--wpf", StringComparison.OrdinalIgnoreCase));
|
||||
bool useAvalonia = args is not null && Array.Exists(args, a => string.Equals(a, "--avalonia", StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (useAvalonia)
|
||||
{
|
||||
BuildAvaloniaApp().StartWithClassicDesktopLifetime(args ?? Array.Empty<string>());
|
||||
return;
|
||||
}
|
||||
|
||||
if (useWpf)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,12 +1,15 @@
|
|||
{
|
||||
"profiles": {
|
||||
"ImageCatalog (WinForms)": {
|
||||
"commandName": "Project",
|
||||
"commandLineArgs": ""
|
||||
"commandName": "Project"
|
||||
},
|
||||
"ImageCatalog (WPF)": {
|
||||
"commandName": "Project",
|
||||
"commandLineArgs": "--wpf"
|
||||
},
|
||||
"ImageCatalog (Avalonia)": {
|
||||
"commandName": "Project",
|
||||
"commandLineArgs": "--avalonia"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue