public class RTDArrayAddIn : IExcelAddIn
{
// connection to server
private static Timer _timer;
private readonly List<ExcelRtdServer.Topic> topics = new List<ExcelRtdServer.Topic>();
private Dictionary<string, Timer> endpointToTimer = new Dictionary<string, Timer>();
public void AutoOpen()
{
// setup support for RTD functions
ExcelAsyncUtil.Initialize();
// setup error handler
ExcelIntegration.RegisterUnhandledExceptionHandler(ex => "!!! EXCEPTION: " + ex.ToString());
// increase RTD refresh rate since the 2 seconds default is too slow (move as setting to ribbon later)
object app;
object rtd;
app = ExcelDnaUtil.Application;
rtd = app.GetType().InvokeMember("RTD", BindingFlags.GetProperty, null, app, null);
rtd.GetType().InvokeMember("ThrottleInterval", BindingFlags.SetProperty, null, rtd, new object[] { 100 });
}
public void AutoClose()
{
ExcelAsyncUtil.Uninitialize();
}
// event boilerplate stuff
public delegate void ValueSentHandler(TableValueSentEventArgs args);
public static event ValueSentHandler OnValueSent;
public class TableValueSentEventArgs : EventArgs
{
public object[,] Value { get; private set; }
public TableValueSentEventArgs(object[,] Value)
{
this.Value = Value;
}
}
// this gets called by the server if there is a new value
public static void UpdateArrayValue(object[,] x)
{
// invert method call from WCF into event for Rx
if (OnValueSent != null)
{
OnValueSent(new TableValueSentEventArgs(x));
}
}
private static void Start()
{
_timer.Change(1500, 1500);
}
private static void Stop()
{
_timer.Change(-1, -1);
}
private static void Callback(object o)
{
Stop();
UpdateArrayValue(GetNewValues());
Start();
}
private static object[,] GetNewValues()
{
Random random = new Random(Guid.NewGuid().GetHashCode());
int rows = random.Next(3, 15);
int cols = random.Next(3, 15);
object[,] matrix = new object[rows, cols];
//
// fill matrix with random numbers
for (int i = 0; i < rows; i++)
{
for (int j = 0; j < cols; j++)
{
matrix[i, j] = random.NextDouble();
}
}
return matrix;
}
// this function accepts an observer and calls OnNext on it every time the OnValueSent event is raised
// this is the part that chains individual events into streams
// it returns an IDisposable that can be called after the stream is complete
static Func<IObserver<object[,]>, IDisposable> EventToObservable = observer =>
{
OnValueSent += d => observer.OnNext(d.Value); // when a new value was sent, call OnNext on the Observers.
return Disposable.Empty; // we'll keep listening until Excel is closed, there's no end to this stream
};
// this calls the wrapper to actually put it all together
[ExcelFunction("Gets realtime values from server")]
public static object GetArrayValues(string endpointAndQuery)
{
if (_timer == null)
{
_timer = new Timer(Callback); //, null, TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(1));
Start();
}
// a delegate that creates an observable over Event2Observable
Func<IObservable<object[,]>> funcArray2 = () => Observable.Create<object[,]>(EventToObservable);
// pass that to Excel wrapper
var result = RxExcel.Observe("GetArrayValues", null, funcArray2);
if (result.Equals(ExcelError.ExcelErrorNA)) return result;
return XlCall.Excel(XlCall.xlUDF, "Resize", result);
}