This is possibly a dumb question, but my users are surprised that my observables dirty the sheet when they tick.
Put another way, my users are surprised by this workflow:
- New sheet
- Put =Tick() in a cell somewhere
- Let it tick a few times
- Save the worksheet
- Let it tick a few more times
- Close the sheet
Expected behavior: Sheet closes
Actual behavior: Excel prompts to save the sheet before closing
My users find this surprising because *they* didn't change anything between steps 4 and 6. The users feel that the formula they entered is the sheet's "actual" content; the fact that that formula is yielding changing dynamic array values over time is felt to be ephemeral and shouldn't be part of the nature of whether the sheet has changed and needs to be saved.
My silly little test program is below. Do you have an opinion on which of the below is closest to the truth
- This is just the way things are.
- There is an easy workaround
- I am doing things the wrong way
Thank you,
Corey
using System.Diagnostics;
using ExcelDna.Integration;
namespace ExcelRepro;
public static class MyFunctions {
[ExcelFunction(Description = "Ticking value")]
public static object Tick() {
return ExcelAsyncUtil.Observe("Tick", Array.Empty<object>(), () => new MyObservable());
}
}
public class MyObservable : IObservable<object> {
private readonly object _sync = new();
private readonly HashSet<IObserver<object>> _observers = new();
private int _value = 0;
private CancellationTokenSource _cts = new();
public IDisposable Subscribe(IObserver<object> observer) {
lock (_sync) {
_observers.Add(observer);
if (_observers.Count == 1) {
var token = _cts.Token;
Task.Run(() => DoTick(token), token);
}
}
return new ActionAsDisposable(() => {
lock (_sync) {
_observers.Remove(observer);
if (_observers.Count == 0) {
_cts.Cancel();
_cts = new CancellationTokenSource();
}
}
});
}
private async Task DoTick(CancellationToken token) {
try {
var integrationAssembly = typeof(ExcelDnaUtil).Assembly;
var version = integrationAssembly.GetName().Version.ToString();
while (true) {
await Task.Delay(TimeSpan.FromSeconds(1), token);
IObserver<object>[] copyOfObservers;
int currentTick;
lock (_sync) {
currentTick = _value++;
copyOfObservers = _observers.ToArray();
}
var result = new object[2, 2];
result[0, 0] = "version =>";
result[0, 1] = version;
result[1, 0] = currentTick;
result[1, 1] = "<= tick";
foreach (var observer in copyOfObservers) {
observer.OnNext(result);
}
}
} catch (Exception e) {
Debug.WriteLine($"Caught {e}");
}
}
}
public class ActionAsDisposable(Action action) : IDisposable {
public void Dispose() {
action();
}
}