diff --git a/PKHeX.Avalonia/Controls/SlotModel.cs b/PKHeX.Avalonia/Controls/SlotModel.cs
index ad682924d..063a01d34 100644
--- a/PKHeX.Avalonia/Controls/SlotModel.cs
+++ b/PKHeX.Avalonia/Controls/SlotModel.cs
@@ -64,7 +64,10 @@ public partial class SlotModel : ObservableObject
public void SetImage(SKBitmap? skBitmap)
{
+ var old = Image;
Image = SKBitmapToAvaloniaBitmapConverter.ToAvaloniaBitmap(skBitmap);
+ old?.Dispose();
+ skBitmap?.Dispose();
IsEmpty = skBitmap is null;
}
}
diff --git a/PKHeX.Avalonia/Converters/SKBitmapToAvaloniaBitmapConverter.cs b/PKHeX.Avalonia/Converters/SKBitmapToAvaloniaBitmapConverter.cs
index 3d609f8f4..4f6a78b1c 100644
--- a/PKHeX.Avalonia/Converters/SKBitmapToAvaloniaBitmapConverter.cs
+++ b/PKHeX.Avalonia/Converters/SKBitmapToAvaloniaBitmapConverter.cs
@@ -46,4 +46,22 @@ public class SKBitmapToAvaloniaBitmapConverter : IValueConverter
stream.Position = 0;
return new Bitmap(stream);
}
+
+ ///
+ /// Converts an SKBitmap to an Avalonia Bitmap and disposes the input SKBitmap.
+ /// Use this when the caller owns the SKBitmap and will not reuse it after conversion.
+ ///
+ public static Bitmap? ToAvaloniaBitmapAndDispose(SKBitmap? skBitmap)
+ {
+ if (skBitmap is null)
+ return null;
+ try
+ {
+ return ToAvaloniaBitmap(skBitmap);
+ }
+ finally
+ {
+ skBitmap.Dispose();
+ }
+ }
}
diff --git a/PKHeX.Avalonia/ViewModels/MainWindowViewModel.cs b/PKHeX.Avalonia/ViewModels/MainWindowViewModel.cs
index 8bb1160bc..0a06d40db 100644
--- a/PKHeX.Avalonia/ViewModels/MainWindowViewModel.cs
+++ b/PKHeX.Avalonia/ViewModels/MainWindowViewModel.cs
@@ -27,6 +27,11 @@ public partial class MainWindowViewModel : ObservableObject
{
private readonly IDialogService _dialogService;
+ ///
+ /// Guards against concurrent invocations of .
+ ///
+ private bool _isLoading;
+
///
/// The file path from which the current save file was loaded.
/// Used to create an automatic backup before overwriting.
@@ -345,6 +350,9 @@ private async Task DumpAllBoxesAsync()
public async Task LoadFileAsync(string path)
{
+ if (_isLoading)
+ return;
+ _isLoading = true;
try
{
var data = await File.ReadAllBytesAsync(path);
@@ -370,6 +378,10 @@ public async Task LoadFileAsync(string path)
{
await _dialogService.ShowErrorAsync("Load Error", ex.Message);
}
+ finally
+ {
+ _isLoading = false;
+ }
}
private void LoadSaveFile(SaveFile sav, string path)
@@ -937,7 +949,7 @@ private async Task OpenQRDialogAsync()
public void HandleFileDrop(string[] files)
{
- if (files.Length == 0)
+ if (files.Length == 0 || _isLoading)
return;
_ = LoadFileAsync(files[0]);
diff --git a/PKHeX.Avalonia/ViewModels/PKMEditorViewModel.cs b/PKHeX.Avalonia/ViewModels/PKMEditorViewModel.cs
index bed1a7bab..90f3ffc52 100644
--- a/PKHeX.Avalonia/ViewModels/PKMEditorViewModel.cs
+++ b/PKHeX.Avalonia/ViewModels/PKMEditorViewModel.cs
@@ -994,11 +994,15 @@ private void UpdateBallSprite()
try
{
var skBitmap = SpriteUtil.GetBallSprite(Ball);
- BallSprite = SKBitmapToAvaloniaBitmapConverter.ToAvaloniaBitmap(skBitmap);
+ var old = BallSprite;
+ BallSprite = SKBitmapToAvaloniaBitmapConverter.ToAvaloniaBitmapAndDispose(skBitmap);
+ old?.Dispose();
}
catch
{
+ var old = BallSprite;
BallSprite = null;
+ old?.Dispose();
}
}
@@ -2855,7 +2859,9 @@ private void UpdateLegality()
{
if (Entity is null)
{
+ var old = LegalityImage;
LegalityImage = null;
+ old?.Dispose();
Move1Legal = Move2Legal = Move3Legal = Move4Legal = true;
Relearn1Legal = Relearn2Legal = Relearn3Legal = Relearn4Legal = true;
return;
@@ -2868,7 +2874,13 @@ private void UpdateLegality()
var color = valid ? SKColors.Green : SKColors.Red;
using var surface = SKSurface.Create(new SKImageInfo(24, 24));
- if (surface is null) { LegalityImage = null; return; }
+ if (surface is null)
+ {
+ var old2 = LegalityImage;
+ LegalityImage = null;
+ old2?.Dispose();
+ return;
+ }
var canvas = surface.Canvas;
canvas.Clear(SKColors.Transparent);
using var paint = new SKPaint { Color = color, IsAntialias = true };
@@ -2877,7 +2889,9 @@ private void UpdateLegality()
using var image = surface.Snapshot();
using var data = image.Encode(SKEncodedImageFormat.Png, 100);
using var ms = new MemoryStream(data.ToArray());
+ var oldLegality = LegalityImage;
LegalityImage = new Bitmap(ms);
+ oldLegality?.Dispose();
// Move legality
var moves = la.Info.Moves;
@@ -2909,7 +2923,9 @@ private void UpdateLegality()
}
catch
{
+ var old = LegalityImage;
LegalityImage = null;
+ old?.Dispose();
Move1Legal = Move2Legal = Move3Legal = Move4Legal = true;
Relearn1Legal = Relearn2Legal = Relearn3Legal = Relearn4Legal = true;
}
@@ -2921,6 +2937,9 @@ private void UpdateSprite()
return;
var sprite = Entity.Sprite();
+ var old = SpriteImage;
SpriteImage = SKBitmapToAvaloniaBitmapConverter.ToAvaloniaBitmap(sprite);
+ old?.Dispose();
+ sprite.Dispose();
}
}
diff --git a/PKHeX.Avalonia/ViewModels/SAVEditorViewModel.cs b/PKHeX.Avalonia/ViewModels/SAVEditorViewModel.cs
index 5ea8ef209..de915feff 100644
--- a/PKHeX.Avalonia/ViewModels/SAVEditorViewModel.cs
+++ b/PKHeX.Avalonia/ViewModels/SAVEditorViewModel.cs
@@ -303,11 +303,15 @@ private void RefreshBox()
try
{
var wpBitmap = _sav.WallpaperImage(CurrentBox);
- BoxWallpaper = SKBitmapToAvaloniaBitmapConverter.ToAvaloniaBitmap(wpBitmap);
+ var oldWp = BoxWallpaper;
+ BoxWallpaper = SKBitmapToAvaloniaBitmapConverter.ToAvaloniaBitmapAndDispose(wpBitmap);
+ oldWp?.Dispose();
}
catch
{
+ var oldWp = BoxWallpaper;
BoxWallpaper = null;
+ oldWp?.Dispose();
}
int slotCount = Math.Min(30, _sav.BoxSlotCount);
diff --git a/PKHeX.Avalonia/ViewModels/Subforms/QRDialogViewModel.cs b/PKHeX.Avalonia/ViewModels/Subforms/QRDialogViewModel.cs
index 721027211..e6e8ddd6d 100644
--- a/PKHeX.Avalonia/ViewModels/Subforms/QRDialogViewModel.cs
+++ b/PKHeX.Avalonia/ViewModels/Subforms/QRDialogViewModel.cs
@@ -34,9 +34,14 @@ private void GenerateQR(PKM pk)
var qr = QREncode.GenerateQRCode(pk);
var sprite = pk.Sprite();
var composed = QRImageUtil.GetQRImage(qr, sprite);
+ qr.Dispose();
+ sprite.Dispose();
+ _qrBitmap?.Dispose();
_qrBitmap = composed;
+ var old = QrImage;
QrImage = SKBitmapToAvaloniaBitmapConverter.ToAvaloniaBitmap(composed);
+ old?.Dispose();
var lines = pk.GetQRLines();
SummaryText = string.Join("\n", lines);
diff --git a/PKHeX.Avalonia/ViewModels/Subforms/Trainer8ViewModel.cs b/PKHeX.Avalonia/ViewModels/Subforms/Trainer8ViewModel.cs
index 2e8a3f95e..15159a8a5 100644
--- a/PKHeX.Avalonia/ViewModels/Subforms/Trainer8ViewModel.cs
+++ b/PKHeX.Avalonia/ViewModels/Subforms/Trainer8ViewModel.cs
@@ -119,7 +119,7 @@ private void Save()
_sav.Blocks.TrainerCard.TrainerID = int.TryParse(TrainerCardId, out var tcid) ? tcid : 0;
_sav.Blocks.TrainerCard.RotoRallyScore = int.TryParse(RotoRallyScore, out var rr) ? rr : 0;
- _sav.Money = uint.TryParse(Money, out var money) ? money : 0u;
+ _sav.Money = uint.TryParse(Money, out var money) ? (uint)Math.Min(money, (uint)_sav.MaxMoney) : 0u;
var watt = uint.TryParse(Watt, out var w) ? w : 0u;
_sav.MyStatus.Watt = watt;
diff --git a/PKHeX.Avalonia/ViewModels/Subforms/Trainer8aViewModel.cs b/PKHeX.Avalonia/ViewModels/Subforms/Trainer8aViewModel.cs
index 2b1b3708b..66657228c 100644
--- a/PKHeX.Avalonia/ViewModels/Subforms/Trainer8aViewModel.cs
+++ b/PKHeX.Avalonia/ViewModels/Subforms/Trainer8aViewModel.cs
@@ -65,7 +65,7 @@ private void Save()
{
_sav.Gender = (byte)Gender;
_sav.OT = OtName;
- _sav.Money = uint.TryParse(Money, out var money) ? money : 0u;
+ _sav.Money = uint.TryParse(Money, out var money) ? (uint)Math.Min(money, (uint)_sav.MaxMoney) : 0u;
_sav.PlayedHours = (ushort)Math.Clamp(PlayedHours, 0, ushort.MaxValue);
_sav.PlayedMinutes = (ushort)Math.Clamp(PlayedMinutes, 0, 59);
diff --git a/PKHeX.Avalonia/ViewModels/Subforms/Trainer8bViewModel.cs b/PKHeX.Avalonia/ViewModels/Subforms/Trainer8bViewModel.cs
index 957ca3c55..2320132e3 100644
--- a/PKHeX.Avalonia/ViewModels/Subforms/Trainer8bViewModel.cs
+++ b/PKHeX.Avalonia/ViewModels/Subforms/Trainer8bViewModel.cs
@@ -76,7 +76,7 @@ private void Save()
_sav.Gender = (byte)Gender;
_sav.OT = OtName;
_sav.Rival = Rival;
- _sav.Money = uint.TryParse(Money, out var money) ? money : 0u;
+ _sav.Money = uint.TryParse(Money, out var money) ? (uint)Math.Min(money, (uint)_sav.MaxMoney) : 0u;
_sav.BattleTower.BP = (uint)Math.Max(0, Bp);
_sav.PlayedHours = (ushort)Math.Clamp(PlayedHours, 0, ushort.MaxValue);
diff --git a/PKHeX.Avalonia/ViewModels/Subforms/Trainer9ViewModel.cs b/PKHeX.Avalonia/ViewModels/Subforms/Trainer9ViewModel.cs
index f73692cbd..9abde5841 100644
--- a/PKHeX.Avalonia/ViewModels/Subforms/Trainer9ViewModel.cs
+++ b/PKHeX.Avalonia/ViewModels/Subforms/Trainer9ViewModel.cs
@@ -182,7 +182,7 @@ private void Save()
_sav.Version = (GameVersion)(GameIndex + (byte)GameVersion.SL);
_sav.Gender = (byte)Gender;
_sav.OT = OtName;
- _sav.Money = uint.TryParse(Money, out var m) ? m : 0u;
+ _sav.Money = uint.TryParse(Money, out var m) ? (uint)Math.Min(m, (uint)_sav.MaxMoney) : 0u;
_sav.LeaguePoints = uint.TryParse(LeaguePoints, out var lp) ? lp : 0u;
_sav.PlayedHours = (ushort)Math.Clamp(PlayedHours, 0, ushort.MaxValue);
diff --git a/PKHeX.Avalonia/ViewModels/Subforms/Trainer9aViewModel.cs b/PKHeX.Avalonia/ViewModels/Subforms/Trainer9aViewModel.cs
index 1fc36f757..cac207c51 100644
--- a/PKHeX.Avalonia/ViewModels/Subforms/Trainer9aViewModel.cs
+++ b/PKHeX.Avalonia/ViewModels/Subforms/Trainer9aViewModel.cs
@@ -113,7 +113,7 @@ private void Save()
}
_sav.OT = OtName;
- _sav.Money = uint.TryParse(Money, out var m) ? m : 0u;
+ _sav.Money = uint.TryParse(Money, out var m) ? (uint)Math.Min(m, (uint)_sav.MaxMoney) : 0u;
_sav.PlayedHours = (ushort)Math.Clamp(PlayedHours, 0, ushort.MaxValue);
_sav.PlayedMinutes = (ushort)Math.Clamp(PlayedMinutes, 0, 59);
diff --git a/PKHeX.Avalonia/ViewModels/Subforms/WondercardViewModel.cs b/PKHeX.Avalonia/ViewModels/Subforms/WondercardViewModel.cs
index 1cbf95232..fd2b51918 100644
--- a/PKHeX.Avalonia/ViewModels/Subforms/WondercardViewModel.cs
+++ b/PKHeX.Avalonia/ViewModels/Subforms/WondercardViewModel.cs
@@ -37,7 +37,9 @@ public void Refresh()
IsEmpty = Gift.IsEmpty;
Description = IsEmpty ? $"Slot {Index + 1}: (empty)" : $"Slot {Index + 1}: {Gift.CardHeader}";
var skBitmap = Gift.Sprite();
- Sprite = SKBitmapToAvaloniaBitmapConverter.ToAvaloniaBitmap(skBitmap);
+ var old = Sprite;
+ Sprite = SKBitmapToAvaloniaBitmapConverter.ToAvaloniaBitmapAndDispose(skBitmap);
+ old?.Dispose();
}
public void SetGift(DataMysteryGift gift)