diff options
47 files changed, 926 insertions, 1010 deletions
diff --git a/RhSolutions.AddIn/AddIn/ResetBarFunction.cs b/RhSolutions.AddIn/AddIn/ResetBarFunction.cs index a1a34fa..32bddfa 100644 --- a/RhSolutions.AddIn/AddIn/ResetBarFunction.cs +++ b/RhSolutions.AddIn/AddIn/ResetBarFunction.cs @@ -1,9 +1,15 @@ -namespace RhSolutions.AddIn; +#if !NET472 +using System.Runtime.Versioning; +#endif +namespace RhSolutions.AddIn; +#if !NET472 +[SupportedOSPlatform("windows")] +#endif public static class ResetBarFunction { [ExcelFunction] - public static void _ResetStatusBar() + public static void StatusBarReset() { RhSolutionsAddIn.Excel.StatusBar = false; } diff --git a/RhSolutions.AddIn/AddIn/RhSolutionsAddIn.cs b/RhSolutions.AddIn/AddIn/RhSolutionsAddIn.cs index 796e540..0ef2d69 100644 --- a/RhSolutions.AddIn/AddIn/RhSolutionsAddIn.cs +++ b/RhSolutions.AddIn/AddIn/RhSolutionsAddIn.cs @@ -1,8 +1,15 @@ using ExcelDna.IntelliSense; +using RhSolutions.Tools; using System.Net; +#if !NET472 +using System.Runtime.Versioning; +#endif namespace RhSolutions.AddIn; +#if !NET472 +[SupportedOSPlatform("windows")] +#endif public sealed class RhSolutionsAddIn : IExcelAddIn { public static Application Excel { get; private set; } @@ -14,9 +21,12 @@ public sealed class RhSolutionsAddIn : IExcelAddIn IServiceCollection Services = new ServiceCollection(); Services.AddHttpClient() + .AddSingleton((Application)ExcelDnaUtil.Application) .AddSingleton<IDatabaseClient, RhDatabaseClient>() - .AddSingleton<IAddInConfiguration, AddInConfiguration>() - .AddSingleton((Application)ExcelDnaUtil.Application); + .AddSingleton<IAddInConfiguration, RhAddInConfiguration>() + .AddTransient<IFileDialog, ExcelFileDialog>() + .AddTransient<IExcelReader, RhExcelReader>() + .AddTransient<IExcelWriter, RhExcelWriter>(); ServiceProvider = Services.BuildServiceProvider(); Configuration = ServiceProvider.GetService<IAddInConfiguration>(); diff --git a/RhSolutions.AddIn/AddIn/RhSolutionsFunction.cs b/RhSolutions.AddIn/AddIn/RhSolutionsFunction.cs index bb7bba5..d45ed10 100644 --- a/RhSolutions.AddIn/AddIn/RhSolutionsFunction.cs +++ b/RhSolutions.AddIn/AddIn/RhSolutionsFunction.cs @@ -1,23 +1,29 @@ -namespace RhSolutions.AddIn; +#if !NET472 +using System.Runtime.Versioning; +#endif +namespace RhSolutions.AddIn; + +#if !NET472 +[SupportedOSPlatform("windows")] +#endif public class RhSolutionsFunction { [ExcelFunction(Description = "Распознать артикул и попробовать найти его в прайс-листе")] public static object RHSOLUTIONS([ExcelArgument(Name = "\"Строка с названием материала\"")] string line) { IDatabaseClient databaseClient = RhSolutionsAddIn.ServiceProvider.GetService<IDatabaseClient>(); - IEnumerable<Product> requestResult = ExcelAsyncUtil.Run("Database request", line, delegate + + Sku.TryParse(line, out var skus); + + if (ExcelAsyncUtil.Run("Database request", line, delegate { return databaseClient.GetProducts(line) .GetAwaiter() .GetResult(); - }) as IEnumerable<Product>; - - Sku.TryParse(line, out var skus); - - if (requestResult == null) + }) is not IEnumerable<Product> requestResult) { - if (skus.Count() > 0) + if (skus.Any()) { return $"{skus.First()} ..."; } @@ -29,12 +35,12 @@ public class RhSolutionsFunction else { - if (requestResult.Count() == 0 && skus.Count() == 0) + if (!requestResult.Any() && !skus.Any()) { return ExcelError.ExcelErrorNA; } - else if (requestResult.Count() == 0) + else if (!requestResult.Any()) { return $"{skus.First()}"; } diff --git a/RhSolutions.AddIn/Controllers/CombineTool.cs b/RhSolutions.AddIn/Controllers/CombineTool.cs deleted file mode 100644 index 355539f..0000000 --- a/RhSolutions.AddIn/Controllers/CombineTool.cs +++ /dev/null @@ -1,59 +0,0 @@ -using RhSolutions.AddIn; - -namespace RhSolutions.Controllers; - -internal class CombineTool : ToolBase -{ - private List<SourcePriceList> SourceFiles { get; set; } - - public CombineTool() - { - var dialog = RhSolutionsAddIn.Excel.FileDialog[Microsoft.Office.Core.MsoFileDialogType.msoFileDialogFilePicker]; - dialog.AllowMultiSelect = true; - dialog.Filters.Add("Файлы Excel", "*.xls; *.xlsx; *.xlsm"); - - if (dialog.Show() < 0) - { - List<string> files = new(); - - foreach (string file in dialog.SelectedItems) - { - files.Add(file); - } - - SourceFiles = SourcePriceList.GetSourceLists(files.ToArray()); - } - - else - { - throw new Exception("Не выбраны файлы"); - } - } - - public override void FillTarget() - { - using (ProgressBar = new ProgressBar("Заполняю строки...", SourceFiles.Sum(file => file.PositionAmount.Count))) - using (ResultBar = new ResultBar()) - { - foreach (SourcePriceList source in SourceFiles) - { - TargetFile.Sheet.Columns[TargetFile.AmountCell.Column] - .EntireColumn - .Insert(XlInsertShiftDirection.xlShiftToRight, XlInsertFormatOrigin.xlFormatFromRightOrBelow); - - Range newColumnHeader = TargetFile.Sheet.Cells[TargetFile.AmountCell.Row, TargetFile.AmountCell.Column - 1]; - newColumnHeader.Value2 = $"{source.Name}"; - newColumnHeader.WrapText = true; - - foreach (var kvp in source.PositionAmount) - { - FillPositionAmountToColumns(kvp, TargetFile.AmountCell.Column - 1, TargetFile.AmountCell.Column); - ProgressBar.Update(); - } - } - - FilterByAmount(); - ResultBar.Update(); - } - } -} diff --git a/RhSolutions.AddIn/Controllers/ConvertTool.cs b/RhSolutions.AddIn/Controllers/ConvertTool.cs deleted file mode 100644 index 754fde1..0000000 --- a/RhSolutions.AddIn/Controllers/ConvertTool.cs +++ /dev/null @@ -1,27 +0,0 @@ -namespace RhSolutions.Controllers; - -internal class ConvertTool : ToolBase -{ - private SourcePriceList Current { get; set; } - - public ConvertTool() - { - Current = new SourcePriceList(ExcelApp.ActiveWorkbook); - } - - public override void FillTarget() - { - using (ProgressBar = new ProgressBar("Заполняю строки...", Current.PositionAmount.Count)) - using (ResultBar = new ResultBar()) - { - foreach (var kvp in Current.PositionAmount) - { - FillPositionAmountToColumns(kvp, TargetFile.AmountCell.Column); - ProgressBar.Update(); - } - - FilterByAmount(); - ResultBar.Update(); - } - } -}
\ No newline at end of file diff --git a/RhSolutions.AddIn/Controllers/ExportTool.cs b/RhSolutions.AddIn/Controllers/ExportTool.cs deleted file mode 100644 index 16b51aa..0000000 --- a/RhSolutions.AddIn/Controllers/ExportTool.cs +++ /dev/null @@ -1,97 +0,0 @@ -using RhSolutions.Models; -using System.Collections.Generic; -using System.Linq; - -namespace RhSolutions.Controllers; - -internal class ExportTool : ToolBase -{ - private Dictionary<Product, double> PositionAmount; - private readonly Range Selection; - - public ExportTool() - { - Selection = ExcelApp.Selection; - GetSelected(); - - if (PositionAmount.Count == 0) - { - throw new Exception("В выделенном диапазоне не найдены позиции для экспорта"); - } - } - - public override void FillTarget() - { - using (ProgressBar = new ProgressBar("Заполняю строки...", PositionAmount.Count)) - using (ResultBar = new ResultBar()) - { - foreach (var kvp in PositionAmount) - { - FillPositionAmountToColumns(kvp, TargetFile.AmountCell.Column); - ProgressBar.Update(); - } - - FilterByAmount(); - ResultBar.Update(); - } - } - - private void GetSelected() - { - object[,] cells = Selection.Value2; - PositionAmount = new Dictionary<Product, double>(); - - int rowsCount = Selection.Rows.Count; - - for (int row = 1; row <= rowsCount; row++) - { - if (cells[row, 1] == null || cells[row, 2] == null) - continue; - - string sku = null; - double? amount = null; - - for (int column = 1; column <= 2; column++) - { - object current = cells[row, column]; - - if (Sku.TryParse(current.ToString(), out var rauSku)) - { - sku = rauSku.FirstOrDefault().ToString() ?? null; - } - - else if (current.GetType() == typeof(string) - && double.TryParse(current.ToString(), out _)) - { - amount = double.Parse((string)current); - } - - else if (current.GetType() == typeof(double)) - { - amount = (double)current; - } - } - - if (sku == null || amount == null) - { - continue; - } - - Product position = new Product - { - ProductSku = sku - }; - - if (PositionAmount.ContainsKey(position)) - { - PositionAmount[position] += amount.Value; - } - - else - { - PositionAmount.Add(position, amount.Value); - } - } - } -} - diff --git a/RhSolutions.AddIn/Controllers/MergeTool.cs b/RhSolutions.AddIn/Controllers/MergeTool.cs deleted file mode 100644 index 8566ee7..0000000 --- a/RhSolutions.AddIn/Controllers/MergeTool.cs +++ /dev/null @@ -1,56 +0,0 @@ -using RhSolutions.AddIn; -using RhSolutions.Models; -using System; -using System.Collections.Generic; -using System.Linq; - -namespace RhSolutions.Controllers -{ - internal class MergeTool : ToolBase - { - private List<SourcePriceList> SourceFiles { get; set; } - - public MergeTool() - { - var dialog = RhSolutionsAddIn.Excel.FileDialog[Microsoft.Office.Core.MsoFileDialogType.msoFileDialogFilePicker]; - dialog.AllowMultiSelect = true; - dialog.Filters.Add("Файлы Excel", "*.xls; *.xlsx; *.xlsm"); - - if ( dialog.Show() < 0) - { - List<string> files = new(); - - foreach (string file in dialog.SelectedItems) - { - files.Add(file); - } - - SourceFiles = SourcePriceList.GetSourceLists(files.ToArray()); - } - - else - { - throw new Exception("Не выбраны файлы"); - } - } - - public override void FillTarget() - { - using (ProgressBar = new ProgressBar("Заполняю строки...", SourceFiles.Sum(x => x.PositionAmount.Count))) - using (ResultBar = new ResultBar()) - { - foreach (SourcePriceList source in SourceFiles) - { - foreach (var kvp in source.PositionAmount) - { - FillPositionAmountToColumns(kvp, TargetFile.AmountCell.Column); - ProgressBar.Update(); - } - } - - FilterByAmount(); - ResultBar.Update(); - } - } - } -} diff --git a/RhSolutions.AddIn/Controllers/RibbonController.cs b/RhSolutions.AddIn/Controllers/RibbonController.cs index 978732d..880d49f 100644 --- a/RhSolutions.AddIn/Controllers/RibbonController.cs +++ b/RhSolutions.AddIn/Controllers/RibbonController.cs @@ -1,11 +1,17 @@ using ExcelDna.Integration.CustomUI; -using RhSolutions.AddIn; +using RhSolutions.Tools; using System.Reflection; using System.Runtime.InteropServices; +#if! NET472 +using System.Runtime.Versioning; +#endif using System.Windows.Forms; namespace RhSolutions.Controllers; +#if !NET472 +[SupportedOSPlatform("windows")] +#endif [ComVisible(true)] public class RibbonController : ExcelRibbon { @@ -21,10 +27,7 @@ public class RibbonController : ExcelRibbon <group id='priceList' label='Прайс-лист'> <button id='export' getEnabled='GetExportEnabled' label='Экспорт в новый файл' size='normal' imageMso='PivotExportToExcel' onAction='OnToolPressed'/> <button id='convert' getEnabled='GetConvertEnabled' label='Актуализировать' size='normal' imageMso='FileUpdate' onAction='OnToolPressed'/> - <menu id='conjoinMenu' label='Объединить' imageMso='Copy'> - <button id='merge' label='Сложить' onAction='OnToolPressed'/> - <button id='combine' label='По колонкам' onAction='OnToolPressed'/> - </menu> + <button id='merge' label='Объединить' size='normal' imageMso='Copy' onAction='OnToolPressed'/> </group> <group id='rausettings' getLabel='GetVersionLabel'> <button id='setPriceList' getLabel='GetPriceListPathLabel' size='large' imageMso='TableExcelSpreadsheetInsert' onAction='OnSetPricePressed'/> @@ -42,22 +45,17 @@ public class RibbonController : ExcelRibbon public static void RefreshControl(string id) { - if (ribbonUi != null) - { - ribbonUi.InvalidateControl(id); - } + ribbonUi?.InvalidateControl(id); } public void OnSetPricePressed(IRibbonControl control) { - var dialog = RhSolutionsAddIn.Excel - .FileDialog[Microsoft.Office.Core.MsoFileDialogType.msoFileDialogFilePicker]; - dialog.AllowMultiSelect = false; - dialog.Filters.Add("Файлы Excel", "*.xls; *.xlsx; *.xlsm"); + IFileDialog dialog = RhSolutionsAddIn.ServiceProvider.GetService<IFileDialog>(); + string file = dialog.GetFile(); - if (dialog.Show() < 0) + if (!string.IsNullOrEmpty(file)) { - RhSolutionsAddIn.Configuration.SetPriceListPath(dialog.SelectedItems.Item(1)); + RhSolutionsAddIn.Configuration.SetPriceListPath(file); RhSolutionsAddIn.Configuration.SaveSettings(); } } @@ -71,11 +69,9 @@ public class RibbonController : ExcelRibbon "export" => new ExportTool(), "convert" => new ConvertTool(), "merge" => new MergeTool(), - "combine" => new CombineTool(), _ => throw new Exception("Неизвестный инструмент"), }; - tool.OpenNewPrice(); - tool.FillTarget(); + tool.Execute(); } catch (Exception exception) @@ -122,6 +118,6 @@ public class RibbonController : ExcelRibbon public string GetPriceListPathLabel(IRibbonControl control) { string name = RhSolutionsAddIn.Configuration.GetPriceListFileName(); - return string.IsNullOrEmpty(name) ? "Нет файла шаблона!" : name; + return string.IsNullOrEmpty(name) ? "Шаблонный файл" : name; } } diff --git a/RhSolutions.AddIn/Controllers/ToolBase.cs b/RhSolutions.AddIn/Controllers/ToolBase.cs deleted file mode 100644 index 3f9d82d..0000000 --- a/RhSolutions.AddIn/Controllers/ToolBase.cs +++ /dev/null @@ -1,183 +0,0 @@ -using RhSolutions.AddIn; - -namespace RhSolutions.Controllers -{ - internal abstract class ToolBase - { - protected Application ExcelApp = RhSolutionsAddIn.Excel; - protected IAddInConfiguration Configuration = RhSolutionsAddIn.Configuration; - protected TargetPriceList TargetFile { get; set; } - protected ResultBar ResultBar { get; set; } - protected ProgressBar ProgressBar { get; set; } - public abstract void FillTarget(); - - public void OpenNewPrice() - { - if (ExcelApp.Workbooks - .Cast<Workbook>() - .FirstOrDefault(w => w.FullName == Configuration.GetPriceListPath()) != null) - { - throw new ArgumentException("Шаблонный файл редактируется в другом месте"); - } - - Workbook wb = ExcelApp.Workbooks.Open(Configuration.GetPriceListPath(), null, true); - - try - { - TargetFile = new TargetPriceList(wb); - } - - catch (Exception exception) - { - if (wb != null) - { - wb.Close(); - } - - throw exception; - } - } - - protected void FillPositionAmountToColumns(KeyValuePair<Product, double> positionAmount, params int[] columns) - { - Range worksheetCells = TargetFile.Sheet.Cells; - Range skuColumn = TargetFile.SkuCell.EntireColumn; - - int? row = GetPositionRow(skuColumn, positionAmount.Key.ProductSku, positionAmount.Key.ProductLine); - - if (row != null) - { - foreach (int column in columns) - { - Range cell = worksheetCells[row, column]; - cell.AddValue(positionAmount.Value); - } - - ResultBar.IncrementSuccess(); - return; - } - - if (TargetFile.OldSkuCell != null) - { - row = GetPositionRow(TargetFile.OldSkuCell.EntireColumn, positionAmount.Key.ProductSku, positionAmount.Key.ProductLine); - - if (row != null) - { - foreach (int column in columns) - { - Range cell = worksheetCells[row, column]; - cell.AddValue(positionAmount.Value); - } - - ResultBar.IncrementReplaced(); - return; - } - } - - string sku = positionAmount.Key.ProductSku.Substring(1, 6); - row = GetPositionRow(skuColumn, sku, positionAmount.Key.ProductLine); - - if (row != null) - { - foreach (int column in columns) - { - Range cell = worksheetCells[row, column]; - cell.AddValue(positionAmount.Value); - } - - ResultBar.IncrementReplaced(); - return; - } - - FillMissing(positionAmount, columns); - ResultBar.IncrementNotFound(); - } - - protected void FillMissing(KeyValuePair<Product, double> positionAmount, params int[] columns) - { - Range worksheetCells = TargetFile.Sheet.Cells; - Range worksheetRows = TargetFile.Sheet.Rows; - int skuColumn = TargetFile.SkuCell.Column; - int groupColumn = TargetFile.GroupCell.Column; - int nameColumn = TargetFile.NameCell.Column; - - int row = worksheetCells[worksheetRows.Count, skuColumn] - .End[XlDirection.xlUp] - .Row + 1; - - worksheetRows[row] - .EntireRow - .Insert(XlInsertShiftDirection.xlShiftDown, XlInsertFormatOrigin.xlFormatFromLeftOrAbove); - - Range previous = worksheetRows[row - 1]; - Range current = worksheetRows[row]; - - previous.Copy(current); - current.ClearContents(); - - worksheetCells[row, groupColumn].Value2 = positionAmount.Key.ProductLine; - worksheetCells[row, nameColumn].Value2 = positionAmount.Key.Name; - - if (TargetFile.OldSkuCell != null) - { - worksheetCells[row, skuColumn].Value2 = "Не найден"; - worksheetCells[row, TargetFile.OldSkuCell.Column].Value2 = positionAmount.Key.ProductSku; - } - - else - { - worksheetCells[row, skuColumn].Value2 = positionAmount.Key.ProductSku; - } - - foreach (int column in columns) - { - Range cell = worksheetCells[row, column]; - cell.AddValue(positionAmount.Value); - } - } - - protected int? GetPositionRow(Range range, string sku, string group) - { - Range found = range.Find(sku); - string foundGroupValue; - - if (found == null) - { - return null; - } - - int firstFoundRow = found.Row; - - if (string.IsNullOrEmpty(group)) - { - return found.Row; - } - - while (true) - { - foundGroupValue = TargetFile.Sheet.Cells[found.Row, TargetFile.GroupCell.Column].Value2.ToString(); - - if (group.Equals(foundGroupValue)) - { - return found.Row; - } - - found = range.FindNext(found); - - if (found.Row == firstFoundRow) - { - return null; - } - } - } - - protected void FilterByAmount() - { - AutoFilter filter = TargetFile.Sheet.AutoFilter; - int startColumn = filter.Range.Column; - - filter.Range.AutoFilter(TargetFile.AmountCell.Column - startColumn + 1, "<>0", XlAutoFilterOperator.xlAnd, "<>"); - TargetFile.Sheet.Range["A1"].Activate(); - } - } -}
\ No newline at end of file diff --git a/RhSolutions.AddIn/Models/PriceListBase.cs b/RhSolutions.AddIn/Models/PriceListBase.cs deleted file mode 100644 index 95f385d..0000000 --- a/RhSolutions.AddIn/Models/PriceListBase.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Microsoft.Office.Interop.Excel; - -namespace RhSolutions.Models -{ - internal abstract class PriceListBase - { - public Range AmountCell { get; protected set; } - public Range SkuCell { get; protected set; } - public Range GroupCell { get; protected set; } - public Range NameCell { get; protected set; } - - public Worksheet Sheet { get; protected set; } - public string Name { get; protected set; } - } -}
\ No newline at end of file diff --git a/RhSolutions.AddIn/Models/ProgressBar.cs b/RhSolutions.AddIn/Models/ProgressBar.cs deleted file mode 100644 index 82012e5..0000000 --- a/RhSolutions.AddIn/Models/ProgressBar.cs +++ /dev/null @@ -1,22 +0,0 @@ -namespace RhSolutions.Models -{ - internal class ProgressBar : StatusbarBase - { - private double CurrentProgress { get; set; } - private readonly double TaskWeight; - private readonly string Message; - - public ProgressBar(string message, int weight) - { - Message = message; - TaskWeight = weight; - CurrentProgress = 0; - } - - public override void Update() - { - double percent = ++CurrentProgress / TaskWeight * 100; - Excel.StatusBar = $"{Message} Выполнено {percent:#.#} %"; - } - } -} diff --git a/RhSolutions.AddIn/Models/ResultBar.cs b/RhSolutions.AddIn/Models/ResultBar.cs deleted file mode 100644 index 655540c..0000000 --- a/RhSolutions.AddIn/Models/ResultBar.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System; -using System.Text; - -namespace RhSolutions.Models -{ - internal class ResultBar : StatusbarBase - { - private int Success { get; set; } - private int Replaced { get; set; } - private int NotFound { get; set; } - - public ResultBar() - { - Success = 0; - Replaced = 0; - NotFound = 0; - } - - public void IncrementSuccess() => Success++; - public void IncrementReplaced() => Replaced++; - public void IncrementNotFound() => NotFound++; - - public override void Update() - { - StringBuilder sb = new StringBuilder(); - - if (Success > 0) - { - sb.Append($"Успешно экспортировано {Success} артикулов. "); - } - - if (Replaced > 0) - { - sb.Append($"Заменено {Replaced} артикулов. "); - } - - if (NotFound > 0) - { - sb.Append($"Не найдено {NotFound} артикулов."); - } - - Excel.StatusBar = sb.ToString(); - } - } -} diff --git a/RhSolutions.AddIn/Models/SourcePriceList.cs b/RhSolutions.AddIn/Models/SourcePriceList.cs deleted file mode 100644 index db691ec..0000000 --- a/RhSolutions.AddIn/Models/SourcePriceList.cs +++ /dev/null @@ -1,112 +0,0 @@ -using RhSolutions.AddIn; -using System.IO; - -namespace RhSolutions.Models; - -internal class SourcePriceList : PriceListBase -{ - public Dictionary<Product, double> PositionAmount { get; private set; } - - public SourcePriceList(Workbook workbook) - { - if (workbook == null) - { - throw new ArgumentException($"Нет рабочего файла"); - } - - Sheet = workbook.ActiveSheet; - Name = Path.GetFileNameWithoutExtension(workbook.FullName); - - var pricelistParameters = RhSolutionsAddIn.Configuration.GetPriceListParameters(); - - Range[] cells = new[] - { - AmountCell = Sheet.Cells.Find(pricelistParameters["Amount"]), - SkuCell = Sheet.Cells.Find(pricelistParameters["Sku"]), - GroupCell = Sheet.Cells.Find(pricelistParameters["Group"]), - NameCell = Sheet.Cells.Find(pricelistParameters["Name"]) - }; - - if (cells.Any(x => x == null)) - { - throw new ArgumentException($"Файл {Name} не распознан"); - } - - CreatePositionsDict(); - } - - public static List<SourcePriceList> GetSourceLists(string[] files) - { - var ExcelApp = (Application)ExcelDnaUtil.Application; - ProgressBar bar = new ProgressBar("Открываю исходные файлы...", files.Length); - - List<SourcePriceList> sourceFiles = new List<SourcePriceList>(); - - foreach (string file in files) - { - ExcelApp.ScreenUpdating = false; - Workbook wb = ExcelApp.Workbooks.Open(file); - try - { - SourcePriceList priceList = new SourcePriceList(wb); - sourceFiles.Add(priceList); - wb.Close(); - bar.Update(); - } - catch (Exception ex) - { - System.Windows.Forms.MessageBox.Show - (ex.Message, - "Ошибка открытия исходного прайс-листа", - System.Windows.Forms.MessageBoxButtons.OK, - System.Windows.Forms.MessageBoxIcon.Information); - wb.Close(); - bar.Update(); - } - ExcelApp.ScreenUpdating = true; - } - - return sourceFiles; - } - - private void CreatePositionsDict() - { - PositionAmount = new Dictionary<Product, double>(); - - for (int row = AmountCell.Row + 1; row <= Sheet.Cells[Sheet.Rows.Count, AmountCell.Column].End[XlDirection.xlUp].Row; row++) - { - double? amount = Sheet.Cells[row, AmountCell.Column].Value2 as double?; - - if (amount != null && amount.Value != 0) - { - object group = Sheet.Cells[row, GroupCell.Column].Value2; - object name = Sheet.Cells[row, NameCell.Column].Value2; - object sku = Sheet.Cells[row, SkuCell.Column].Value2; - - if (group == null || name == null || sku == null) - continue; - - if (!Sku.TryParse(sku.ToString(), out _)) - continue; - - Product p = new Product - { - ProductSku = sku.ToString(), - ProductLine = group.ToString(), - Name = name.ToString() - }; - - if (PositionAmount.ContainsKey(p)) - { - PositionAmount[p] += amount.Value; - } - - else - { - PositionAmount.Add(p, amount.Value); - } - } - } - } -} - diff --git a/RhSolutions.AddIn/Models/StatusbarBase.cs b/RhSolutions.AddIn/Models/StatusbarBase.cs deleted file mode 100644 index fbec70e..0000000 --- a/RhSolutions.AddIn/Models/StatusbarBase.cs +++ /dev/null @@ -1,18 +0,0 @@ -using Microsoft.Office.Interop.Excel; -using RhSolutions.AddIn; -using System; - -namespace RhSolutions.Models -{ - internal abstract class StatusbarBase : IDisposable - { - protected Application Excel = RhSolutionsAddIn.Excel; - - public abstract void Update(); - - public void Dispose() - { - Excel.OnTime(DateTime.Now + new TimeSpan(0, 0, 5), "_ResetStatusBar"); - } - } -} diff --git a/RhSolutions.AddIn/Models/TargetPriceList.cs b/RhSolutions.AddIn/Models/TargetPriceList.cs deleted file mode 100644 index 15ba066..0000000 --- a/RhSolutions.AddIn/Models/TargetPriceList.cs +++ /dev/null @@ -1,39 +0,0 @@ -using RhSolutions.AddIn; -using System.IO; - -namespace RhSolutions.Models; - -internal class TargetPriceList : PriceListBase -{ - public Range OldSkuCell { get; private set; } - - public TargetPriceList(Workbook workbook) - { - if (workbook == null) - { - throw new ArgumentException("Невозможно открыть книгу шаблонного файла. " + - "Возможно открыт файл с именем, совпадающим с именем шаблонного файла."); - } - - Sheet = workbook.ActiveSheet; - Name = Path.GetFileNameWithoutExtension(workbook.FullName); - - var pricelistParameters = RhSolutionsAddIn.Configuration.GetPriceListParameters(); - - Range[] cells = new[] - { - AmountCell = Sheet.Cells.Find(pricelistParameters["Amount"]), - SkuCell = Sheet.Cells.Find(pricelistParameters["Sku"]), - GroupCell = Sheet.Cells.Find(pricelistParameters["Group"]), - NameCell = Sheet.Cells.Find(pricelistParameters["Name"]) - }; - - OldSkuCell = Sheet.Cells.Find(pricelistParameters["OldSku"]); - - if (cells.Any(x => x == null)) - { - throw new ArgumentException($"Шаблон {Name} не является прайс-листом"); - } - } -} - diff --git a/RhSolutions.AddIn/RhSolutions.AddIn.csproj b/RhSolutions.AddIn/RhSolutions.AddIn.csproj index 536248e..81fcb6a 100644 --- a/RhSolutions.AddIn/RhSolutions.AddIn.csproj +++ b/RhSolutions.AddIn/RhSolutions.AddIn.csproj @@ -36,7 +36,4 @@ <PackageReference Include="Newtonsoft.Json" Version="13.0.3" /> <PackageReference Include="RhSolutions.Sku" Version="0.1.1" /> </ItemGroup> - <ItemGroup> - <ProjectReference Include="..\RhSolutions.ExcelExtensions\RhSolutions.ExcelExtensions.csproj" /> - </ItemGroup> </Project>
\ No newline at end of file diff --git a/RhSolutions.AddIn/Services/ExcelFileDialog.cs b/RhSolutions.AddIn/Services/ExcelFileDialog.cs new file mode 100644 index 0000000..9e70d46 --- /dev/null +++ b/RhSolutions.AddIn/Services/ExcelFileDialog.cs @@ -0,0 +1,45 @@ +namespace RhSolutions.Services; + +public class ExcelFileDialog : IFileDialog +{ + private Application _application; + + public ExcelFileDialog(Application application) + { + _application = application; + } + + public string GetFile() + { + var dialog = _application.FileDialog[Microsoft.Office.Core.MsoFileDialogType.msoFileDialogFilePicker]; + dialog.AllowMultiSelect = false; + dialog.Filters.Add("Файлы Excel", "*.xls; *.xlsx; *.xlsm"); + + if (dialog.Show() == -1) + { + return dialog.SelectedItems.Item(1); + } + else return string.Empty; + } + + public string[] GetFiles() + { + var dialog = _application.FileDialog[Microsoft.Office.Core.MsoFileDialogType.msoFileDialogFilePicker]; + dialog.AllowMultiSelect = true; + dialog.Filters.Add("Файлы Excel", "*.xls; *.xlsx; *.xlsm"); + + if (dialog.Show() == -1) + { + List<string> files = new(); + + foreach (string file in dialog.SelectedItems) + { + files.Add(file); + } + + return files.ToArray(); + } + + else return Array.Empty<string>(); + } +} diff --git a/RhSolutions.AddIn/Services/IAddInConfiguration.cs b/RhSolutions.AddIn/Services/IAddInConfiguration.cs index 3c09007..4eb79f1 100644 --- a/RhSolutions.AddIn/Services/IAddInConfiguration.cs +++ b/RhSolutions.AddIn/Services/IAddInConfiguration.cs @@ -6,7 +6,7 @@ public interface IAddInConfiguration { public string GetPriceListPath(); public string GetPriceListFileName(); - public Dictionary<string, string> GetPriceListParameters(); + public Dictionary<string, string> GetPriceListHeaders(); public event SettingChangingEventHandler OnSettingsChange; public void SetPriceListPath(string value); public void SaveSettings(); diff --git a/RhSolutions.AddIn/Services/IExcelReader.cs b/RhSolutions.AddIn/Services/IExcelReader.cs new file mode 100644 index 0000000..a134e4c --- /dev/null +++ b/RhSolutions.AddIn/Services/IExcelReader.cs @@ -0,0 +1,8 @@ +namespace RhSolutions.Services; + +public interface IExcelReader +{ + public Dictionary<Product, double> ReadProducts(Range range); + public List<(string, Dictionary<Product, double>)> ReadProducts(IEnumerable<Worksheet> worksheets); + public List<(string, Dictionary<Product, double>)> ReadProducts(string[] files); +} diff --git a/RhSolutions.AddIn/Services/IExcelWriter.cs b/RhSolutions.AddIn/Services/IExcelWriter.cs new file mode 100644 index 0000000..5d2ea29 --- /dev/null +++ b/RhSolutions.AddIn/Services/IExcelWriter.cs @@ -0,0 +1,7 @@ +namespace RhSolutions.Services; + +public interface IExcelWriter +{ + public void WriteProducts(IEnumerable<(string, Dictionary<Product, double>)> products); + public void WriteProducts(Dictionary<Product, double> products); +} diff --git a/RhSolutions.AddIn/Services/IFileDialog.cs b/RhSolutions.AddIn/Services/IFileDialog.cs new file mode 100644 index 0000000..6c85522 --- /dev/null +++ b/RhSolutions.AddIn/Services/IFileDialog.cs @@ -0,0 +1,7 @@ +namespace RhSolutions.Services; + +public interface IFileDialog +{ + public string[] GetFiles(); + public string GetFile(); +} diff --git a/RhSolutions.AddIn/Services/AddInConfiguration.cs b/RhSolutions.AddIn/Services/RhAddInConfiguration.cs index 970cf52..620f9b7 100644 --- a/RhSolutions.AddIn/Services/AddInConfiguration.cs +++ b/RhSolutions.AddIn/Services/RhAddInConfiguration.cs @@ -3,18 +3,18 @@ using System.IO; namespace RhSolutions.Services; -public class AddInConfiguration : ApplicationSettingsBase, IAddInConfiguration +public class RhAddInConfiguration : ApplicationSettingsBase, IAddInConfiguration { - private Dictionary<string, string> _priceListParameters; + private readonly Dictionary<string, string> _priceListHeaders; - public AddInConfiguration() + public RhAddInConfiguration() { - _priceListParameters = new Dictionary<string, string>() + _priceListHeaders = new Dictionary<string, string>() { ["Amount"] = AmountHeader, ["OldSku"] = OldSkuHeader, ["Sku"] = SkuHeader, - ["Group"] = GroupHeader, + ["ProductLine"] = ProductLineHeader, ["Name"] = NameHeader }; } @@ -52,11 +52,11 @@ public class AddInConfiguration : ApplicationSettingsBase, IAddInConfiguration [UserScopedSetting] [DefaultSettingValue("Программа")] - public string GroupHeader + public string ProductLineHeader { get { - return (string)this[nameof(GroupHeader)]; + return (string)this[nameof(ProductLineHeader)]; } } @@ -99,5 +99,5 @@ public class AddInConfiguration : ApplicationSettingsBase, IAddInConfiguration public string GetPriceListPath() => PriceListPath; public void SetPriceListPath(string value) => PriceListPath = value; public void SaveSettings() => base.Save(); - public Dictionary<string, string> GetPriceListParameters() => _priceListParameters; + public Dictionary<string, string> GetPriceListHeaders() => _priceListHeaders; } diff --git a/RhSolutions.AddIn/Services/RhExcelReader.cs b/RhSolutions.AddIn/Services/RhExcelReader.cs new file mode 100644 index 0000000..0749482 --- /dev/null +++ b/RhSolutions.AddIn/Services/RhExcelReader.cs @@ -0,0 +1,169 @@ +using System.IO; +using RhSolutions.Tools; +#if !NET472 +using System.Runtime.Versioning; +using RhSolutions.Tools; +#endif + +namespace RhSolutions.Services; + +#if !NET472 +[SupportedOSPlatform("windows")] +#endif +public class RhExcelReader : IExcelReader, IDisposable +{ + private ProgressBar _progressBar; + private readonly Dictionary<string, string> headers; + private readonly Application _application; + + public RhExcelReader(Application application, IAddInConfiguration configuration) + { + _application = application; + headers = configuration.GetPriceListHeaders(); + } + + public Dictionary<Product, double> ReadProducts(Range range) + { + object[,] cells = range.Value2; + Dictionary<Product, double> readResult = new(); + + for (int row = 1; row <= range.Rows.Count; row++) + { + if (cells[row, 1] == null || cells[row, 2] == null) + continue; + + string currentSku = null; + double? currentAmount = null; + + for (int column = 1; column <= 2; column++) + { + object currentCell = cells[row, column]; + + if (Sku.TryParse(currentCell.ToString(), out var validSku)) + { + currentSku = validSku.FirstOrDefault().ToString() ?? null; + } + + else if (currentCell.GetType() == typeof(string) + && double.TryParse(currentCell.ToString(), out _)) + { + currentAmount = double.Parse((string)currentCell); + } + + else if (currentCell.GetType() == typeof(double)) + { + currentAmount = (double)currentCell; + } + } + + if (currentSku == null || currentAmount == null) + { + continue; + } + + Product product = new() { ProductSku = currentSku }; + + if (readResult.ContainsKey(product)) + { + readResult[product] += currentAmount.Value; + } + + else + { + readResult.Add(product, currentAmount.Value); + } + } + + return readResult; + } + + public List<(string, Dictionary<Product, double>)> + ReadProducts(IEnumerable<Worksheet> worksheets) + { + List<(string, Dictionary<Product, double>)> result = new(); + foreach (Worksheet worksheet in worksheets) + { + if (!worksheet.IsRehauSource()) + { + continue; + } + + string wbName = Path.GetFileNameWithoutExtension( + worksheet.Parent.Name); + + Range AmountCell = worksheet.Cells.Find(headers["Amount"]), + SkuCell = worksheet.Cells.Find(headers["Sku"]), + ProductLineCelll = worksheet.Cells.Find(headers["ProductLine"]), + NameCell = worksheet.Cells.Find(headers["Name"]); + var lastRowIndex = worksheet.Cells[worksheet.Rows.Count, AmountCell.Column] + .End[XlDirection.xlUp].Row; + + Dictionary<Product, double> readResult = new(); + + for (int row = AmountCell.Row + 1; row <= lastRowIndex; row++) + { + double? amount = worksheet.Cells[row, AmountCell.Column].Value2 as double?; + + if (amount != null && amount.Value != 0) + { + object programLine = worksheet.Cells[row, ProductLineCelll.Column].Value2; + object name = worksheet.Cells[row, NameCell.Column].Value2; + object sku = worksheet.Cells[row, SkuCell.Column].Value2; + + if (programLine == null || name == null || sku == null) + continue; + + if (!Sku.TryParse(sku.ToString(), out _)) + continue; + + Product p = new() + { + ProductSku = sku.ToString(), + ProductLine = programLine.ToString(), + Name = name.ToString() + }; + + if (readResult.ContainsKey(p)) + { + readResult[p] += amount.Value; + } + + else + { + readResult.Add(p, amount.Value); + } + } + + } + + result.Add((wbName, readResult)); + } + return result; + } + + public List<(string, Dictionary<Product, double>)> ReadProducts(string[] files) + { + _progressBar = new("Открываю исходные файлы...", files.Length); + List<Worksheet> worksheets = new(); + + _application.ScreenUpdating = false; + foreach (string file in files) + { + Workbook wb = _application.Workbooks.Open(file); + worksheets.Add(wb.ActiveSheet); + _progressBar.Update(); + } + _application.ScreenUpdating = true; + var result = ReadProducts(worksheets); + foreach (var ws in worksheets) + { + ws.Parent.Close(); + } + return result; + } + + public void Dispose() + { + _progressBar.Dispose(); + } +}
\ No newline at end of file diff --git a/RhSolutions.AddIn/Services/RhExcelWriter.cs b/RhSolutions.AddIn/Services/RhExcelWriter.cs new file mode 100644 index 0000000..6fd43fc --- /dev/null +++ b/RhSolutions.AddIn/Services/RhExcelWriter.cs @@ -0,0 +1,256 @@ +#if !NET472 +using System.Runtime.Versioning; +using RhSolutions.Tools; +#endif + +using RhSolutions.Tools; + +namespace RhSolutions.Services; + +#if !NET472 +[SupportedOSPlatform("windows")] +#endif +public class RhExcelWriter : IExcelWriter, IDisposable +{ + private readonly Application _application; + private Worksheet _worksheet; + private readonly ResultBar _resultBar; + private readonly Dictionary<string, string> _headers; + private readonly string _pricelistPath; + private ProgressBar _progressBar; + + private Range _amountCell, + _skuCell, + _programLineCell, + _nameCell, + _oldSkuCell; + + public RhExcelWriter(Application application, IAddInConfiguration configuration) + { + _application = application; + _pricelistPath = configuration.GetPriceListPath(); + _resultBar = new(); + _headers = configuration.GetPriceListHeaders(); + } + + public void WriteProducts(Dictionary<Product, double> products) + { + WriteProducts(new[] { (string.Empty, products) }); + } + + public void WriteProducts(IEnumerable<(string, Dictionary<Product, double>)> products) + { + _worksheet = OpenNewPrice(); + + if (!_worksheet.IsRehauSource()) + { + _application.ActiveWorkbook.Close(); + throw new ArgumentException( + $"Целевой файл {_application.ActiveWorkbook.Name} не является прайс-листом."); + } + + _amountCell = _worksheet.Cells.Find(_headers["Amount"]); + _skuCell = _worksheet.Cells.Find(_headers["Sku"]); + _programLineCell = _worksheet.Cells.Find(_headers["ProductLine"]); + _nameCell = _worksheet.Cells.Find(_headers["Name"]); + _oldSkuCell = _worksheet.Cells.Find(_headers["OldSku"]); + + _progressBar = new("Заполняю строки...", products + .Select(p => p.Item2) + .Sum(set => set.Count)); + + if (products.Count() == 1) + { + foreach (var kvp in products.First().Item2) + { + FillPositionAmountToColumns(kvp, _amountCell.Column); + _progressBar.Update(); + } + FilterByAmount(); + _resultBar.Update(); + } + + else + { + foreach (var product in products) + { + _worksheet.Columns[_amountCell.Column] + .EntireColumn + .Insert(XlInsertShiftDirection.xlShiftToRight, XlInsertFormatOrigin.xlFormatFromRightOrBelow); + + Range newColumnHeader = _worksheet.Cells[_amountCell.Row, _amountCell.Column - 1]; + newColumnHeader.Value2 = $"{product.Item1}"; + newColumnHeader.WrapText = true; + + foreach (var kvp in product.Item2) + { + FillPositionAmountToColumns(kvp, _amountCell.Column - 1, _amountCell.Column); + _progressBar.Update(); + } + } + + FilterByAmount(); + _resultBar.Update(); + } + } + + private Worksheet OpenNewPrice() + { + if (_application.Workbooks + .Cast<Workbook>() + .FirstOrDefault(w => w.FullName == _pricelistPath) != null) + { + throw new ArgumentException("Шаблонный файл редактируется в другом месте"); + } + + return _application.Workbooks.Open(_pricelistPath, null, true).ActiveSheet; + } + + private void FillPositionAmountToColumns(KeyValuePair<Product, double> positionAmount, params int[] columns) + { + Range worksheetCells = _worksheet.Cells; + Range skuColumn = _skuCell.EntireColumn; + + int? row = GetPositionRow(skuColumn, positionAmount.Key.ProductSku, positionAmount.Key.ProductLine); + + if (row != null) + { + foreach (int column in columns) + { + Range cell = worksheetCells[row, column]; + cell.AddValue(positionAmount.Value); + } + + _resultBar.IncrementSuccess(); + return; + } + + if (_oldSkuCell != null) + { + row = GetPositionRow(_oldSkuCell.EntireColumn, positionAmount.Key.ProductSku, positionAmount.Key.ProductLine); + + if (row != null) + { + foreach (int column in columns) + { + Range cell = worksheetCells[row, column]; + cell.AddValue(positionAmount.Value); + } + + _resultBar.IncrementReplaced(); + return; + } + } + + string sku = positionAmount.Key.ProductSku.Substring(1, 6); + row = GetPositionRow(skuColumn, sku, positionAmount.Key.ProductLine); + + if (row != null) + { + foreach (int column in columns) + { + Range cell = worksheetCells[row, column]; + cell.AddValue(positionAmount.Value); + } + + _resultBar.IncrementReplaced(); + return; + } + + FillMissing(positionAmount, columns); + _resultBar.IncrementNotFound(); + } + + private void FillMissing(KeyValuePair<Product, double> positionAmount, params int[] columns) + { + Range worksheetCells = _worksheet.Cells; + Range worksheetRows = _worksheet.Rows; + int skuColumn = _skuCell.Column; + int groupColumn = _programLineCell.Column; + int nameColumn = _nameCell.Column; + + int row = worksheetCells[worksheetRows.Count, skuColumn] + .End[XlDirection.xlUp] + .Row + 1; + + worksheetRows[row] + .EntireRow + .Insert(XlInsertShiftDirection.xlShiftDown, XlInsertFormatOrigin.xlFormatFromLeftOrAbove); + + Range previous = worksheetRows[row - 1]; + Range current = worksheetRows[row]; + + previous.Copy(current); + current.ClearContents(); + + worksheetCells[row, groupColumn].Value2 = positionAmount.Key.ProductLine; + worksheetCells[row, nameColumn].Value2 = positionAmount.Key.Name; + + if (_oldSkuCell != null) + { + worksheetCells[row, skuColumn].Value2 = "Не найден"; + worksheetCells[row, _oldSkuCell.Column].Value2 = positionAmount.Key.ProductSku; + } + + else + { + worksheetCells[row, skuColumn].Value2 = positionAmount.Key.ProductSku; + } + + foreach (int column in columns) + { + Range cell = worksheetCells[row, column]; + cell.AddValue(positionAmount.Value); + } + } + + private int? GetPositionRow(Range range, string sku, string group) + { + Range found = range.Find(sku); + string foundGroupValue; + + if (found == null) + { + return null; + } + + int firstFoundRow = found.Row; + + if (string.IsNullOrEmpty(group)) + { + return found.Row; + } + + while (true) + { + foundGroupValue = _worksheet.Cells[found.Row, _programLineCell.Column].Value2.ToString(); + + if (group.Equals(foundGroupValue)) + { + return found.Row; + } + + found = range.FindNext(found); + + if (found.Row == firstFoundRow) + { + return null; + } + } + } + + private void FilterByAmount() + { + AutoFilter filter = _worksheet.AutoFilter; + int startColumn = filter.Range.Column; + + filter.Range.AutoFilter(_amountCell.Column - startColumn + 1, "<>0", XlAutoFilterOperator.xlAnd, "<>"); + _worksheet.Range["A1"].Activate(); + } + + public void Dispose() + { + _progressBar.Dispose(); + _resultBar.Dispose(); + } +} diff --git a/RhSolutions.AddIn/Models/WorksheetExtensions.cs b/RhSolutions.AddIn/Services/WorksheetExtensions.cs index da38203..409cd5f 100644 --- a/RhSolutions.AddIn/Models/WorksheetExtensions.cs +++ b/RhSolutions.AddIn/Services/WorksheetExtensions.cs @@ -1,23 +1,28 @@ -using RhSolutions.AddIn; +#if !NET472 +using System.Runtime.Versioning; +#endif namespace RhSolutions.Services; +#if !NET472 +[SupportedOSPlatform("windows")] +#endif public static class WorksheetExtensions { public static bool IsRehauSource(this Worksheet worksheet) { Range amountCell; Range skuCell; - Range groupCell; + Range programLineCell; Range nameCell; - var pricelistParameters = RhSolutionsAddIn.Configuration.GetPriceListParameters(); + var pricelistParameters = RhSolutionsAddIn.Configuration.GetPriceListHeaders(); Range[] cells = new[] { amountCell = worksheet.Cells.Find(pricelistParameters["Amount"]), skuCell = worksheet.Cells.Find(pricelistParameters["Sku"]), - groupCell = worksheet.Cells.Find(pricelistParameters["Group"]), + programLineCell = worksheet.Cells.Find(pricelistParameters["ProductLine"]), nameCell = worksheet.Cells.Find(pricelistParameters["Name"]) }; diff --git a/RhSolutions.AddIn/Tools/ConvertTool.cs b/RhSolutions.AddIn/Tools/ConvertTool.cs new file mode 100644 index 0000000..c9782a1 --- /dev/null +++ b/RhSolutions.AddIn/Tools/ConvertTool.cs @@ -0,0 +1,20 @@ +using RhSolutions.AddIn; +#if !NET472 +using System.Runtime.Versioning; +#endif + +namespace RhSolutions.Tools; + +#if !NET472 +[SupportedOSPlatform("windows")] +#endif +internal class ConvertTool : ToolBase +{ + public override void Execute() + { + Application app = RhSolutionsAddIn.Excel.Application; + Worksheet worksheet = app.ActiveWorkbook.ActiveSheet; + var products = _reader.ReadProducts(new[] { worksheet }); + _writer.WriteProducts(products); + } +}
\ No newline at end of file diff --git a/RhSolutions.AddIn/Services/EventsUtil.cs b/RhSolutions.AddIn/Tools/EventsUtil.cs index bd7dd7b..4bd6b89 100644 --- a/RhSolutions.AddIn/Services/EventsUtil.cs +++ b/RhSolutions.AddIn/Tools/EventsUtil.cs @@ -2,9 +2,15 @@ using RhSolutions.AddIn; using RhSolutions.Controllers; using System.Configuration; +#if !NET472 +using System.Runtime.Versioning; +#endif -namespace RhSolutions.Services +namespace RhSolutions.Tools { +#if !NET472 + [SupportedOSPlatform("windows")] +#endif internal static class EventsUtil { public static void Initialize() diff --git a/RhSolutions.AddIn/Tools/ExportTool.cs b/RhSolutions.AddIn/Tools/ExportTool.cs new file mode 100644 index 0000000..c943cf1 --- /dev/null +++ b/RhSolutions.AddIn/Tools/ExportTool.cs @@ -0,0 +1,20 @@ +using RhSolutions.AddIn; +#if !NET472 +using System.Runtime.Versioning; +#endif + +namespace RhSolutions.Tools; + +#if !NET472 +[SupportedOSPlatform("windows")] +#endif +internal class ExportTool : ToolBase +{ + public override void Execute() + { + Application app = RhSolutionsAddIn.Excel.Application; + var products = _reader.ReadProducts(app.Selection); + _writer.WriteProducts(products); + } +} + diff --git a/RhSolutions.AddIn/Tools/MergeTool.cs b/RhSolutions.AddIn/Tools/MergeTool.cs new file mode 100644 index 0000000..03cf104 --- /dev/null +++ b/RhSolutions.AddIn/Tools/MergeTool.cs @@ -0,0 +1,23 @@ +#if !NET472 +using System.Runtime.Versioning; +using RhSolutions; +using RhSolutions.Models; +using RhSolutions.Tools; +#endif + + +namespace RhSolutions.Tools; + +#if !NET472 +[SupportedOSPlatform("windows")] +#endif +internal class MergeTool : ToolBase +{ + public override void Execute() + { + IFileDialog dialog = RhSolutionsAddIn.ServiceProvider.GetRequiredService<IFileDialog>(); + string[] files = dialog.GetFiles(); + var products = _reader.ReadProducts(files); + _writer.WriteProducts(products); + } +} diff --git a/RhSolutions.AddIn/Tools/ProgressBar.cs b/RhSolutions.AddIn/Tools/ProgressBar.cs new file mode 100644 index 0000000..709f28c --- /dev/null +++ b/RhSolutions.AddIn/Tools/ProgressBar.cs @@ -0,0 +1,28 @@ +#if !NET472 +using System.Runtime.Versioning; +#endif + +namespace RhSolutions.Tools; + +#if !NET472 +[SupportedOSPlatform("windows")] +#endif +internal class ProgressBar : StatusbarBase +{ + private double CurrentProgress { get; set; } + private readonly double TaskWeight; + private readonly string Message; + + public ProgressBar(string message, int weight) + { + Message = message; + TaskWeight = weight; + CurrentProgress = 0; + } + + public override void Update() + { + double percent = ++CurrentProgress / TaskWeight * 100; + Excel.StatusBar = $"{Message} Выполнено {percent:#.#} %"; + } +} diff --git a/RhSolutions.AddIn/Tools/ResultBar.cs b/RhSolutions.AddIn/Tools/ResultBar.cs new file mode 100644 index 0000000..b14f307 --- /dev/null +++ b/RhSolutions.AddIn/Tools/ResultBar.cs @@ -0,0 +1,49 @@ +using System.Text; +#if !NET472 +using System.Runtime.Versioning; +#endif + +namespace RhSolutions.Tools; + +#if !NET472 +[SupportedOSPlatform("windows")] +#endif +internal class ResultBar : StatusbarBase +{ + private int Success { get; set; } + private int Replaced { get; set; } + private int NotFound { get; set; } + + public ResultBar() + { + Success = 0; + Replaced = 0; + NotFound = 0; + } + + public void IncrementSuccess() => Success++; + public void IncrementReplaced() => Replaced++; + public void IncrementNotFound() => NotFound++; + + public override void Update() + { + StringBuilder sb = new StringBuilder(); + + if (Success > 0) + { + sb.Append($"Успешно экспортировано {Success} артикулов. "); + } + + if (Replaced > 0) + { + sb.Append($"Заменено {Replaced} артикулов. "); + } + + if (NotFound > 0) + { + sb.Append($"Не найдено {NotFound} артикулов."); + } + + Excel.StatusBar = sb.ToString(); + } +} diff --git a/RhSolutions.AddIn/Tools/StatusbarBase.cs b/RhSolutions.AddIn/Tools/StatusbarBase.cs new file mode 100644 index 0000000..fe30eaa --- /dev/null +++ b/RhSolutions.AddIn/Tools/StatusbarBase.cs @@ -0,0 +1,24 @@ +#if !NET472 +using System.Runtime.Versioning; +using RhSolutions; +using RhSolutions.Models; +using RhSolutions.Tools; +#endif + + +namespace RhSolutions.Tools; + +#if !NET472 +[SupportedOSPlatform("windows")] +#endif +internal abstract class StatusbarBase : IDisposable +{ + protected Application Excel = RhSolutionsAddIn.Excel; + + public abstract void Update(); + + public void Dispose() + { + Excel.OnTime(DateTime.Now + new TimeSpan(0, 0, 5), "StatusBarReset"); + } +} diff --git a/RhSolutions.AddIn/Tools/ToolBase.cs b/RhSolutions.AddIn/Tools/ToolBase.cs new file mode 100644 index 0000000..9c37bc3 --- /dev/null +++ b/RhSolutions.AddIn/Tools/ToolBase.cs @@ -0,0 +1,22 @@ +#if !NET472 +using System.Runtime.Versioning; +#endif + +namespace RhSolutions.Tools; + +#if !NET472 +[SupportedOSPlatform("windows")] +#endif +internal abstract class ToolBase +{ + protected readonly IExcelReader _reader; + protected readonly IExcelWriter _writer; + + public ToolBase() + { + _reader = RhSolutionsAddIn.ServiceProvider.GetRequiredService<IExcelReader>(); + _writer = RhSolutionsAddIn.ServiceProvider.GetRequiredService<IExcelWriter>(); + } + + public abstract void Execute(); +}
\ No newline at end of file diff --git a/RhSolutions.AddIn/Usings.cs b/RhSolutions.AddIn/Usings.cs index 929f09f..0196089 100644 --- a/RhSolutions.AddIn/Usings.cs +++ b/RhSolutions.AddIn/Usings.cs @@ -1,9 +1,11 @@ global using ExcelDna.Integration; global using Microsoft.Extensions.DependencyInjection; global using Microsoft.Office.Interop.Excel; +global using RhSolutions.AddIn; global using RhSolutions.Models; global using RhSolutions.Services; +global using RhSolutions.Tools; global using System; global using System.Collections.Generic; global using System.Linq; -global using Range = Microsoft.Office.Interop.Excel.Range;
\ No newline at end of file +global using Range = Microsoft.Office.Interop.Excel.Range; diff --git a/RhSolutions.ExcelExtensions/Cell.cs b/RhSolutions.ExcelExtensions/Cell.cs new file mode 100644 index 0000000..19a2017 --- /dev/null +++ b/RhSolutions.ExcelExtensions/Cell.cs @@ -0,0 +1,26 @@ +namespace RhSolutions.ExcelExtensions; + +public sealed class Cell +{ + public Table ParentTable { get; } + public Row ParentRow + { + get => ParentTable.Rows[ParentTable.Range.Row - _range.Row]; + } + public Column ParentColumn + { + get => ParentTable.Columns[ParentTable.Range.Column - _range.Column]; + } + public object Value + { + get => _range.Cells[1, 1].Value2; + set => _range.Cells[1, 1].Value2 = value; + } + private Range _range; + + public Cell(Range range, Table table) + { + _range = range; + ParentTable = table; + } +} diff --git a/RhSolutions.ExcelExtensions/Column.cs b/RhSolutions.ExcelExtensions/Column.cs index 53a1f3d..341897c 100644 --- a/RhSolutions.ExcelExtensions/Column.cs +++ b/RhSolutions.ExcelExtensions/Column.cs @@ -1,44 +1,62 @@ -using System.Collections; +namespace RhSolutions.ExcelExtensions; -namespace RhSolutions.ExcelExtensions; - -public sealed class Column : Table, IEnumerable<TableCell> +public sealed class Column { + public Table ParentTable { get; } public string Header { - get => Range.Cells[1, 1].Value.ToString(); + get => _range.Cells[1, 1].Value2.ToString() ?? String.Empty; } public int Index { - get => Range.Column - ParentTable.Range.Column; + get => _range.Column - ParentTable.Range.Column; } public int Length { - get => Range.Rows.Count; - } - - public Column(Range range, Table table) : base(range, table) - { - Range = range; - ParentTable = table; + get => _range.Rows.Count; } + private Cell[] _cells; + private readonly Range _range; - public TableCell this[int index] + public Column(Range range, Table table) { - get => new(Range.Cells[index + 1, 1], ParentTable); + _cells = new Cell[range.Rows.Count]; + _range = range; + ParentTable = table ?? + throw new ArgumentNullException("table"); } - public IEnumerator<TableCell> GetEnumerator() + public Cell this[int index] { - return new ColumnEnumerator(Range, ParentTable); + get + { + if (_cells[index] == null) + { + _cells[index] = new Cell(_range.Cells[index + 1, 1], ParentTable); + return _cells[index]; + } + else + { + return _cells[index]; + } + } + set + { + if (_cells[index] == null) + { + _cells[index] = new Cell(_range.Cells[index + 1, 1], ParentTable); + _cells[index].Value = value; + } + else + { + _cells[index].Value = value; + } + } } - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - - public Column AddLeft() { - Range.EntireColumn + _range.EntireColumn .Insert(XlInsertShiftDirection.xlShiftToRight, XlInsertFormatOrigin.xlFormatFromRightOrBelow); diff --git a/RhSolutions.ExcelExtensions/ColumnEnumerator.cs b/RhSolutions.ExcelExtensions/ColumnEnumerator.cs deleted file mode 100644 index 9880b5d..0000000 --- a/RhSolutions.ExcelExtensions/ColumnEnumerator.cs +++ /dev/null @@ -1,54 +0,0 @@ -using System.Collections; - -namespace RhSolutions.ExcelExtensions; - -public class ColumnEnumerator : IEnumerator<TableCell> -{ - public Range Range { get; } - public Table ParentTable { get; } - private int position = 0; - object IEnumerator.Current - { - get - { - return Current; - } - } - - public TableCell Current - { - get - { - try - { - return new TableCell(Range.Cells[position, 1], ParentTable); - } - catch (IndexOutOfRangeException) - { - throw new InvalidOperationException(); - } - } - } - - public ColumnEnumerator(Range range, Table table) - { - Range = range; - ParentTable = table; - } - - public bool MoveNext() - { - position++; - return (position <= Range.Rows.Count); - } - - public void Reset() - { - position = 0; - } - - public void Dispose() - { - - } -} diff --git a/RhSolutions.ExcelExtensions/Columns.cs b/RhSolutions.ExcelExtensions/Columns.cs index 1fbc0ef..014b755 100644 --- a/RhSolutions.ExcelExtensions/Columns.cs +++ b/RhSolutions.ExcelExtensions/Columns.cs @@ -4,35 +4,45 @@ namespace RhSolutions.ExcelExtensions; public class Columns : IEnumerable<Column> { - public Range Range { get; } public Table ParentTable { get; } public int Length { - get => Range.Columns.Count; + get => _range.Columns.Count; } + private Column[] _columns; + private Range _range; - public Columns(Range range, Table parentTable) + public Columns(Table parentTable) { - Range = range; ParentTable = parentTable; + _range = parentTable.Range; + _columns = new Column[Length]; } public Column this[int index] { get { - if (index < 0 || index + 1 > Range.Columns.Count) + if (index < 0 || index >= Length) { throw new IndexOutOfRangeException(); } - return new Column(Range.Columns[index + 1], ParentTable); + if (_columns[index] == null) + { + _columns[index] = new Column(_range.Columns[index + 1], ParentTable); + return _columns[index]; + } + else + { + return _columns[index]; + } } } public IEnumerator<Column> GetEnumerator() { - return new ColumnsEnumerator(Range, ParentTable); + return new ColumnsEnumerator(this); } IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); diff --git a/RhSolutions.ExcelExtensions/ColumnsEnumerator.cs b/RhSolutions.ExcelExtensions/ColumnsEnumerator.cs index 578e2b0..40ad4d6 100644 --- a/RhSolutions.ExcelExtensions/ColumnsEnumerator.cs +++ b/RhSolutions.ExcelExtensions/ColumnsEnumerator.cs @@ -4,9 +4,8 @@ namespace RhSolutions.ExcelExtensions; public class ColumnsEnumerator: IEnumerator<Column> { - public Range Range { get; } - public Table ParentTable { get; } - private int position = 0; + private Columns _columns; + private int position = -1; object IEnumerator.Current { get @@ -21,7 +20,7 @@ public class ColumnsEnumerator: IEnumerator<Column> { try { - return new Column(Range.Columns[position], ParentTable); + return _columns[position]; } catch (IndexOutOfRangeException) { @@ -30,21 +29,20 @@ public class ColumnsEnumerator: IEnumerator<Column> } } - public ColumnsEnumerator(Range range, Table table) + public ColumnsEnumerator(Columns columns) { - Range = range; - ParentTable = table; + _columns = columns; } public bool MoveNext() { position++; - return (position <= Range.Columns.Count); + return (position < _columns.Length); } public void Reset() { - position = 0; + position = -1; } public void Dispose() diff --git a/RhSolutions.ExcelExtensions/Row.cs b/RhSolutions.ExcelExtensions/Row.cs index 32617b4..01df2e4 100644 --- a/RhSolutions.ExcelExtensions/Row.cs +++ b/RhSolutions.ExcelExtensions/Row.cs @@ -1,33 +1,54 @@ -using System.Collections; +namespace RhSolutions.ExcelExtensions; -namespace RhSolutions.ExcelExtensions; - -public sealed class Row : Table, IEnumerable<TableCell> +public sealed class Row { + public Table ParentTable { get; } public int Index { - get => Range.Row - ParentTable.Range.Row; + get => _range.Row - ParentTable.Range.Row; } public int Length { - get => Range.Columns.Count; + get => _range.Columns.Count; } + private readonly Cell[] _cells; + private readonly Range _range; - public Row(Range range, Table table) : base(range, table) + public Row(Range range, Table table) { - Range = range; - ParentTable = table; + _cells = new Cell[range.Columns.Count]; + _range = range; + ParentTable = table ?? + throw new ArgumentNullException("table"); } - public TableCell this[int index] + public Cell this[int index] { - get => new(Range.Cells[1, index + 1], ParentTable); + get + { + if (index < 0 || index >= Length) + { + throw new IndexOutOfRangeException(); + } + + if (_cells[index] == null) + { + _cells[index] = new Cell(_range.Cells[1, index + 1], ParentTable); + return _cells[index]; + } + else + { + return _cells[index]; + } + } } - public IEnumerator<TableCell> GetEnumerator() + public Cell this[string header] { - return new RowEnumerator(Range, ParentTable); + get + { + int columnIndex = ParentTable.ColumnByHeader(header).Index; + return this[columnIndex]; + } } - - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); } diff --git a/RhSolutions.ExcelExtensions/RowEnumerator.cs b/RhSolutions.ExcelExtensions/RowEnumerator.cs deleted file mode 100644 index fd56dcd..0000000 --- a/RhSolutions.ExcelExtensions/RowEnumerator.cs +++ /dev/null @@ -1,54 +0,0 @@ -using System.Collections; - -namespace RhSolutions.ExcelExtensions; - -public class RowEnumerator : IEnumerator<TableCell> -{ - public Range Range { get; } - public Table ParentTable { get; } - private int position = 0; - object IEnumerator.Current - { - get - { - return Current; - } - } - - public TableCell Current - { - get - { - try - { - return new TableCell(Range.Cells[1, position], ParentTable); - } - catch (IndexOutOfRangeException) - { - throw new InvalidOperationException(); - } - } - } - - public RowEnumerator(Range range, Table parentTable) - { - Range = range; - ParentTable = parentTable; - } - - public bool MoveNext() - { - position++; - return (position <= Range.Columns.Count); - } - - public void Reset() - { - position = 0; - } - - public void Dispose() - { - - } -}
\ No newline at end of file diff --git a/RhSolutions.ExcelExtensions/Rows.cs b/RhSolutions.ExcelExtensions/Rows.cs index 1c0bc0d..c6d4c01 100644 --- a/RhSolutions.ExcelExtensions/Rows.cs +++ b/RhSolutions.ExcelExtensions/Rows.cs @@ -4,35 +4,40 @@ namespace RhSolutions.ExcelExtensions; public class Rows : IEnumerable<Row> { - public Range Range { get; } public Table ParentTable { get; } public int Length { - get => Range.Rows.Count; + get => _range.Rows.Count; } + private Row[] _rows; + private Range _range; - public Rows(Range range, Table parentTable) + public Rows(Table parentTable) { - Range = range; ParentTable = parentTable; + _range = parentTable.Range; + _rows = new Row[Length]; } public Row this[int index] { get { - if (index < 0 || index + 1 > Range.Rows.Count) + if (_rows[index] == null) { - throw new IndexOutOfRangeException(); + _rows[index] = new Row(_range.Rows[index + 1], ParentTable); + return _rows[index]; + } + else + { + return _rows[index]; } - - return new Row(Range.Rows[index + 1], ParentTable); } } public IEnumerator<Row> GetEnumerator() { - return new RowsEnumerator(Range, ParentTable); + return new RowsEnumerator(this); } IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); diff --git a/RhSolutions.ExcelExtensions/RowsEnumerator.cs b/RhSolutions.ExcelExtensions/RowsEnumerator.cs index b53db98..4d68a9c 100644 --- a/RhSolutions.ExcelExtensions/RowsEnumerator.cs +++ b/RhSolutions.ExcelExtensions/RowsEnumerator.cs @@ -4,9 +4,8 @@ namespace RhSolutions.ExcelExtensions; public class RowsEnumerator : IEnumerator<Row> { - public Range Range { get; } - public Table ParentTable { get; } - private int position = 0; + private Rows _rows; + private int position = -1; object IEnumerator.Current { get @@ -21,7 +20,7 @@ public class RowsEnumerator : IEnumerator<Row> { try { - return new Row(Range.Rows[position], ParentTable); + return _rows[position]; } catch (IndexOutOfRangeException) { @@ -30,26 +29,24 @@ public class RowsEnumerator : IEnumerator<Row> } } - public RowsEnumerator(Range range, Table table) + public RowsEnumerator(Rows rows) { - Range = range; - ParentTable = table; + _rows = rows; } public bool MoveNext() { position++; - return (position <= Range.Rows.Count); + return (position < _rows.Length); } public void Reset() { - position = 0; + position = -1; } public void Dispose() { } - }
\ No newline at end of file diff --git a/RhSolutions.ExcelExtensions/Table.cs b/RhSolutions.ExcelExtensions/Table.cs index a19a4b4..aad2151 100644 --- a/RhSolutions.ExcelExtensions/Table.cs +++ b/RhSolutions.ExcelExtensions/Table.cs @@ -6,29 +6,40 @@ public class Table public Table ParentTable { get; protected set; } public Rows Rows { get; } public Columns Columns { get; } + private Dictionary<string, Column> _columnsByHeader; public Table(Range range) { Range = range; - ParentTable = null; - Rows = new Rows(Range, this); - Columns = new Columns(Range, this); + ParentTable = this; + Rows = new Rows(this); + Columns = new Columns(this); + _columnsByHeader = new(); + + foreach(var column in Columns) + { + if (_columnsByHeader.ContainsKey(column.Header)) + { + throw new ArgumentException($"Заголовок столбца {column.Header} не уникален"); + } + else + { + _columnsByHeader.Add(column.Header, column); + } + } } - public Table(Range range, Table table) + public Column ColumnByHeader(string header) { - Range = range; - ParentTable = table; - Rows = new Rows(Range, this); - Columns = new Columns(Range, this); + return _columnsByHeader[header]; } - public TableCell this[int row, int column] + public Cell this[int row, int column] { - get => new(Range.Cells[row + 1, column + 1], this); + get => this.Rows[row][column]; } - public IEnumerable<TableCell> Find(object item) + public IEnumerable<Cell> Search(object item) { Range firstFound = Range.Find(item); if (firstFound == null) @@ -40,7 +51,7 @@ public class Table while (true) { - yield return new TableCell(nextFound, ParentTable ?? this); + yield return this[nextFound.Row - ParentTable.Range.Row, nextFound.Column - ParentTable.Range.Column]; nextFound = Range.FindNext(nextFound); if (nextFound.Row == firstFound.Row diff --git a/RhSolutions.ExcelExtensions/TableCell.cs b/RhSolutions.ExcelExtensions/TableCell.cs deleted file mode 100644 index 822673e..0000000 --- a/RhSolutions.ExcelExtensions/TableCell.cs +++ /dev/null @@ -1,24 +0,0 @@ -namespace RhSolutions.ExcelExtensions; - -public sealed class TableCell : Table -{ - public Row ParentRow - { - get => ParentTable.Rows[ParentTable.Range.Row - Range.Row]; - } - public Column ParentColumn - { - get => ParentTable.Columns[ParentTable.Range.Column - Range.Column]; - } - public object Value - { - get => Range.Cells[1, 1].Value2; - set => Range.Cells[1, 1].Value2 = value; - } - - public TableCell(Range range, Table table) : base(range, table) - { - Range = range; - ParentTable = table; - } -} diff --git a/RhSolutions.Tests/ExcelTablesTests.cs b/RhSolutions.Tests/ExcelTablesTests.cs deleted file mode 100644 index 78264ac..0000000 --- a/RhSolutions.Tests/ExcelTablesTests.cs +++ /dev/null @@ -1,65 +0,0 @@ -namespace RhSolutions.Tests; - -[ExcelTestSettings(OutOfProcess = true, Workbook = @"TestWorkbooks\ExcelTableTest.xlsx")] -public class ExcelTablesTests : IDisposable -{ - ExcelExtensions.Table table; - - public ExcelTablesTests() - { - Util.Application.Workbooks.Add(); - - Worksheet worksheet = Util.Workbook.Sheets[1]; - Range range = worksheet.Range["E7:G9"]; - table = new(range); - } - - [ExcelFact] - public void CanReadExcelTable() - { - Assert.Equal(3, table.Columns.Where(c => c.Header.StartsWith("Столбец")).Count()); - Assert.Equal("Столбец 1", table.Rows.First()[0].Value); - Assert.Equal("Столбец 2", table[0, 1].Value); - Assert.Equal(123d, table[1, 1].Value); - Assert.Null(table[1, 2].Value); - } - - [ExcelFact] - public void CanModifyTableCells() - { - table[2, 1].Value = 1; - table[1, 1].Value = (double)table[1, 1].Value + 123; - Assert.Equal(1d, table[2, 1].Value); - Assert.Equal(246d, table[1, 1].Value); - } - - [ExcelFact] - public void CanFindInTable() - { - table[2, 0].Value = "Find!"; - table[2, 1].Value = "Find this!"; - table[2, 2].Value = "Find that!"; - - - var cells = table.Find("Find"); - Assert.Collection(cells, item => Assert.Equal("Find!", item.Value), - item => Assert.Equal("Find this!", item.Value), - item => Assert.Equal("Find that!", item.Value)); - Assert.Equal(0, table.Find("Значение").First().ParentColumn.Index); - Assert.Equal(3, table.Rows[2].Find("Find").Count()); - Assert.Empty(table.Columns[1].Find("Пусто")); - } - - [ExcelFact] - public void CanAddColumns() - { - int originalCount = table.Columns.Count(); - table.Columns[1].AddLeft(); - Assert.Equal(originalCount + 1, table.Columns.Count()); - } - - public void Dispose() - { - Util.Application.ActiveWindow.Close(SaveChanges: false); - } -} diff --git a/RhSolutions.sln b/RhSolutions.sln index 8db9bf3..8d94b9b 100644 --- a/RhSolutions.sln +++ b/RhSolutions.sln @@ -7,8 +7,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RhSolutions.AddIn", "RhSolu EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RhSolutions.Tests", "RhSolutions.Tests\RhSolutions.Tests.csproj", "{6EECCDDB-741C-404A-874F-BB8656265162}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RhSolutions.ExcelExtensions", "RhSolutions.ExcelExtensions\RhSolutions.ExcelExtensions.csproj", "{ADB862A8-5CC6-4509-A4F7-9907E84F5801}" -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -23,10 +21,6 @@ Global {6EECCDDB-741C-404A-874F-BB8656265162}.Debug|Any CPU.Build.0 = Debug|Any CPU {6EECCDDB-741C-404A-874F-BB8656265162}.Release|Any CPU.ActiveCfg = Release|Any CPU {6EECCDDB-741C-404A-874F-BB8656265162}.Release|Any CPU.Build.0 = Release|Any CPU - {ADB862A8-5CC6-4509-A4F7-9907E84F5801}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {ADB862A8-5CC6-4509-A4F7-9907E84F5801}.Debug|Any CPU.Build.0 = Debug|Any CPU - {ADB862A8-5CC6-4509-A4F7-9907E84F5801}.Release|Any CPU.ActiveCfg = Release|Any CPU - {ADB862A8-5CC6-4509-A4F7-9907E84F5801}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE |