Skip to content

Commit

Permalink
Added stateful model.
Browse files Browse the repository at this point in the history
  • Loading branch information
smourier committed Apr 5, 2024
1 parent f6f88bb commit d5c7499
Show file tree
Hide file tree
Showing 13 changed files with 231 additions and 86 deletions.
36 changes: 10 additions & 26 deletions SheetReader.AppTest/MainWindow.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public MainWindow()
tc.ItemsSource = Sheets;
}

public ObservableCollection<SheetData> Sheets { get; } = new();
public ObservableCollection<BookDocumentSheet> Sheets { get; } = [];

protected override void OnKeyDown(KeyEventArgs e)
{
Expand All @@ -28,7 +28,7 @@ protected override void OnKeyDown(KeyEventArgs e)
}

private void Exit_Click(object sender, RoutedEventArgs e) => Close();
private void About_Click(object sender, RoutedEventArgs e) => MessageBox.Show(Assembly.GetEntryAssembly()!.GetCustomAttribute<AssemblyTitleAttribute>()!.Title + " - " + (IntPtr.Size == 4 ? "32" : "64") + "-bit" + Environment.NewLine + "Copyright (C) 2021-" + DateTime.Now.Year + " Simon Mourier. All rights reserved.", Assembly.GetEntryAssembly().GetCustomAttribute<AssemblyTitleAttribute>()!.Title, MessageBoxButton.OK, MessageBoxImage.Information);
private void About_Click(object sender, RoutedEventArgs e) => MessageBox.Show(Assembly.GetEntryAssembly()!.GetCustomAttribute<AssemblyTitleAttribute>()!.Title + " - " + (IntPtr.Size == 4 ? "32" : "64") + "-bit" + Environment.NewLine + "Copyright (C) 2021-" + DateTime.Now.Year + " Simon Mourier. All rights reserved.", Assembly.GetEntryAssembly()!.GetCustomAttribute<AssemblyTitleAttribute>()!.Title, MessageBoxButton.OK, MessageBoxImage.Information);
private void Open_Click(object sender, RoutedEventArgs e)
{
var ofd = new OpenFileDialog
Expand All @@ -49,30 +49,13 @@ private void Open_Click(object sender, RoutedEventArgs e)
private void Load(string fileName)
{
Sheets.Clear();
var book = new Book();
try
{
foreach (var sheet in book.EnumerateSheets(fileName))
var book = new BookDocument();
book.Load(fileName);
foreach (var sheet in book.Sheets)
{
// we need to load data for WPF data binding
var sheetData = new SheetData { Name = sheet.Name, IsHidden = !sheet.IsVisible };
foreach (var row in sheet.EnumerateRows())
{
var rowData = new RowData(row.Index, row.EnumerateCells().ToList());
sheetData.Rows.Add(rowData);
}

sheetData.Columns = sheet.EnumerateColumns().ToList();
if (sheetData.Columns.Count > 0)
{
sheetData.LastColumnIndex = sheetData.Columns.Max(x => x.Index);
}

if (sheetData.Rows.Count > 0)
{
sheetData.LastRowIndex = sheetData.Rows.Max(x => x.RowIndex);
}
Sheets.Add(sheetData);
Sheets.Add(sheet);
}
}
catch (Exception ex)
Expand All @@ -84,10 +67,11 @@ private void Load(string fileName)
// not sure why, but with only 1 tab, binding doesn't work...
if (Sheets.Count == 1)
{
var dummy = new SheetData();
Sheets.Add(dummy);
Sheets.Remove(dummy);
var first = Sheets[0];
Sheets.Add(first);
Sheets.RemoveAt(1);
}

Title = "Sheet Reader - " + Path.GetFileName(fileName);
}

Expand Down
7 changes: 4 additions & 3 deletions SheetReader.AppTest/SheetControl.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
using System;
using System.Globalization;
using System.Linq;
using System.Windows;
using System.Windows.Media;

namespace SheetReader.AppTest
{
public class SheetControl : FrameworkElement
{
public static readonly DependencyProperty SheetProperty = DependencyProperty.Register(nameof(Sheet), typeof(SheetData), typeof(SheetControl),
public static readonly DependencyProperty SheetProperty = DependencyProperty.Register(nameof(Sheet), typeof(BookDocumentSheet), typeof(SheetControl),
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsRender));

public static readonly DependencyProperty ColumnSizeProperty = DependencyProperty.Register(nameof(ColumnSize), typeof(double), typeof(SheetControl),
Expand All @@ -16,7 +17,7 @@ public class SheetControl : FrameworkElement
public static readonly DependencyProperty RowHeightProperty = DependencyProperty.Register(nameof(RowHeight), typeof(double), typeof(SheetControl),
new FrameworkPropertyMetadata(20.0, FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsRender));

public SheetData Sheet { get { return (SheetData)GetValue(SheetProperty); } set { SetValue(SheetProperty, value); } }
public BookDocumentSheet Sheet { get { return (BookDocumentSheet)GetValue(SheetProperty); } set { SetValue(SheetProperty, value); } }
public double ColumnSize { get { return (double)GetValue(ColumnSizeProperty); } set { SetValue(ColumnSizeProperty, value); } }
public double RowHeight { get { return (double)GetValue(RowHeightProperty); } set { SetValue(RowHeightProperty, value); } }

Expand Down Expand Up @@ -119,7 +120,7 @@ protected override void OnRender(DrawingContext drawingContext)
}
}

foreach (var row in Sheet.Rows)
foreach (var row in Sheet.Rows.Values.OrderBy(r => r.RowIndex))
{
foreach (var cell in row.Cells)
{
Expand Down
28 changes: 0 additions & 28 deletions SheetReader.AppTest/SheetData.cs

This file was deleted.

3 changes: 3 additions & 0 deletions SheetReader.Wpf.Test/GlobalSuppressions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
using System.Diagnostics.CodeAnalysis;

[assembly: SuppressMessage("Interoperability", "SYSLIB1054:Use 'LibraryImportAttribute' instead of 'DllImportAttribute' to generate P/Invoke marshalling code at compile time", Justification = "Mostly useless")]
5 changes: 4 additions & 1 deletion SheetReader.Wpf.Test/MainWindow.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,10 @@
Header="Open _Recent"
IsEnabled="False">
<Separator />
<MenuItem Name="ClearRecentFiles" Header="Clear Recent List" Click="ClearRecentFiles_Click" />
<MenuItem
Name="ClearRecentFiles"
Click="ClearRecentFiles_Click"
Header="Clear Recent List" />
</MenuItem>
<Separator />
<MenuItem
Expand Down
73 changes: 46 additions & 27 deletions SheetReader.Wpf/SheetControl.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
using System;
using System.Diagnostics;
using System.Globalization;
using System.Runtime.CompilerServices;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using SheetReader.Wpf.Utilities;

namespace SheetReader.Wpf
{
Expand Down Expand Up @@ -46,6 +47,8 @@ static SheetControl()
DefaultStyleKeyProperty.OverrideMetadata(typeof(SheetControl), new FrameworkPropertyMetadata(typeof(SheetControl)));
}

private static void Log(object? message = null, [CallerMemberName] string? methodName = null) => EventProvider.Default.WriteMessageEvent(methodName + ":" + message);

private ScrollViewer? _scrollViewer;
private SheetGrid? _grid;

Expand All @@ -68,7 +71,6 @@ public override void OnApplyTemplate()

private void OnScrollChanged(object sender, ScrollChangedEventArgs e)
{
Trace.WriteLine("OnScrollChanged");
_grid?.InvalidateVisual();
}

Expand Down Expand Up @@ -100,7 +102,6 @@ protected override Size MeasureCore(Size availableSize)

protected override void OnRender(DrawingContext drawingContext)
{
Trace.WriteLine("OnRender size:" + RenderSize);
if (control.Sheet == null || control.Sheet.Columns.Count == 0 || control.Sheet.Rows.Count == 0)
return;

Expand All @@ -109,20 +110,33 @@ protected override void OnRender(DrawingContext drawingContext)

var dpi = VisualTreeHelper.GetDpi(this);
var colWidth = control.GetColWidth();
var colFullWidth = colWidth + control.GridLineSize;
var rowHeight = control.GetRowHeight();
var fontSize = rowHeight * 0.6;
var rowFullHeight = rowHeight + control.GridLineSize;

var rowMargin = control.GetRowMargin();
var headerHeight = rowHeight;
var rowsHeaderWidth = control.GetRowMargin();
var rowsHeaderFullWidth = rowsHeaderWidth + control.GridLineSize;
var columnsHeaderHeight = rowHeight;
var columnsHeaderFullHeight = columnsHeaderHeight + control.GridLineSize;

var offsetX = control._scrollViewer?.HorizontalOffset ?? 0;
var offsetY = control._scrollViewer?.VerticalOffset ?? 0;
var viewWidth = control._scrollViewer?.ViewportWidth ?? RenderSize.Width;
var viewHeight = control._scrollViewer?.ViewportHeight ?? RenderSize.Height;

var firstColumnIndex = 0;
var offsetX = control._scrollViewer?.ContentHorizontalOffset ?? 0;
var firstDrawnColumnIndex = Math.Max((int)((offsetX - rowsHeaderFullWidth) / colFullWidth), 0);
var lastDrawnColumnIndex = Math.Max(Math.Min((int)((offsetX - rowsHeaderFullWidth + viewWidth) / colFullWidth), control.Sheet.LastColumnIndex), firstDrawnColumnIndex);

var firstDrawnRowIndex = Math.Max((int)((offsetY - columnsHeaderFullHeight) / rowFullHeight), 0);
var lastDrawnRowIndex = Math.Max(Math.Min((int)((offsetY - columnsHeaderFullHeight + viewHeight) / rowFullHeight), control.Sheet.LastRowIndex), firstDrawnRowIndex);

Log("firstCol:" + firstDrawnColumnIndex + " lastCol:" + lastDrawnColumnIndex + " firstRow:" + firstDrawnRowIndex + " lastRow:" + lastDrawnRowIndex);

// header backgrounds
drawingContext.DrawRectangle(Brushes.LightGray, null, new Rect(offsetX, headerHeight, rowMargin + offsetX, (control.Sheet.LastRowIndex + 1) * rowHeight));
drawingContext.DrawRectangle(Brushes.LightGray, null, new Rect(rowMargin, 0, (control.Sheet.LastColumnIndex + 1) * colWidth, headerHeight));
drawingContext.DrawRectangle(Brushes.LightGray, null, new Rect(offsetX, columnsHeaderHeight, rowsHeaderWidth, (control.Sheet.LastRowIndex + 1) * rowHeight));
drawingContext.DrawRectangle(Brushes.LightGray, null, new Rect(rowsHeaderWidth, offsetY, (control.Sheet.LastColumnIndex + 1) * colWidth, columnsHeaderHeight));

var maxWidth = Math.Min(colWidth * (control.Sheet.LastColumnIndex + 1) + rowMargin, RenderSize.Width);
var maxWidth = Math.Min(colWidth * (control.Sheet.LastColumnIndex + 1) + rowsHeaderWidth, RenderSize.Width);
var maxHeight = Math.Min(rowHeight * (control.Sheet.LastRowIndex + 2), RenderSize.Height);

// includes col header
Expand All @@ -135,61 +149,66 @@ protected override void OnRender(DrawingContext drawingContext)
}
else
{
x = colWidth * i - (colWidth - rowMargin);
x = colWidth * i - (colWidth - rowsHeaderWidth);
}

drawingContext.DrawLine(pen, new Point(x, 0), new Point(x, maxHeight));
drawingContext.DrawLine(pen, new Point(x, offsetY), new Point(x, maxHeight));

// draw col name
if (i < control.Sheet.LastColumnIndex + 1)
{
var name = GetExcelColumnName(i);
var formatted = new FormattedText(name, CultureInfo.CurrentCulture, FlowDirection.LeftToRight, typeFace, fontSize, Brushes.Black, dpi.PixelsPerDip)
var formatted = new FormattedText(name, CultureInfo.CurrentCulture, FlowDirection.LeftToRight, typeFace, control.FontSize, control.Foreground, dpi.PixelsPerDip)
{
MaxTextWidth = rowMargin,
MaxTextWidth = rowsHeaderWidth,
MaxLineCount = 1
};

var xoffset = (colWidth - formatted.Width) / 2; // center horizontally
var yoffset = (headerHeight - formatted.Height) / 2; // center vertically
drawingContext.DrawText(formatted, new Point(xoffset + rowMargin + i * colWidth + pen.Thickness, yoffset + pen.Thickness));
var yoffset = offsetY + (columnsHeaderHeight - formatted.Height) / 2; // center vertically
drawingContext.DrawText(formatted, new Point(xoffset + rowsHeaderWidth + i * colWidth + pen.Thickness, yoffset + pen.Thickness));
}
}

// includess row margin
for (var i = 0; i < control.Sheet.LastRowIndex + 2 + 1; i++)
{
var y = rowHeight * i;
drawingContext.DrawLine(pen, new Point(0, y), new Point(maxWidth, y));
drawingContext.DrawLine(pen, new Point(offsetX, y), new Point(maxWidth, y));

// draw row #
if (i < control.Sheet.LastRowIndex + 1)
{
var formatted = new FormattedText((i + 1).ToString(), CultureInfo.CurrentCulture, FlowDirection.LeftToRight, typeFace, fontSize, Brushes.Black, dpi.PixelsPerDip)
var formatted = new FormattedText((i + 1).ToString(), CultureInfo.CurrentCulture, FlowDirection.LeftToRight, typeFace, control.FontSize, control.Foreground, dpi.PixelsPerDip)
{
MaxTextWidth = rowMargin,
MaxTextWidth = rowsHeaderWidth,
MaxLineCount = 1
};

var xoffset = (rowMargin - formatted.Width) / 2; // center horizontally
var xoffset = offsetX + (rowsHeaderWidth - formatted.Width) / 2; // center horizontally
var yoffset = (rowHeight - formatted.Height) / 2; // center vertically
drawingContext.DrawText(formatted, new Point(xoffset + pen.Thickness, headerHeight + rowHeight * i + yoffset + pen.Thickness));
drawingContext.DrawText(formatted, new Point(xoffset + pen.Thickness, columnsHeaderHeight + rowHeight * i + yoffset + pen.Thickness));
}
}

foreach (var row in control.Sheet.Rows)
for (var i = firstDrawnRowIndex; i <= lastDrawnRowIndex; i++)
{
foreach (var cell in row.Cells)
var row = control.Sheet.Rows[i];
for (var j = firstDrawnColumnIndex; j <= lastDrawnColumnIndex; j++)
{
if (j >= row.Cells.Count)
continue;

var cell = row.Cells[j];
var text = string.Format("{0}", cell.Value);
var formatted = new FormattedText(text, CultureInfo.CurrentCulture, FlowDirection.LeftToRight, typeFace, fontSize, Brushes.Black, dpi.PixelsPerDip)
var formatted = new FormattedText(text, CultureInfo.CurrentCulture, FlowDirection.LeftToRight, typeFace, control.FontSize, control.Foreground, dpi.PixelsPerDip)
{
MaxTextWidth = colWidth,
MaxLineCount = 1
};

var y = headerHeight + rowHeight * row.RowIndex;
var x = rowMargin + colWidth * cell.ColumnIndex;
var y = columnsHeaderHeight + rowHeight * row.RowIndex;
var x = rowsHeaderWidth + colWidth * cell.ColumnIndex;
var yoffset = (rowHeight - formatted.Height) / 2; // center vertically
drawingContext.DrawText(formatted, new Point(x + pen.Thickness, y + yoffset + pen.Thickness));
}
Expand Down
2 changes: 2 additions & 0 deletions SheetReader.Wpf/Themes/Generic.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
x:Name="PART_ScrollViewer"
Grid.Row="1"
Grid.Column="1"
Background="{TemplateBinding Background}"
Foreground="{TemplateBinding Foreground}"
HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Auto" />
</ControlTemplate>
Expand Down
44 changes: 44 additions & 0 deletions SheetReader.Wpf/Utilities/EventProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Threading;

namespace SheetReader.Wpf.Utilities
{
public sealed class EventProvider : IDisposable
{
// use WpfTraceSpy to read this https://github.com/smourier/TraceSpy ("ETW messages support")
public static EventProvider Default { get; } = new(new Guid("964d4572-adb9-4f3a-8170-fcbecec27467"));

private long _handle;
public Guid Id { get; }

public EventProvider(Guid id)
{
Id = id;
var hr = EventRegister(id, IntPtr.Zero, IntPtr.Zero, out _handle);
if (hr != 0)
throw new Win32Exception(hr);
}

public bool WriteMessageEvent(string text, byte level = 0, long keywords = 0) => EventWriteString(_handle, level, keywords, text) == 0;

public void Dispose()
{
var handle = Interlocked.Exchange(ref _handle, 0);
if (handle != 0)
{
_ = EventUnregister(handle);
}
}

[DllImport("advapi32")]
private static extern int EventRegister([MarshalAs(UnmanagedType.LPStruct)] Guid ProviderId, IntPtr EnableCallback, IntPtr CallbackContext, out long RegHandle);

[DllImport("advapi32")]
private static extern int EventUnregister(long RegHandle);

[DllImport("advapi32")]
private static extern int EventWriteString(long RegHandle, byte Level, long Keyword, [MarshalAs(UnmanagedType.LPWStr)] string String);
}
}
2 changes: 1 addition & 1 deletion SheetReader/Book.cs
Original file line number Diff line number Diff line change
Expand Up @@ -330,7 +330,7 @@ public XlsxSheet(XlsxBook book, XElement element, XmlReader reader)
Book = book;
Element = element;
Reader = reader;
Name = element.Attribute("name")?.Value;
Name = element.Attribute("name")?.Value!;
var state = element.Attribute("state")?.Value;
if (state.EqualsIgnoreCase("hidden"))
{
Expand Down
Loading

0 comments on commit d5c7499

Please sign in to comment.