wip
This commit is contained in:
@@ -27,18 +27,28 @@ internal sealed class SensorColumnMeasurer
|
||||
private readonly MainWindowViewModel _viewModel;
|
||||
private readonly IStartupTracer _startupTrace;
|
||||
private readonly Dictionary<(string Text, bool Bold), double> _cache = new();
|
||||
private readonly List<Grid> _rowGrids = [];
|
||||
private readonly List<RowMeasurement> _rows = [];
|
||||
private readonly Dictionary<SensorTreeItemViewModel, RowMeasurement> _rowsByItem = new();
|
||||
private readonly DispatcherQueueTimer _settleTimer;
|
||||
private readonly double[] _columnWidths = [DefaultDeviceColumnWidth, 120, 120, 120];
|
||||
private readonly double[] _measuredColumnWidths = [DefaultDeviceColumnWidth, 120, 120, 120];
|
||||
private TextBlock? _measurementTextBlock;
|
||||
private Grid? _header;
|
||||
private double _stableDeviceColumnWidth = DefaultDeviceColumnWidth;
|
||||
private SensorDisplayColumn _dirtyColumns = SensorDisplayColumn.All;
|
||||
private bool _settled;
|
||||
private bool _requiresFullMeasurement = true;
|
||||
private bool _lastShowValueColumn;
|
||||
private bool _lastShowMinColumn;
|
||||
private bool _lastShowMaxColumn;
|
||||
|
||||
public SensorColumnMeasurer(DispatcherQueue dispatcherQueue, MainWindowViewModel viewModel, IStartupTracer startupTrace)
|
||||
{
|
||||
_viewModel = viewModel ?? throw new ArgumentNullException(nameof(viewModel));
|
||||
_startupTrace = startupTrace;
|
||||
_lastShowValueColumn = viewModel.ShowValueColumn;
|
||||
_lastShowMinColumn = viewModel.ShowMinColumn;
|
||||
_lastShowMaxColumn = viewModel.ShowMaxColumn;
|
||||
|
||||
_settleTimer = dispatcherQueue.CreateTimer();
|
||||
_settleTimer.Interval = SettleDelay;
|
||||
@@ -47,7 +57,7 @@ internal sealed class SensorColumnMeasurer
|
||||
|
||||
public event EventHandler? SettleTriggered;
|
||||
|
||||
public int RowCount => _rowGrids.Count;
|
||||
public int RowCount => _rows.Count;
|
||||
|
||||
public int CacheEntryCount => _cache.Count;
|
||||
|
||||
@@ -59,12 +69,20 @@ internal sealed class SensorColumnMeasurer
|
||||
{
|
||||
_stableDeviceColumnWidth = NormalizeDeviceColumnWidth(_viewModel.Settings.GetValue(DeviceColumnWidthSetting, DefaultDeviceColumnWidth));
|
||||
_columnWidths[0] = _stableDeviceColumnWidth;
|
||||
InvalidateAll();
|
||||
}
|
||||
|
||||
public Grid CreateRowGrid()
|
||||
public Grid CreateRowGrid(SensorTreeItemViewModel item, int depth)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(item);
|
||||
|
||||
Grid grid = CreateColumnGrid();
|
||||
_rowGrids.Add(grid);
|
||||
RowMeasurement row = new(item, grid, depth);
|
||||
_rows.Add(row);
|
||||
_rowsByItem[item] = row;
|
||||
item.DisplayColumnsChanged += Item_DisplayColumnsChanged;
|
||||
_requiresFullMeasurement = true;
|
||||
_dirtyColumns = SensorDisplayColumn.All;
|
||||
return grid;
|
||||
}
|
||||
|
||||
@@ -77,12 +95,17 @@ internal sealed class SensorColumnMeasurer
|
||||
|
||||
public void ResetRows()
|
||||
{
|
||||
_rowGrids.Clear();
|
||||
foreach (RowMeasurement row in _rows)
|
||||
row.Item.DisplayColumnsChanged -= Item_DisplayColumnsChanged;
|
||||
|
||||
_rows.Clear();
|
||||
_rowsByItem.Clear();
|
||||
InvalidateAll();
|
||||
}
|
||||
|
||||
public void ScheduleSettle()
|
||||
{
|
||||
if (_rowGrids.Count == 0)
|
||||
if (_rows.Count == 0)
|
||||
return;
|
||||
|
||||
_settled = false;
|
||||
@@ -95,37 +118,55 @@ internal sealed class SensorColumnMeasurer
|
||||
_settleTimer.Stop();
|
||||
}
|
||||
|
||||
public void InvalidateAll()
|
||||
{
|
||||
_requiresFullMeasurement = true;
|
||||
_dirtyColumns = SensorDisplayColumn.All;
|
||||
foreach (RowMeasurement row in _rows)
|
||||
row.DirtyColumns = SensorDisplayColumn.All;
|
||||
}
|
||||
|
||||
public void UpdateWidths()
|
||||
{
|
||||
double sensorWidth = MeasureText("Sensor", true) + SensorColumnPadding;
|
||||
double valueWidth = _viewModel.ShowValueColumn ? MeasureText("Value", true) + ValueColumnPadding : 0;
|
||||
double minWidth = _viewModel.ShowMinColumn ? MeasureText("Min", true) + ValueColumnPadding : 0;
|
||||
double maxWidth = _viewModel.ShowMaxColumn ? MeasureText("Max", true) + ValueColumnPadding : 0;
|
||||
if (ColumnVisibilityChanged())
|
||||
InvalidateAll();
|
||||
|
||||
foreach (SensorTreeItemViewModel root in _viewModel.RootItems)
|
||||
MeasureColumnWidths(root, 0, ref sensorWidth, ref valueWidth, ref minWidth, ref maxWidth);
|
||||
bool widthsChanged = false;
|
||||
if (_requiresFullMeasurement)
|
||||
{
|
||||
MeasureAllRows();
|
||||
_requiresFullMeasurement = false;
|
||||
widthsChanged = UpdateDisplayedColumnWidths();
|
||||
}
|
||||
else if (_dirtyColumns != SensorDisplayColumn.None)
|
||||
{
|
||||
MeasureDirtyRows();
|
||||
widthsChanged = UpdateDisplayedColumnWidths();
|
||||
}
|
||||
|
||||
double deviceColumnWidth = NormalizeDeviceColumnWidth(sensorWidth);
|
||||
if (!_settled)
|
||||
deviceColumnWidth = Math.Max(deviceColumnWidth, _stableDeviceColumnWidth);
|
||||
|
||||
_columnWidths[0] = deviceColumnWidth;
|
||||
_columnWidths[1] = Math.Ceiling(valueWidth);
|
||||
_columnWidths[2] = Math.Ceiling(minWidth);
|
||||
_columnWidths[3] = Math.Ceiling(maxWidth);
|
||||
|
||||
if (_header != null)
|
||||
ApplyColumnWidths(_header);
|
||||
foreach (Grid row in _rowGrids)
|
||||
ApplyColumnWidths(row);
|
||||
if (widthsChanged)
|
||||
ApplyColumnWidthsToRegisteredGrids();
|
||||
|
||||
RecordDeviceColumnWidthIfChanged();
|
||||
}
|
||||
|
||||
private void Item_DisplayColumnsChanged(object? sender, SensorDisplayColumn columns)
|
||||
{
|
||||
if (columns == SensorDisplayColumn.None || sender is not SensorTreeItemViewModel item)
|
||||
return;
|
||||
|
||||
if (!_rowsByItem.TryGetValue(item, out RowMeasurement? row))
|
||||
return;
|
||||
|
||||
row.DirtyColumns |= columns;
|
||||
_dirtyColumns |= columns;
|
||||
}
|
||||
|
||||
private void SettleTimer_Tick(DispatcherQueueTimer sender, object args)
|
||||
{
|
||||
_settleTimer.Stop();
|
||||
_settled = true;
|
||||
InvalidateAll();
|
||||
SettleTriggered?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
@@ -152,27 +193,183 @@ internal sealed class SensorColumnMeasurer
|
||||
grid.ColumnDefinitions[i].Width = new GridLength(_columnWidths[i]);
|
||||
}
|
||||
|
||||
private void MeasureColumnWidths(
|
||||
SensorTreeItemViewModel item,
|
||||
int depth,
|
||||
ref double sensorWidth,
|
||||
ref double valueWidth,
|
||||
ref double minWidth,
|
||||
ref double maxWidth)
|
||||
private void ApplyColumnWidthsToRegisteredGrids()
|
||||
{
|
||||
if (item.RowVisibility == Visibility.Visible)
|
||||
if (_header != null)
|
||||
ApplyColumnWidths(_header);
|
||||
|
||||
foreach (RowMeasurement row in _rows)
|
||||
ApplyColumnWidths(row.Grid);
|
||||
}
|
||||
|
||||
private bool ColumnVisibilityChanged()
|
||||
{
|
||||
bool changed = _lastShowValueColumn != _viewModel.ShowValueColumn
|
||||
|| _lastShowMinColumn != _viewModel.ShowMinColumn
|
||||
|| _lastShowMaxColumn != _viewModel.ShowMaxColumn;
|
||||
|
||||
_lastShowValueColumn = _viewModel.ShowValueColumn;
|
||||
_lastShowMinColumn = _viewModel.ShowMinColumn;
|
||||
_lastShowMaxColumn = _viewModel.ShowMaxColumn;
|
||||
return changed;
|
||||
}
|
||||
|
||||
private void MeasureAllRows()
|
||||
{
|
||||
SetHeaderColumnWidths();
|
||||
|
||||
foreach (RowMeasurement row in _rows)
|
||||
{
|
||||
sensorWidth = Math.Max(sensorWidth, MeasureText(item.Text) + SensorColumnPadding + depth * TreeIndentWidth);
|
||||
if (_viewModel.ShowValueColumn)
|
||||
valueWidth = Math.Max(valueWidth, MeasureText(item.Value) + ValueColumnPadding);
|
||||
if (_viewModel.ShowMinColumn)
|
||||
minWidth = Math.Max(minWidth, MeasureText(item.Min) + ValueColumnPadding);
|
||||
if (_viewModel.ShowMaxColumn)
|
||||
maxWidth = Math.Max(maxWidth, MeasureText(item.Max) + ValueColumnPadding);
|
||||
MeasureRowColumn(row, SensorDisplayColumn.Sensor);
|
||||
MeasureRowColumn(row, SensorDisplayColumn.Value);
|
||||
MeasureRowColumn(row, SensorDisplayColumn.Min);
|
||||
MeasureRowColumn(row, SensorDisplayColumn.Max);
|
||||
row.DirtyColumns = SensorDisplayColumn.None;
|
||||
}
|
||||
|
||||
foreach (SensorTreeItemViewModel child in item.Children)
|
||||
MeasureColumnWidths(child, depth + 1, ref sensorWidth, ref valueWidth, ref minWidth, ref maxWidth);
|
||||
_dirtyColumns = SensorDisplayColumn.None;
|
||||
}
|
||||
|
||||
private void MeasureDirtyRows()
|
||||
{
|
||||
SensorDisplayColumn dirtyColumns = _dirtyColumns;
|
||||
foreach (SensorDisplayColumn column in EnumerateColumns(dirtyColumns))
|
||||
{
|
||||
bool shouldRecalculate = false;
|
||||
int columnIndex = GetColumnIndex(column);
|
||||
|
||||
foreach (RowMeasurement row in _rows)
|
||||
{
|
||||
if ((row.DirtyColumns & column) == 0)
|
||||
continue;
|
||||
|
||||
double oldWidth = row.ColumnWidths[columnIndex];
|
||||
double newWidth = GetRowColumnWidth(row, column);
|
||||
row.ColumnWidths[columnIndex] = newWidth;
|
||||
|
||||
if (newWidth > _measuredColumnWidths[columnIndex])
|
||||
{
|
||||
_measuredColumnWidths[columnIndex] = newWidth;
|
||||
}
|
||||
else if (oldWidth >= _measuredColumnWidths[columnIndex] - 0.01 && newWidth < oldWidth)
|
||||
{
|
||||
shouldRecalculate = true;
|
||||
}
|
||||
|
||||
row.DirtyColumns &= ~column;
|
||||
}
|
||||
|
||||
if (shouldRecalculate)
|
||||
RecalculateMeasuredColumnWidth(column);
|
||||
}
|
||||
|
||||
_dirtyColumns = SensorDisplayColumn.None;
|
||||
foreach (RowMeasurement row in _rows)
|
||||
_dirtyColumns |= row.DirtyColumns;
|
||||
}
|
||||
|
||||
private void SetHeaderColumnWidths()
|
||||
{
|
||||
_measuredColumnWidths[0] = MeasureText("Sensor", true) + SensorColumnPadding;
|
||||
_measuredColumnWidths[1] = _viewModel.ShowValueColumn ? MeasureText("Value", true) + ValueColumnPadding : 0;
|
||||
_measuredColumnWidths[2] = _viewModel.ShowMinColumn ? MeasureText("Min", true) + ValueColumnPadding : 0;
|
||||
_measuredColumnWidths[3] = _viewModel.ShowMaxColumn ? MeasureText("Max", true) + ValueColumnPadding : 0;
|
||||
}
|
||||
|
||||
private void MeasureRowColumn(RowMeasurement row, SensorDisplayColumn column)
|
||||
{
|
||||
int columnIndex = GetColumnIndex(column);
|
||||
double width = GetRowColumnWidth(row, column);
|
||||
row.ColumnWidths[columnIndex] = width;
|
||||
_measuredColumnWidths[columnIndex] = Math.Max(_measuredColumnWidths[columnIndex], width);
|
||||
}
|
||||
|
||||
private void RecalculateMeasuredColumnWidth(SensorDisplayColumn column)
|
||||
{
|
||||
int columnIndex = GetColumnIndex(column);
|
||||
SetHeaderColumnWidth(column);
|
||||
|
||||
foreach (RowMeasurement row in _rows)
|
||||
_measuredColumnWidths[columnIndex] = Math.Max(_measuredColumnWidths[columnIndex], row.ColumnWidths[columnIndex]);
|
||||
}
|
||||
|
||||
private void SetHeaderColumnWidth(SensorDisplayColumn column)
|
||||
{
|
||||
int columnIndex = GetColumnIndex(column);
|
||||
_measuredColumnWidths[columnIndex] = column switch
|
||||
{
|
||||
SensorDisplayColumn.Sensor => MeasureText("Sensor", true) + SensorColumnPadding,
|
||||
SensorDisplayColumn.Value => _viewModel.ShowValueColumn ? MeasureText("Value", true) + ValueColumnPadding : 0,
|
||||
SensorDisplayColumn.Min => _viewModel.ShowMinColumn ? MeasureText("Min", true) + ValueColumnPadding : 0,
|
||||
SensorDisplayColumn.Max => _viewModel.ShowMaxColumn ? MeasureText("Max", true) + ValueColumnPadding : 0,
|
||||
_ => 0
|
||||
};
|
||||
}
|
||||
|
||||
private double GetRowColumnWidth(RowMeasurement row, SensorDisplayColumn column)
|
||||
{
|
||||
if (row.Item.RowVisibility != Visibility.Visible)
|
||||
return 0;
|
||||
|
||||
return column switch
|
||||
{
|
||||
SensorDisplayColumn.Sensor => MeasureText(row.Item.Text) + SensorColumnPadding + row.Depth * TreeIndentWidth,
|
||||
SensorDisplayColumn.Value => _viewModel.ShowValueColumn ? MeasureText(row.Item.Value) + ValueColumnPadding : 0,
|
||||
SensorDisplayColumn.Min => _viewModel.ShowMinColumn ? MeasureText(row.Item.Min) + ValueColumnPadding : 0,
|
||||
SensorDisplayColumn.Max => _viewModel.ShowMaxColumn ? MeasureText(row.Item.Max) + ValueColumnPadding : 0,
|
||||
_ => 0
|
||||
};
|
||||
}
|
||||
|
||||
private bool UpdateDisplayedColumnWidths()
|
||||
{
|
||||
double deviceColumnWidth = NormalizeDeviceColumnWidth(_measuredColumnWidths[0]);
|
||||
if (!_settled)
|
||||
deviceColumnWidth = Math.Max(deviceColumnWidth, _stableDeviceColumnWidth);
|
||||
|
||||
double[] widths =
|
||||
[
|
||||
deviceColumnWidth,
|
||||
Math.Ceiling(_measuredColumnWidths[1]),
|
||||
Math.Ceiling(_measuredColumnWidths[2]),
|
||||
Math.Ceiling(_measuredColumnWidths[3])
|
||||
];
|
||||
|
||||
bool changed = false;
|
||||
for (int i = 0; i < _columnWidths.Length; i++)
|
||||
{
|
||||
if (Math.Abs(_columnWidths[i] - widths[i]) < 0.01)
|
||||
continue;
|
||||
|
||||
_columnWidths[i] = widths[i];
|
||||
changed = true;
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
private static IEnumerable<SensorDisplayColumn> EnumerateColumns(SensorDisplayColumn columns)
|
||||
{
|
||||
if ((columns & SensorDisplayColumn.Sensor) != 0)
|
||||
yield return SensorDisplayColumn.Sensor;
|
||||
if ((columns & SensorDisplayColumn.Value) != 0)
|
||||
yield return SensorDisplayColumn.Value;
|
||||
if ((columns & SensorDisplayColumn.Min) != 0)
|
||||
yield return SensorDisplayColumn.Min;
|
||||
if ((columns & SensorDisplayColumn.Max) != 0)
|
||||
yield return SensorDisplayColumn.Max;
|
||||
}
|
||||
|
||||
private static int GetColumnIndex(SensorDisplayColumn column)
|
||||
{
|
||||
return column switch
|
||||
{
|
||||
SensorDisplayColumn.Sensor => 0,
|
||||
SensorDisplayColumn.Value => 1,
|
||||
SensorDisplayColumn.Min => 2,
|
||||
SensorDisplayColumn.Max => 3,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(column), column, null)
|
||||
};
|
||||
}
|
||||
|
||||
private double MeasureText(string text, bool bold = false)
|
||||
@@ -206,7 +403,7 @@ internal sealed class SensorColumnMeasurer
|
||||
|
||||
private void RecordDeviceColumnWidthIfChanged()
|
||||
{
|
||||
if (!_settled || _rowGrids.Count == 0)
|
||||
if (!_settled || _rows.Count == 0)
|
||||
return;
|
||||
|
||||
double measuredWidth = NormalizeDeviceColumnWidth(_columnWidths[0]);
|
||||
@@ -216,6 +413,26 @@ internal sealed class SensorColumnMeasurer
|
||||
return;
|
||||
|
||||
_viewModel.Settings.SetValue(DeviceColumnWidthSetting, measuredWidth);
|
||||
_startupTrace.Mark("MainWindow.RecordDeviceColumnWidth", FormattableString.Invariant($"width={measuredWidth:F0}, rows={_rowGrids.Count}"));
|
||||
_startupTrace.Mark("MainWindow.RecordDeviceColumnWidth", FormattableString.Invariant($"width={measuredWidth:F0}, rows={_rows.Count}"));
|
||||
}
|
||||
|
||||
private sealed class RowMeasurement
|
||||
{
|
||||
public RowMeasurement(SensorTreeItemViewModel item, Grid grid, int depth)
|
||||
{
|
||||
Item = item;
|
||||
Grid = grid;
|
||||
Depth = depth;
|
||||
}
|
||||
|
||||
public SensorTreeItemViewModel Item { get; }
|
||||
|
||||
public Grid Grid { get; }
|
||||
|
||||
public int Depth { get; }
|
||||
|
||||
public double[] ColumnWidths { get; } = new double[4];
|
||||
|
||||
public SensorDisplayColumn DirtyColumns { get; set; } = SensorDisplayColumn.All;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user