Skip to content

Commit 8a82c44

Browse files
committed
- Fix #152: CSV import should ignore currency symbols.
- Fix #150: unhandled exception - Fix #149: export of an account fails with error popup - Fix #147: scrolling during reconcile - Fix #146: unnecessary error popups doing category change - Fix #143: Feature - export accounts - currency, start and end - Feature: add logging to %TEMP%\MyMoney\Logs
1 parent 2b07733 commit 8a82c44

File tree

23 files changed

+600
-234
lines changed

23 files changed

+600
-234
lines changed

Source/WPF/MoneyPackage/Package.appxmanifest

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<Package xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10" xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10" xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities" IgnorableNamespaces="uap rescap">
3-
<Identity Name="43906ChrisLovett.MyMoney.Net" Publisher="CN=Chris Lovett, O=Chris Lovett, S=Washington, C=US" Version="2.1.0.47" />
3+
<Identity Name="43906ChrisLovett.MyMoney.Net" Publisher="CN=Chris Lovett, O=Chris Lovett, S=Washington, C=US" Version="2.1.0.48" />
44
<Properties>
55
<DisplayName>MyMoney.Net</DisplayName>
66
<PublisherDisplayName>Chris Lovett</PublisherDisplayName>

Source/WPF/MyMoney/App.xaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
33
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
44
Startup="MyApplicationStartup"
5+
DispatcherUnhandledException="OnUnhandledException"
56
xmlns:ui="http://schemas.modernwpf.com/2019">
67

78

Source/WPF/MyMoney/App.xaml.cs

Lines changed: 136 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@
22
using System.Collections.Generic;
33
using System.Diagnostics;
44
using System.IO;
5+
using System.Threading.Tasks;
56
using System.Windows;
7+
using System.Windows.Interop;
8+
using System.Windows.Threading;
69
using System.Xml.Linq;
710
using Walkabout.Configuration;
811
using Walkabout.Help;
@@ -23,6 +26,10 @@ namespace Walkabout
2326
/// </summary>
2427
public partial class App : Application
2528
{
29+
private ILogger rootLog;
30+
private Log appLog;
31+
private string logsLocation;
32+
2633
private void MyApplicationStartup(object sender, StartupEventArgs e)
2734
{
2835
Settings settings = null;
@@ -31,6 +38,16 @@ private void MyApplicationStartup(object sender, StartupEventArgs e)
3138
using (PerformanceBlock.Create(ComponentId.Money, CategoryId.View, MeasurementId.AppInitialize))
3239
{
3340
#endif
41+
var path = Path.Combine(Path.GetTempPath(), "MyMoney");
42+
var logs = Path.Combine(path, "Logs");
43+
Directory.CreateDirectory(logs);
44+
this.rootLog = new Log(logs);
45+
this.appLog = Log.GetLogger("App");
46+
Log.CheckCrashLog(path);
47+
48+
Debug.WriteLine($"Writing logs to {logs}");
49+
appLog.Info("Launching MyMoney.Net");
50+
3451
HelpService.Initialize();
3552

3653
Process currentRunningInstanceOfMyMoney = null;
@@ -61,7 +78,11 @@ private void MyApplicationStartup(object sender, StartupEventArgs e)
6178
}
6279

6380
// Load the application settings
64-
settings = LoadSettings(noSettings);
81+
settings = this.LoadSettings(noSettings);
82+
83+
System.Windows.Forms.Application.SetUnhandledExceptionMode(System.Windows.Forms.UnhandledExceptionMode.CatchException);
84+
AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(this.OnAppDomainUnhandledException);
85+
TaskScheduler.UnobservedTaskException += this.TaskScheduler_UnobservedTaskException1;
6586

6687
#if PerformanceBlocks
6788
}
@@ -72,7 +93,15 @@ private void MyApplicationStartup(object sender, StartupEventArgs e)
7293
mainWindow.Show();
7394
}
7495

75-
public static Settings LoadSettings(bool noSettings)
96+
protected override void OnExit(ExitEventArgs e)
97+
{
98+
this.appLog?.Info($"App terminating with exit code {e.ApplicationExitCode}");
99+
this.appLog?.Dispose();
100+
this.rootLog?.Dispose();
101+
base.OnExit(e);
102+
}
103+
104+
public Settings LoadSettings(bool noSettings)
76105
{
77106
// make sure the directory exists.
78107
ProcessHelper.CreateSettingsDirectory();
@@ -100,7 +129,7 @@ public static Settings LoadSettings(bool noSettings)
100129
}
101130
catch (Exception ex)
102131
{
103-
Debug.WriteLine("### Error reading settings: " + ex.Message);
132+
this.appLog.Error("### Error reading settings: " + ex.Message);
104133
}
105134

106135
Settings.TheSettings = s;
@@ -214,12 +243,115 @@ private static Process FindCurrentRunningMoneyApplication()
214243
return null;
215244
}
216245

246+
// stop re-entrancy
247+
private bool handlingException;
217248

249+
private void OnUnhandledException(object sender, System.Windows.Threading.DispatcherUnhandledExceptionEventArgs e)
250+
{
251+
this.appLog.Error("Unhandled app exception", e.Exception);
252+
if (this.handlingException)
253+
{
254+
e.Handled = false;
255+
}
256+
else
257+
{
258+
this.handlingException = true;
259+
UiDispatcher.Invoke(new Action(() =>
260+
{
261+
try
262+
{
263+
e.Handled = this.HandleUnhandledException(e.Exception);
264+
}
265+
catch (Exception)
266+
{
267+
}
268+
this.handlingException = false;
269+
}));
270+
}
271+
}
272+
273+
private void OnAppDomainUnhandledException(object sender, UnhandledExceptionEventArgs e)
274+
{
275+
this.appLog.Error("Unhandled app domain exception", e.ExceptionObject as Exception);
276+
if (!this.handlingException)
277+
{
278+
this.handlingException = true;
279+
UiDispatcher.Invoke(new Action(() =>
280+
{
281+
try
282+
{
283+
this.HandleUnhandledException(e.ExceptionObject);
284+
}
285+
catch (Exception)
286+
{
287+
}
288+
this.handlingException = false;
289+
}));
290+
}
291+
}
292+
293+
private void TaskScheduler_UnobservedTaskException1(object sender, UnobservedTaskExceptionEventArgs e)
294+
{
295+
this.appLog.Error("Unhandled task scheduler exception", e.Exception);
296+
if (!this.handlingException)
297+
{
298+
this.handlingException = true;
299+
e.SetObserved();
300+
UiDispatcher.Invoke(new Action(() =>
301+
{
302+
try
303+
{
304+
this.HandleUnhandledException(e.Exception);
305+
}
306+
catch (Exception)
307+
{
308+
}
309+
this.handlingException = false;
310+
}));
311+
}
312+
}
218313

314+
public bool HandleUnhandledException(object exceptionObject)
315+
{
316+
Exception ex = exceptionObject as Exception;
317+
string message = null;
318+
string details = null;
319+
if (ex == null && exceptionObject != null)
320+
{
321+
ex = new Exception(exceptionObject.GetType().FullName + ": " + exceptionObject.ToString());
322+
}
219323

324+
message = ex.Message;
325+
details = ex.ToString();
220326

327+
try
328+
{
329+
MessageBoxEx.Show(message + " - " + Log.ReportLogging, "Unhandled Exception", details, MessageBoxButton.OK, MessageBoxImage.Error);
221330

222-
}
331+
MainWindow mw = (MainWindow)Application.Current.MainWindow;
332+
if (mw != null && mw.IsVisible)
333+
{
334+
mw.SaveIfDirty("Unhandled exception, do you want to save your changes?", null);
335+
}
336+
return true;
337+
}
338+
catch (Exception)
339+
{
340+
// hmmm, if we can't show the dialog then perhaps this is some sort of stack overflow.
341+
// save the details to a file, terminate the process
342+
this.appLog.FatalUnhandledException(message, ex);
343+
return false;
344+
}
345+
}
223346

347+
private void TaskScheduler_UnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e)
348+
{
349+
if (e.Exception != null)
350+
{
351+
this.HandleUnhandledException(e.Exception);
352+
}
353+
e.SetObserved();
354+
}
224355

356+
}
225357
}

Source/WPF/MyMoney/Commands/Commands.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ public static class AppCommands
5555
public static readonly RoutedUICommand CommandAddSampleData;
5656
public static readonly RoutedUICommand CommandTroubleshootCheckTransfer;
5757
public static readonly RoutedUICommand CommandViewChanges;
58+
public static readonly RoutedUICommand CommandViewLogs;
5859

5960
// ContextMenu on Graphs & Charts
6061
public static readonly RoutedUICommand CommandYearToDate;
@@ -124,6 +125,7 @@ static AppCommands()
124125
CommandAddSampleData = new RoutedUICommand("AddSampleData", "Add Sample Data", typeof(AppCommands));
125126
CommandTroubleshootCheckTransfer = new RoutedUICommand("TroubleshootCheckTransfer", "Check Transfer", typeof(AppCommands));
126127
CommandViewChanges = new RoutedUICommand("CommandViewChanges", "View Changes", typeof(AppCommands));
128+
CommandViewLogs = new RoutedUICommand("CommandViewLogs", "View Logs", typeof(AppCommands));
127129

128130
// Cusrtom Report range dialog
129131
CommandYearToDate = new RoutedUICommand("Year to date", "CommandYearToDate", typeof(AppCommands));

Source/WPF/MyMoney/Controls/MoneyDataGrid.cs

Lines changed: 37 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.Collections;
33
using System.Collections.Generic;
44
using System.ComponentModel;
5+
using System.Diagnostics;
56
using System.Windows;
67
using System.Windows.Controls;
78
using System.Windows.Controls.Primitives;
@@ -649,45 +650,54 @@ public void ClearAutoEdit()
649650
/// </summary>
650651
public virtual bool MoveFocusToNextEditableField()
651652
{
652-
if (this.SelectedItem == null)
653+
if (this.SelectedItem == null || this.CurrentColumn== null)
653654
{
654655
return false;
655656
}
656657

657-
// First check if the current column has more than one editable control.
658-
FrameworkElement contentPresenter = this.CurrentColumn.GetCellContent(this.SelectedItem);
659-
if (contentPresenter == null)
660-
{
661-
return false;
662-
}
663-
List<Control> editors = new List<Control>();
664-
WpfHelper.FindEditableControls(contentPresenter, editors);
665-
666-
// Move focus to the next editor in the cell if there is one.
667-
// This can happen on the "Payee/Category/Memo" column which has 3 editors in one column.
668-
for (int i = 0; i < editors.Count; i++)
658+
try
669659
{
670-
Control editor = editors[i];
671-
if (editor.IsKeyboardFocusWithin && i + 1 < editors.Count)
660+
// First check if the current column has more than one editable control.
661+
FrameworkElement contentPresenter = this.CurrentColumn.GetCellContent(this.SelectedItem);
662+
if (contentPresenter == null)
672663
{
673-
editor = editors[i + 1];
674-
this.OnStartEdit(editor);
675-
return true;
664+
return false;
676665
}
677-
}
678-
{
679-
// Begin edit on the next column, and set focus on the text edit field and skip non-editable columns.
680-
DataGridColumn c = this.CurrentColumn;
681-
int i = this.Columns.IndexOf(c) + 1;
682-
for (int n = this.Columns.Count; i < n; i++)
666+
List<Control> editors = new List<Control>();
667+
WpfHelper.FindEditableControls(contentPresenter, editors);
668+
669+
// Move focus to the next editor in the cell if there is one.
670+
// This can happen on the "Payee/Category/Memo" column which has 3 editors in one column.
671+
for (int i = 0; i < editors.Count; i++)
683672
{
684-
DataGridColumn next = this.Columns[i];
685-
if (next != null && !next.IsReadOnly)
673+
Control editor = editors[i];
674+
if (editor.IsKeyboardFocusWithin && i + 1 < editors.Count)
686675
{
687-
this.CurrentColumn = next;
676+
editor = editors[i + 1];
677+
this.OnStartEdit(editor);
688678
return true;
689679
}
690680
}
681+
682+
{
683+
// Begin edit on the next column, and set focus on the text edit field and skip non-editable columns.
684+
DataGridColumn c = this.CurrentColumn;
685+
int i = this.Columns.IndexOf(c) + 1;
686+
for (int n = this.Columns.Count; i < n; i++)
687+
{
688+
DataGridColumn next = this.Columns[i];
689+
if (next != null && !next.IsReadOnly)
690+
{
691+
this.CurrentColumn = next;
692+
return true;
693+
}
694+
}
695+
}
696+
}
697+
catch (Exception ex)
698+
{
699+
Debug.WriteLine("Unhandled exception in MoveFocusToNextEditableField: " + ex.Message);
700+
// customers reporting NullReferenceExceptions from this code, so protecting it with try/catch.
691701
}
692702
return false;
693703
}

Source/WPF/MyMoney/Controls/OfxDownloadControl.xaml.cs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -278,7 +278,8 @@ private void OnDetailsClick(object sender, RoutedEventArgs e)
278278

279279
private void GetNewPassword(OfxDownloadData ofxData)
280280
{
281-
var info = OfxRequest.GetSignonInfo(this.myMoney, ofxData.OnlineAccount);
281+
var req = new OfxRequest(ofxData.OnlineAccount, this.myMoney, null);
282+
var info = req.GetSignonInfo();
282283

283284
ChangePasswordDialog dialog = new ChangePasswordDialog(info, ofxData.OnlineAccount, this.myMoney);
284285
dialog.Owner = Application.Current.MainWindow;
@@ -306,7 +307,8 @@ private void GetNewPassword(OfxDownloadData ofxData)
306307

307308
private void GetLogin(OfxDownloadData ofxData)
308309
{
309-
var info = OfxRequest.GetSignonInfo(this.myMoney, ofxData.OnlineAccount);
310+
var req = new OfxRequest(ofxData.OnlineAccount, this.myMoney, null);
311+
var info = req.GetSignonInfo();
310312

311313
string msg = (ofxData.Error != null) ? ofxData.Error.Message : null;
312314

@@ -388,7 +390,8 @@ private void OnChallengeCompleted(object sender, EventArgs e)
388390

389391
private void GetAuthenticationToken(OfxDownloadData ofxData, OfxErrorCode code)
390392
{
391-
var info = OfxRequest.GetSignonInfo(this.myMoney, ofxData.OnlineAccount);
393+
var req = new OfxRequest(ofxData.OnlineAccount, this.myMoney, null);
394+
var info = req.GetSignonInfo();
392395
if (info != null)
393396
{
394397
this.PromptForAuthToken(ofxData, info, code);

Source/WPF/MyMoney/Database/CsvStore.cs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ public static void WriteTransaction(StreamWriter writer, Transaction t, Optional
171171
{
172172
writer.Write(",\"{0}\"", CsvSafeString(t.GetAccountCurrency().Symbol));
173173
}
174-
writer.WriteLine(",\"{0}\"", CsvSafeString("" + t.Memo));
174+
writer.WriteLine(",\"{0}\"", CsvSafeString(t.Memo));
175175
}
176176

177177
public static void WriteInvestmentHeader(StreamWriter writer)
@@ -203,7 +203,7 @@ public static void WriteInvestment(StreamWriter writer, Investment i)
203203
CsvSafeString(t.InvestmentUnits.ToString()),
204204
CsvSafeString(t.InvestmentUnitPrice.ToString("C2")),
205205
CsvSafeString(t.Amount.ToString("C2")),
206-
CsvSafeString("" + t.Memo));
206+
CsvSafeString(t.Memo));
207207
}
208208

209209
public static void WriteLoanPaymentHeader(StreamWriter writer)
@@ -232,6 +232,10 @@ public virtual void Backup(string path)
232232

233233
public static string CsvSafeString(string s)
234234
{
235+
if (string.IsNullOrEmpty(s))
236+
{
237+
return "";
238+
}
235239
// everything is going inside double quotes, so we have to protect any existing double quotes
236240
// which is done by replacing them with 2 quotes like this "".
237241
s = s.Replace("\"", "\"\"");

0 commit comments

Comments
 (0)