Come posso creare un componente aggiuntivo di automazione Excel in tempo reale in C# usando RTDServer?

StackOverflow https://stackoverflow.com/questions/5397607

Domanda

Mi è stato assegnato il compito di scrivere un componente aggiuntivo di Automazione Excel in tempo reale in C# usando RTDServer per lavoro. Ho fatto molto affidamento sulla consapevolezza che mi sono imbattuto in Stack Overflow. Ho deciso di esprimere i miei ringraziamenti scrivendo un come documentare che unisce tutto ciò che ho imparato. Kenny Kerr's Server Excel RTD: implementazione C# minima L'articolo mi ha aiutato a iniziare. Ho trovato commenti di Mike Rosenblum e Govert particolarmente utile.

È stato utile?

Soluzione

(In alternativa all'approccio descritto di seguito è necessario considerare l'uso Excel-DNA. Excel-DNA consente di creare un server RTD senza registrazione. La registrazione COM richiede privilegi amministrativi che possono portare a mal di testa. Detto questo, il codice seguente funziona bene.)

Per creare un componente aggiuntivo di automazione Excel in tempo reale in C# usando RTDServer:

1) Crea un progetto di libreria di classe C# in Visual Studio e inserisci quanto segue:

using System;
using System.Threading;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using Microsoft.Office.Interop.Excel;

namespace StackOverflow
{
    public class Countdown
    {
        public int CurrentValue { get; set; }
    }

    [Guid("EBD9B4A9-3E17-45F0-A1C9-E134043923D3")]
    [ProgId("StackOverflow.RtdServer.ProgId")]
    public class RtdServer : IRtdServer
    {
        private readonly Dictionary<int, Countdown> _topics = new Dictionary<int, Countdown>();
        private Timer _timer;

        public int ServerStart(IRTDUpdateEvent rtdUpdateEvent)
        {
            _timer = new Timer(delegate { rtdUpdateEvent.UpdateNotify(); }, null, TimeSpan.Zero, TimeSpan.FromSeconds(5));
            return 1;
        }

        public object ConnectData(int topicId, ref Array strings, ref bool getNewValues)
        {
            var start = Convert.ToInt32(strings.GetValue(0).ToString());
            getNewValues = true;

            _topics[topicId] = new Countdown { CurrentValue = start };

            return start;
        }

        public Array RefreshData(ref int topicCount)
        {
            var data = new object[2, _topics.Count];
            var index = 0;

            foreach (var entry in _topics)
            {
                --entry.Value.CurrentValue;
                data[0, index] = entry.Key;
                data[1, index] = entry.Value.CurrentValue;
                ++index;
            }

            topicCount = _topics.Count;

            return data;
        }

        public void DisconnectData(int topicId)
        {
            _topics.Remove(topicId);
        }

        public int Heartbeat() { return 1; }

        public void ServerTerminate() { _timer.Dispose(); }

        [ComRegisterFunctionAttribute]
        public static void RegisterFunction(Type t)
        {
            Microsoft.Win32.Registry.ClassesRoot.CreateSubKey(@"CLSID\{" + t.GUID.ToString().ToUpper() + @"}\Programmable");
            var key = Microsoft.Win32.Registry.ClassesRoot.OpenSubKey(@"CLSID\{" + t.GUID.ToString().ToUpper() + @"}\InprocServer32", true);
            if (key != null)
                key.SetValue("", System.Environment.SystemDirectory + @"\mscoree.dll", Microsoft.Win32.RegistryValueKind.String);
        }

        [ComUnregisterFunctionAttribute]
        public static void UnregisterFunction(Type t)
        {
            Microsoft.Win32.Registry.ClassesRoot.DeleteSubKey(@"CLSID\{" + t.GUID.ToString().ToUpper() + @"}\Programmable");
        }
    }
}

2) Fare clic con il tasto destro sul progetto e aggiungere> Nuovo elemento ...> Classe di installazione. Passa alla visualizzazione del codice e inserisci quanto segue:

using System.Collections;
using System.ComponentModel;
using System.Diagnostics;
using System.Runtime.InteropServices;

namespace StackOverflow
{
    [RunInstaller(true)]
    public partial class RtdServerInstaller : System.Configuration.Install.Installer
    {
        public RtdServerInstaller()
        {
            InitializeComponent();
        }

        [System.Security.Permissions.SecurityPermission(System.Security.Permissions.SecurityAction.Demand)]
        public override void Commit(IDictionary savedState)
        {
            base.Commit(savedState);

            var registrationServices = new RegistrationServices();
            if (registrationServices.RegisterAssembly(GetType().Assembly, AssemblyRegistrationFlags.SetCodeBase))
                Trace.TraceInformation("Types registered successfully");
            else
                Trace.TraceError("Unable to register types");
        }

        [System.Security.Permissions.SecurityPermission(System.Security.Permissions.SecurityAction.Demand)]
        public override void Install(IDictionary stateSaver)
        {
            base.Install(stateSaver);

            var registrationServices = new RegistrationServices();
            if (registrationServices.RegisterAssembly(GetType().Assembly, AssemblyRegistrationFlags.SetCodeBase))
                Trace.TraceInformation("Types registered successfully");
            else
                Trace.TraceError("Unable to register types");
        }

        public override void Uninstall(IDictionary savedState)
        {
            var registrationServices = new RegistrationServices();
            if (registrationServices.UnregisterAssembly(GetType().Assembly))
                Trace.TraceInformation("Types unregistered successfully");
            else
                Trace.TraceError("Unable to unregister types");

            base.Uninstall(savedState);
        }
    }
}

3) Fare clic con il pulsante destro del mouse sulle proprietà del progetto e dare il controllo a quanto segue: Applicazione> Informazioni sull'assemblaggio ...> Rendi il gruppo visibile e build> Registrati per COM Interop

3.1) Fare clic con il tasto destro sul progetto Aggiungi riferimento ...> Scheda .NET> Microsoft.Office.Interop.excel

4) Build Solution (F6)

5) Esegui Excel. Vai a Opzioni Excel> Aggiungenti> Gestisci componenti aggiuntivi Excel> Automazione e seleziona "StackOverflow.Rtdserver"

6) Immettere "= rtd (" stackoverflow.rtdserver.progid ",, 200)" in una cella.

7) Attraversa le dita e spero che funzioni!

Altri suggerimenti

Chiamare UpdateNotify dal thread del timer causerà infine strani errori o disconnessioni da Excel.

Il metodo updateNotify () deve essere chiamato solo dallo stesso thread che chiama ServerStart (). Non è documentato nell'aiuto di RTDServer, ma è una restrizione di com.

La correzione è semplice. Utilizzare DispatchersynchronizationContext per acquisire il thread che chiama ServerStart e usalo per spedire le chiamate per aggiornare:

public class RtdServer : IRtdServer
{
    private IRTDUpdateEvent _rtdUpdateEvent;
    private SynchronizationContext synchronizationContext;

    public int ServerStart( IRTDUpdateEvent rtdUpdateEvent )
    {
        this._rtdUpdateEvent = rtdUpdateEvent;
        synchronizationContext = new DispatcherSynchronizationContext();
        _timer = new Timer(delegate { PostUpdateNotify(); }, null, TimeSpan.Zero, TimeSpan.FromSeconds(5));
        return 1;
    }


    // Notify Excel of updated results
    private void PostUpdateNotify()
    {
        // Must only call rtdUpdateEvent.UpdateNotify() from the thread that calls ServerStart.
        // Use synchronizationContext which captures the thread dispatcher.
        synchronizationContext.Post( delegate(object state) { _rtdUpdateEvent.UpdateNotify(); }, null);
    }

    // etc
} // end of class

Seguire le due risposte precedenti per il server RTD ha funzionato per me. Tuttavia mi sono imbattuto in un problema quando ho su una macchina X64 che esegue Excel X64. Nel mio caso, fino a quando non ho cambiato la "piattaforma target" del progetto a X64, Excel ha sempre mostrato #n/a.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top