Refactor MetadataEditorViewModel to use StringWrapper for string collections and update bindings in XAML

This commit is contained in:
2025-12-14 21:00:45 +00:00
parent 7599d57f21
commit b70751991d
2 changed files with 96 additions and 70 deletions

View File

@@ -54,6 +54,19 @@ using File = System.IO.File;
namespace Aaru.Gui.ViewModels.Windows; namespace Aaru.Gui.ViewModels.Windows;
// Helper class to wrap strings for two-way binding in collections
public sealed partial class StringWrapper : ObservableObject
{
[ObservableProperty]
string _value;
public StringWrapper() => _value = string.Empty;
public StringWrapper(string value) => _value = value;
public override string ToString() => Value;
}
public sealed partial class MetadataEditorViewModel : ViewModelBase public sealed partial class MetadataEditorViewModel : ViewModelBase
{ {
readonly Window _view; readonly Window _view;
@@ -68,7 +81,7 @@ public sealed partial class MetadataEditorViewModel : ViewModelBase
ObservableCollection<AudioMediaViewModel> _audioMedias = []; ObservableCollection<AudioMediaViewModel> _audioMedias = [];
[ObservableProperty] [ObservableProperty]
ObservableCollection<string> _authors = []; ObservableCollection<StringWrapper> _authors = [];
// Complex object lists // Complex object lists
[ObservableProperty] [ObservableProperty]
@@ -81,17 +94,17 @@ public sealed partial class MetadataEditorViewModel : ViewModelBase
ObservableCollection<BookViewModel> _books = []; ObservableCollection<BookViewModel> _books = [];
[ObservableProperty] [ObservableProperty]
ObservableCollection<string> _categories = []; ObservableCollection<StringWrapper> _categories = [];
// String lists // String lists
[ObservableProperty] [ObservableProperty]
ObservableCollection<string> _developers = []; ObservableCollection<StringWrapper> _developers = [];
[ObservableProperty] [ObservableProperty]
string _filePath; string _filePath;
[ObservableProperty] [ObservableProperty]
ObservableCollection<string> _keywords = []; ObservableCollection<StringWrapper> _keywords = [];
// Enum lists // Enum lists
[ObservableProperty] [ObservableProperty]
@@ -117,10 +130,10 @@ public sealed partial class MetadataEditorViewModel : ViewModelBase
ObservableCollection<PciViewModel> _pciCards = []; ObservableCollection<PciViewModel> _pciCards = [];
[ObservableProperty] [ObservableProperty]
ObservableCollection<string> _performers = []; ObservableCollection<StringWrapper> _performers = [];
[ObservableProperty] [ObservableProperty]
ObservableCollection<string> _publishers = []; ObservableCollection<StringWrapper> _publishers = [];
[ObservableProperty] [ObservableProperty]
DateTime? _releaseDate; DateTime? _releaseDate;
@@ -135,10 +148,10 @@ public sealed partial class MetadataEditorViewModel : ViewModelBase
string _serialNumber; string _serialNumber;
[ObservableProperty] [ObservableProperty]
ObservableCollection<string> _subcategories = []; ObservableCollection<StringWrapper> _subcategories = [];
[ObservableProperty] [ObservableProperty]
ObservableCollection<string> _systems = []; ObservableCollection<StringWrapper> _systems = [];
[ObservableProperty] [ObservableProperty]
string _title; string _title;
@@ -210,8 +223,9 @@ public sealed partial class MetadataEditorViewModel : ViewModelBase
// Enum lists // Enum lists
if(metadata.Languages != null) if(metadata.Languages != null)
foreach(Language lang in metadata.Languages) {
Languages.Add(new LocalizedEnumValue<Language>(lang)); foreach(Language lang in metadata.Languages) Languages.Add(new LocalizedEnumValue<Language>(lang));
}
if(metadata.Architectures != null) if(metadata.Architectures != null)
{ {
@@ -221,16 +235,19 @@ public sealed partial class MetadataEditorViewModel : ViewModelBase
// Complex objects // Complex objects
if(metadata.Barcodes != null) if(metadata.Barcodes != null)
foreach(Barcode barcode in metadata.Barcodes) {
Barcodes.Add(new BarcodeViewModel(barcode)); foreach(Barcode barcode in metadata.Barcodes) Barcodes.Add(new BarcodeViewModel(barcode));
}
if(metadata.Magazines != null) if(metadata.Magazines != null)
foreach(Magazine magazine in metadata.Magazines) {
Magazines.Add(new MagazineViewModel(magazine)); foreach(Magazine magazine in metadata.Magazines) Magazines.Add(new MagazineViewModel(magazine));
}
if(metadata.Books != null) if(metadata.Books != null)
foreach(Book book in metadata.Books) {
Books.Add(new BookViewModel(book)); foreach(Book book in metadata.Books) Books.Add(new BookViewModel(book));
}
if(metadata.RequiredOperatingSystems != null) if(metadata.RequiredOperatingSystems != null)
{ {
@@ -239,32 +256,39 @@ public sealed partial class MetadataEditorViewModel : ViewModelBase
} }
if(metadata.UserManuals != null) if(metadata.UserManuals != null)
foreach(UserManual manual in metadata.UserManuals) {
UserManuals.Add(new UserManualViewModel(manual)); foreach(UserManual manual in metadata.UserManuals) UserManuals.Add(new UserManualViewModel(manual));
}
if(metadata.OpticalDiscs != null) if(metadata.OpticalDiscs != null)
foreach(OpticalDisc disc in metadata.OpticalDiscs) {
OpticalDiscs.Add(new OpticalDiscViewModel(disc)); foreach(OpticalDisc disc in metadata.OpticalDiscs) OpticalDiscs.Add(new OpticalDiscViewModel(disc));
}
if(metadata.Advertisements != null) if(metadata.Advertisements != null)
foreach(Advertisement ad in metadata.Advertisements) {
Advertisements.Add(new AdvertisementViewModel(ad)); foreach(Advertisement ad in metadata.Advertisements) Advertisements.Add(new AdvertisementViewModel(ad));
}
if(metadata.LinearMedias != null) if(metadata.LinearMedias != null)
foreach(LinearMedia media in metadata.LinearMedias) {
LinearMedias.Add(new LinearMediaViewModel(media)); foreach(LinearMedia media in metadata.LinearMedias) LinearMedias.Add(new LinearMediaViewModel(media));
}
if(metadata.PciCards != null) if(metadata.PciCards != null)
foreach(Pci pci in metadata.PciCards) {
PciCards.Add(new PciViewModel(pci)); foreach(Pci pci in metadata.PciCards) PciCards.Add(new PciViewModel(pci));
}
if(metadata.BlockMedias != null) if(metadata.BlockMedias != null)
foreach(BlockMedia media in metadata.BlockMedias) {
BlockMedias.Add(new BlockMediaViewModel(media)); foreach(BlockMedia media in metadata.BlockMedias) BlockMedias.Add(new BlockMediaViewModel(media));
}
if(metadata.AudioMedias != null) if(metadata.AudioMedias != null)
foreach(AudioMedia media in metadata.AudioMedias) {
AudioMedias.Add(new AudioMediaViewModel(media)); foreach(AudioMedia media in metadata.AudioMedias) AudioMedias.Add(new AudioMediaViewModel(media));
}
} }
catch(Exception ex) catch(Exception ex)
{ {
@@ -276,12 +300,12 @@ public sealed partial class MetadataEditorViewModel : ViewModelBase
} }
} }
static void LoadStringList([CanBeNull] List<string> source, ObservableCollection<string> target) static void LoadStringList([CanBeNull] List<string> source, ObservableCollection<StringWrapper> target)
{ {
if(source == null) return; if(source == null) return;
target.Clear(); target.Clear();
foreach(string item in source) target.Add(item); foreach(string item in source) target.Add(new StringWrapper(item));
} }
static void LoadEnumList<T>([CanBeNull] List<T> source, ObservableCollection<T> target) where T : struct, Enum static void LoadEnumList<T>([CanBeNull] List<T> source, ObservableCollection<T> target) where T : struct, Enum
@@ -305,14 +329,14 @@ public sealed partial class MetadataEditorViewModel : ViewModelBase
ReleaseDate = ReleaseDate, ReleaseDate = ReleaseDate,
PartNumber = PartNumber, PartNumber = PartNumber,
SerialNumber = SerialNumber, SerialNumber = SerialNumber,
Developers = Developers.Any() ? [..Developers] : null, Developers = Developers.Any() ? [..Developers.Select(d => d.Value)] : null,
Publishers = Publishers.Any() ? [..Publishers] : null, Publishers = Publishers.Any() ? [..Publishers.Select(p => p.Value)] : null,
Authors = Authors.Any() ? [..Authors] : null, Authors = Authors.Any() ? [..Authors.Select(a => a.Value)] : null,
Performers = Performers.Any() ? [..Performers] : null, Performers = Performers.Any() ? [..Performers.Select(p => p.Value)] : null,
Keywords = Keywords.Any() ? [..Keywords] : null, Keywords = Keywords.Any() ? [..Keywords.Select(k => k.Value)] : null,
Categories = Categories.Any() ? [..Categories] : null, Categories = Categories.Any() ? [..Categories.Select(c => c.Value)] : null,
Subcategories = Subcategories.Any() ? [..Subcategories] : null, Subcategories = Subcategories.Any() ? [..Subcategories.Select(s => s.Value)] : null,
Systems = Systems.Any() ? [..Systems] : null, Systems = Systems.Any() ? [..Systems.Select(s => s.Value)] : null,
Languages = Languages.Any() ? [..Languages.Select(l => l.Value)] : null, Languages = Languages.Any() ? [..Languages.Select(l => l.Value)] : null,
Architectures = Architectures.Any() ? [..Architectures.Select(a => a.Value)] : null, Architectures = Architectures.Any() ? [..Architectures.Select(a => a.Value)] : null,
Barcodes = Barcodes.Any() ? [..Barcodes.Select(static b => b.ToModel())] : null, Barcodes = Barcodes.Any() ? [..Barcodes.Select(static b => b.ToModel())] : null,
@@ -395,60 +419,61 @@ public sealed partial class MetadataEditorViewModel : ViewModelBase
// Commands for adding items to simple string lists // Commands for adding items to simple string lists
[RelayCommand] [RelayCommand]
void AddDeveloper() => Developers.Add(string.Empty); void AddDeveloper() => Developers.Add(new StringWrapper());
[RelayCommand] [RelayCommand]
void RemoveDeveloper(string item) => Developers.Remove(item); void RemoveDeveloper(StringWrapper item) => Developers.Remove(item);
[RelayCommand] [RelayCommand]
void AddPublisher() => Publishers.Add(string.Empty); void AddPublisher() => Publishers.Add(new StringWrapper());
[RelayCommand] [RelayCommand]
void RemovePublisher(string item) => Publishers.Remove(item); void RemovePublisher(StringWrapper item) => Publishers.Remove(item);
[RelayCommand] [RelayCommand]
void AddAuthor() => Authors.Add(string.Empty); void AddAuthor() => Authors.Add(new StringWrapper());
[RelayCommand] [RelayCommand]
void RemoveAuthor(string item) => Authors.Remove(item); void RemoveAuthor(StringWrapper item) => Authors.Remove(item);
[RelayCommand] [RelayCommand]
void AddPerformer() => Performers.Add(string.Empty); void AddPerformer() => Performers.Add(new StringWrapper());
[RelayCommand] [RelayCommand]
void RemovePerformer(string item) => Performers.Remove(item); void RemovePerformer(StringWrapper item) => Performers.Remove(item);
[RelayCommand] [RelayCommand]
void AddKeyword() => Keywords.Add(string.Empty); void AddKeyword() => Keywords.Add(new StringWrapper());
[RelayCommand] [RelayCommand]
void RemoveKeyword(string item) => Keywords.Remove(item); void RemoveKeyword(StringWrapper item) => Keywords.Remove(item);
[RelayCommand] [RelayCommand]
void AddCategory() => Categories.Add(string.Empty); void AddCategory() => Categories.Add(new StringWrapper());
[RelayCommand] [RelayCommand]
void RemoveCategory(string item) => Categories.Remove(item); void RemoveCategory(StringWrapper item) => Categories.Remove(item);
[RelayCommand] [RelayCommand]
void AddSubcategory() => Subcategories.Add(string.Empty); void AddSubcategory() => Subcategories.Add(new StringWrapper());
[RelayCommand] [RelayCommand]
void RemoveSubcategory(string item) => Subcategories.Remove(item); void RemoveSubcategory(StringWrapper item) => Subcategories.Remove(item);
[RelayCommand] [RelayCommand]
void AddSystem() => Systems.Add(string.Empty); void AddSystem() => Systems.Add(new StringWrapper());
[RelayCommand] [RelayCommand]
void RemoveSystem(string item) => Systems.Remove(item); void RemoveSystem(StringWrapper item) => Systems.Remove(item);
// Commands for adding items to enum lists // Commands for adding items to enum lists
[RelayCommand] [RelayCommand]
void AddLanguage(object parameter) void AddLanguage(object parameter)
{ {
if(parameter is LocalizedEnumValue<Language> langValue) if(parameter is LocalizedEnumValue<Language> langValue)
if(!Languages.Any(l => l.Value == langValue.Value)) {
Languages.Add(langValue); if(!Languages.Any(l => l.Value == langValue.Value)) Languages.Add(langValue);
}
} }
[RelayCommand] [RelayCommand]
@@ -458,8 +483,9 @@ public sealed partial class MetadataEditorViewModel : ViewModelBase
void AddArchitecture(object parameter) void AddArchitecture(object parameter)
{ {
if(parameter is LocalizedEnumValue<Architecture> archValue) if(parameter is LocalizedEnumValue<Architecture> archValue)
if(!Architectures.Any(a => a.Value == archValue.Value)) {
Architectures.Add(archValue); if(!Architectures.Any(a => a.Value == archValue.Value)) Architectures.Add(archValue);
}
} }
[RelayCommand] [RelayCommand]

View File

@@ -237,7 +237,7 @@
Content="{x:Static gui:GUI.Button_Remove}" Content="{x:Static gui:GUI.Button_Remove}"
Command="{Binding $parent[Window].DataContext.RemoveDeveloperCommand}" Command="{Binding $parent[Window].DataContext.RemoveDeveloperCommand}"
CommandParameter="{Binding}" /> CommandParameter="{Binding}" />
<TextBox Text="{Binding Mode=TwoWay}" <TextBox Text="{Binding Value, Mode=TwoWay}"
Watermark="{x:Static gui:GUI.Watermark_Enter_developer_name}" /> Watermark="{x:Static gui:GUI.Watermark_Enter_developer_name}" />
</DockPanel> </DockPanel>
</DataTemplate> </DataTemplate>
@@ -266,7 +266,7 @@
Content="{x:Static gui:GUI.Button_Remove}" Content="{x:Static gui:GUI.Button_Remove}"
Command="{Binding $parent[Window].DataContext.RemovePublisherCommand}" Command="{Binding $parent[Window].DataContext.RemovePublisherCommand}"
CommandParameter="{Binding}" /> CommandParameter="{Binding}" />
<TextBox Text="{Binding}" <TextBox Text="{Binding Value, Mode=TwoWay}"
Watermark="{x:Static gui:GUI.Watermark_Enter_publisher_name}" /> Watermark="{x:Static gui:GUI.Watermark_Enter_publisher_name}" />
</DockPanel> </DockPanel>
</DataTemplate> </DataTemplate>
@@ -295,7 +295,7 @@
Content="{x:Static gui:GUI.Button_Remove}" Content="{x:Static gui:GUI.Button_Remove}"
Command="{Binding $parent[Window].DataContext.RemoveAuthorCommand}" Command="{Binding $parent[Window].DataContext.RemoveAuthorCommand}"
CommandParameter="{Binding}" /> CommandParameter="{Binding}" />
<TextBox Text="{Binding}" <TextBox Text="{Binding Value, Mode=TwoWay}"
Watermark="{x:Static gui:GUI.Watermark_Enter_author_name}" /> Watermark="{x:Static gui:GUI.Watermark_Enter_author_name}" />
</DockPanel> </DockPanel>
</DataTemplate> </DataTemplate>
@@ -324,7 +324,7 @@
Content="{x:Static gui:GUI.Button_Remove}" Content="{x:Static gui:GUI.Button_Remove}"
Command="{Binding $parent[Window].DataContext.RemovePerformerCommand}" Command="{Binding $parent[Window].DataContext.RemovePerformerCommand}"
CommandParameter="{Binding}" /> CommandParameter="{Binding}" />
<TextBox Text="{Binding}" <TextBox Text="{Binding Value, Mode=TwoWay}"
Watermark="{x:Static gui:GUI.Watermark_Enter_performer_name}" /> Watermark="{x:Static gui:GUI.Watermark_Enter_performer_name}" />
</DockPanel> </DockPanel>
</DataTemplate> </DataTemplate>
@@ -360,7 +360,7 @@
Content="{x:Static gui:GUI.Button_Remove}" Content="{x:Static gui:GUI.Button_Remove}"
Command="{Binding $parent[Window].DataContext.RemoveKeywordCommand}" Command="{Binding $parent[Window].DataContext.RemoveKeywordCommand}"
CommandParameter="{Binding}" /> CommandParameter="{Binding}" />
<TextBox Text="{Binding}" <TextBox Text="{Binding Value, Mode=TwoWay}"
Watermark="{x:Static gui:GUI.Watermark_Enter_keyword}" /> Watermark="{x:Static gui:GUI.Watermark_Enter_keyword}" />
</DockPanel> </DockPanel>
</DataTemplate> </DataTemplate>
@@ -389,7 +389,7 @@
Content="{x:Static gui:GUI.Button_Remove}" Content="{x:Static gui:GUI.Button_Remove}"
Command="{Binding $parent[Window].DataContext.RemoveCategoryCommand}" Command="{Binding $parent[Window].DataContext.RemoveCategoryCommand}"
CommandParameter="{Binding}" /> CommandParameter="{Binding}" />
<TextBox Text="{Binding}" <TextBox Text="{Binding Value, Mode=TwoWay}"
Watermark="{x:Static gui:GUI.Watermark_Enter_category}" /> Watermark="{x:Static gui:GUI.Watermark_Enter_category}" />
</DockPanel> </DockPanel>
</DataTemplate> </DataTemplate>
@@ -418,7 +418,7 @@
Content="{x:Static gui:GUI.Button_Remove}" Content="{x:Static gui:GUI.Button_Remove}"
Command="{Binding $parent[Window].DataContext.RemoveSubcategoryCommand}" Command="{Binding $parent[Window].DataContext.RemoveSubcategoryCommand}"
CommandParameter="{Binding}" /> CommandParameter="{Binding}" />
<TextBox Text="{Binding}" <TextBox Text="{Binding Value, Mode=TwoWay}"
Watermark="{x:Static gui:GUI.Watermark_Enter_subcategory}" /> Watermark="{x:Static gui:GUI.Watermark_Enter_subcategory}" />
</DockPanel> </DockPanel>
</DataTemplate> </DataTemplate>
@@ -447,7 +447,7 @@
Content="{x:Static gui:GUI.Button_Remove}" Content="{x:Static gui:GUI.Button_Remove}"
Command="{Binding $parent[Window].DataContext.RemoveSystemCommand}" Command="{Binding $parent[Window].DataContext.RemoveSystemCommand}"
CommandParameter="{Binding}" /> CommandParameter="{Binding}" />
<TextBox Text="{Binding}" <TextBox Text="{Binding Value, Mode=TwoWay}"
Watermark="{x:Static gui:GUI.Watermark_Enter_system}" /> Watermark="{x:Static gui:GUI.Watermark_Enter_system}" />
</DockPanel> </DockPanel>
</DataTemplate> </DataTemplate>
@@ -488,7 +488,7 @@
Content="{x:Static gui:GUI.Button_Remove}" Content="{x:Static gui:GUI.Button_Remove}"
Command="{Binding $parent[Window].DataContext.RemoveLanguageCommand}" Command="{Binding $parent[Window].DataContext.RemoveLanguageCommand}"
CommandParameter="{Binding}" /> CommandParameter="{Binding}" />
<TextBlock Text="{Binding}" <TextBlock Text="{Binding Value, Mode=TwoWay}"
VerticalAlignment="Center" VerticalAlignment="Center"
Margin="8,0" /> Margin="8,0" />
</DockPanel> </DockPanel>
@@ -523,7 +523,7 @@
Content="{x:Static gui:GUI.Button_Remove}" Content="{x:Static gui:GUI.Button_Remove}"
Command="{Binding $parent[Window].DataContext.RemoveArchitectureCommand}" Command="{Binding $parent[Window].DataContext.RemoveArchitectureCommand}"
CommandParameter="{Binding}" /> CommandParameter="{Binding}" />
<TextBlock Text="{Binding}" <TextBlock Text="{Binding Value, Mode=TwoWay}"
VerticalAlignment="Center" VerticalAlignment="Center"
Margin="8,0" /> Margin="8,0" />
</DockPanel> </DockPanel>