Refactor path handling in UI components
- Removed redundant folder and file opening methods from AiTabView and FaceAiTabView. - Introduced PathPickerField control to streamline path selection and opening functionality across multiple views. - Updated FaceAiTabView and GeneralTabView to utilize PathPickerField for source and destination path selection. - Created PathShellService to encapsulate logic for opening paths in the file explorer. - Simplified XAML structure by replacing manual grid definitions with PathPickerField components. - Removed unused namespaces and cleaned up code for better readability and maintainability.
This commit is contained in:
parent
f3ac1ea920
commit
398cfa310e
10 changed files with 484 additions and 562 deletions
|
|
@ -1,6 +1,7 @@
|
|||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:avaloniaDataGrid="clr-namespace:Avalonia.Controls;assembly=Avalonia.Controls.DataGrid"
|
||||
xmlns:controls="clr-namespace:ImageCatalog_2.Controls"
|
||||
xmlns:iconPacks="https://github.com/MahApps/IconPacks.Avalonia"
|
||||
x:Class="ImageCatalog_2.AvaloniaViews.AiTabView"
|
||||
x:CompileBindings="False">
|
||||
|
|
@ -31,25 +32,13 @@
|
|||
FontWeight="SemiBold" />
|
||||
</Grid>
|
||||
|
||||
<Grid ColumnDefinitions="Auto,*,Auto,Auto" ColumnSpacing="6">
|
||||
<TextBlock Grid.Column="0" Text="Sorgente:" VerticalAlignment="Center" />
|
||||
<TextBox Grid.Column="1"
|
||||
Text="{Binding DestinationPath, Mode=OneWay}"
|
||||
IsReadOnly="True"
|
||||
VerticalAlignment="Center" />
|
||||
<Button Grid.Column="2" Width="104" Command="{Binding SelectDestinationFolderCommand}">
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Spacing="6">
|
||||
<iconPacks:PackIconMaterial Kind="FolderOpenOutline" Width="14" Height="14" />
|
||||
<TextBlock Text="Scegli..." />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
<Button Grid.Column="3" Width="72" Click="OpenAiSourceFolder_Click">
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Spacing="6">
|
||||
<iconPacks:PackIconMaterial Kind="Folder" Width="14" Height="14" />
|
||||
<TextBlock Text="Apri" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</Grid>
|
||||
<controls:PathPickerField Label="Sorgente:"
|
||||
Text="{Binding DestinationPath, Mode=TwoWay}"
|
||||
IsTextReadOnly="True"
|
||||
PreferenceKey="Picker.DestinationFolder.LastPath"
|
||||
PickerTitle="Seleziona cartella sorgente AI"
|
||||
PickerMode="Folder"
|
||||
AppendDirectorySeparator="True" />
|
||||
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Left" Spacing="8">
|
||||
<Button Command="{Binding StartAiCommand}" Width="132">
|
||||
|
|
@ -67,22 +56,14 @@
|
|||
</StackPanel>
|
||||
|
||||
<TextBlock Text="Output CSV" FontWeight="Bold" />
|
||||
<Grid ColumnDefinitions="Auto,*,Auto,Auto" ColumnSpacing="6">
|
||||
<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="104" Command="{Binding SelectCsvOutputCommand}" Grid.Column="2">
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Spacing="6">
|
||||
<iconPacks:PackIconMaterial Kind="FileOutline" Width="14" Height="14" />
|
||||
<TextBlock Text="Scegli..." />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
<Button Width="72" Grid.Column="3" Click="OpenCsvOutputFolder_Click">
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Spacing="6">
|
||||
<iconPacks:PackIconMaterial Kind="Folder" Width="14" Height="14" />
|
||||
<TextBlock Text="Apri" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</Grid>
|
||||
<controls:PathPickerField Label="Percorso CSV:"
|
||||
Text="{Binding CsvOutputPath, Mode=TwoWay}"
|
||||
PreferenceKey="Picker.CsvOutput.LastPath"
|
||||
PickerTitle="Salva CSV"
|
||||
PickerMode="SaveFile"
|
||||
FileTypeName="CSV"
|
||||
FilePatterns="*.csv"
|
||||
DefaultExtension="csv" />
|
||||
|
||||
<TextBlock Text="Anteprima risultati" FontWeight="Bold" />
|
||||
<ProgressBar Minimum="0" Maximum="100" Value="{Binding AiProgress}" Height="16" Margin="0,0,0,4" />
|
||||
|
|
@ -108,22 +89,12 @@
|
|||
<ScrollViewer VerticalScrollBarVisibility="Auto">
|
||||
<StackPanel Margin="8" Spacing="8">
|
||||
<TextBlock Text="Modelli" FontWeight="Bold" />
|
||||
<Grid ColumnDefinitions="Auto,*,Auto,Auto" ColumnSpacing="6">
|
||||
<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="104" Command="{Binding SelectModelsFolderCommand}" Grid.Column="2">
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Spacing="6">
|
||||
<iconPacks:PackIconMaterial Kind="FolderOpenOutline" Width="14" Height="14" />
|
||||
<TextBlock Text="Scegli..." />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
<Button Width="72" Grid.Column="3" Click="OpenModelsFolder_Click">
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Spacing="6">
|
||||
<iconPacks:PackIconMaterial Kind="Folder" Width="14" Height="14" />
|
||||
<TextBlock Text="Apri" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</Grid>
|
||||
<controls:PathPickerField Label="Cartella modelli:"
|
||||
Text="{Binding ModelsFolderPath, Mode=TwoWay}"
|
||||
PreferenceKey="Picker.ModelsFolder.LastPath"
|
||||
PickerTitle="Seleziona cartella modelli"
|
||||
PickerMode="Folder"
|
||||
AppendDirectorySeparator="True" />
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</TabItem>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,4 @@
|
|||
using Avalonia.Controls;
|
||||
using Avalonia.Interactivity;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
|
||||
namespace ImageCatalog_2.AvaloniaViews;
|
||||
|
||||
public partial class AiTabView : Avalonia.Controls.UserControl
|
||||
|
|
@ -11,56 +7,4 @@ public partial class AiTabView : Avalonia.Controls.UserControl
|
|||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void OpenModelsFolder_Click(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
if (DataContext is DataModel model)
|
||||
{
|
||||
OpenInExplorer(model.ModelsFolderPath);
|
||||
}
|
||||
}
|
||||
|
||||
private void OpenCsvOutputFolder_Click(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
if (DataContext is not DataModel model)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var directory = Path.GetDirectoryName(model.CsvOutputPath);
|
||||
OpenInExplorer(string.IsNullOrWhiteSpace(directory) ? model.CsvOutputPath : directory);
|
||||
}
|
||||
|
||||
private void OpenAiSourceFolder_Click(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
if (DataContext is DataModel model)
|
||||
{
|
||||
OpenInExplorer(model.DestinationPath);
|
||||
}
|
||||
}
|
||||
|
||||
private static void OpenInExplorer(string? path)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(path))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var normalizedPath = path.Trim().Trim('"');
|
||||
try
|
||||
{
|
||||
if (File.Exists(normalizedPath))
|
||||
{
|
||||
Process.Start("explorer.exe", $"/select,\"{normalizedPath}\"");
|
||||
}
|
||||
else if (Directory.Exists(normalizedPath))
|
||||
{
|
||||
Process.Start(new ProcessStartInfo { FileName = normalizedPath, UseShellExecute = true });
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore failures when opening Explorer.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:avaloniaDataGrid="clr-namespace:Avalonia.Controls;assembly=Avalonia.Controls.DataGrid"
|
||||
xmlns:controls="clr-namespace:ImageCatalog_2.Controls"
|
||||
xmlns:iconPacks="https://github.com/MahApps/IconPacks.Avalonia"
|
||||
xmlns:converters="clr-namespace:ImageCatalog_2.Converters"
|
||||
x:Class="ImageCatalog_2.AvaloniaViews.FaceAiTabView"
|
||||
|
|
@ -37,34 +38,21 @@
|
|||
<TextBlock Grid.Column="4" Text="Usa --multicore in CPU e --multiprocess in GPU." VerticalAlignment="Center" TextWrapping="Wrap" Opacity="0.75" />
|
||||
</Grid>
|
||||
|
||||
<Grid ColumnDefinitions="Auto,*,Auto,Auto" ColumnSpacing="6">
|
||||
<TextBlock Grid.Column="0" Text="Sorgente:" VerticalAlignment="Center" />
|
||||
<TextBox Grid.Column="1" Name="FaceDestinationPathTextBox" Text="{Binding DestinationPath, Mode=OneWay}" IsReadOnly="True" />
|
||||
<Button Grid.Column="3" Name="FaceOpenDestinationButton" Click="OpenFaceDestinationFolder_Click" Width="72" Margin="6,0,0,0">
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Spacing="6">
|
||||
<iconPacks:PackIconMaterial Kind="Folder" Width="14" Height="14" />
|
||||
<TextBlock Text="Apri" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</Grid>
|
||||
<controls:PathPickerField Label="Sorgente:"
|
||||
Text="{Binding DestinationPath, Mode=TwoWay}"
|
||||
IsTextReadOnly="True"
|
||||
PreferenceKey="Picker.DestinationFolder.LastPath"
|
||||
PickerTitle="Seleziona cartella sorgente Face Encoder"
|
||||
PickerMode="Folder"
|
||||
AppendDirectorySeparator="True" />
|
||||
|
||||
<TextBlock Text="Output encodings" FontWeight="Bold" Margin="0,4,0,0" />
|
||||
<Grid ColumnDefinitions="Auto,*,Auto,Auto" ColumnSpacing="6">
|
||||
<TextBlock Grid.Column="0" Text="Cartella out:" VerticalAlignment="Center" />
|
||||
<TextBox Grid.Column="1" Name="FaceOutputFolderTextBox" Text="{Binding FaceOutputFolderPath, Mode=TwoWay}" Watermark="C:\output\face_encoder" />
|
||||
<Button Grid.Column="2" Name="FaceSelectOutputButton" Click="SelectFaceOutputFolder_Click" Width="104" Margin="6,0,0,0">
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Spacing="6">
|
||||
<iconPacks:PackIconMaterial Kind="FolderOutline" Width="14" Height="14" />
|
||||
<TextBlock Text="Scegli..." />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
<Button Grid.Column="3" Name="FaceOpenOutputButton" Click="OpenFaceOutputFolder_Click" Width="72" Margin="6,0,0,0">
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Spacing="6">
|
||||
<iconPacks:PackIconMaterial Kind="Folder" Width="14" Height="14" />
|
||||
<TextBlock Text="Apri" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</Grid>
|
||||
<controls:PathPickerField Label="Cartella out:"
|
||||
Text="{Binding FaceOutputFolderPath, Mode=TwoWay}"
|
||||
Watermark="C:\output\face_encoder"
|
||||
PreferenceKey="Picker.FaceOutputFolder.LastPath"
|
||||
PickerTitle="Seleziona la cartella output per encodings e log"
|
||||
PickerMode="Folder" />
|
||||
<TextBlock Text="I file vengono creati come face_encodings_yyyyMMdd_HHmmss_nomecartella.pkl e encoder_log_yyyyMMdd_HHmmss_nomecartella.txt."
|
||||
TextWrapping="Wrap"
|
||||
Opacity="0.75" />
|
||||
|
|
@ -116,22 +104,12 @@
|
|||
TextWrapping="Wrap"
|
||||
Opacity="0.75" />
|
||||
|
||||
<Grid ColumnDefinitions="Auto,*,Auto,Auto" ColumnSpacing="6">
|
||||
<TextBlock Grid.Column="0" Text="Percorso:" VerticalAlignment="Center" />
|
||||
<TextBox Grid.Column="1" Name="FaceExecutablePathTextBox" Text="{Binding FaceExecutablePath, Mode=TwoWay}" Watermark="C:\tools\Face_Recognition_Windows" />
|
||||
<Button Grid.Column="2" Name="FaceSelectExecutableButton" Click="SelectFaceExecutable_Click" Width="104" Margin="6,0,0,0">
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Spacing="6">
|
||||
<iconPacks:PackIconMaterial Kind="FolderOutline" Width="14" Height="14" />
|
||||
<TextBlock Text="Scegli..." />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
<Button Grid.Column="3" Name="FaceOpenExecutableButton" Click="OpenFaceExecutableFolder_Click" Width="72" Margin="6,0,0,0">
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Spacing="6">
|
||||
<iconPacks:PackIconMaterial Kind="Folder" Width="14" Height="14" />
|
||||
<TextBlock Text="Apri" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</Grid>
|
||||
<controls:PathPickerField Label="Percorso:"
|
||||
Text="{Binding FaceExecutablePath, Mode=TwoWay}"
|
||||
Watermark="C:\tools\Face_Recognition_Windows"
|
||||
PreferenceKey="Picker.FaceExecutableFolder.LastPath"
|
||||
PickerTitle="Seleziona la cartella Face Recognition Windows"
|
||||
PickerMode="Folder" />
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</TabItem>
|
||||
|
|
@ -146,40 +124,24 @@
|
|||
TextWrapping="Wrap" Opacity="0.8" />
|
||||
|
||||
<TextBlock Text="Eseguibile matcher" FontWeight="Bold" Margin="0,4,0,0" />
|
||||
<Grid ColumnDefinitions="Auto,*,Auto,Auto" ColumnSpacing="6">
|
||||
<TextBlock Grid.Column="0" Text="Percorso:" VerticalAlignment="Center" />
|
||||
<TextBox Grid.Column="1" Name="FaceMatcherExecutablePathTextBox" Text="{Binding FaceMatcherExecutablePath, Mode=TwoWay}" Watermark="C:\tools\Face_Recognition_Windows\face_matcher.exe" />
|
||||
<Button Grid.Column="2" Click="SelectFaceMatcherExecutable_Click" Width="104" Margin="6,0,0,0">
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Spacing="6">
|
||||
<iconPacks:PackIconMaterial Kind="FileOutline" Width="14" Height="14" />
|
||||
<TextBlock Text="Scegli..." />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
<Button Grid.Column="3" Click="OpenFaceMatcherExecutable_Click" Width="72" Margin="6,0,0,0">
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Spacing="6">
|
||||
<iconPacks:PackIconMaterial Kind="Folder" Width="14" Height="14" />
|
||||
<TextBlock Text="Apri" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</Grid>
|
||||
<controls:PathPickerField Label="Percorso:"
|
||||
Text="{Binding FaceMatcherExecutablePath, Mode=TwoWay}"
|
||||
Watermark="C:\tools\Face_Recognition_Windows\face_matcher.exe"
|
||||
PreferenceKey="Picker.FaceMatcherExecutable.LastPath"
|
||||
PickerTitle="Seleziona face_matcher.exe"
|
||||
PickerMode="OpenFile"
|
||||
FileTypeName="Eseguibile"
|
||||
FilePatterns="*.exe" />
|
||||
|
||||
<TextBlock Text="Immagine da testare" FontWeight="Bold" Margin="0,4,0,0" />
|
||||
<Grid ColumnDefinitions="Auto,*,Auto,Auto" ColumnSpacing="6">
|
||||
<TextBlock Grid.Column="0" Text="Immagine:" VerticalAlignment="Center" />
|
||||
<TextBox Grid.Column="1" Name="FaceMatcherImagePathTextBox" Text="{Binding FaceMatcherSelectedImagePath, Mode=TwoWay}" Watermark="Percorso immagine da usare per il match" />
|
||||
<Button Grid.Column="2" Click="SelectFaceMatcherImage_Click" Width="104" Margin="6,0,0,0">
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Spacing="6">
|
||||
<iconPacks:PackIconMaterial Kind="ImageOutline" Width="14" Height="14" />
|
||||
<TextBlock Text="Scegli..." />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
<Button Grid.Column="3" Click="OpenFaceMatcherImage_Click" Width="72" Margin="6,0,0,0">
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Spacing="6">
|
||||
<iconPacks:PackIconMaterial Kind="Folder" Width="14" Height="14" />
|
||||
<TextBlock Text="Apri" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</Grid>
|
||||
<controls:PathPickerField Label="Immagine:"
|
||||
Text="{Binding FaceMatcherSelectedImagePath, Mode=TwoWay}"
|
||||
Watermark="Percorso immagine da usare per il match"
|
||||
PreferenceKey="Picker.FaceMatcherImage.LastPath"
|
||||
PickerTitle="Seleziona immagine per il match"
|
||||
PickerMode="OpenFile"
|
||||
FileTypeName="Immagini"
|
||||
FilePatterns="*.jpg;*.jpeg;*.png;*.bmp;*.gif;*.webp" />
|
||||
<TextBlock Text="Questa immagine resta solo runtime e non viene salvata nel file impostazioni."
|
||||
TextWrapping="Wrap"
|
||||
Opacity="0.75" />
|
||||
|
|
@ -193,68 +155,43 @@
|
|||
</Border>
|
||||
</StackPanel>
|
||||
|
||||
<Grid ColumnDefinitions="Auto,*,Auto" ColumnSpacing="6">
|
||||
<TextBlock Grid.Column="0" Text="Destinazione attuale:" VerticalAlignment="Center" />
|
||||
<TextBox Grid.Column="1" Name="FaceMatcherDestinationPathTextBox" Text="{Binding DestinationPath, Mode=OneWay}" IsReadOnly="True" />
|
||||
<Button Grid.Column="2" Click="OpenFaceDestinationFolder_Click" Width="72" Margin="6,0,0,0">
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Spacing="6">
|
||||
<iconPacks:PackIconMaterial Kind="Folder" Width="14" Height="14" />
|
||||
<TextBlock Text="Apri" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</Grid>
|
||||
<controls:PathPickerField Label="Destinazione attuale:"
|
||||
Text="{Binding DestinationPath, Mode=TwoWay}"
|
||||
IsTextReadOnly="True"
|
||||
PreferenceKey="Picker.DestinationFolder.LastPath"
|
||||
PickerTitle="Seleziona cartella destinazione Face Matcher"
|
||||
PickerMode="Folder"
|
||||
AppendDirectorySeparator="True" />
|
||||
|
||||
<TextBlock Text="Encodings e file di appoggio" FontWeight="Bold" Margin="0,4,0,0" />
|
||||
<Grid ColumnDefinitions="Auto,*,Auto,Auto" ColumnSpacing="6">
|
||||
<TextBlock Grid.Column="0" Text="Encodings .pkl:" VerticalAlignment="Center" />
|
||||
<TextBox Grid.Column="1" Name="FaceMatcherEncodingsPathTextBox" Text="{Binding FaceMatcherEncodingsPath, Mode=TwoWay}" Watermark="Se vuoto usa l'ultimo .pkl in output encodings" />
|
||||
<Button Grid.Column="2" Click="SelectFaceMatcherEncodings_Click" Width="104" Margin="6,0,0,0">
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Spacing="6">
|
||||
<iconPacks:PackIconMaterial Kind="FileCodeOutline" Width="14" Height="14" />
|
||||
<TextBlock Text="Scegli..." />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
<Button Grid.Column="3" Click="OpenFaceMatcherEncodings_Click" Width="72" Margin="6,0,0,0">
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Spacing="6">
|
||||
<iconPacks:PackIconMaterial Kind="Folder" Width="14" Height="14" />
|
||||
<TextBlock Text="Apri" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</Grid>
|
||||
<controls:PathPickerField Label="Encodings .pkl:"
|
||||
Text="{Binding FaceMatcherEncodingsPath, Mode=TwoWay}"
|
||||
Watermark="Se vuoto usa l'ultimo .pkl in output encodings"
|
||||
PreferenceKey="Picker.FaceMatcherEncodings.LastPath"
|
||||
PickerTitle="Seleziona file encodings .pkl"
|
||||
PickerMode="OpenFile"
|
||||
FileTypeName="Encodings"
|
||||
FilePatterns="*.pkl" />
|
||||
|
||||
<Grid ColumnDefinitions="Auto,*,Auto,Auto" ColumnSpacing="6">
|
||||
<TextBlock Grid.Column="0" Text="Risultato CSV:" VerticalAlignment="Center" />
|
||||
<TextBox Grid.Column="1" Name="FaceMatcherOutputPathTextBox" Text="{Binding FaceMatcherOutputPath, Mode=TwoWay}" Watermark="Opzionale: file/cartella output CSV" />
|
||||
<Button Grid.Column="2" Click="SelectFaceMatcherOutput_Click" Width="104" Margin="6,0,0,0">
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Spacing="6">
|
||||
<iconPacks:PackIconMaterial Kind="FileDelimitedOutline" Width="14" Height="14" />
|
||||
<TextBlock Text="Scegli..." />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
<Button Grid.Column="3" Click="OpenFaceMatcherOutput_Click" Width="72" Margin="6,0,0,0">
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Spacing="6">
|
||||
<iconPacks:PackIconMaterial Kind="Folder" Width="14" Height="14" />
|
||||
<TextBlock Text="Apri" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</Grid>
|
||||
<controls:PathPickerField Label="Risultato CSV:"
|
||||
Text="{Binding FaceMatcherOutputPath, Mode=TwoWay}"
|
||||
Watermark="Opzionale: file/cartella output CSV"
|
||||
PreferenceKey="Picker.FaceMatcherOutput.LastPath"
|
||||
PickerTitle="Seleziona output CSV del matcher"
|
||||
PickerMode="SaveFile"
|
||||
FileTypeName="CSV"
|
||||
FilePatterns="*.csv"
|
||||
DefaultExtension="csv" />
|
||||
|
||||
<Grid ColumnDefinitions="Auto,*,Auto,Auto" ColumnSpacing="6">
|
||||
<TextBlock Grid.Column="0" Text="Log matcher:" VerticalAlignment="Center" />
|
||||
<TextBox Grid.Column="1" Name="FaceMatcherLogPathTextBox" Text="{Binding FaceMatcherLogPath, Mode=TwoWay}" Watermark="Opzionale: file/cartella log txt" />
|
||||
<Button Grid.Column="2" Click="SelectFaceMatcherLog_Click" Width="104" Margin="6,0,0,0">
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Spacing="6">
|
||||
<iconPacks:PackIconMaterial Kind="FileDocumentOutline" Width="14" Height="14" />
|
||||
<TextBlock Text="Scegli..." />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
<Button Grid.Column="3" Click="OpenFaceMatcherLog_Click" Width="72" Margin="6,0,0,0">
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Spacing="6">
|
||||
<iconPacks:PackIconMaterial Kind="Folder" Width="14" Height="14" />
|
||||
<TextBlock Text="Apri" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</Grid>
|
||||
<controls:PathPickerField Label="Log matcher:"
|
||||
Text="{Binding FaceMatcherLogPath, Mode=TwoWay}"
|
||||
Watermark="Opzionale: file/cartella log txt"
|
||||
PreferenceKey="Picker.FaceMatcherLog.LastPath"
|
||||
PickerTitle="Seleziona log TXT del matcher"
|
||||
PickerMode="SaveFile"
|
||||
FileTypeName="Log"
|
||||
FilePatterns="*.txt;*.log"
|
||||
DefaultExtension="txt" />
|
||||
|
||||
<Grid ColumnDefinitions="Auto,100,*" ColumnSpacing="6">
|
||||
<TextBlock Grid.Column="0" Text="Tolleranza:" VerticalAlignment="Center" />
|
||||
|
|
|
|||
|
|
@ -4,15 +4,11 @@ using Avalonia.Input;
|
|||
using Avalonia.Layout;
|
||||
using Avalonia.Media;
|
||||
using Avalonia.Media.Imaging;
|
||||
using Avalonia.Platform.Storage;
|
||||
using Avalonia.Threading;
|
||||
using ImageCatalog_2.Models;
|
||||
using ImageCatalog_2.Services;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
|
@ -22,11 +18,9 @@ namespace ImageCatalog_2.AvaloniaViews;
|
|||
public partial class FaceAiTabView : Avalonia.Controls.UserControl
|
||||
{
|
||||
private INotifyPropertyChanged? _faceAiPropertySource;
|
||||
private readonly PickerPreferenceService _pickerPreferenceService;
|
||||
|
||||
public FaceAiTabView()
|
||||
{
|
||||
_pickerPreferenceService = Program.ServiceProvider.GetRequiredService<PickerPreferenceService>();
|
||||
InitializeComponent();
|
||||
DataContextChanged += OnDataContextChanged;
|
||||
}
|
||||
|
|
@ -74,142 +68,6 @@ public partial class FaceAiTabView : Avalonia.Controls.UserControl
|
|||
});
|
||||
}
|
||||
|
||||
private async void SelectFaceExecutable_Click(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
var currentPath = DataContext is DataModel currentModel ? currentModel.FaceExecutablePath : null;
|
||||
var folders = await OpenFolderPickerAsync("Seleziona la cartella Face Recognition Windows", PickerPreferenceKeys.FaceExecutableFolder, currentPath);
|
||||
if (folders.Count > 0 && DataContext is DataModel model)
|
||||
{
|
||||
model.FaceExecutablePath = folders[0].Path.LocalPath;
|
||||
_pickerPreferenceService.RememberPath(PickerPreferenceKeys.FaceExecutableFolder, model.FaceExecutablePath);
|
||||
}
|
||||
}
|
||||
|
||||
private async void SelectFaceOutputFolder_Click(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
var currentPath = DataContext is DataModel currentModel ? currentModel.FaceOutputFolderPath : null;
|
||||
var folders = await OpenFolderPickerAsync("Seleziona la cartella output per encodings e log", PickerPreferenceKeys.FaceOutputFolder, currentPath);
|
||||
if (folders.Count > 0 && DataContext is DataModel model)
|
||||
{
|
||||
model.FaceOutputFolderPath = folders[0].Path.LocalPath;
|
||||
_pickerPreferenceService.RememberPath(PickerPreferenceKeys.FaceOutputFolder, model.FaceOutputFolderPath);
|
||||
}
|
||||
}
|
||||
|
||||
private async void SelectFaceMatcherExecutable_Click(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
var files = await OpenFilePickerAsync(
|
||||
"Seleziona face_matcher.exe",
|
||||
[new FilePickerFileType("Eseguibile") { Patterns = ["*.exe"] }],
|
||||
PickerPreferenceKeys.FaceMatcherExecutable,
|
||||
DataContext is DataModel currentModel ? currentModel.FaceMatcherExecutablePath : null);
|
||||
|
||||
if (files.Count > 0 && DataContext is DataModel model)
|
||||
{
|
||||
model.FaceMatcherExecutablePath = files[0].Path.LocalPath;
|
||||
_pickerPreferenceService.RememberPath(PickerPreferenceKeys.FaceMatcherExecutable, model.FaceMatcherExecutablePath);
|
||||
}
|
||||
}
|
||||
|
||||
private async void SelectFaceMatcherImage_Click(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
var files = await OpenFilePickerAsync(
|
||||
"Seleziona immagine per il match",
|
||||
[new FilePickerFileType("Immagini") { Patterns = ["*.jpg", "*.jpeg", "*.png", "*.bmp", "*.gif", "*.webp"] }],
|
||||
PickerPreferenceKeys.FaceMatcherImage,
|
||||
DataContext is DataModel currentModel ? currentModel.FaceMatcherSelectedImagePath : null);
|
||||
|
||||
if (files.Count > 0 && DataContext is DataModel model)
|
||||
{
|
||||
model.FaceMatcherSelectedImagePath = files[0].Path.LocalPath;
|
||||
_pickerPreferenceService.RememberPath(PickerPreferenceKeys.FaceMatcherImage, model.FaceMatcherSelectedImagePath);
|
||||
}
|
||||
}
|
||||
|
||||
private async void SelectFaceMatcherEncodings_Click(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
var files = await OpenFilePickerAsync(
|
||||
"Seleziona file encodings .pkl",
|
||||
[new FilePickerFileType("Encodings") { Patterns = ["*.pkl"] }],
|
||||
PickerPreferenceKeys.FaceMatcherEncodings,
|
||||
DataContext is DataModel currentModel ? currentModel.FaceMatcherEncodingsPath : null);
|
||||
|
||||
if (files.Count > 0 && DataContext is DataModel model)
|
||||
{
|
||||
model.FaceMatcherEncodingsPath = files[0].Path.LocalPath;
|
||||
_pickerPreferenceService.RememberPath(PickerPreferenceKeys.FaceMatcherEncodings, model.FaceMatcherEncodingsPath);
|
||||
}
|
||||
}
|
||||
|
||||
private async void SelectFaceMatcherOutput_Click(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
var file = await SaveFilePickerAsync(
|
||||
"Seleziona output CSV del matcher",
|
||||
"csv",
|
||||
[new FilePickerFileType("CSV") { Patterns = ["*.csv"] }],
|
||||
PickerPreferenceKeys.FaceMatcherOutput,
|
||||
DataContext is DataModel currentModel ? currentModel.FaceMatcherOutputPath : null);
|
||||
|
||||
if (file is not null && DataContext is DataModel model)
|
||||
{
|
||||
model.FaceMatcherOutputPath = file.Path.LocalPath;
|
||||
_pickerPreferenceService.RememberPath(PickerPreferenceKeys.FaceMatcherOutput, model.FaceMatcherOutputPath);
|
||||
}
|
||||
}
|
||||
|
||||
private async void SelectFaceMatcherLog_Click(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
var file = await SaveFilePickerAsync(
|
||||
"Seleziona log TXT del matcher",
|
||||
"txt",
|
||||
[new FilePickerFileType("Log") { Patterns = ["*.txt", "*.log"] }],
|
||||
PickerPreferenceKeys.FaceMatcherLog,
|
||||
DataContext is DataModel currentModel ? currentModel.FaceMatcherLogPath : null);
|
||||
|
||||
if (file is not null && DataContext is DataModel model)
|
||||
{
|
||||
model.FaceMatcherLogPath = file.Path.LocalPath;
|
||||
_pickerPreferenceService.RememberPath(PickerPreferenceKeys.FaceMatcherLog, model.FaceMatcherLogPath);
|
||||
}
|
||||
}
|
||||
|
||||
private void OpenFaceExecutableFolder_Click(object? sender, RoutedEventArgs e) => OpenFromTextBox("FaceExecutablePathTextBox");
|
||||
|
||||
private void OpenFaceOutputFolder_Click(object? sender, RoutedEventArgs e) => OpenFromTextBox("FaceOutputFolderTextBox");
|
||||
|
||||
private void OpenFaceMatcherExecutable_Click(object? sender, RoutedEventArgs e) => OpenFromTextBox("FaceMatcherExecutablePathTextBox");
|
||||
|
||||
private void OpenFaceMatcherImage_Click(object? sender, RoutedEventArgs e) => OpenFromTextBox("FaceMatcherImagePathTextBox");
|
||||
|
||||
private void OpenFaceMatcherEncodings_Click(object? sender, RoutedEventArgs e) => OpenFromTextBox("FaceMatcherEncodingsPathTextBox");
|
||||
|
||||
private void OpenFaceMatcherOutput_Click(object? sender, RoutedEventArgs e) => OpenFromTextBox("FaceMatcherOutputPathTextBox");
|
||||
|
||||
private void OpenFaceMatcherLog_Click(object? sender, RoutedEventArgs e) => OpenFromTextBox("FaceMatcherLogPathTextBox");
|
||||
|
||||
private void OpenFaceDestinationFolder_Click(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
string? path = null;
|
||||
if (DataContext is DataModel model)
|
||||
{
|
||||
path = (model.DestinationPath ?? string.Empty).Trim();
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(path))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (Directory.Exists(path))
|
||||
{
|
||||
OpenInExplorer(path);
|
||||
return;
|
||||
}
|
||||
|
||||
var directory = Path.GetDirectoryName(path);
|
||||
OpenInExplorer(string.IsNullOrWhiteSpace(directory) ? path : directory);
|
||||
}
|
||||
|
||||
private async void OpenFaceMatcherPreview_Click(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
if (sender is not Button { Tag: FaceMatcherResultItem item })
|
||||
|
|
@ -444,14 +302,14 @@ public partial class FaceAiTabView : Avalonia.Controls.UserControl
|
|||
Grid.SetRow(footer, 2);
|
||||
|
||||
var openFileButton = new Button { Content = "Apri file" };
|
||||
openFileButton.Click += (_, _) => OpenInExplorer(item.ResolvedImagePath);
|
||||
openFileButton.Click += (_, _) => PathShellService.OpenInExplorer(item.ResolvedImagePath);
|
||||
footer.Children.Add(openFileButton);
|
||||
|
||||
var openFolderButton = new Button { Content = "Apri cartella" };
|
||||
openFolderButton.Click += (_, _) =>
|
||||
{
|
||||
var directory = Path.GetDirectoryName(item.ResolvedImagePath);
|
||||
OpenInExplorer(string.IsNullOrWhiteSpace(directory) ? item.ResolvedImagePath : directory);
|
||||
PathShellService.OpenInExplorer(string.IsNullOrWhiteSpace(directory) ? item.ResolvedImagePath : directory);
|
||||
};
|
||||
footer.Children.Add(openFolderButton);
|
||||
|
||||
|
|
@ -464,104 +322,4 @@ public partial class FaceAiTabView : Avalonia.Controls.UserControl
|
|||
return dialog;
|
||||
}
|
||||
|
||||
private void OpenFromTextBox(string textBoxName)
|
||||
{
|
||||
var textBox = this.FindControl<Avalonia.Controls.TextBox>(textBoxName);
|
||||
var path = textBox?.Text?.Trim();
|
||||
if (string.IsNullOrWhiteSpace(path))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (Directory.Exists(path) || File.Exists(path))
|
||||
{
|
||||
OpenInExplorer(path);
|
||||
return;
|
||||
}
|
||||
|
||||
var directory = Path.GetDirectoryName(path);
|
||||
OpenInExplorer(string.IsNullOrWhiteSpace(directory) ? path : directory);
|
||||
}
|
||||
|
||||
private async Task<IReadOnlyList<IStorageFolder>> OpenFolderPickerAsync(string title, string preferenceKey, string? currentPath)
|
||||
{
|
||||
var topLevel = TopLevel.GetTopLevel(this);
|
||||
var storageProvider = topLevel?.StorageProvider;
|
||||
if (storageProvider is null)
|
||||
{
|
||||
return Array.Empty<IStorageFolder>();
|
||||
}
|
||||
|
||||
var suggestedStartLocation = await _pickerPreferenceService.TryGetStartFolderAsync(storageProvider, preferenceKey, currentPath);
|
||||
|
||||
return await storageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions
|
||||
{
|
||||
Title = title,
|
||||
SuggestedStartLocation = suggestedStartLocation
|
||||
});
|
||||
}
|
||||
|
||||
private async Task<IReadOnlyList<IStorageFile>> OpenFilePickerAsync(string title, IReadOnlyList<FilePickerFileType> fileTypes, string preferenceKey, string? currentPath)
|
||||
{
|
||||
var topLevel = TopLevel.GetTopLevel(this);
|
||||
var storageProvider = topLevel?.StorageProvider;
|
||||
if (storageProvider is null)
|
||||
{
|
||||
return Array.Empty<IStorageFile>();
|
||||
}
|
||||
|
||||
var suggestedStartLocation = await _pickerPreferenceService.TryGetStartFolderAsync(storageProvider, preferenceKey, currentPath);
|
||||
|
||||
return await storageProvider.OpenFilePickerAsync(new FilePickerOpenOptions
|
||||
{
|
||||
Title = title,
|
||||
FileTypeFilter = fileTypes,
|
||||
SuggestedStartLocation = suggestedStartLocation
|
||||
});
|
||||
}
|
||||
|
||||
private async Task<IStorageFile?> SaveFilePickerAsync(string title, string defaultExtension, IReadOnlyList<FilePickerFileType> fileTypes, string preferenceKey, string? currentPath)
|
||||
{
|
||||
var topLevel = TopLevel.GetTopLevel(this);
|
||||
var storageProvider = topLevel?.StorageProvider;
|
||||
if (storageProvider is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var suggestedStartLocation = await _pickerPreferenceService.TryGetStartFolderAsync(storageProvider, preferenceKey, currentPath);
|
||||
|
||||
return await storageProvider.SaveFilePickerAsync(new FilePickerSaveOptions
|
||||
{
|
||||
Title = title,
|
||||
DefaultExtension = defaultExtension,
|
||||
FileTypeChoices = fileTypes,
|
||||
SuggestedStartLocation = suggestedStartLocation
|
||||
});
|
||||
}
|
||||
|
||||
private static void OpenInExplorer(string? path)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(path))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var normalizedPath = path.Trim().Trim('"');
|
||||
try
|
||||
{
|
||||
if (File.Exists(normalizedPath))
|
||||
{
|
||||
Process.Start("explorer.exe", $"/select,\"{normalizedPath}\"");
|
||||
}
|
||||
else if (Directory.Exists(normalizedPath))
|
||||
{
|
||||
Process.Start(new ProcessStartInfo { FileName = normalizedPath, UseShellExecute = true });
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore failures when opening Explorer.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,43 +1,24 @@
|
|||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:iconPacks="https://github.com/MahApps/IconPacks.Avalonia"
|
||||
xmlns:controls="clr-namespace:ImageCatalog_2.Controls"
|
||||
x:Class="ImageCatalog_2.AvaloniaViews.GeneralTabView">
|
||||
<ScrollViewer>
|
||||
<StackPanel Margin="4" Spacing="8">
|
||||
<TextBlock Text="Percorsi" FontWeight="Bold" />
|
||||
<StackPanel Margin="0,2,0,0" Spacing="6">
|
||||
<Grid Margin="0,0,0,2" ColumnDefinitions="Auto,*,Auto,Auto" ColumnSpacing="6">
|
||||
<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="104" Command="{Binding SelectSourceFolderCommand}" Grid.Column="2">
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Spacing="6">
|
||||
<iconPacks:PackIconMaterial Kind="FolderOpenOutline" Width="14" Height="14" />
|
||||
<TextBlock Text="Scegli..." />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
<Button Width="72" Grid.Column="3" Click="OpenSourceFolder_Click">
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Spacing="6">
|
||||
<iconPacks:PackIconMaterial Kind="Folder" Width="14" Height="14" />
|
||||
<TextBlock Text="Apri" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</Grid>
|
||||
<Grid ColumnDefinitions="Auto,*,Auto,Auto" ColumnSpacing="6">
|
||||
<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="104" Command="{Binding SelectDestinationFolderCommand}" Grid.Column="2">
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Spacing="6">
|
||||
<iconPacks:PackIconMaterial Kind="FolderOpenOutline" Width="14" Height="14" />
|
||||
<TextBlock Text="Scegli..." />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
<Button Width="72" Grid.Column="3" Click="OpenDestinationFolder_Click">
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Spacing="6">
|
||||
<iconPacks:PackIconMaterial Kind="Folder" Width="14" Height="14" />
|
||||
<TextBlock Text="Apri" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</Grid>
|
||||
<controls:PathPickerField Margin="0,0,0,2"
|
||||
Label="Sorgente:"
|
||||
Text="{Binding SourcePath, Mode=TwoWay}"
|
||||
PreferenceKey="Picker.SourceFolder.LastPath"
|
||||
PickerTitle="Seleziona cartella sorgente"
|
||||
PickerMode="Folder"
|
||||
AppendDirectorySeparator="True" />
|
||||
<controls:PathPickerField Label="Destinazione:"
|
||||
Text="{Binding DestinationPath, Mode=TwoWay}"
|
||||
PreferenceKey="Picker.DestinationFolder.LastPath"
|
||||
PickerTitle="Seleziona cartella destinazione"
|
||||
PickerMode="Folder"
|
||||
AppendDirectorySeparator="True" />
|
||||
</StackPanel>
|
||||
|
||||
<Grid ColumnDefinitions="*,*" ColumnSpacing="24" Margin="0,4,0,0">
|
||||
|
|
|
|||
|
|
@ -1,9 +1,4 @@
|
|||
using Avalonia.Controls;
|
||||
using Avalonia.Interactivity;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
|
||||
namespace ImageCatalog_2.AvaloniaViews;
|
||||
|
||||
public partial class GeneralTabView : Avalonia.Controls.UserControl
|
||||
|
|
@ -12,45 +7,4 @@ public partial class GeneralTabView : Avalonia.Controls.UserControl
|
|||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void OpenSourceFolder_Click(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
if (DataContext is DataModel model)
|
||||
{
|
||||
OpenInExplorer(model.SourcePath);
|
||||
}
|
||||
}
|
||||
|
||||
private void OpenDestinationFolder_Click(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
if (DataContext is DataModel model)
|
||||
{
|
||||
OpenInExplorer(model.DestinationPath);
|
||||
}
|
||||
}
|
||||
|
||||
private static void OpenInExplorer(string? path)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(path))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var normalizedPath = path.Trim().Trim('"');
|
||||
try
|
||||
{
|
||||
if (File.Exists(normalizedPath))
|
||||
{
|
||||
Process.Start("explorer.exe", $"/select,\"{normalizedPath}\"");
|
||||
}
|
||||
else if (Directory.Exists(normalizedPath))
|
||||
{
|
||||
Process.Start(new ProcessStartInfo { FileName = normalizedPath, UseShellExecute = true });
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore failures when opening Explorer.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
34
imagecatalog/Controls/PathPickerField.axaml
Normal file
34
imagecatalog/Controls/PathPickerField.axaml
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:iconPacks="https://github.com/MahApps/IconPacks.Avalonia"
|
||||
x:Class="ImageCatalog_2.Controls.PathPickerField"
|
||||
x:Name="Root">
|
||||
<Grid ColumnDefinitions="Auto,*,Auto,Auto" ColumnSpacing="6">
|
||||
<TextBlock Grid.Column="0"
|
||||
Text="{Binding Label, ElementName=Root}"
|
||||
VerticalAlignment="Center"
|
||||
Margin="0,0,8,0" />
|
||||
<TextBox Grid.Column="1"
|
||||
Text="{Binding Text, ElementName=Root, Mode=TwoWay}"
|
||||
IsReadOnly="{Binding IsTextReadOnly, ElementName=Root}"
|
||||
Watermark="{Binding Watermark, ElementName=Root}"
|
||||
VerticalAlignment="Center" />
|
||||
<Button Grid.Column="2"
|
||||
Width="104"
|
||||
IsVisible="{Binding ShowPickerButton, ElementName=Root}"
|
||||
Click="PickPath_Click">
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Spacing="6">
|
||||
<iconPacks:PackIconMaterial Kind="FolderOpenOutline" Width="14" Height="14" />
|
||||
<TextBlock Text="Scegli..." />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
<Button Grid.Column="3"
|
||||
Width="72"
|
||||
Click="OpenPath_Click">
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Spacing="6">
|
||||
<iconPacks:PackIconMaterial Kind="Folder" Width="14" Height="14" />
|
||||
<TextBlock Text="Apri" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
285
imagecatalog/Controls/PathPickerField.axaml.cs
Normal file
285
imagecatalog/Controls/PathPickerField.axaml.cs
Normal file
|
|
@ -0,0 +1,285 @@
|
|||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Data;
|
||||
using Avalonia.Platform.Storage;
|
||||
using ImageCatalog_2.Services;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace ImageCatalog_2.Controls;
|
||||
|
||||
public partial class PathPickerField : UserControl
|
||||
{
|
||||
private readonly PickerPreferenceService _pickerPreferenceService;
|
||||
|
||||
public static readonly StyledProperty<string> LabelProperty =
|
||||
AvaloniaProperty.Register<PathPickerField, string>(nameof(Label), string.Empty);
|
||||
|
||||
public static readonly StyledProperty<string> TextProperty =
|
||||
AvaloniaProperty.Register<PathPickerField, string>(nameof(Text), string.Empty, defaultBindingMode: BindingMode.TwoWay);
|
||||
|
||||
public static readonly StyledProperty<string> WatermarkProperty =
|
||||
AvaloniaProperty.Register<PathPickerField, string>(nameof(Watermark), string.Empty);
|
||||
|
||||
public static readonly StyledProperty<string> PreferenceKeyProperty =
|
||||
AvaloniaProperty.Register<PathPickerField, string>(nameof(PreferenceKey), string.Empty);
|
||||
|
||||
public static readonly StyledProperty<string> PickerTitleProperty =
|
||||
AvaloniaProperty.Register<PathPickerField, string>(nameof(PickerTitle), string.Empty);
|
||||
|
||||
public static readonly StyledProperty<string> FileTypeNameProperty =
|
||||
AvaloniaProperty.Register<PathPickerField, string>(nameof(FileTypeName), string.Empty);
|
||||
|
||||
public static readonly StyledProperty<string> FilePatternsProperty =
|
||||
AvaloniaProperty.Register<PathPickerField, string>(nameof(FilePatterns), string.Empty);
|
||||
|
||||
public static readonly StyledProperty<string> DefaultExtensionProperty =
|
||||
AvaloniaProperty.Register<PathPickerField, string>(nameof(DefaultExtension), string.Empty);
|
||||
|
||||
public static readonly StyledProperty<PathPickerSelectionMode> PickerModeProperty =
|
||||
AvaloniaProperty.Register<PathPickerField, PathPickerSelectionMode>(nameof(PickerMode), PathPickerSelectionMode.Folder);
|
||||
|
||||
public static readonly StyledProperty<bool> IsTextReadOnlyProperty =
|
||||
AvaloniaProperty.Register<PathPickerField, bool>(nameof(IsTextReadOnly), false);
|
||||
|
||||
public static readonly StyledProperty<bool> ShowPickerButtonProperty =
|
||||
AvaloniaProperty.Register<PathPickerField, bool>(nameof(ShowPickerButton), true);
|
||||
|
||||
public static readonly StyledProperty<bool> AppendDirectorySeparatorProperty =
|
||||
AvaloniaProperty.Register<PathPickerField, bool>(nameof(AppendDirectorySeparator), false);
|
||||
|
||||
public PathPickerField()
|
||||
{
|
||||
_pickerPreferenceService = Program.ServiceProvider.GetRequiredService<PickerPreferenceService>();
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public string Label
|
||||
{
|
||||
get => GetValue(LabelProperty);
|
||||
set => SetValue(LabelProperty, value);
|
||||
}
|
||||
|
||||
public string Text
|
||||
{
|
||||
get => GetValue(TextProperty);
|
||||
set => SetValue(TextProperty, value);
|
||||
}
|
||||
|
||||
public string Watermark
|
||||
{
|
||||
get => GetValue(WatermarkProperty);
|
||||
set => SetValue(WatermarkProperty, value);
|
||||
}
|
||||
|
||||
public string PreferenceKey
|
||||
{
|
||||
get => GetValue(PreferenceKeyProperty);
|
||||
set => SetValue(PreferenceKeyProperty, value);
|
||||
}
|
||||
|
||||
public string PickerTitle
|
||||
{
|
||||
get => GetValue(PickerTitleProperty);
|
||||
set => SetValue(PickerTitleProperty, value);
|
||||
}
|
||||
|
||||
public string FileTypeName
|
||||
{
|
||||
get => GetValue(FileTypeNameProperty);
|
||||
set => SetValue(FileTypeNameProperty, value);
|
||||
}
|
||||
|
||||
public string FilePatterns
|
||||
{
|
||||
get => GetValue(FilePatternsProperty);
|
||||
set => SetValue(FilePatternsProperty, value);
|
||||
}
|
||||
|
||||
public string DefaultExtension
|
||||
{
|
||||
get => GetValue(DefaultExtensionProperty);
|
||||
set => SetValue(DefaultExtensionProperty, value);
|
||||
}
|
||||
|
||||
public PathPickerSelectionMode PickerMode
|
||||
{
|
||||
get => GetValue(PickerModeProperty);
|
||||
set => SetValue(PickerModeProperty, value);
|
||||
}
|
||||
|
||||
public bool IsTextReadOnly
|
||||
{
|
||||
get => GetValue(IsTextReadOnlyProperty);
|
||||
set => SetValue(IsTextReadOnlyProperty, value);
|
||||
}
|
||||
|
||||
public bool ShowPickerButton
|
||||
{
|
||||
get => GetValue(ShowPickerButtonProperty);
|
||||
set => SetValue(ShowPickerButtonProperty, value);
|
||||
}
|
||||
|
||||
public bool AppendDirectorySeparator
|
||||
{
|
||||
get => GetValue(AppendDirectorySeparatorProperty);
|
||||
set => SetValue(AppendDirectorySeparatorProperty, value);
|
||||
}
|
||||
|
||||
private async void PickPath_Click(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
|
||||
{
|
||||
var topLevel = TopLevel.GetTopLevel(this);
|
||||
var storageProvider = topLevel?.StorageProvider;
|
||||
if (storageProvider is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var selectedPath = await PickPathAsync(storageProvider);
|
||||
if (string.IsNullOrWhiteSpace(selectedPath))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Text = selectedPath;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(PreferenceKey))
|
||||
{
|
||||
_pickerPreferenceService.RememberPath(PreferenceKey, selectedPath);
|
||||
}
|
||||
}
|
||||
|
||||
private void OpenPath_Click(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
|
||||
{
|
||||
PathShellService.OpenInExplorer(Text);
|
||||
}
|
||||
|
||||
private async Task<string?> PickPathAsync(IStorageProvider storageProvider)
|
||||
{
|
||||
var suggestedStartLocation = await TryGetSuggestedStartLocationAsync(storageProvider);
|
||||
var pickerTitle = ResolvePickerTitle();
|
||||
|
||||
switch (PickerMode)
|
||||
{
|
||||
case PathPickerSelectionMode.Folder:
|
||||
{
|
||||
var folders = await storageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions
|
||||
{
|
||||
Title = pickerTitle,
|
||||
SuggestedStartLocation = suggestedStartLocation
|
||||
});
|
||||
|
||||
return folders.Count == 0
|
||||
? null
|
||||
: NormalizeSelectedPath(folders[0].Path.LocalPath);
|
||||
}
|
||||
case PathPickerSelectionMode.OpenFile:
|
||||
{
|
||||
var files = await storageProvider.OpenFilePickerAsync(new FilePickerOpenOptions
|
||||
{
|
||||
Title = pickerTitle,
|
||||
SuggestedStartLocation = suggestedStartLocation,
|
||||
FileTypeFilter = BuildFileTypes()
|
||||
});
|
||||
|
||||
return files.Count == 0
|
||||
? null
|
||||
: files[0].Path.LocalPath;
|
||||
}
|
||||
case PathPickerSelectionMode.SaveFile:
|
||||
{
|
||||
var file = await storageProvider.SaveFilePickerAsync(new FilePickerSaveOptions
|
||||
{
|
||||
Title = pickerTitle,
|
||||
SuggestedStartLocation = suggestedStartLocation,
|
||||
DefaultExtension = NormalizeDefaultExtension(DefaultExtension),
|
||||
FileTypeChoices = BuildFileTypes()
|
||||
});
|
||||
|
||||
return file?.Path.LocalPath;
|
||||
}
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<IStorageFolder?> TryGetSuggestedStartLocationAsync(IStorageProvider storageProvider)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(PreferenceKey))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return await _pickerPreferenceService.TryGetStartFolderAsync(storageProvider, PreferenceKey, Text);
|
||||
}
|
||||
|
||||
private IReadOnlyList<FilePickerFileType> BuildFileTypes()
|
||||
{
|
||||
var patterns = (FilePatterns ?? string.Empty)
|
||||
.Split(';', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
||||
|
||||
if (patterns.Length == 0)
|
||||
{
|
||||
return Array.Empty<FilePickerFileType>();
|
||||
}
|
||||
|
||||
var fileTypeName = string.IsNullOrWhiteSpace(FileTypeName)
|
||||
? "File"
|
||||
: FileTypeName.Trim();
|
||||
|
||||
return
|
||||
[
|
||||
new FilePickerFileType(fileTypeName)
|
||||
{
|
||||
Patterns = patterns.ToList()
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
private string ResolvePickerTitle()
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(PickerTitle))
|
||||
{
|
||||
return PickerTitle;
|
||||
}
|
||||
|
||||
var cleanedLabel = string.IsNullOrWhiteSpace(Label)
|
||||
? "percorso"
|
||||
: Label.Trim().TrimEnd(':');
|
||||
|
||||
return PickerMode == PathPickerSelectionMode.SaveFile
|
||||
? $"Salva {cleanedLabel.ToLowerInvariant()}"
|
||||
: $"Seleziona {cleanedLabel.ToLowerInvariant()}";
|
||||
}
|
||||
|
||||
private string NormalizeSelectedPath(string selectedPath)
|
||||
{
|
||||
if (PickerMode != PathPickerSelectionMode.Folder || !AppendDirectorySeparator)
|
||||
{
|
||||
return selectedPath;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(selectedPath))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
return selectedPath.EndsWith(Path.DirectorySeparatorChar)
|
||||
|| selectedPath.EndsWith(Path.AltDirectorySeparatorChar)
|
||||
? selectedPath
|
||||
: selectedPath + Path.DirectorySeparatorChar;
|
||||
}
|
||||
|
||||
private static string NormalizeDefaultExtension(string extension)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(extension))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
return extension.Trim().TrimStart('.');
|
||||
}
|
||||
}
|
||||
8
imagecatalog/Controls/PathPickerSelectionMode.cs
Normal file
8
imagecatalog/Controls/PathPickerSelectionMode.cs
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
namespace ImageCatalog_2.Controls;
|
||||
|
||||
public enum PathPickerSelectionMode
|
||||
{
|
||||
Folder,
|
||||
OpenFile,
|
||||
SaveFile
|
||||
}
|
||||
50
imagecatalog/Services/PathShellService.cs
Normal file
50
imagecatalog/Services/PathShellService.cs
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
|
||||
namespace ImageCatalog_2.Services;
|
||||
|
||||
public static class PathShellService
|
||||
{
|
||||
public static void OpenInExplorer(string? path)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(path))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var normalizedPath = path.Trim().Trim('"');
|
||||
|
||||
try
|
||||
{
|
||||
if (File.Exists(normalizedPath))
|
||||
{
|
||||
Process.Start("explorer.exe", $"/select,\"{normalizedPath}\"");
|
||||
return;
|
||||
}
|
||||
|
||||
if (Directory.Exists(normalizedPath))
|
||||
{
|
||||
Process.Start(new ProcessStartInfo
|
||||
{
|
||||
FileName = normalizedPath,
|
||||
UseShellExecute = true
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
var containingDirectory = Path.GetDirectoryName(normalizedPath);
|
||||
if (!string.IsNullOrWhiteSpace(containingDirectory) && Directory.Exists(containingDirectory))
|
||||
{
|
||||
Process.Start(new ProcessStartInfo
|
||||
{
|
||||
FileName = containingDirectory,
|
||||
UseShellExecute = true
|
||||
});
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore failures when opening Explorer.
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue