diff --git a/Makefile b/Makefile index d53b59fb..44a856a8 100644 --- a/Makefile +++ b/Makefile @@ -1,18 +1,16 @@ SHELL := /bin/bash -.PHONY: credits publish-alpha all +.PHONY: credits publish all help: # Target publish-alpha: Pushes the current alpha version to the server using the latest tag # Also pushes the branch and tags to the remote repository -credits: XLToolbox/html/credits.html +credits: XLToolbox/Resources/html/credits.html -XLToolbox/html/credits.html: ../web/content/about.html.haml - sed -e '1,//d; /vim:/d; s/^\( \)\{3\}//' ../web/content/about.html.haml | perl -0777 -pe 's/\[([^]]+)\]\([^)]+\)/\1/msg' | pandoc -H XLToolbox/html/style.html > XLToolbox/html/credits.html +XLToolbox/Resources/html/credits.html: ../web/content/about.haml + sed -e '1,//d; /vim:/d; s/^\( \)\{4\}//' ../web/content/about.haml | perl -0777 -pe 's/\[([^]]+)\]\([^)]+\)/\1/msg' | pandoc -H XLToolbox/Resources/html/style.html > XLToolbox/Resources/html/credits.html -GITTAG=$(shell git describe master) -VERSION=$(GITTAG:v%=%) -publish-beta: - scp publish/release/XLToolbox-$(VERSION).exe bovender@frs.sourceforge.net:/home/frs/project/xltoolbox/beta/ +publish: git push git push --tags + publish/create-release.rb diff --git a/Tests/Csv/CsvFileTest.cs b/Tests/Csv/CsvFileTest.cs index 3dd2ecd5..38a38139 100755 --- a/Tests/Csv/CsvFileTest.cs +++ b/Tests/Csv/CsvFileTest.cs @@ -39,20 +39,13 @@ public void ExportSimpleCsv() // For testing we 'hide' a pipe symbol in the field. ws.Cells[4, 5] = "wor|d"; ws.Cells[4, 6] = 88.5; - CsvFile csv = new CsvFile(); + CsvExporter model = new CsvExporter(); string fn = System.IO.Path.GetTempFileName(); - csv.FileName = fn; - csv.FieldSeparator = "|"; + model.FileName = fn; + model.FieldSeparator = "|"; // Use a funky decimal separator - csv.DecimalSeparator = "~"; - csv.Export(); - Task t = new Task(() => - { - while (csv.IsProcessing) { } - }); - t.Start(); - t.Wait(5000); - Assert.IsFalse(csv.IsProcessing, "Exporter is still processing!"); + model.DecimalSeparator = "~"; + model.Execute(); string contents = System.IO.File.ReadAllText(fn); string expected = String.Format( "hello|13{0}\"wor|d\"|88~5{0}", @@ -67,24 +60,25 @@ public void ExportLargeCsv() Worksheet ws = Instance.Default.ActiveWorkbook.Worksheets.Add(); ws.Cells[1, 1] = "hello"; ws.Cells[1000, 16384]= "world"; - CsvFile csv = new CsvFile(); + CsvExporter model = new CsvExporter(); + CsvExportViewModel vm = new CsvExportViewModel(model); string fn = System.IO.Path.GetTempFileName(); - csv.FileName = fn; + vm.FileName = fn; bool progressCompletedRaised = false; - csv.ProcessSucceeded += (sender, args) => + vm.ProcessFinishedMessage.Sent += (sender, args) => { progressCompletedRaised = true; }; - csv.Export(); + vm.StartProcess(); Task t = new Task(() => { - while (csv.IsProcessing) { } + while (model.IsProcessing) { } }); t.Start(); t.Wait(15000); - if (csv.IsProcessing) + if (vm.IsProcessing) { - csv.CancelExport(); + vm.CancelProcess(); Assert.Inconclusive("CSV export took too long, did not finish."); // Do not delete the file, leave it for inspection } @@ -105,12 +99,13 @@ public void CsvExportPerformance() Worksheet ws = Instance.Default.ActiveWorkbook.Worksheets.Add(); ws.Cells[1, 1] = "hello"; ws.Cells[200, 5] = "world"; - CsvFile csv = new CsvFile(); + CsvExporter model = new CsvExporter(); + CsvExportViewModel vm = new CsvExportViewModel(model); string fn = System.IO.Path.GetTempFileName(); - csv.FileName = fn; + model.FileName = fn; bool running = true; long start = 0; - csv.ProcessSucceeded += (sender, args) => + vm.ProcessFinishedMessage.Sent += (sender, args) => { Console.WriteLine(method + ": *** Export completed ***"); long stop = DateTime.Now.Ticks; @@ -130,7 +125,8 @@ public void CsvExportPerformance() ); waitTask.Start(); start = DateTime.Now.Ticks; - csv.Export(ws.UsedRange); + model.Range = ws.UsedRange; + vm.StartProcess(); waitTask.Wait(-1); } diff --git a/Tests/Excel/WorkbookViewModelTest.cs b/Tests/Excel/WorkbookViewModelTest.cs index 13083787..04146b54 100755 --- a/Tests/Excel/WorkbookViewModelTest.cs +++ b/Tests/Excel/WorkbookViewModelTest.cs @@ -25,12 +25,19 @@ namespace XLToolbox.Test.Excel { + /// /// Unit tests for the XLToolbox.Core.Excel namespace. /// [TestFixture] class WorkbookViewModelTest { + [TestFixtureTearDown] + public void TearDown() + { + Instance.Default.Dispose(); + } + [Test] public void WorkbookViewModelProperties() { @@ -60,6 +67,7 @@ public void MoveSheetsUp() // With no sheets selected, the move-up command should // be disabled. + wvm.Sheets.First(sheet => sheet.IsSelected).IsSelected = false; Assert.IsFalse(wvm.MoveSheetUp.CanExecute(null), "Move command is enabled, should be disabled with no sheets selected."); @@ -91,6 +99,7 @@ public void MoveSheetsDown() // With no sheets selected, the move-down command should // be disabled. + wvm.Sheets.First(sheet => sheet.IsSelected).IsSelected = false; Assert.IsFalse(wvm.MoveSheetDown.CanExecute(null), "Move-down command is enabled, should be disabled with no sheets selected."); @@ -122,10 +131,12 @@ public void MoveFirstSheetDown() // With no sheets selected, the move-down command should // be disabled. + wb.Sheets[wb.Sheets.Count].Activate(); + wvm.Sheets.First(sheet => sheet.IsSelected).IsSelected = false; Assert.IsFalse(wvm.MoveSheetDown.CanExecute(null), "Move-down command is enabled, should be disabled with no sheets selected."); - svm.IsSelected = true; + wb.Sheets[1].Activate(); Assert.IsTrue(wvm.MoveSheetDown.CanExecute(null), "Move-down command is disabled, should be enabled with one sheet selected."); @@ -140,15 +151,16 @@ public void MoveSheetsToTop() { Workbook wb = Instance.Default.CreateWorkbook(8); WorkbookViewModel wvm = new WorkbookViewModel(wb); + wvm.Sheets.First(sheet => sheet.IsSelected).IsSelected = false; // Without sheets selected, the Move-to-top command should be disabled Assert.IsFalse(wvm.MoveSheetsToTop.CanExecute(null), "The Move-to-top command should be disabled without selected sheets."); - // Select the fourth and sixth sheets and remember their names - SheetViewModel svm4 = wvm.Sheets[3]; - svm4.IsSelected = true; - string sheetName4 = svm4.DisplayString; + // // Select the fourth and sixth sheets and remember their names + // SheetViewModel svm4 = wvm.Sheets[3]; + // svm4.IsSelected = true; + // string sheetName4 = svm4.DisplayString; SheetViewModel svm6 = wvm.Sheets[5]; svm6.IsSelected = true; @@ -168,9 +180,9 @@ public void MoveSheetsToTop() // Verify that the display strings of the view models correspond to // the names of the worksheets in the workbook, to make sure that // the worksheets have indeed been rearranged as well. - Assert.AreEqual(sheetName4, wb.Sheets[1].Name, - "Moving the sheets to top was not performed on the actual workbook"); - Assert.AreEqual(sheetName6, wb.Sheets[2].Name, + // Assert.AreEqual(sheetName4, wb.Sheets[1].Name, + // "Moving the sheets to top was not performed on the actual workbook"); + Assert.AreEqual(sheetName6, wb.Sheets[1].Name, "Moving the sheets to top was not performed for all sheets on the actual workbook"); } @@ -181,13 +193,14 @@ public void MoveSheetsToBottom() WorkbookViewModel wvm = new WorkbookViewModel(wb); // Without sheets selected, the Move-to-bottom command should be disabled + wvm.Sheets.First(sheet => sheet.IsSelected).IsSelected = false; Assert.IsFalse(wvm.MoveSheetsToBottom.CanExecute(null), "The Move-to-bottom command should be disabled without selected sheets."); // Select the fourth and sixth sheets and remember their names - SheetViewModel svm2 = wvm.Sheets[1]; - svm2.IsSelected = true; - string sheetName2 = svm2.DisplayString; + // SheetViewModel svm2 = wvm.Sheets[1]; + // svm2.IsSelected = true; + // string sheetName2 = svm2.DisplayString; SheetViewModel svm4 = wvm.Sheets[3]; svm4.IsSelected = true; @@ -207,8 +220,8 @@ public void MoveSheetsToBottom() // Verify that the display strings of the view models correspond to // the names of the worksheets in the workbook, to make sure that // the worksheets have indeed been rearranged as well. - Assert.AreEqual(sheetName2, wb.Sheets[wb.Sheets.Count-1].Name, - "Moving the sheets to bottom was not performed on the actual workbook"); + // Assert.AreEqual(sheetName2, wb.Sheets[wb.Sheets.Count-1].Name, + // "Moving the sheets to bottom was not performed on the actual workbook"); Assert.AreEqual(sheetName4, wb.Sheets[wb.Sheets.Count].Name, "Moving the sheets to bottom was not performed for all sheets on the actual workbook"); } @@ -219,10 +232,11 @@ public void DeleteSheets() Workbook wb = Instance.Default.CreateWorkbook(8); int oldCount = wb.Sheets.Count; WorkbookViewModel wvm = new WorkbookViewModel(wb); + wvm.Sheets.First(sheet => sheet.IsSelected).IsSelected = false; Assert.IsFalse(wvm.DeleteSheets.CanExecute(null), "Delete sheets command should be disabled with no sheets selected."); wvm.Sheets[2].IsSelected = true; - wvm.Sheets[4].IsSelected = true; + // wvm.Sheets[4].IsSelected = true; string sheetName3 = wvm.Sheets[2].DisplayString; string sheetName5 = wvm.Sheets[4].DisplayString; int numSelected = wvm.NumSelectedSheets; @@ -256,12 +270,12 @@ public void DeleteSheets() }, String.Format("Sheet {0} (sheetName3) should have been deleted but is still there.", sheetName3) ); - Assert.Throws(typeof(System.Runtime.InteropServices.COMException), () => - { - obj = wb.Sheets[sheetName5]; - }, - String.Format("Sheet {0} (sheetName5) should have been deleted but is still there.", sheetName5) - ); + // Assert.Throws(typeof(System.Runtime.InteropServices.COMException), () => + // { + // obj = wb.Sheets[sheetName5]; + // }, + // String.Format("Sheet {0} (sheetName5) should have been deleted but is still there.", sheetName5) + // ); } [Test] @@ -283,6 +297,7 @@ public void RenameSheet() string oldName = wvm.Sheets[0].DisplayString; string newName = "valid new name"; bool messageSent = false; + wvm.Sheets.First(sheet => sheet.IsSelected).IsSelected = false; Assert.False(wvm.RenameSheet.CanExecute(null), "Rename sheet command should be disabled with no sheet selected."); wvm.Sheets[0].IsSelected = true; diff --git a/Tests/Export/ExporterTest.cs b/Tests/Export/ExporterTest.cs index 3b506c2e..7c84c561 100755 --- a/Tests/Export/ExporterTest.cs +++ b/Tests/Export/ExporterTest.cs @@ -17,27 +17,36 @@ */ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Text; -using System.IO; -using NUnit.Framework; +using System.Threading; +using System.Threading.Tasks; using Microsoft.Office.Interop.Excel; -using XLToolbox.Export; -using XLToolbox.Excel.ViewModels; +using NUnit.Framework; using Bovender.Unmanaged; +using XLToolbox.Excel.ViewModels; +using XLToolbox.Export; using XLToolbox.Export.Models; -using System.Threading.Tasks; +using XLToolbox.Export.ViewModels; namespace XLToolbox.Test.Export { [TestFixture] class ExporterTest { - [SetUp] + [TestFixtureSetUp] public void SetUp() { // Force starting Excel Instance i = Instance.Default; + SynchronizationContext.SetSynchronizationContext(new SynchronizationContext()); + } + + [TestFixtureTearDown] + public void TearDown() + { + Instance.Default.Dispose(); } [Test] @@ -68,8 +77,8 @@ public void ExportChartObject(FileType fileType, int dpi, ColorSpace colorSpace) settings.Width = 160; settings.Height = 40; File.Delete(settings.FileName); - Exporter exporter = new Exporter(); - exporter.ExportSelection(settings); + Exporter exporter = new Exporter(settings); + exporter.Execute(); Assert.IsTrue(File.Exists(settings.FileName)); } @@ -85,13 +94,12 @@ public void ExportChartSheet() settings.FileName = Path.GetFileNameWithoutExtension(Path.GetTempFileName()) + preset.FileType.ToFileNameExtension(); File.Delete(settings.FileName); - Exporter exporter = new Exporter(); - exporter.ExportSelectionQuick(settings); + Exporter exporter = new Exporter(settings, true); + exporter.Execute(); Assert.IsTrue(File.Exists(settings.FileName), "Output file was not created."); } [Test] - [RequiresSTA] [TestCase(BatchExportScope.ActiveSheet, BatchExportObjects.Charts, BatchExportLayout.SingleItems, 1)] [TestCase(BatchExportScope.ActiveWorkbook, BatchExportObjects.Charts, BatchExportLayout.SingleItems, 7)] [TestCase(BatchExportScope.ActiveWorkbook, BatchExportObjects.Charts, BatchExportLayout.SheetLayout, 4)] @@ -120,17 +128,16 @@ public void BatchExport( settings.Layout = layout; settings.Objects = objects; settings.Scope = scope; - Exporter exporter = new Exporter(); + BatchExporter exporter = new BatchExporter(settings); + BatchExportSettingsViewModel vm = new BatchExportSettingsViewModel(exporter); bool finished = false; - exporter.ProcessSucceeded += (sender, args) => { finished = true; }; - exporter.ExportBatchAsync(settings); - Task checkFinishedTask = new Task(() => - { - while (finished == false) ; - }); - checkFinishedTask.Start(); - checkFinishedTask.Wait(10000); - Assert.IsTrue(finished, "Export progress did not finish, timeout reached."); + bool abort = false; + vm.ProcessFinishedMessage.Sent += (sender, args) => { finished = true; }; + vm.StartProcess(); + Timer t = new Timer((obj) => abort = true, null, 8000, Timeout.Infinite); + while (!finished && !abort) ; + t.Dispose(); + Assert.IsFalse(abort, "Export progress did not finish, timeout reached."); Assert.AreEqual(expectedNumberOfFiles, Directory.GetFiles(settings.Path).Length); Directory.Delete(settings.Path, true); diff --git a/Tests/Properties/AssemblyInfo.cs b/Tests/Properties/AssemblyInfo.cs index ce461fad..e2402777 100755 --- a/Tests/Properties/AssemblyInfo.cs +++ b/Tests/Properties/AssemblyInfo.cs @@ -49,5 +49,5 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("7.0.0.26")] -[assembly: AssemblyFileVersion("7.0.0.26")] +[assembly: AssemblyVersion("7.0.0.27")] +[assembly: AssemblyFileVersion("7.0.0.27")] diff --git a/Tests/Tests.csproj b/Tests/Tests.csproj index 2b619e42..d0a3dc06 100755 --- a/Tests/Tests.csproj +++ b/Tests/Tests.csproj @@ -51,15 +51,15 @@ - + False - ..\packages\Bovender.0.10.0.0\lib\net40\Bovender.dll + ..\packages\Bovender.0.11.0.0\lib\net40\Bovender.dll - + False - ..\packages\Bovender.0.10.0.0\lib\net40\Bovender.dll + ..\packages\Bovender.0.11.0.0\lib\net40\Bovender.dll diff --git a/Tests/packages.config b/Tests/packages.config index 00f3b376..4fa43502 100755 --- a/Tests/packages.config +++ b/Tests/packages.config @@ -19,7 +19,7 @@ limitations under the License. --> - + diff --git a/VERSION b/VERSION index 4e4bfa10..081ece0a 100644 --- a/VERSION +++ b/VERSION @@ -1,2 +1,2 @@ -7.0.0-beta.7 -7.0.0.26 +7.0.0 +7.0.0.27 diff --git a/XLToolbox.zreproj b/XLToolbox.zreproj new file mode 100755 index 00000000..ebba91e6 --- /dev/null +++ b/XLToolbox.zreproj @@ -0,0 +1,62 @@ + + + + #### + False + 0 + 118 + True + False + <XtraSerializer version="1.0" application="View"> + <property name="#LayoutVersion" /> + <property name="ActiveFilterString" /> +</XtraSerializer> + <XtraSerializer version="1.0" application="View"> + <property name="#LayoutVersion" /> + <property name="SortInfo">~Xtra#Base64AAEAAAD/////AQAAAAAAAAAMAgAAAF1EZXZFeHByZXNzLlh0cmFHcmlkLnYxNi4xLCBWZXJzaW9uPTE2LjEuNC4wLCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWI4OGQxNzU0ZDcwMGU0OWEFAQAAADhEZXZFeHByZXNzLlh0cmFHcmlkLkNvbHVtbnMuR3JpZENvbHVtblNvcnRJbmZvQ29sbGVjdGlvbgQAAAAKZ3JvdXBDb3VudAVjbG9uZQ9jbG9uZUdyb3VwQ291bnQTQ29sbGVjdGlvbkJhc2UrbGlzdAADAAMIsgFTeXN0ZW0uQ29sbGVjdGlvbnMuR2VuZXJpYy5MaXN0YDFbW0RldkV4cHJlc3MuWHRyYUdyaWQuQ29sdW1ucy5HcmlkQ29sdW1uU29ydEluZm8sIERldkV4cHJlc3MuWHRyYUdyaWQudjE2LjEsIFZlcnNpb249MTYuMS40LjAsIEN1bHR1cmU9bmV1dHJhbCwgUHVibGljS2V5VG9rZW49Yjg4ZDE3NTRkNzAwZTQ5YV1dCBxTeXN0ZW0uQ29sbGVjdGlvbnMuQXJyYXlMaXN0AgAAAAAAAAAJAwAAAAAAAAAJBAAAAAQDAAAAsgFTeXN0ZW0uQ29sbGVjdGlvbnMuR2VuZXJpYy5MaXN0YDFbW0RldkV4cHJlc3MuWHRyYUdyaWQuQ29sdW1ucy5HcmlkQ29sdW1uU29ydEluZm8sIERldkV4cHJlc3MuWHRyYUdyaWQudjE2LjEsIFZlcnNpb249MTYuMS40LjAsIEN1bHR1cmU9bmV1dHJhbCwgUHVibGljS2V5VG9rZW49Yjg4ZDE3NTRkNzAwZTQ5YV1dAwAAAAZfaXRlbXMFX3NpemUIX3ZlcnNpb24EAAAwRGV2RXhwcmVzcy5YdHJhR3JpZC5Db2x1bW5zLkdyaWRDb2x1bW5Tb3J0SW5mb1tdAgAAAAgICQUAAAAAAAAAAAAAAAQEAAAAHFN5c3RlbS5Db2xsZWN0aW9ucy5BcnJheUxpc3QDAAAABl9pdGVtcwVfc2l6ZQhfdmVyc2lvbgUAAAgICQYAAAABAAAABAAAAAcFAAAAAAEAAAAAAAAABC5EZXZFeHByZXNzLlh0cmFHcmlkLkNvbHVtbnMuR3JpZENvbHVtblNvcnRJbmZvAgAAABAGAAAABAAAAAkHAAAADQMMCAAAABlEZXZFeHByZXNzLlh0cmFHcmlkLnYxNi4xBQcAAAAuRGV2RXhwcmVzcy5YdHJhR3JpZC5Db2x1bW5zLkdyaWRDb2x1bW5Tb3J0SW5mbwIAAAAJU29ydE9yZGVyCkNvbHVtbk5hbWUAAQgIAAAAAQAAAAYJAAAABWNvbGRlCw==</property> +</XtraSerializer> + #### + False + -1 + + + <?xml version='1.0' encoding='utf-8'?><items><item type="2" checksum="2064426735" /></items> + X:\Code\xltoolbox\NG\XLToolboxForExcel + + + X:\Code\xltoolbox\NG\XLToolbox\Strings.resx;X:\Code\xltoolbox\NG\XLToolbox\Strings.de.resx + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/XLToolbox/Csv/CsvExportViewModel.cs b/XLToolbox/Csv/CsvExportViewModel.cs index 0a428a35..4c3a43d9 100755 --- a/XLToolbox/Csv/CsvExportViewModel.cs +++ b/XLToolbox/Csv/CsvExportViewModel.cs @@ -26,16 +26,17 @@ using Microsoft.Office.Interop.Excel; using System.Threading.Tasks; using System.Threading; +using System.Diagnostics; namespace XLToolbox.Csv { - class CsvExportViewModel : ProcessViewModelBase + public class CsvExportViewModel : ProcessViewModelBase { #region Factory public static CsvExportViewModel FromLastUsed() { - return new CsvExportViewModel(CsvFile.LastExport()); + return new CsvExportViewModel(CsvExporter.LastExport()); } #endregion @@ -44,40 +45,40 @@ public static CsvExportViewModel FromLastUsed() public string FileName { - get { return _csvFile.FileName; } + get { return CsvExporter.FileName; } set { - _csvFile.FileName = value; + CsvExporter.FileName = value; OnPropertyChanged("FileName"); } } public string FieldSeparator { - get { return _csvFile.FieldSeparator; } + get { return CsvExporter.FieldSeparator; } set { - _csvFile.FieldSeparator = value; + CsvExporter.FieldSeparator = value; OnPropertyChanged("FieldSeparator"); } } public string DecimalSeparator { - get { return _csvFile.DecimalSeparator; } + get { return CsvExporter.DecimalSeparator; } set { - _csvFile.DecimalSeparator = value; + CsvExporter.DecimalSeparator = value; OnPropertyChanged("DecimalSeparator"); } } public string ThousandsSeparator { - get { return _csvFile.ThousandsSeparator; } + get { return CsvExporter.ThousandsSeparator; } set { - _csvFile.ThousandsSeparator = value; + CsvExporter.ThousandsSeparator = value; OnPropertyChanged("ThousandsSeparator"); } } @@ -138,24 +139,34 @@ public Message ChooseExportFileNameMessage #region Constructors public CsvExportViewModel() - : this(new CsvFile()) { } + : this(new CsvExporter()) { } - protected CsvExportViewModel(CsvFile model) - : base() + public CsvExportViewModel(CsvExporter model) + : base(model) + { } + + #endregion + + #region Implementation of ProcessViewModel + + protected override int GetPercentCompleted() { - _csvFile = model; - ProcessModel = _csvFile; // also hooks up events + return Convert.ToInt32(100d * CsvExporter.CellsProcessed / CsvExporter.CellsTotal); } #endregion - #region ViewModelBase implementation + #region Private properties - public override object RevealModelObject() + private CsvExporter CsvExporter { - return _csvFile; + [DebuggerStepThrough] + get + { + return ProcessModel as CsvExporter; + } } - + #endregion #region Private methods @@ -174,7 +185,7 @@ private void ConfirmChooseFileName(FileNameMessageContent messageContent) { if (messageContent.Confirmed) { - _csvFile.FileName = messageContent.Value; + CsvExporter.FileName = messageContent.Value; DoExport(); } } @@ -185,6 +196,7 @@ private void DoExport() { store.Put("csv_path", System.IO.Path.GetDirectoryName(FileName)); }; + ((CsvExporter)ProcessModel).Range = Range; StartProcess(); } @@ -192,42 +204,10 @@ private void DoExport() #region Private fields - CsvFile _csvFile; DelegatingCommand _chooseFileNameCommand; DelegatingCommand _exportCommand; Message _chooseExportFileNameMessage; #endregion - - #region Implementation of ProcessViewModel - - protected override void CancelProcess() - { - _csvFile.CancelExport(); - } - - protected override void Execute() - { - if (Range != null) - { - _csvFile.Export(Range); - } - else - { - _csvFile.Export(); - } - } - - protected override int GetPercentCompleted() - { - return Convert.ToInt32(100d * _csvFile.CellsProcessed / _csvFile.CellsTotal); - } - - protected override bool IsProcessing() - { - return _csvFile.IsProcessing; - } - - #endregion } } diff --git a/XLToolbox/Csv/CsvExporter.cs b/XLToolbox/Csv/CsvExporter.cs new file mode 100755 index 00000000..1b780be3 --- /dev/null +++ b/XLToolbox/Csv/CsvExporter.cs @@ -0,0 +1,231 @@ +/* CsvExporter.cs + * part of Daniel's XL Toolbox NG + * + * Copyright 2014-2016 Daniel Kraus + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Text; +using YamlDotNet.Serialization; +using Microsoft.Office.Interop.Excel; +using XLToolbox.Excel; + +namespace XLToolbox.Csv +{ + public class CsvExporter : CsvFileBase + { + #region Factory + + public static CsvExporter LastExport() + { + CsvSettings settings = UserSettings.UserSettings.Default.CsvSettings; + if (settings == null) + { + return new CsvExporter(); + } + else + { + return new CsvExporter(settings); + } + } + + #endregion + + #region Public properties + + /// + /// Gets whether the exporter is currently processing. + /// + [YamlIgnore] + public bool IsProcessing { get; private set; } + + /// + /// Gets the number of cells that were already processed + /// during export. + /// + [YamlIgnore] + public long CellsProcessed { get; private set; } + + /// + /// Gets the total number of cells to export. + /// + [YamlIgnore] + public long CellsTotal { get; private set; } + + [YamlIgnore] + public Range Range { get; set; } + + #endregion + + #region Constructor + + public CsvExporter() + : base() + { } + + public CsvExporter(CsvSettings settings) + : base(settings) + { } + + #endregion + + #region Implementation of ProcessModel + + /// + /// Exports the Range to a CSV file, using the file name specified + /// in the property. + /// + /// Range to export. + public override bool Execute() + { + Logger.Info("Export: Exporting CSV: FS='{0}', DS='{1}', TS='{2}'", + FieldSeparator, DecimalSeparator, ThousandsSeparator); + bool result = false; + UserSettings.UserSettings.Default.CsvSettings = Settings; + if (Range == null) + { + Range = UsedRange(); + } + if (Range == null) + { + throw new InvalidOperationException("Cannot export CSV: No range given, and the used range cannot be determined."); + } + + StreamWriter sw = null; + try + { + // StreamWriter buffers the output; using a StringBuilder + // doesn't speed up things (tried it) + sw = File.CreateText(FileName); + CellsTotal = Range.CellsCount(); + Logger.Info("Number of cells: {0}", CellsTotal); + CellsProcessed = 0; + string fs = FieldSeparator; + if (fs == "\\t") { fs = "\t"; } // Convert "\t" to tab characters + + // Get all values in an array + foreach (Range row in Range.Rows) + { + // object[,] values = range.Value2; + object[,] values = row.Value2; + if (values != null) + { + //for (long row = 1; row <= values.GetLength(0); row++) + //{ + for (long col = 1; col <= values.GetLength(1); col++) + { + CellsProcessed++; + + // If this is not the first field in the line, write a field separator. + if (col > 1) + { + sw.Write(fs); + } + + // object value = values[row, col]; + object value = values[1, col]; // 1-based index! + if (value != null) + { + if (value is string) + { + string s = value as string; + if (s.Contains(fs) || s.Contains("\"")) + { + s = "\"" + s.Replace("\"", "\"\"") + "\""; + } + sw.Write(s); + } + else + { + double d = Convert.ToDouble(value); + sw.Write(d.ToString(NumberFormatInfo)); + } + } + if (IsCancellationRequested) break; + } + sw.WriteLine(); + } + if (IsCancellationRequested) + { + sw.WriteLine(UNFINISHED_EXPORT); + sw.WriteLine("Cancelled by user."); + Logger.Info("CSV export cancelled by user"); + break; + } + // } + } + sw.Close(); + } + catch (IOException ioException) + { + IsProcessing = false; + throw ioException; + } + catch (Exception anyException) + { + IsProcessing = false; + if (sw != null) + { + sw.WriteLine(UNFINISHED_EXPORT); + sw.WriteLine(anyException.ToString()); + sw.Close(); + } + throw anyException; + } + finally + { + IsProcessing = false; + Logger.Info("CSV export task finished"); + } + return result; + } + + #endregion + + #region Private helper methods + + private Range UsedRange() + { + Worksheet worksheet = Excel.ViewModels.Instance.Default.ActiveWorkbook.ActiveSheet as Worksheet; + if (worksheet != null) + { + return worksheet.UsedRange; + } + else + { + return null; + } + } + + #endregion + + #region Private constant + + const string UNFINISHED_EXPORT = "*** UNFINISHED EXPORT ***"; + + #endregion + + #region Class logger + + private static NLog.Logger Logger { get { return _logger.Value; } } + + private static readonly Lazy _logger = new Lazy(() => NLog.LogManager.GetCurrentClassLogger()); + + #endregion + } +} diff --git a/XLToolbox/Csv/CsvFile.cs b/XLToolbox/Csv/CsvFile.cs deleted file mode 100755 index a23366ca..00000000 --- a/XLToolbox/Csv/CsvFile.cs +++ /dev/null @@ -1,309 +0,0 @@ -/* CsvFile.cs - * part of Daniel's XL Toolbox NG - * - * Copyright 2014-2016 Daniel Kraus - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -using System; -using System.IO; -using Microsoft.Office.Interop.Excel; -using System.Globalization; -using XLToolbox.Excel; -using System.Threading.Tasks; -using System.Xml.Serialization; - -namespace XLToolbox.Csv -{ - /// - /// Provides import/export settings and methods for CSV files. - /// - public class CsvFile : Bovender.Mvvm.Models.ProcessModel - { - #region Factory - - public static CsvFile LastImport() - { - CsvFile c = UserSettings.UserSettings.Default.CsvImport; - if (c == null) - { - c = new CsvFile(); - } - return c; - } - - public static CsvFile LastExport() - { - CsvFile c = UserSettings.UserSettings.Default.CsvExport; - if (c == null) - { - c = new CsvFile(); - } - return c; - } - - #endregion - - #region Public properties - - public string FileName { get; set; } - - public string FieldSeparator { get; set; } - - public string DecimalSeparator - { - get { return NumberFormatInfo.NumberDecimalSeparator; } - set { NumberFormatInfo.NumberDecimalSeparator = value; } - } - - public string ThousandsSeparator - { - get { return NumberFormatInfo.NumberGroupSeparator; } - set { NumberFormatInfo.NumberGroupSeparator = value; } - } - - /// - /// Returns a System.Globalization.NumberFormatInfo object - /// whose properties are set according to the current - /// properties (namely, .) - /// This property is mainly used internally, but available - /// publicly for convenience. - /// - public NumberFormatInfo NumberFormatInfo - { - get - { - if (_numberFormatInfo == null) - { - _numberFormatInfo = CultureInfo.InvariantCulture.NumberFormat.Clone() as NumberFormatInfo; - _numberFormatInfo.NumberGroupSeparator = ""; - } - return _numberFormatInfo; - } - } - - /// - /// Gets whether the exporter is currently processing. - /// - [XmlIgnore] - public bool IsProcessing { get; private set; } - - /// - /// Gets the number of cells that were already processed - /// during export. - /// - [XmlIgnore] - public long CellsProcessed { get; private set; } - - /// - /// Gets the total number of cells to export. - /// - [XmlIgnore] - public long CellsTotal { get; private set; } - - #endregion - - #region Constructor - - public CsvFile() - { - FieldSeparator = ","; - DecimalSeparator = "."; - ThousandsSeparator = ""; - } - - #endregion - - #region Import/export methods - - public void Import() - { - Logger.Info("Importing CSV: FS='{0}', DS='{1}', TS='{2}'", - FieldSeparator, DecimalSeparator, ThousandsSeparator); - UserSettings.UserSettings.Default.CsvImport = this; - Excel.ViewModels.Instance.Default.Application.Workbooks.OpenText( - FileName, - DataType: XlTextParsingType.xlDelimited, - Other: true, OtherChar: StringParam(FieldSeparator), - DecimalSeparator: StringParam(DecimalSeparator), - ThousandsSeparator: StringParam(ThousandsSeparator), - Local: false, ConsecutiveDelimiter: false, - Origin: XlPlatform.xlWindows - ); - } - - /// - /// Exports the used range of the active worksheet to a CSV file, - /// using the file name specified in the property. - /// - public void Export() - { - Worksheet worksheet = Excel.ViewModels.Instance.Default.ActiveWorkbook.ActiveSheet as Worksheet; - if (worksheet == null) - { - throw new InvalidOperationException("Cannot export chart to CSV file."); - } - Export(worksheet.UsedRange); - } - - /// - /// Exports a range to a CSV file, using the file name specified - /// in the property. - /// - /// Range to export. - public void Export(Range range) - { - Logger.Info("Exporting CSV: FS='{0}', DS='{1}', TS='{2}'", - FieldSeparator, DecimalSeparator, ThousandsSeparator); - UserSettings.UserSettings.Default.CsvExport = this; - IsProcessing = true; - Task t = new Task(() => - { - StreamWriter sw = null; - try - { - // StreamWriter buffers the output; using a StringBuilder - // doesn't speed up things (tried it) - sw = File.CreateText(FileName); - CellsTotal = range.CellsCount(); - Logger.Info("Number of cells: {0}", CellsTotal); - CellsProcessed = 0; - _cancelExport = false; - string fs = FieldSeparator; - if (fs == "\\t") { fs = "\t"; } // Convert "\t" to tab characters - - - // Get all values in an array - foreach (Range row in range.Rows) - { - // object[,] values = range.Value2; - object[,] values = row.Value2; - if (values != null) - { - //for (long row = 1; row <= values.GetLength(0); row++) - //{ - for (long col = 1; col <= values.GetLength(1); col++) - { - CellsProcessed++; - - // If this is not the first field in the line, write a field separator. - if (col > 1) - { - sw.Write(fs); - } - - // object value = values[row, col]; - object value = values[1, col]; // 1-based index! - if (value != null) - { - if (value is string) - { - string s = value as string; - if (s.Contains(fs) || s.Contains("\"")) - { - s = "\"" + s.Replace("\"", "\"\"") + "\""; - } - sw.Write(s); - } - else - { - double d = Convert.ToDouble(value); - sw.Write(d.ToString(NumberFormatInfo)); - } - } - if (_cancelExport) break; - } - sw.WriteLine(); - } - if (_cancelExport) - { - sw.WriteLine(UNFINISHED_EXPORT); - sw.WriteLine("Cancelled by user."); - Logger.Info("CSV export cancelled by user"); - break; - } - // } - } - sw.Close(); - if (!_cancelExport) OnProcessSucceeded(); - } - catch (IOException e) - { - IsProcessing = false; - OnProcessFailed(e); - } - catch (Exception e1) - { - IsProcessing = false; - if (sw != null) - { - sw.WriteLine(UNFINISHED_EXPORT); - sw.WriteLine(e1.ToString()); - sw.Close(); - } - OnProcessFailed(e1); - } - finally - { - IsProcessing = false; - Logger.Info("CSV export task finished"); - } - }); - t.Start(); - } - - public void CancelExport() - { - _cancelExport = true; - } - - #endregion - - #region Private methods - - /// - /// Helper function that converts empty strings to Type.Missing. - /// - /// String to convert. - /// String or Type.Missing if string is null or empty. - /// This function is necessary because Workbooks.OpenText will - /// throw a COM exception if one of the optional parameters is an empty - /// string. - /// - object StringParam(string s) - { - if (String.IsNullOrEmpty(s)) - { - return Type.Missing; - } - else - { - return s; - } - } - - #endregion - - #region Fields - - NumberFormatInfo _numberFormatInfo; - bool _cancelExport; - - #endregion - - #region Private constant - - const string UNFINISHED_EXPORT = "*** UNFINISHED EXPORT ***"; - #endregion - } -} diff --git a/XLToolbox/Csv/CsvFileBase.cs b/XLToolbox/Csv/CsvFileBase.cs new file mode 100755 index 00000000..fc8b8543 --- /dev/null +++ b/XLToolbox/Csv/CsvFileBase.cs @@ -0,0 +1,131 @@ +/* CsvFile.cs + * part of Daniel's XL Toolbox NG + * + * Copyright 2014-2016 Daniel Kraus + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +using System; +using System.IO; +using Microsoft.Office.Interop.Excel; +using System.Globalization; +using XLToolbox.Excel; +using System.Threading.Tasks; +using System.Xml.Serialization; + +namespace XLToolbox.Csv +{ + /// + /// Provides import/export settings and methods for CSV files. + /// + public abstract class CsvFileBase : Bovender.Mvvm.Models.ProcessModel + { + #region Properties + + public string FileName { get; set; } + + public string FieldSeparator + { + get + { + return Settings.FieldSeparator; + } + set + { + Settings.FieldSeparator = value; + } + } + + public string DecimalSeparator + { + get + { + return Settings.DecimalSeparator; + } + set + { + Settings.DecimalSeparator = value; + } + } + + public string ThousandsSeparator + { + get + { + return Settings.ThousandsSeparator; + } + set + { + Settings.ThousandsSeparator = value; + } + } + + public CsvSettings Settings + { + get + { + if (_settings == null) + { + _settings = new CsvSettings(); + } + return _settings; + } + set + { + _settings = value; + } + } + + #endregion + + #region Constructors + + /// + /// Instantiates the class with a new CsvSettings object. + /// + protected CsvFileBase() + : base() + { } + + /// + /// Instantiates the class with a given CsvSettings object. + /// + /// Settings to initialize the + /// class with. + protected CsvFileBase(CsvSettings settings) + : this() + { + Settings = settings; + } + + #endregion + + #region Protected property + + protected NumberFormatInfo NumberFormatInfo + { + get + { + return Settings.NumberFormatInfo; + } + } + + #endregion + + #region Private fields + + private CsvSettings _settings; + + #endregion + } +} diff --git a/XLToolbox/Csv/CsvImportViewModel.cs b/XLToolbox/Csv/CsvImportViewModel.cs index b74a7a31..742f97b7 100755 --- a/XLToolbox/Csv/CsvImportViewModel.cs +++ b/XLToolbox/Csv/CsvImportViewModel.cs @@ -22,16 +22,17 @@ using Bovender.Mvvm.ViewModels; using Bovender.Mvvm.Messaging; using Bovender.Mvvm; +using System.Diagnostics; namespace XLToolbox.Csv { - class CsvImportViewModel : ViewModelBase + public class CsvImportViewModel : ProcessViewModelBase { #region Factory public static CsvImportViewModel FromLastUsed() { - return new CsvImportViewModel(CsvFile.LastImport()); + return new CsvImportViewModel(CsvImporter.LastImport()); } #endregion @@ -40,40 +41,40 @@ public static CsvImportViewModel FromLastUsed() public string FileName { - get { return _csvFile.FileName; } + get { return Importer.FileName; } set { - _csvFile.FileName = value; + Importer.FileName = value; OnPropertyChanged("FileName"); } } public string FieldSeparator { - get { return _csvFile.FieldSeparator; } + get { return Importer.FieldSeparator; } set { - _csvFile.FieldSeparator = value; + Importer.FieldSeparator = value; OnPropertyChanged("FieldSeparator"); } } public string DecimalSeparator { - get { return _csvFile.DecimalSeparator; } + get { return Importer.DecimalSeparator; } set { - _csvFile.DecimalSeparator = value; + Importer.DecimalSeparator = value; OnPropertyChanged("DecimalSeparator"); } } public string ThousandsSeparator { - get { return _csvFile.ThousandsSeparator; } + get { return Importer.ThousandsSeparator; } set { - _csvFile.ThousandsSeparator = value; + Importer.ThousandsSeparator = value; OnPropertyChanged("ThousandsSeparator"); } } @@ -129,21 +130,19 @@ public Message ChooseImportFileNameMessage #region Constructors public CsvImportViewModel() - : this(new CsvFile()) { } + : this(new CsvImporter()) { } - protected CsvImportViewModel(CsvFile model) - : base() - { - _csvFile = model; - } + protected CsvImportViewModel(CsvImporter model) + : base(model) + { } #endregion - #region ViewModelBase implementation + #region ProcessViewModelBase implementation - public override object RevealModelObject() + protected override int GetPercentCompleted() { - return _csvFile; + return 50; // TODO } #endregion @@ -164,7 +163,7 @@ private void ConfirmChooseFileName(FileNameMessageContent messageContent) { if (messageContent.Confirmed) { - _csvFile.FileName = messageContent.Value; + Importer.FileName = messageContent.Value; DoImport(); } } @@ -175,15 +174,26 @@ private void DoImport() { store.Put("csv_path", System.IO.Path.GetDirectoryName(FileName)); } - _csvFile.Import(); + Importer.Execute(); CloseViewCommand.Execute(null); } #endregion + #region Private properties + + private CsvImporter Importer + { + [DebuggerStepThrough] + get + { + return ProcessModel as CsvImporter; + } + } + #endregion + #region Private fields - CsvFile _csvFile; DelegatingCommand _chooseFileNameCommand; DelegatingCommand _importCommand; Message _chooseImportFileNameMessage; diff --git a/XLToolbox/Csv/CsvImporter.cs b/XLToolbox/Csv/CsvImporter.cs new file mode 100755 index 00000000..33f40b9f --- /dev/null +++ b/XLToolbox/Csv/CsvImporter.cs @@ -0,0 +1,112 @@ +using Microsoft.Office.Interop.Excel; +/* CsvImporter.cs + * part of Daniel's XL Toolbox NG + * + * Copyright 2014-2016 Daniel Kraus + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; + +namespace XLToolbox.Csv +{ + public class CsvImporter : CsvFileBase + { + #region Factory + + public static CsvImporter LastImport() + { + CsvSettings settings = UserSettings.UserSettings.Default.CsvSettings; + if (settings == null) + { + return new CsvImporter(); + } + else + { + return new CsvImporter(settings); + } + } + + #endregion + + #region Implementation of ProcessModel + + public override bool Execute() + { + Logger.Info("Importing CSV: FS='{0}', DS='{1}', TS='{2}'", + FieldSeparator, DecimalSeparator, ThousandsSeparator); + UserSettings.UserSettings.Default.CsvSettings = Settings; + Excel.ViewModels.Instance.Default.Application.Workbooks.OpenText( + FileName, + DataType: XlTextParsingType.xlDelimited, + Other: true, OtherChar: StringParam(FieldSeparator), + DecimalSeparator: StringParam(DecimalSeparator), + ThousandsSeparator: StringParam(ThousandsSeparator), + Local: false, ConsecutiveDelimiter: false, + Origin: XlPlatform.xlWindows + ); + return true; + } + + #endregion + + #region Constructors + + public CsvImporter() + : base() + { } + + public CsvImporter(CsvSettings settings) + : base(settings) + { } + + #endregion + + #region Private methods + + /// + /// Helper function that converts empty strings to Type.Missing. + /// + /// String to convert. + /// String or Type.Missing if string is null or empty. + /// This function is necessary because Workbooks.OpenText will + /// throw a COM exception if one of the optional parameters is an empty + /// string. + /// + object StringParam(string s) + { + if (String.IsNullOrEmpty(s)) + { + return Type.Missing; + } + else + { + return s; + } + } + + #endregion + + #region Class logger + + private static NLog.Logger Logger { get { return _logger.Value; } } + + private static readonly Lazy _logger = new Lazy(() => NLog.LogManager.GetCurrentClassLogger()); + + #endregion + } +} diff --git a/XLToolbox/Csv/CsvSettings.cs b/XLToolbox/Csv/CsvSettings.cs new file mode 100755 index 00000000..71c5a4ce --- /dev/null +++ b/XLToolbox/Csv/CsvSettings.cs @@ -0,0 +1,84 @@ +/* CsvSettings.cs + * part of Daniel's XL Toolbox NG + * + * Copyright 2014-2016 Daniel Kraus + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; + +namespace XLToolbox.Csv +{ + public class CsvSettings + { + #region Public properties + + public string FieldSeparator { get; set; } + + public string DecimalSeparator + { + get { return NumberFormatInfo.NumberDecimalSeparator; } + set { NumberFormatInfo.NumberDecimalSeparator = value; } + } + + public string ThousandsSeparator + { + get { return NumberFormatInfo.NumberGroupSeparator; } + set { NumberFormatInfo.NumberGroupSeparator = value; } + } + + /// + /// Returns a System.Globalization.NumberFormatInfo object + /// whose properties are set according to the current + /// properties (namely, .) + /// This property is mainly used internally, but available + /// publicly for convenience. + /// + [YamlDotNet.Serialization.YamlIgnore] + public NumberFormatInfo NumberFormatInfo + { + get + { + if (_numberFormatInfo == null) + { + _numberFormatInfo = CultureInfo.InvariantCulture.NumberFormat.Clone() as NumberFormatInfo; + _numberFormatInfo.NumberGroupSeparator = ""; + } + return _numberFormatInfo; + } + } + + #endregion + + #region Constructor + + public CsvSettings() + { + FieldSeparator = ","; + DecimalSeparator = "."; + ThousandsSeparator = ""; + } + + #endregion + + #region Fields + + private NumberFormatInfo _numberFormatInfo; + + #endregion + } +} diff --git a/XLToolbox/Dispatcher.cs b/XLToolbox/Dispatcher.cs index 4abd48d0..718f233f 100755 --- a/XLToolbox/Dispatcher.cs +++ b/XLToolbox/Dispatcher.cs @@ -65,7 +65,7 @@ public static void Execute(Command cmd) case Command.BatchExportLast: BatchExportLast(); break; case Command.ExportScreenshot: ExportScreenshot(); break; case Command.Donate: OpenDonatePage(); break; - case Command.ThrowError: throw new InsufficientMemoryException(); + case Command.ThrowError: throw new ExceptionHandler.TestException("This exception was thrown for testing purposes"); case Command.QuitExcel: QuitExcel(); break; case Command.OpenCsv: OpenCsv(); break; case Command.OpenCsvWithParams: OpenCsvWithSettings(); break; @@ -154,7 +154,7 @@ static void CheckForUpdates() static void SheetManager() { - SheetManagerPane.Default.Visible = true; + TaskPaneManager.Default.Visible = true; // wvm.InjectAndShowInThread(); } @@ -167,8 +167,8 @@ static void ExportSelection() } SingleExportSettings settings = SingleExportSettings.CreateForSelection(preset); SingleExportSettingsViewModel vm = new SingleExportSettingsViewModel(settings); - vm.ShowProgressMessage.Sent += ShowProgressMessage_Sent; - vm.ProcessFailedMessage.Sent += ProcessFailedMessage_Sent; + vm.ShowProgressMessage.Sent += Exporter_ShowProgress_Sent; + vm.ProcessFinishedMessage.Sent += Exporter_ProcessFinished_Sent; vm.InjectInto().ShowDialogInForm(); } @@ -187,8 +187,8 @@ static void BatchExport() vm = new BatchExportSettingsViewModel(); } vm.SanitizeOptions(); - vm.ShowProgressMessage.Sent += ShowProgressMessage_Sent; - vm.ProcessFailedMessage.Sent += ProcessFailedMessage_Sent; + vm.ShowProgressMessage.Sent += Exporter_ShowProgress_Sent; + vm.ProcessFinishedMessage.Sent += Exporter_ProcessFinished_Sent; vm.InjectInto().ShowDialogInForm(); } @@ -385,19 +385,22 @@ static Csv.CsvExportViewModel CreateCsvExportViewModel(Xl.Range range) }; args.Content.InjectInto().Show(); }; - vm.ProcessFailedMessage.Sent += (sender, args) => + vm.ProcessFinishedMessage.Sent += (sender, args) => { - Logger.Info("Received ExportFailedMessage, informing user"); - Bovender.Mvvm.Actions.ProcessCompletedAction action = new ProcessCompletedAction( - args.Content, Strings.CsvExportFailed, Strings.CsvExportFailed, Strings.Close); - action.Invoke(args); + if (args.Content.Exception != null) + { + Logger.Info("Received ProcessFinishedMessage with exception"); + Bovender.Mvvm.Actions.ProcessCompletedAction action = new ProcessCompletedAction( + args.Content, Strings.CsvExportFailed, Strings.CsvExportFailed, Strings.Close); + action.Invoke(args); + } }; return vm; } - internal static void ShowProgressMessage_Sent(object sender, MessageArgs e) + internal static void Exporter_ShowProgress_Sent(object sender, MessageArgs e) { - Logger.Info("Creating process view"); + Logger.Info("Exporter_ShowProgress_Sent: Creating process view"); e.Content.CancelButtonText = Strings.Cancel; e.Content.Caption = Strings.Export; e.Content.CompletedMessage.Sent += (sender2, args2) => @@ -407,12 +410,19 @@ internal static void ShowProgressMessage_Sent(object sender, MessageArgs().ShowDialogInForm(); } - internal static void ProcessFailedMessage_Sent(object sender, MessageArgs e) + internal static void Exporter_ProcessFinished_Sent(object sender, MessageArgs e) { - Logger.Info("Received ExportFailedMessage, informing user"); - Bovender.Mvvm.Actions.ProcessCompletedAction action = new ProcessCompletedAction( - e.Content, Strings.ExportFailed, Strings.ExportFailedMessage, Strings.Close); - action.Invoke(e); + if (e.Content.Exception != null) + { + Logger.Info("Exporter_ProcessFinished_Sent: Received ProcessFinishedMessage with exception, informing user"); + Bovender.Mvvm.Actions.ProcessCompletedAction action = new ProcessCompletedAction( + e.Content, Strings.ExportFailed, Strings.ExportFailedMessage, Strings.Close); + action.Invoke(e); + } + else + { + Logger.Info("Exporter_ProcessFinished_Sent: Exporter process has finished"); + } } #endregion diff --git a/XLToolbox/Excel/ViewModels/Instance.cs b/XLToolbox/Excel/ViewModels/Instance.cs index 2c618daa..423524ff 100755 --- a/XLToolbox/Excel/ViewModels/Instance.cs +++ b/XLToolbox/Excel/ViewModels/Instance.cs @@ -25,6 +25,7 @@ using Bovender.Mvvm.Messaging; using System.Diagnostics; using System.IO; +using System.Globalization; namespace XLToolbox.Excel.ViewModels { @@ -58,6 +59,12 @@ public static Instance Default #endregion + #region Events + + public event EventHandler ShuttingDown; + + #endregion + #region Commands public DelegatingCommand QuitInteractivelyCommand @@ -169,6 +176,24 @@ public string ActivePath } } + /// + /// Gets the major version number of the Excel instance + /// as an integer. + /// + public int MajorVersion + { + get + { + if (_majorVersion == 0) + { + _majorVersion = Convert.ToInt32( + Application.Version.Split('.')[0], + CultureInfo.InvariantCulture); + } + return _majorVersion; + } + } + /// /// Gets the Excel version and build number in a human-friendly form. /// @@ -183,39 +208,39 @@ public string HumanFriendlyVersion { get { - Application app = Application; - string name = String.Empty; - string sp = String.Empty; - switch (Convert.ToInt32(app.Version.Split('.')[0])) + string versionName = String.Empty; + string servicePack = String.Empty; + int build = Application.Build; + switch (MajorVersion) { // Very old versions are ignored (won't work with VSTO anyway) - case 11: name = "2003"; break; - case 12: name = "2007"; break; - case 14: name = "2010"; break; - case 15: name = "2013"; break; - case 16: name = "365"; break; // I believe (sparse information on the web) - } - int build = app.Build; - switch (app.Version) - { - case "15.0": - // 2013 SP information: http://support.microsoft.com/kb/2817430/en-us - if (build >= 4569) { sp = " SP1"; } + case 11: + versionName = "2003"; + break; + case 12: + versionName = "2007"; + // 2007 SP information: http://support.microsoft.com/kb/928116/en-us + if (build >= 6611) { servicePack = " SP3"; } + else if (build >= 6425) { servicePack = " SP2"; } + else if (build >= 6241) { servicePack = " SP1"; } break; - case "14.0": + case 14: // 2010 SP information: http://support.microsoft.com/kb/2121559/en-us - if (build >= 7015) { sp = " SP2"; } - else if (build >= 6029) { sp = " SP1"; } + versionName = "2010"; + if (build >= 7015) { servicePack = " SP2"; } + else if (build >= 6029) { servicePack = " SP1"; } break; - case "12.0": - // 2007 SP information: http://support.microsoft.com/kb/928116/en-us - if (build >= 6611) { sp = " SP3"; } - else if (build >= 6425) { sp = " SP2"; } - else if (build >= 6241) { sp = " SP1"; } + case 15: + // 2013 SP information: http://support.microsoft.com/kb/2817430/en-us + versionName = "2013"; + if (build >= 4569) { servicePack = " SP1"; } break; + case 16: + versionName = "365"; + break; // I believe (sparse information on the web) } return String.Format("{0}{1} ({2}.{3})", - name, sp, app.Version, app.Build); + versionName, servicePack, Application.Version, Application.Build); } } @@ -294,6 +319,18 @@ public int CountSavedWorkbooks } } + /// + /// Gets whether the current Excel instance has an SDI (Excel 2013+) + /// or not (Excel 2007/2010). + /// + public bool IsSingleDocumentInterface + { + get + { + return MajorVersion >= 15; + } + } + #endregion #region Public methods @@ -510,17 +547,36 @@ public override object RevealModelObject() /// Shuts down the current instance of Excel; no warning message will be shown. /// If an instance of this class exists, an error will be thrown. /// - void Shutdown() + private void Shutdown() { if (_application != null) { _application.DisplayAlerts = false; - Logger.Info("Now quitting Excel."); - _application.Quit(); + OnShuttingDown(); + Logger.Info("Shutdown: Now quitting Excel."); + System.Threading.Timer t = new System.Threading.Timer((obj) => + { + ((Application)obj).Quit(); + }, _application, 150, System.Threading.Timeout.Infinite); + // _application.Quit(); _application = null; } } + private void OnShuttingDown() + { + EventHandler h = ShuttingDown; + if (h != null) + { + Logger.Info("OnShuttingDown: {0} event subscriber(s)", h.GetInvocationList().Length); + h(this, new InstanceShutdownEventArgs(Application)); + } + else + { + Logger.Info("OnShuttingDown: No-one is listening."); + } + } + private void DoQuitInteractively() { Logger.Info("DoQuitInteractively"); @@ -613,13 +669,13 @@ private bool CloseAllWorkbooksThenShutdown() // Try if the workbook has been closed if (n == CountOpenWorkbooks) return false; } - Logger.Info("Examining the situation."); + Logger.Info("CloseAllWorkbooksThenShutdown: Examining the situation."); if (Application.Workbooks.Count == 0) { Logger.Info("No more workbooks left."); CloseViewCommand.Execute(null); Shutdown(); - Logger.Info("Shutting down."); + Logger.Info("CloseAllWorkbooksThenShutdown: Shutting down."); return true; } else @@ -635,6 +691,7 @@ private bool CloseAllWorkbooksThenShutdown() private bool _disposed; private Application _application; + private int _majorVersion; private DelegatingCommand _quitInteractivelyCommand; private DelegatingCommand _quitSavingChangesCommand; private DelegatingCommand _quitDiscardingChangesCommand; diff --git a/XLToolbox/Excel/ViewModels/InstanceShutdownEventArgs.cs b/XLToolbox/Excel/ViewModels/InstanceShutdownEventArgs.cs new file mode 100755 index 00000000..36d1da1b --- /dev/null +++ b/XLToolbox/Excel/ViewModels/InstanceShutdownEventArgs.cs @@ -0,0 +1,35 @@ +using Microsoft.Office.Interop.Excel; +/* InstanceShutdownEventArgs.cs + * part of Daniel's XL Toolbox NG + * + * Copyright 2014-2016 Daniel Kraus + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace XLToolbox.Excel.ViewModels +{ + public class InstanceShutdownEventArgs : EventArgs + { + public Application Application { get; private set; } + + public InstanceShutdownEventArgs(Application application) + { + Application = application; + } + } +} diff --git a/XLToolbox/Excel/ViewModels/WorkbookViewModel.cs b/XLToolbox/Excel/ViewModels/WorkbookViewModel.cs index 1b086751..514213bf 100755 --- a/XLToolbox/Excel/ViewModels/WorkbookViewModel.cs +++ b/XLToolbox/Excel/ViewModels/WorkbookViewModel.cs @@ -16,15 +16,14 @@ * limitations under the License. */ using System; -using System.Collections.ObjectModel; using System.ComponentModel; +using System.Linq; +using System.Collections.ObjectModel; using Microsoft.Office.Interop.Excel; using Bovender.Mvvm; using Bovender.Mvvm.ViewModels; using Bovender.Mvvm.Messaging; -using System.Text; using System.Threading; -using System.Threading.Tasks; namespace XLToolbox.Excel.ViewModels { @@ -34,59 +33,6 @@ namespace XLToolbox.Excel.ViewModels /// public class WorkbookViewModel : ViewModelBase { - /// - /// Interval at which to refresh the sheet list when monitoring - /// the Workbook (see command, - /// method). - /// - const int MONITOR_INTERVAL = 700; // ms - - #region Private fields - - private Workbook _workbook; - private ObservableCollection _sheets; - private SheetViewModel _lastSelectedSheet; - private DelegatingCommand _moveSheetUp; - private DelegatingCommand _moveSheetsToTop; - private DelegatingCommand _moveSheetDown; - private DelegatingCommand _moveSheetsToBottom; - private DelegatingCommand _deleteSheets; - private DelegatingCommand _renameSheet; - private DelegatingCommand _monitorWorkbook; - private DelegatingCommand _unmonitorWorkbook; - private Message _confirmDeleteMessage; - private Message _renameSheetMessage; - private bool _monitoring; - private string _lastSheetsString; - - #endregion - - #region Protected properties - - protected Workbook Workbook - { - get - { - return _workbook; - } - set - { - _workbook = value; - OnPropertyChanged("Workbook"); - BuildSheetList(); - if (_workbook != null) - { - DisplayString = _workbook.Name; - } - else - { - DisplayString = String.Empty; - } - } - } - - #endregion - #region Public properties public int NumSelectedSheets { get; private set; } @@ -310,8 +256,16 @@ public DelegatingCommand UnmonitorWorkbook public WorkbookViewModel() { - Excel.ViewModels.Instance.Default.Application.WorkbookActivate += Application_WorkbookActivate; - Excel.ViewModels.Instance.Default.Application.WorkbookDeactivate += Application_WorkbookDeactivate; + if (!XLToolbox.Excel.ViewModels.Instance.Default.IsSingleDocumentInterface) + { + // Change the workbook model only if this is not an SDI application + Excel.ViewModels.Instance.Default.Application.WorkbookActivate += Application_WorkbookActivate; + Excel.ViewModels.Instance.Default.Application.WorkbookDeactivate += Application_WorkbookDeactivate; + } + Instance.Default.ShuttingDown += (sender, args) => + { + DoUnmonitorWorkbook(); + }; } public WorkbookViewModel(Workbook workbook) @@ -322,6 +276,15 @@ public WorkbookViewModel(Workbook workbook) #endregion + #region Implementation of ViewModelBase's abstract methods + + public override object RevealModelObject() + { + return _workbook; + } + + #endregion + #region Protected methods protected void BuildSheetList() @@ -333,8 +296,8 @@ protected void BuildSheetList() SheetViewModel svm; foreach (dynamic sheet in Workbook.Sheets) { - // Directly comparing the Visible property with XlSheetVisibility.xlSheetVisible - // caused exceptions. Therefore we compare directly with the value of 0. + // Need to cats because directly comparing the Visible property with + // XlSheetVisibility.xlSheetVisible caused exceptions. if ((XlSheetVisibility)sheet.Visible == XlSheetVisibility.xlSheetVisible) { svm = new SheetViewModel(sheet); @@ -342,7 +305,9 @@ protected void BuildSheetList() sheets.Add(svm); } }; + Workbook.SheetActivate += SheetActivated; Sheets = sheets; + SheetActivated(Workbook.ActiveSheet); } else { @@ -388,6 +353,7 @@ void Application_WorkbookDeactivate(Workbook Wb) private void DoMoveSheetUp() { + _lockEvents = true; // When iterating over the worksheet view models in the Sheets collection // as well as over the sheets collection of the workbook, keep in mind // that Excel workbook collections are 1-based. @@ -399,10 +365,12 @@ private void DoMoveSheetUp() Sheets.Move(i, i - 1); } } + _lockEvents = false; } private void DoMoveSheetsToTop() { + _lockEvents = true; int currentTop = 0; for (int i = 1; i < Sheets.Count; i++) { @@ -413,6 +381,7 @@ private void DoMoveSheetsToTop() currentTop++; } } + _lockEvents = false; } private bool CanMoveSheetUp() @@ -427,6 +396,7 @@ private bool CanMoveSheetsToTop() private void DoMoveSheetDown() { + _lockEvents = true; // When iterating over the worksheet view models in the Sheets collection // as well as over the sheets collection of the workbook, keep in mind // that Excel workbook collections are 1-based. @@ -438,10 +408,12 @@ private void DoMoveSheetDown() Sheets.Move(i, i + 1); } } + _lockEvents = false; } private void DoMoveSheetsToBottom() { + _lockEvents = true; int currentBottom = Sheets.Count - 1; for (int i = currentBottom-1; i >= 0; i--) { @@ -452,6 +424,7 @@ private void DoMoveSheetsToBottom() currentBottom--; } } + _lockEvents = false; } private bool CanMoveSheetDown() @@ -500,6 +473,7 @@ private bool CanDeleteSheets() private void DoRenameSheet() { + Logger.Info("DoRenameSheet"); StringMessageContent content = new StringMessageContent(); content.Value = _lastSelectedSheet.DisplayString; content.Validator = (value) => @@ -527,8 +501,13 @@ private void ConfirmRenameSheet(StringMessageContent stringMessage) { if (CanRenameSheet() && stringMessage.Confirmed) { + Logger.Info("ConfirmRenameSheet: confirmed"); _lastSelectedSheet.DisplayString = stringMessage.Value; } + else + { + Logger.Info("ConfirmRenameSheet: not confirmed or unable to rename sheet"); + } } private bool CanRenameSheet() @@ -538,57 +517,113 @@ private bool CanRenameSheet() private void DoMonitorWorkbook() { - if (!_monitoring) + if (_timer == null) { Logger.Info("Begin monitoring workbook"); - _monitoring = true; - Task.Factory.StartNew(() => - { - while (_monitoring) - { - CheckSheetsChanged(); - Thread.Sleep(MONITOR_INTERVAL); - } - }); + _timer = new Timer( + CheckSheetsChanged, + null, + Properties.Settings.Default.WorkbookMonitorIntervalMilliseconds, + Properties.Settings.Default.WorkbookMonitorIntervalMilliseconds); } } private bool CanMonitorWorkbook() { - return _workbook != null && !_monitoring; + return _workbook != null && _timer == null; } private void DoUnmonitorWorkbook() { - Logger.Info("Stop monitoring workbook"); - _monitoring = false; - CheckSheetsChanged(); + if (_timer != null) + { + Logger.Info("Stop monitoring workbook"); + _timer.Dispose(); + _timer = null; + CheckSheetsChanged(null); + } } private bool CanUnmonitorWorkbook() { - return _monitoring; + return _timer == null; } - private void CheckSheetsChanged() + private void CheckSheetsChanged(object state) { - string sheetsString = SheetsString; - if (sheetsString != _lastSheetsString) + if (!_lockEvents) { - _lastSheetsString = sheetsString; - BuildSheetList(); + string sheetsString = SheetsString; + if (sheetsString != _lastSheetsString) + { + Logger.Info("CheckSheetsChanged: Change in worksheets detected, rebuilding list"); + _lastSheetsString = sheetsString; + BuildSheetList(); + } + } + } + + private void SheetActivated(dynamic sheet) + { + if (sheet != null && !_lockEvents) + { + SheetViewModel svm = Sheets.FirstOrDefault(s => s.IsSelected); + if (svm != null) + { + svm.IsSelected = false; + } + // Excel collection indexes are 1-based; .NET 0-based. + Sheets[sheet.Index - 1].IsSelected = true; } } #endregion - #region Implementation of ViewModelBase's abstract methods + #region Private fields - public override object RevealModelObject() + private Workbook _workbook; + private ObservableCollection _sheets; + private SheetViewModel _lastSelectedSheet; + private DelegatingCommand _moveSheetUp; + private DelegatingCommand _moveSheetsToTop; + private DelegatingCommand _moveSheetDown; + private DelegatingCommand _moveSheetsToBottom; + private DelegatingCommand _deleteSheets; + private DelegatingCommand _renameSheet; + private DelegatingCommand _monitorWorkbook; + private DelegatingCommand _unmonitorWorkbook; + private Message _confirmDeleteMessage; + private Message _renameSheetMessage; + private string _lastSheetsString; + private Timer _timer; + private bool _lockEvents; + + #endregion + + #region Protected properties + + protected Workbook Workbook { - return _workbook; + get + { + return _workbook; + } + set + { + _workbook = value; + OnPropertyChanged("Workbook"); + BuildSheetList(); + if (_workbook != null) + { + DisplayString = _workbook.Name; + } + else + { + DisplayString = String.Empty; + } + } } - + #endregion #region Class logger diff --git a/XLToolbox/ExceptionHandler/TestException.cs b/XLToolbox/ExceptionHandler/TestException.cs new file mode 100755 index 00000000..20e60e7d --- /dev/null +++ b/XLToolbox/ExceptionHandler/TestException.cs @@ -0,0 +1,35 @@ +/* TestException.cs + * part of Daniel's XL Toolbox NG + * + * Copyright 2014-2016 Daniel Kraus + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +using System; +using System.Runtime.Serialization; + +namespace XLToolbox.ExceptionHandler +{ + [Serializable] + class TestException : Exception + { + public TestException() { } + public TestException(string message) : base(message) { } + public TestException(string message, + Exception innerException) + : base(message, innerException) { } + public TestException(SerializationInfo info, + StreamingContext context) + : base(info, context) { } + } +} diff --git a/XLToolbox/Export/BatchExporter.cs b/XLToolbox/Export/BatchExporter.cs new file mode 100755 index 00000000..144574df --- /dev/null +++ b/XLToolbox/Export/BatchExporter.cs @@ -0,0 +1,317 @@ +using Microsoft.Office.Interop.Excel; +/* BatchExporter.cs + * part of Daniel's XL Toolbox NG + * + * Copyright 2014-2016 Daniel Kraus + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using XLToolbox.Excel.ViewModels; +using XLToolbox.Export.Models; + +namespace XLToolbox.Export +{ + public class BatchExporter : Bovender.Mvvm.Models.ProcessModel + { + #region Public properties + + public BatchExportSettings Settings { get; set; } + + public int PercentCompleted + { + get + { + if (_batchFileName != null && _numTotal != 0) + { + return Convert.ToInt32(100d * _batchFileName.Counter / _numTotal); + } + else + { + return 0; + } + } + } + + #endregion + + #region Constructor + + public BatchExporter(BatchExportSettings settings) + : base() + { + Settings = settings; + _exporter = new Exporter(settings.Preset); + } + + #endregion + + #region Implementation of ProcessModel + + public override bool Execute() + { + _batchFileName = new ExportFileName( + Settings.Path, + Settings.FileName, + Settings.Preset.FileType); + bool result = false; + try + { + Instance.Default.DisableScreenUpdating(); + switch (Settings.Scope) + { + case BatchExportScope.ActiveSheet: + _numTotal = CountInSheet(Instance.Default.Application.ActiveSheet); + ExportSheet(Instance.Default.Application.ActiveSheet); + break; + case BatchExportScope.ActiveWorkbook: + _numTotal = CountInWorkbook(Instance.Default.ActiveWorkbook); + ExportWorkbook(Instance.Default.ActiveWorkbook); + break; + case BatchExportScope.OpenWorkbooks: + _numTotal = CountInAllWorkbooks(); + ExportAllWorkbooks(); + break; + default: + throw new NotImplementedException(String.Format( + "Batch export not implemented for {0}", + Settings.Scope)); + } + Instance.Default.EnableScreenUpdating(); + } + catch (Exception e) + { + Instance.Default.EnableScreenUpdating(); + throw e; + } + return result; + } + + #endregion + + #region Private export methods + + private void ExportAllWorkbooks() + { + foreach (Workbook wb in Instance.Default.Application.Workbooks) + { + ExportWorkbook(wb); + if (IsCancellationRequested) break; + } + } + + private void ExportWorkbook(Workbook workbook) + { + ((_Workbook)workbook).Activate(); + foreach (dynamic ws in workbook.Sheets) + { + ExportSheet(ws); + if (IsCancellationRequested) break; + } + } + + private void ExportSheet(dynamic sheet) + { + sheet.Activate(); + switch (Settings.Layout) + { + case BatchExportLayout.SheetLayout: + ExportSheetLayout(sheet); + break; + case BatchExportLayout.SingleItems: + ExportSheetItems(sheet); + break; + default: + throw new NotImplementedException( + String.Format("Export of {0} not implemented.", Settings.Layout) + ); + } + } + + private void ExportSheetLayout(dynamic sheet) + { + SheetViewModel svm = new SheetViewModel(sheet); + switch (Settings.Objects) + { + case BatchExportObjects.Charts: + svm.SelectCharts(); + break; + case BatchExportObjects.ChartsAndShapes: + svm.SelectShapes(); + break; + default: + throw new NotImplementedException(Settings.Objects.ToString()); + } + _exporter.FileName = _batchFileName.GenerateNext(sheet); + _exporter.Execute(); + } + + private void ExportSheetItems(dynamic sheet) + { + SheetViewModel svm = new SheetViewModel(sheet); + if (svm.IsChart) + { + svm.SelectCharts(); + ExportSelection(svm.Sheet); + } + else + { + switch (Settings.Objects) + { + case BatchExportObjects.Charts: + ExportSheetChartItems(svm.Worksheet); + break; + case BatchExportObjects.ChartsAndShapes: + ExportSheetAllItems(svm.Worksheet); + break; + default: + throw new NotImplementedException( + "Single-item export not implemented for " + Settings.Objects.ToString()); + } + } + } + + private void ExportSheetChartItems(Worksheet worksheet) + { + // Must use an index-based for loop here. + // A foreach loop caused lots of 0x800a03ec errors from Excel + // (for whatever reason). + ChartObjects cos = worksheet.ChartObjects(); + for (int i = 1; i <= cos.Count; i++) + { + cos.Item(i).Select(); + ExportSelection(worksheet); + if (IsCancellationRequested) break; + } + } + + private void ExportSheetAllItems(Worksheet worksheet) + { + foreach (Shape sh in worksheet.Shapes) + { + sh.Select(true); + ExportSelection(worksheet); + if (IsCancellationRequested) break; + } + } + + private void ExportSelection(dynamic sheet) + { + _exporter.FileName = _batchFileName.GenerateNext(sheet); + _exporter.Execute(); + } + + #endregion + + #region Private counting methods + + private int CountInAllWorkbooks() + { + int n = 0; + foreach (Workbook wb in Instance.Default.Application.Workbooks) + { + n += CountInWorkbook(wb); + } + return n; + } + + private int CountInWorkbook(Workbook workbook) + { + int n = 0; + foreach (Worksheet ws in workbook.Worksheets) + { + n += CountInSheet(ws); + } + return n; + } + + private int CountInSheet(dynamic worksheet) + { + switch (Settings.Layout) + { + case BatchExportLayout.SheetLayout: + return CountInSheetLayout(worksheet); + case BatchExportLayout.SingleItems: + return CountInSheetItems(worksheet); + default: + throw new NotImplementedException( + String.Format("Export of {0} not implemented.", Settings.Layout) + ); + } + } + + /// + /// Returns 1 if the contains at least + /// one chart or drawing object, since all charts/drawing objects will + /// be exported together into one file. + /// + /// Worksheet to examine. + /// 1 if sheet contains charts/drawings, 0 if not. + private int CountInSheetLayout(dynamic worksheet) + { + SheetViewModel svm = new SheetViewModel(worksheet); + switch (Settings.Objects) + { + case BatchExportObjects.Charts: + return svm.CountCharts() > 0 ? 1 : 0; + case BatchExportObjects.ChartsAndShapes: + return svm.CountShapes() > 0 ? 1 : 0; + default: + throw new NotImplementedException(String.Format( + "Export of {0} not implemented.", Settings.Objects)); + } + } + + private int CountInSheetItems(dynamic worksheet) + { + SheetViewModel svm = new SheetViewModel(worksheet); + switch (Settings.Objects) + { + case BatchExportObjects.Charts: + return svm.CountCharts(); + case BatchExportObjects.ChartsAndShapes: + return svm.CountShapes(); + default: + throw new NotImplementedException(String.Format( + "Export of {0} not implemented.", Settings.Objects)); + } + } + + /*private FREE_IMAGE_FORMAT FileTypeToFreeImage(FileType fileType) + { + FREE_IMAGE_FORMAT fif; + if (_fileTypeToFreeImage.TryGetValue(fileType, out fif)) + { + return fif; + } + else + { + throw new NotImplementedException( + "No FREE_IMAGE_FORMAT match for " + fileType.ToString()); + } + } + */ + #endregion + + #region Private fields + + private Exporter _exporter; + private int _numTotal; + private ExportFileName _batchFileName; + + #endregion + } +} diff --git a/XLToolbox/Export/ExportFileName.cs b/XLToolbox/Export/ExportFileName.cs index 56ac1038..19a6329a 100755 --- a/XLToolbox/Export/ExportFileName.cs +++ b/XLToolbox/Export/ExportFileName.cs @@ -72,10 +72,10 @@ public ExportFileName(string directory, string template, FileType fileType) /// internal counter. /// /// - public string GenerateNext(dynamic worksheet) + public string GenerateNext(dynamic sheet) { - CurrentWorkbookName = worksheet.Parent.Name; - CurrentWorksheetName = worksheet.Name; + CurrentWorkbookName = sheet.Parent.Name; + CurrentWorksheetName = sheet.Name; Counter++; string s = _regex.Replace(Template, SubstituteVariable); // If no index placeholder exists in the template, add the index at the end. diff --git a/XLToolbox/Export/Exporter.cs b/XLToolbox/Export/Exporter.cs index b811067f..cca4b19d 100755 --- a/XLToolbox/Export/Exporter.cs +++ b/XLToolbox/Export/Exporter.cs @@ -36,7 +36,16 @@ public class Exporter : Bovender.Mvvm.Models.ProcessModel, IDisposable { #region Properties - public bool IsProcessing { get; private set; } + public string FileName { get; set; } + + /// + /// Gets or sets whether to do a quick export or not. + /// Quick export means that the selection is exported + /// at the original size. + /// + public bool QuickExport { get; set; } + + public Preset Preset { get; set; } public int PercentCompleted { @@ -44,7 +53,7 @@ public int PercentCompleted { if (_tiledBitmap != null) { - return _tiledBitmap.PercentCompleted * 80 / 100 + _percentCompleted; + return _tiledBitmap.PercentCompleted * 50 / 100 + _percentCompleted; } else { @@ -62,149 +71,77 @@ public int PercentCompleted #region Public methods - /// - /// Performs a quick export using a given Preset, but - /// without altering the size of the current selection: - /// The dimension properties of the SingleExportSettings - /// object that defines the operation are ignored. - /// - /// - public void ExportSelectionQuick(SingleExportSettings settings) + public override bool Execute() { - Logger.Info("ExportSelectionQuick"); - ExportSelection(settings.Preset, settings.FileName); - } - - /// - /// Exports the current selection from Excel to a graphics file - /// using the parameters defined in - /// - /// Parameters for the graphic export. - /// Target file name. - public void ExportSelection(SingleExportSettings settings) - { - Logger.Info("ExportSelection"); - if (settings == null) + if (Preset == null) { - Logger.Fatal("SingleExportSettings is null"); - throw new ArgumentNullException("settings", - "Must have SingleExportSettings object for the export."); + throw new InvalidOperationException("Cannot export because no Preset was given"); } - double w = settings.Unit.ConvertTo(settings.Width, Unit.Point); - double h = settings.Unit.ConvertTo(settings.Height, Unit.Point); - IsProcessing = true; - Task t = new Task(() => + if (String.IsNullOrWhiteSpace(FileName) && _settings != null) { - Logger.Info("Beginning export task"); - try - { - ExportSelection(settings.Preset, w, h, settings.FileName); - IsProcessing = false; - if (!_cancelled) OnProcessSucceeded(); - } - catch (Exception e) - { - IsProcessing = false; - Logger.Warn("Exception occurred, raising ExportFailed event"); - Logger.Warn(e); - OnProcessFailed(e); - } - finally - { - Logger.Info("Export task finished"); - } - }); - t.Start(); - } - - /// - /// Starts a batch export of charts and/or drawing objects - /// (shapes) asynchronously. Callers should subscribe to the - /// and - /// events to learn about status changes. The operation can - /// be cancelled by caling the - /// method. - /// - /// Settings describing the desired operation. - public void ExportBatchAsync(BatchExportSettings settings) - { - Logger.Info("ExportBatchAsync"); - if (IsProcessing) + FileName = _settings.FileName; + } + if (String.IsNullOrWhiteSpace(FileName)) { - Logger.Warn("Cannot respawn, already running"); - throw new InvalidOperationException( - "Cannot start batch export while an operation is in progress."); + throw new InvalidOperationException("Cannot export because no file name was given"); } - - Task t = new Task(() => + bool result = false; + double width; + double height; + if (QuickExport) { - IsProcessing = true; - try - { - Instance.Default.DisableScreenUpdating(); - switch (_batchSettings.Scope) - { - case BatchExportScope.ActiveSheet: - _numTotal = CountInSheet(Instance.Default.Application.ActiveSheet); - ExportSheet(Instance.Default.Application.ActiveSheet); - break; - case BatchExportScope.ActiveWorkbook: - _numTotal = CountInWorkbook(Instance.Default.ActiveWorkbook); - ExportWorkbook(Instance.Default.ActiveWorkbook); - break; - case BatchExportScope.OpenWorkbooks: - _numTotal = CountInAllWorkbooks(); - ExportAllWorkbooks(); - break; - default: - throw new NotImplementedException(String.Format( - "Batch export not implemented for {0}", - settings.Scope)); - } - IsProcessing = false; - Logger.Info("Finish async task"); - if (!_cancelled) OnProcessSucceeded(); - } - catch (Exception e) - { - IsProcessing = false; - OnProcessFailed(e); - } - finally + if (SelectionViewModel.Selection == null) { - IsProcessing = false; - Instance.Default.EnableScreenUpdating(); + Logger.Fatal("ExportAtOriginalSize: No selection!"); + throw new InvalidOperationException("Cannot export because nothing is selected in Excel"); } - }); - - _batchSettings = settings; - _cancelled = false; - _batchFileName = new ExportFileName(settings.Path, settings.FileName, - settings.Preset.FileType); - Logger.Info("Start asynchronous export"); - t.Start(); - } - - /// - /// Cancels a running export. - /// - public void CancelExport() - { - if (IsProcessing) + width = SelectionViewModel.Bounds.Width; + height = SelectionViewModel.Bounds.Height; + } + else { - _cancelled = true; - if (_tiledBitmap != null) + if (_settings == null) { - _tiledBitmap.Cancel(); + Logger.Fatal("ExportAtOriginalSize: No export settings!"); + throw new InvalidOperationException("Cannot export because no export settings were given; want to perform quick export?"); } + width = _settings.Unit.ConvertTo(_settings.Width, Unit.Point); + height = _settings.Unit.ConvertTo(_settings.Height, Unit.Point); } + ExportWithDimensions(width, height); + return result; } #endregion - #region Constructor and disposing + #region Constructors + + public Exporter(SingleExportSettings settings) + : this() + { + _settings = settings; + if (_settings != null) + { + Preset = _settings.Preset; + } + } + + public Exporter(SingleExportSettings settings, bool quickExport) + : this(settings) + { + QuickExport = quickExport; + } + + public Exporter(Preset preset) + : this() + { + // Without SingleExportSettings, we can only perform a quick export + QuickExport = true; + Preset = preset; + } - public Exporter() + protected Exporter() + : base() { _dllManager = new DllManager(); _dllManager.LoadDll("freeimage.dll"); @@ -215,13 +152,9 @@ public Exporter() }; } - /* - public Exporter(Preset preset) - : this() - { - Preset = preset; - } - * */ + #endregion + + #region Disposing ~Exporter() { @@ -236,34 +169,24 @@ public void Dispose() protected void Dispose(bool calledFromDispose) { - if (calledFromDispose && !_disposed) + if (!_disposed) { - _dllManager.UnloadDll("freeimage.dll"); + if (calledFromDispose) + { + // Free managed resources + _dllManager.UnloadDll("freeimage.dll"); + if (_tiledBitmap != null) + { + _tiledBitmap.Dispose(); + } + } _disposed = true; } } #endregion - #region Private export methods - - /// - /// Performs the actual graphic export with the dimensions of - /// the current selection. - /// - /// Export preset to use. - /// File name of target file. - private void ExportSelection(Preset preset, string fileName) - { - Logger.Info("ExportSelection(preset, fileName"); - SelectionViewModel svm = new SelectionViewModel(Instance.Default.Application); - if (svm.Selection == null) - { - Logger.Warn("No selection"); - throw new InvalidOperationException("Nothing selected in Excel."); - } - ExportSelection(preset, svm.Bounds.Width, svm.Bounds.Height, fileName); - } + #region Private methods /// /// Performs the actual export for a given selection. This method is @@ -273,13 +196,16 @@ private void ExportSelection(Preset preset, string fileName) /// Width of the output graphic. /// Height of the output graphic. /// Destination filename (must contain placeholders). - private void ExportSelection(Preset preset, double widthInPoints, double heightInPoints, - string fileName) + private void ExportWithDimensions(double widthInPoints, double heightInPoints) { - Logger.Info("ExportSelection(preset, widthInPoints, heightInPoints, filename)"); + if (Preset == null) + { + Logger.Fatal("ExportWithDimensions: No export preset!"); + throw new InvalidOperationException("Cannot export without export preset"); + } + Logger.Info("ExportWithDimensions"); // Copy current selection to clipboard - SelectionViewModel svm = new SelectionViewModel(Instance.Default.Application); - svm.CopyToClipboard(); + SelectionViewModel.CopyToClipboard(); // Get a metafile view of the clipboard content // Must not dispose the WorkingClipboard instance before the metafile @@ -289,321 +215,99 @@ private void ExportSelection(Preset preset, double widthInPoints, double heightI { Logger.Info("Get metafile"); emf = clipboard.GetMetafile(); - switch (preset.FileType) + switch (Preset.FileType) { case FileType.Emf: - ExportEmf(emf, fileName); + ExportEmf(emf); break; case FileType.Png: case FileType.Tiff: - ExportViaFreeImage(emf, preset, widthInPoints, heightInPoints, fileName); + ExportViaFreeImage(emf, widthInPoints, heightInPoints); break; default: throw new NotImplementedException(String.Format( - "No export implementation for {0}.", preset.FileType)); + "No export implementation for {0}.", Preset.FileType)); } } } - private void ExportViaFreeImage(Metafile metafile, - Preset preset, double width, double height, string fileName) + private void ExportViaFreeImage(Metafile metafile, double width, double height) { Logger.Info("ExportViaFreeImage"); - Logger.Info("Preset: {0}", preset); + Logger.Info("Preset: {0}", Preset); Logger.Info("Width: {0}; height: {1}", width, height); // Calculate the number of pixels needed for the requested // output size and resolution; size is given in points (1/72 in), // resolution is given in dpi. - int px = (int)Math.Round(width / 72 * preset.Dpi); - int py = (int)Math.Round(height / 72 * preset.Dpi); + int px = (int)Math.Round(width / 72 * Preset.Dpi); + int py = (int)Math.Round(height / 72 * Preset.Dpi); Logger.Info("Pixels: x: {0}; y: {1}", px, py); - + Cancelling += Exporter_Cancelling; + PercentCompleted = 10; _tiledBitmap = new TiledBitmap(px, py); - PercentCompleted = 25; - FreeImageBitmap fib = _tiledBitmap.CreateFreeImageBitmap(metafile, preset.Transparency); + FreeImageBitmap fib = _tiledBitmap.CreateFreeImageBitmap(metafile, Preset.Transparency); + ConvertColor(fib); + fib.SetResolution(Preset.Dpi, Preset.Dpi); + fib.Comment = Versioning.SemanticVersion.BrandName; + PercentCompleted = 30; + Logger.Info("Saving {0} file", Preset.FileType); + fib.Save( + FileName, + Preset.FileType.ToFreeImageFormat(), + GetSaveFlags() + ); + Cancelling -= Exporter_Cancelling; + PercentCompleted = 50; + } - PercentCompleted = 70; - if (preset.UseColorProfile) + private void ConvertColor(FreeImageBitmap freeImageBitmap) + { + if (Preset.UseColorProfile) { - ConvertColorCms(preset, fib); + Logger.Info("ConvertColorCms: Convert color using profile"); + ViewModels.ColorProfileViewModel targetProfile = + ViewModels.ColorProfileViewModel.CreateFromName(Preset.ColorProfile); + targetProfile.TransformFromStandardProfile(freeImageBitmap); + freeImageBitmap.ConvertColorDepth(Preset.ColorSpace.ToFreeImageColorDepth()); } else { - ConvertColor(preset, fib); + Logger.Info("ConvertColor: Convert color without profile"); + freeImageBitmap.ConvertColorDepth(Preset.ColorSpace.ToFreeImageColorDepth()); } - - PercentCompleted = 85; - if (preset.ColorSpace == ColorSpace.Monochrome) + if (Preset.ColorSpace == ColorSpace.Monochrome) { - SetMonochromePalette(fib); + SetMonochromePalette(freeImageBitmap); } - - fib.SetResolution(preset.Dpi, preset.Dpi); - fib.Comment = Versioning.SemanticVersion.BrandName; - Logger.Info("Saving {0} file", preset.FileType); - PercentCompleted = 85; - fib.Save( - fileName, - preset.FileType.ToFreeImageFormat(), - GetSaveFlags(preset) - ); - PercentCompleted = 100; - } - - private void ConvertColorCms(Preset preset, FreeImageBitmap freeImageBitmap) - { - Logger.Info("Convert color using profile"); - ViewModels.ColorProfileViewModel targetProfile = - ViewModels.ColorProfileViewModel.CreateFromName(preset.ColorProfile); - targetProfile.TransformFromStandardProfile(freeImageBitmap); - freeImageBitmap.ConvertColorDepth(preset.ColorSpace.ToFreeImageColorDepth()); - } - - private void ConvertColor(Preset preset, FreeImageBitmap freeImageBitmap) - { - Logger.Info("Convert color without profile"); - freeImageBitmap.ConvertColorDepth(preset.ColorSpace.ToFreeImageColorDepth()); } private void SetMonochromePalette(FreeImageBitmap freeImageBitmap) { - Logger.Info("Convert to monochrome"); + Logger.Info("SetMonochromePalette: Convert to monochrome"); freeImageBitmap.Palette.SetValue(new RGBQUAD(Color.Black), 0); freeImageBitmap.Palette.SetValue(new RGBQUAD(Color.White), 1); } - private void ExportEmf(Metafile metafile, string fileName) + private void ExportEmf(Metafile metafile) { + Logger.Info("ExportEmf: exporting..."); IntPtr handle = metafile.GetHenhmetafile(); PercentCompleted = 50; Logger.Info("ExportEmf, handle: {0}", handle); - Bovender.Unmanaged.Pinvoke.CopyEnhMetaFile(handle, fileName); + Bovender.Unmanaged.Pinvoke.CopyEnhMetaFile(handle, FileName); PercentCompleted = 100; } - private void ExportAllWorkbooks() - { - foreach (Workbook wb in Instance.Default.Application.Workbooks) - { - ExportWorkbook(wb); - if (_cancelled) break; - } - } - - private void ExportWorkbook(Workbook workbook) + private FREE_IMAGE_SAVE_FLAGS GetSaveFlags() { - ComputeBatchProgress(); - ((_Workbook)workbook).Activate(); - foreach (dynamic ws in workbook.Sheets) - { - ExportSheet(ws); - if (_cancelled) break; - } - } - - private void ExportSheet(dynamic sheet) - { - ComputeBatchProgress(); - sheet.Activate(); - switch (_batchSettings.Layout) - { - case BatchExportLayout.SheetLayout: - ExportSheetLayout(sheet); - break; - case BatchExportLayout.SingleItems: - ExportSheetItems(sheet); - break; - default: - throw new NotImplementedException( - String.Format("Export of {0} not implemented.", _batchSettings.Layout) - ); - } - } - - private void ExportSheetLayout(dynamic sheet) - { - SheetViewModel svm = new SheetViewModel(sheet); - switch (_batchSettings.Objects) - { - case BatchExportObjects.Charts: - svm.SelectCharts(); - break; - case BatchExportObjects.ChartsAndShapes: - svm.SelectShapes(); - break; - default: - throw new NotImplementedException(_batchSettings.Objects.ToString()); - } - ExportSelection( - _batchSettings.Preset, - _batchFileName.GenerateNext(sheet) - ); - ComputeBatchProgress(); - } - - private void ExportSheetItems(dynamic sheet) - { - SheetViewModel svm = new SheetViewModel(sheet); - if (svm.IsChart) - { - svm.SelectCharts(); - ExportSelection( - _batchSettings.Preset, - _batchFileName.GenerateNext(sheet) - ); - } - else - { - switch (_batchSettings.Objects) - { - case BatchExportObjects.Charts: - ExportSheetChartItems(svm.Worksheet); - break; - case BatchExportObjects.ChartsAndShapes: - ExportSheetAllItems(svm.Worksheet); - break; - default: - throw new NotImplementedException( - "Single-item export not implemented for " + _batchSettings.Objects.ToString()); - } - } - ComputeBatchProgress(); - } - - private void ExportSheetChartItems(Worksheet worksheet) - { - // Must use an index-based for loop here. - // A foreach loop caused lots of 0x800a03ec errors from Excel - // (for whatever reason). - ChartObjects cos = worksheet.ChartObjects(); - for (int i = 1; i <= cos.Count; i++) - { - cos.Item(i).Select(); - ExportSelection(_batchSettings.Preset, _batchFileName.GenerateNext(worksheet)); - ComputeBatchProgress(); - if (_cancelled) break; - } - } - - private void ExportSheetAllItems(Worksheet worksheet) - { - foreach (Shape sh in worksheet.Shapes) - { - sh.Select(true); - ExportSelection(_batchSettings.Preset, _batchFileName.GenerateNext(worksheet)); - ComputeBatchProgress(); - if (_cancelled) break; - } - } - - #endregion - - #region Private counting methods - - private int CountInAllWorkbooks() - { - int n = 0; - foreach (Workbook wb in Instance.Default.Application.Workbooks) - { - n += CountInWorkbook(wb); - } - return n; - } - - private int CountInWorkbook(Workbook workbook) - { - int n = 0; - foreach (Worksheet ws in workbook.Worksheets) - { - n += CountInSheet(ws); - } - return n; - } - - private int CountInSheet(dynamic worksheet) - { - switch (_batchSettings.Layout) - { - case BatchExportLayout.SheetLayout: - return CountInSheetLayout(worksheet); - case BatchExportLayout.SingleItems: - return CountInSheetItems(worksheet); - default: - throw new NotImplementedException( - String.Format("Export of {0} not implemented.", _batchSettings.Layout) - ); - } - } - - /// - /// Returns 1 if the contains at least - /// one chart or drawing object, since all charts/drawing objects will - /// be exported together into one file. - /// - /// Worksheet to examine. - /// 1 if sheet contains charts/drawings, 0 if not. - private int CountInSheetLayout(dynamic worksheet) - { - SheetViewModel svm = new SheetViewModel(worksheet); - switch (_batchSettings.Objects) - { - case BatchExportObjects.Charts: - return svm.CountCharts() > 0 ? 1 : 0; - case BatchExportObjects.ChartsAndShapes: - return svm.CountShapes() > 0 ? 1 : 0; - default: - throw new NotImplementedException(String.Format( - "Export of {0} not implemented.", _batchSettings.Objects)); - } - } - - private int CountInSheetItems(dynamic worksheet) - { - SheetViewModel svm = new SheetViewModel(worksheet); - switch (_batchSettings.Objects) - { - case BatchExportObjects.Charts: - return svm.CountCharts(); - case BatchExportObjects.ChartsAndShapes: - return svm.CountShapes(); - default: - throw new NotImplementedException(String.Format( - "Export of {0} not implemented.", _batchSettings.Objects)); - } - } - - /*private FREE_IMAGE_FORMAT FileTypeToFreeImage(FileType fileType) - { - FREE_IMAGE_FORMAT fif; - if (_fileTypeToFreeImage.TryGetValue(fileType, out fif)) - { - return fif; - } - else - { - throw new NotImplementedException( - "No FREE_IMAGE_FORMAT match for " + fileType.ToString()); - } - } - */ - #endregion - - #region Private helper methods - - private void ComputeBatchProgress() - { - PercentCompleted = Convert.ToInt32(100d * _batchFileName.Counter / _numTotal); - } - - private FREE_IMAGE_SAVE_FLAGS GetSaveFlags(Preset preset) - { - switch (preset.FileType) + Logger.Info("GetSaveFlags"); + switch (Preset.FileType) { case FileType.Png: return FREE_IMAGE_SAVE_FLAGS.PNG_Z_BEST_COMPRESSION | FREE_IMAGE_SAVE_FLAGS.PNG_INTERLACED; case FileType.Tiff: - switch (preset.ColorSpace) + switch (Preset.ColorSpace) { case ColorSpace.Monochrome: return FREE_IMAGE_SAVE_FLAGS.TIFF_CCITTFAX4; @@ -617,41 +321,53 @@ private FREE_IMAGE_SAVE_FLAGS GetSaveFlags(Preset preset) } } - /* - /// - /// Adds a file extension to the file name if missing. - /// - /// File name, possibly without extension. - /// File name with extension. - private string SanitizeFileName(Preset preset, string fileName) + private void Exporter_Cancelling(object sender, Bovender.Mvvm.Models.ProcessModelEventArgs args) + { + if (_tiledBitmap != null) + { + _tiledBitmap.Cancel(); + } + } + + #endregion + + #region Protected properties + + protected SelectionViewModel SelectionViewModel { - string extension = preset.FileType.ToFileNameExtension(); - if (!fileName.ToUpper().EndsWith(extension.ToUpper())) + get { - fileName += extension; + if (_selectionViewModel == null) + { + _selectionViewModel = new SelectionViewModel(Instance.Default.Application); + } + return _selectionViewModel; } - return fileName; } - */ #endregion #region Private fields private DllManager _dllManager; + private SingleExportSettings _settings; private bool _disposed; - private BatchExportSettings _batchSettings; - private ExportFileName _batchFileName; - private bool _cancelled; - private int _numTotal; private Dictionary _fileTypeToFreeImage; private TiledBitmap _tiledBitmap; private int _percentCompleted; + private SelectionViewModel _selectionViewModel; #endregion #region Private constants #endregion + #region Class logger + + private static NLog.Logger Logger { get { return _logger.Value; } } + + private static readonly Lazy _logger = new Lazy(() => NLog.LogManager.GetCurrentClassLogger()); + + #endregion } } diff --git a/XLToolbox/Export/QuickExporter.cs b/XLToolbox/Export/QuickExporter.cs index 1298edce..724c6c99 100755 --- a/XLToolbox/Export/QuickExporter.cs +++ b/XLToolbox/Export/QuickExporter.cs @@ -51,8 +51,8 @@ public void ExportSelection() SingleExportSettings settings = SingleExportSettings.CreateForSelection(preset); SingleExportSettingsViewModel svm = new SingleExportSettingsViewModel(settings); svm.ChooseFileNameMessage.Sent += ChooseFileNameMessage_Sent; - svm.ShowProgressMessage.Sent += Dispatcher.ShowProgressMessage_Sent; - svm.ProcessFailedMessage.Sent += Dispatcher.ProcessFailedMessage_Sent; + svm.ShowProgressMessage.Sent += Dispatcher.Exporter_ShowProgress_Sent; + svm.ProcessFinishedMessage.Sent += Dispatcher.Exporter_ProcessFinished_Sent; if (svm.ChooseFileNameCommand.CanExecute(null)) { svm.ChooseFileNameCommand.Execute(null); @@ -72,8 +72,8 @@ public void ExportBatch() if ((bvm != null) && bvm.ChooseFolderCommand.CanExecute(null)) { bvm.ChooseFolderMessage.Sent += ChooseFolderMessage_Sent; - bvm.ShowProgressMessage.Sent += Dispatcher.ShowProgressMessage_Sent; - bvm.ProcessFailedMessage.Sent += Dispatcher.ProcessFailedMessage_Sent; + bvm.ShowProgressMessage.Sent += Dispatcher.Exporter_ShowProgress_Sent; + bvm.ProcessFinishedMessage.Sent += Dispatcher.Exporter_ProcessFinished_Sent; bvm.ChooseFolderCommand.Execute(null); } else @@ -86,8 +86,8 @@ public void ExportBatch() bvm = new BatchExportSettingsViewModel(); // Do not 'sanitize' the export options, so that the user // can see the selected, but disabled options. - bvm.ShowProgressMessage.Sent += Dispatcher.ShowProgressMessage_Sent; - bvm.ProcessFailedMessage.Sent += Dispatcher.ProcessFailedMessage_Sent; + bvm.ShowProgressMessage.Sent += Dispatcher.Exporter_ShowProgress_Sent; + bvm.ProcessFinishedMessage.Sent += Dispatcher.Exporter_ProcessFinished_Sent; bvm.InjectInto().ShowDialog(); } else diff --git a/XLToolbox/Export/ViewModels/BatchExportSettingsViewModel.cs b/XLToolbox/Export/ViewModels/BatchExportSettingsViewModel.cs index f6a1e295..5c2b2a7a 100755 --- a/XLToolbox/Export/ViewModels/BatchExportSettingsViewModel.cs +++ b/XLToolbox/Export/ViewModels/BatchExportSettingsViewModel.cs @@ -306,10 +306,14 @@ public BatchExportSettingsViewModel() { } public BatchExportSettingsViewModel(BatchExportSettings settings) - : base() + : this(new BatchExporter(settings as BatchExportSettings)) + { } + + public BatchExportSettingsViewModel(BatchExporter batchExporter) + : base(batchExporter) { - Settings = settings; - PresetViewModels.Select(settings.Preset); + Settings = batchExporter.Settings; + PresetViewModels.Select(Settings.Preset); FileName = String.Format("{{{0}}}_{{{1}}}_{{{2}}}", Strings.Workbook, Strings.Worksheet, Strings.Index); Scope.PropertyChanged += Scope_PropertyChanged; @@ -320,8 +324,8 @@ public BatchExportSettingsViewModel(BatchExportSettings settings) #endregion - #region Implementation of SettingsViewModelBase - + #region Implementation of abstract methods + /// /// Determines the suggested target directory and sends the /// ChooseFileNameMessage. @@ -345,6 +349,18 @@ private bool CanChooseFolder() return CanExport(); } + protected override int GetPercentCompleted() + { + if (Exporter != null) + { + return Exporter.PercentCompleted; + } + else + { + return 0; + } + } + protected override void DoExport() { Logger.Info("DoExport"); @@ -358,17 +374,9 @@ protected override void DoExport() protected override bool CanExport() { - return CanExecuteMatrix[Scope.AsEnum][Objects.AsEnum][Layout.AsEnum] && - (Settings.Preset != null); - } - - #endregion - - #region Implementation of ProcessViewModelBase - - protected override void Execute() - { - Exporter.ExportBatchAsync(Settings as BatchExportSettings); + return (Settings != null) && + (Settings.Preset != null) && + CanExecuteMatrix[Scope.AsEnum][Objects.AsEnum][Layout.AsEnum]; } #endregion @@ -727,6 +735,15 @@ private ScopeStates CanExecuteMatrix } } + private BatchExporter Exporter + { + [DebuggerStepThrough] + get + { + return ProcessModel as BatchExporter; + } + } + #endregion #region Private fields diff --git a/XLToolbox/Export/ViewModels/PresetsRepositoryViewModel.cs b/XLToolbox/Export/ViewModels/PresetsRepositoryViewModel.cs index 8e2fa922..8f886e56 100755 --- a/XLToolbox/Export/ViewModels/PresetsRepositoryViewModel.cs +++ b/XLToolbox/Export/ViewModels/PresetsRepositoryViewModel.cs @@ -171,6 +171,11 @@ public void Select(Preset preset) throw new ArgumentNullException("preset", "Cannot select PresetViewModel without Preset"); } PresetViewModel pvm = ViewModels.FirstOrDefault(p => p.IsViewModelOf(preset)); + if (pvm == null) + { + pvm = new PresetViewModel(preset); + ViewModels.Add(pvm); + } pvm.IsSelected = true; } diff --git a/XLToolbox/Export/ViewModels/SettingsViewModelBase.cs b/XLToolbox/Export/ViewModels/SettingsViewModelBase.cs index e39dcf37..e527694c 100755 --- a/XLToolbox/Export/ViewModels/SettingsViewModelBase.cs +++ b/XLToolbox/Export/ViewModels/SettingsViewModelBase.cs @@ -90,19 +90,6 @@ public string FileName protected Settings Settings { get; set; } - protected Exporter Exporter - { - get - { - if (_exporter == null) - { - _exporter = new Exporter(); - ProcessModel = _exporter; // assigning property hooks up events - } - return _exporter; - } - } - #endregion #region Commands @@ -154,8 +141,8 @@ public Message EditPresetsMessage #region Constructor - public SettingsViewModelBase() - : base() + public SettingsViewModelBase(Bovender.Mvvm.Models.ProcessModel exporter) + : base(exporter) { } #endregion @@ -228,21 +215,6 @@ public override object RevealModelObject() return Settings; } - protected override bool IsProcessing() - { - return Exporter.IsProcessing; - } - - protected override int GetPercentCompleted() - { - return Exporter.PercentCompleted; - } - - protected override void CancelProcess() - { - Exporter.CancelExport(); - } - #endregion #region Private methods @@ -263,7 +235,6 @@ private void PresetViewModels_PropertyChanged(object sender, PropertyChangedEven DelegatingCommand _editPresetsCommand; Message _editPresetsMessage; PresetsRepositoryViewModel _presetsRepositoryViewModel; - Exporter _exporter; #endregion diff --git a/XLToolbox/Export/ViewModels/SingleExportSettingsViewModel.cs b/XLToolbox/Export/ViewModels/SingleExportSettingsViewModel.cs index 60ef622f..de935c75 100755 --- a/XLToolbox/Export/ViewModels/SingleExportSettingsViewModel.cs +++ b/XLToolbox/Export/ViewModels/SingleExportSettingsViewModel.cs @@ -24,6 +24,7 @@ using XLToolbox.Export.Models; using System.Threading; using System.Threading.Tasks; +using System.Diagnostics; namespace XLToolbox.Export.ViewModels { @@ -231,7 +232,7 @@ public SingleExportSettingsViewModel() { } public SingleExportSettingsViewModel(SingleExportSettings singleExportSettings) - : base() + : base(new Exporter(singleExportSettings)) { Settings = singleExportSettings; PresetViewModels.Select(Settings.Preset); @@ -249,15 +250,14 @@ public SingleExportSettingsViewModel(SingleExportSettings singleExportSettings) /// protected override void DoExport() { - StartProcess(); - // if (CanExport()) - // { - // // Logger.Info("DoExport"); - // // SelectedPreset.Store(); - // // UserSettings.UserSettings.Default.ExportUnit = Units.AsEnum; - // // SaveExportPath(); - // StartProcess(); - // } + if (CanExport()) + { + Logger.Info("DoExport"); + SelectedPreset.Store(); + UserSettings.UserSettings.Default.ExportUnit = Units.AsEnum; + SaveExportPath(); + StartProcess(); + } } protected override bool CanExport() @@ -268,15 +268,22 @@ protected override bool CanExport() (Width > 0) && (Height > 0); } - protected override void Execute() + protected override bool BeforeStartProcess() { Settings.Preset = SelectedPreset.RevealModelObject() as Preset; - Exporter.ExportSelection(Settings as SingleExportSettings); + return base.BeforeStartProcess(); } protected override int GetPercentCompleted() { - return Exporter.PercentCompleted; + if (Exporter != null) + { + return Exporter.PercentCompleted; + } + else + { + return 0; + } } #endregion @@ -301,6 +308,19 @@ protected override void SaveExportPath() #endregion + #region Private properties + + private Exporter Exporter + { + [DebuggerStepThrough] + get + { + return ProcessModel as Exporter; + } + } + + #endregion + #region Private methods private void DoChooseFileName() diff --git a/XLToolbox/Greeter/GreeterView.xaml b/XLToolbox/Greeter/GreeterView.xaml index 7de8ac7f..122719a7 100755 --- a/XLToolbox/Greeter/GreeterView.xaml +++ b/XLToolbox/Greeter/GreeterView.xaml @@ -19,8 +19,10 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - public class LogFile + public class LogFile : Bovender.Logging.LogFile { #region Singleton - public static LogFile Default { get { return _lazy.Value; } } + new public static LogFile Default { get { return _lazy.Value; } } - private static readonly Lazy _lazy = new Lazy(() => new LogFile()); + private static readonly Lazy _lazy = new Lazy( + () => + { + LogFile logFile = new LogFile(); + Bovender.Logging.LogFile.LogFileProvider = new Func(() => logFile); + return logFile; + }); #endregion @@ -46,7 +52,7 @@ public class LogFile /// Gets whether file logging is enabled, without initializing /// the singleton instance if it isn't. /// - public static bool IsInitializedAndEnabled + new public static bool IsInitializedAndEnabled { get { @@ -58,32 +64,10 @@ public static bool IsInitializedAndEnabled #region Properties - public bool IsFileLoggingEnabled - { - get - { - return _fileLoggingEnabled; - } - set - { - if (value != _fileLoggingEnabled) - { - if (value) - { - EnableFileLogging(); - } - else - { - DisableFileLogging(); - } - } - } - } - /// /// Gets the folder where log files are stored. /// - public string LogFolder + public override string LogFolder { get { @@ -98,161 +82,19 @@ public string LogFolder } } - /// - /// Gets the complete path and file name of the 'current' log file. - /// - public string CurrentLogPath - { - get - { - if (_currentLogPath == null) - { - _currentLogPath = System.IO.Path.Combine(LogFolder, LOG_FILE_NAME); - } - return _currentLogPath; - } - } - - /// - /// Gets the contents of the current log file. Returns an error - /// string if the log file could not be read (e.g. if it does not - /// exist). - /// - public string CurrentLog - { - get - { - try - { - return System.IO.File.ReadAllText(CurrentLogPath); - } - catch (System.IO.IOException e) - { - return e.Message; - } - } - } - - /// - /// Gets whether the 'current' log file is available, - /// i.e. if the logfile exists. If loggint is disabled, - /// this file contains the information when file logging - /// was enabled last. - /// - public bool IsCurrentLogAvailable - { - get - { - return System.IO.File.Exists(CurrentLogPath); - } - } - #endregion #region Constructor private LogFile() - { - _config = new LoggingConfiguration(); - LogManager.Configuration = _config; - - } - - #endregion - - #region Public methods - - public void ShowFolderInExplorer() - { - System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo(LogFolder)); - } - - public void EnableDebugLogging() - { - if (!_debugLogginEnabled) - { - _debugLogginEnabled = true; - DebuggerTarget t = new DebuggerTarget(); - _config.AddTarget(DEBUG_TARGET, t); - _config.LoggingRules.Add(new LoggingRule("*", LogLevel.Debug, t)); - LogManager.ReconfigExistingLoggers(); - } - } - - #endregion - - #region Protected properties and methods - - /// - /// Gets the complete path and file name layout for the archive files. - /// - protected string ArchivedLogsPath - { - get - { - if (_archivedLogsPath == null) - { - _archivedLogsPath = System.IO.Path.Combine(LogFolder, ARCHIVE_FILE_NAME); - } - return _archivedLogsPath; - } - } - - protected void EnableFileLogging() - { - _fileLoggingEnabled = true; - if (_fileTarget == null) - { - _fileTarget = new FileTarget(); - _fileTarget.FileName = CurrentLogPath; - _fileTarget.ArchiveFileName = ArchivedLogsPath; - _fileTarget.ArchiveNumbering = ArchiveNumberingMode.Date; - _fileTarget.ArchiveDateFormat = ARCHIVE_DATE_FORMAT; - _fileTarget.ArchiveEvery = FileArchivePeriod.Day; - _fileTarget.MaxArchiveFiles = MAX_ARCHIVE_FILES; - AsyncTargetWrapper wrapper = new AsyncTargetWrapper(_fileTarget); - _config.AddTarget(FILE_TARGET, wrapper); - } - if (_fileRule == null) - { - _fileRule = new LoggingRule("*", LogLevel.Info, _fileTarget); - } - _config.LoggingRules.Add(_fileRule); - LogManager.ReconfigExistingLoggers(); - Logger.Info("===== Begin file logging ====="); - } - - protected void DisableFileLogging() - { - Logger.Info("Disabling file logging"); - _config.LoggingRules.Remove(_fileRule); - LogManager.ReconfigExistingLoggers(); - _fileLoggingEnabled = false; - } + : base() + { } #endregion #region Private fields - private string _logFolder; - private string _currentLogPath; - private string _archivedLogsPath; - private bool _debugLogginEnabled; - private bool _fileLoggingEnabled; - private LoggingConfiguration _config; - private FileTarget _fileTarget; - private LoggingRule _fileRule; - - #endregion - - #region Private constants - - private const string FILE_TARGET = "file"; - private const string DEBUG_TARGET = "debug"; - private const string LOG_FILE_NAME = "current-log.txt"; - private const string ARCHIVE_FILE_NAME = "log-archived-on-{#}.txt"; - private const string ARCHIVE_DATE_FORMAT = "yyyy-MM-dd"; - private const int MAX_ARCHIVE_FILES = 7; + string _logFolder; #endregion diff --git a/XLToolbox/Mvvm/Actions/ShowHtmlAction.cs b/XLToolbox/Mvvm/Actions/ShowHtmlAction.cs index b5f40b04..d417eb4e 100755 --- a/XLToolbox/Mvvm/Actions/ShowHtmlAction.cs +++ b/XLToolbox/Mvvm/Actions/ShowHtmlAction.cs @@ -32,13 +32,17 @@ class ShowHtmlAction : StringMessageAction public string HtmlResource { get; set; } protected override Window CreateView() + { + return new HtmlFileView(); + } + + protected override ViewModelBase GetDataContext(MessageContent messageContent) { if (!string.IsNullOrEmpty(HtmlResource)) { HtmlFileViewModel vm = new HtmlFileViewModel(HtmlResource); vm.Caption = Caption; - Window view = vm.InjectInto(); - return view; + return vm; } else { diff --git a/XLToolbox/Mvvm/Views/StringMessageContentView.xaml b/XLToolbox/Mvvm/Views/StringMessageContentView.xaml index 8ad423b6..90a18dad 100755 --- a/XLToolbox/Mvvm/Views/StringMessageContentView.xaml +++ b/XLToolbox/Mvvm/Views/StringMessageContentView.xaml @@ -32,13 +32,13 @@ - -