2013-04-22 7 views
0

COM (홈 메이드) 타이머를 실행하는 동안 Excel에서 오류가 발생합니다. 기본적으로 Excel은 타이머를 인스턴스화하고 초기화 한 다음 시작합니다. 그런 다음 Timer는 Excel에서 catch하는 이벤트를 발생시키는 모든 X 밀리 초를 틱 (꽤 표준적인 작업)합니다. 매 초마다 빠른 속도로 체크하지 않기 때문에 Excel 자체를 타이머로 사용하지 않습니다. (내 용도로는 너무 길다)Excel 스레드로 COM 개체를 실행할 때 충돌 발생

내 문제는 이벤트가 발생하는 동안 스프레드 시트를 클릭 한 상태로 타이머, 엑셀 꽤 나쁜 충돌. 불행히도 사용자는 스프레드 시트를 클릭하고 타이머가 실행되는 동안 수정할 필요가 있습니다.

나는 나의 타이머에서 IMessageFilter 인터페이스를 사용할 수있는 곳을 보았다. 이것은 이벤트가 발생했을 때 Excel이 사용 중이면 타이머가이를 볼 수 있고 이에 따라 행동 할 수 있어야합니다. 그러나 제대로 구현할 수 없었습니다.

누군가 나를 도와 줄 수 있다면 좋을 것입니다. 여기에, 나는에서 WithEvents ExcelTimer.ExcelTimer 객체를 전달 싱글이 Excel에서

내 싱글의 코드입니다 : 여기

소스 내가 사용하고 코드 여기

Option Explicit 
Private Const m_sMODULE_NAME = "cTimerManager" 

Public WithEvents oCsharpTimer As ExcelTimer.ExcelTimer 

Private Sub Class_Initialize() 
    Set oCsharpTimer = New ExcelTimer.ExcelTimer 

    'The following two lines are called dynamically from somewhere else 
    'normally but for simplicity of my post I have put them here 
    oCsharpTimer.Initialize 500 
    oCsharpTimer.StartTimer 
End Sub 

Private Sub oCsharpTimer_TimeTickEvt(ByVal o As Variant, ByVal Time As String) 
    Const sPROCEDURE_NAME = "oCsharpTimer_TimeTickEvt" 
    On Error GoTo ErrorHandler 

    '"Send" confirmation with time to the COM object. 
    oCsharpTimer.TimeReceived Time 

    'Do whatever I wanna do when the event is trigger 

CleanUp: 
    Exit Sub 

ErrorHandler: 
    'My Error handling structure 
    If ProcessError(m_sMODULE_NAME, sPROCEDURE_NAME, Err) Then 
     Stop 
     Resume 
    Else 
     Resume Next 
    End If 
End Sub 

가있다

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using System.Threading; 
using System.Runtime.InteropServices; 
using System.Windows.Forms; 


namespace ExcelTimer 
{ 
    public delegate void EventTimeRaiser(object o, string Time); 

    //COM Interface 
    public interface ICOMExcelTimer 
    { 
     [DispId(1)]   
     void StartTimer(); 
     [DispId(2)] 
     void StopTimer(); 
     [DispId(3)] 
     void Initialize(int TimeInMilliseconds); 
     [DispId(4)] 
     void TimeReceived(string ReceivedTime); 
    } 

    //Event interface 
    [InterfaceType(ComInterfaceType.InterfaceIsIDispatch)] 
    public interface ICOMExcelTimerEvent 
    { 
     [DispId(1000)] 
     void TimeTickEvt(object o, string Time); 
    } 


    [ClassInterface(ClassInterfaceType.None), 
    ComSourceInterfaces(typeof(ICOMExcelTimerEvent)), 
    ComVisible(true)] 
    public class ExcelTimer : ICOMExcelTimer, IMessageFilter 
    { 
     private event EventTimeRaiser TimeTickEvt; 
     private bool _started; 
     private bool _initialised; 
     private int _timeInMilliseconds; 
     private string _lastTimeReceived; 
     private Control _control; 
     private Thread _timerThread; 
     private IAsyncResult _AsynchronousResult; 

     [ComVisible(true)] 
     public void Initialize(int TimeInMilliSeconds) 
     { 
      //To be called by Excel to set which timer parameters it wants 
      _timeInMilliseconds = TimeInMilliSeconds; 
      _initialised = true; 

      //Make sure we clear the last confirmation received 
      //since we are re-initialising the object 
      _lastTimeReceived = ""; 
     } 


     [ComVisible(true)] 
     public void TimeReceived(string ReceivedTime) 
     { 
      //Store the last time received. Excel calls this function 
      _lastTimeReceived = ReceivedTime; 
     } 

     public ExcelTimer() 
     { 
      _lastTimeReceived = ""; 
     } 

     [ComVisible(true)] 
     //Start the Timer 
     public void StartTimer() 
     { 
      //If the timer has not been initialised just yet 
      if (!_initialised) 
      { 
       //Sends back an error message to Excel 
       TimeTickEvt(this, "Error: Timer Not Initialised"); 
       return; 
      } 

      try 
      { 
       //Start the timer 
       _timerThread = new Thread(new ThreadStart(TimeTicking)); 

       //Start the Thread 
       _started = true; 
       _timerThread.Start(); 
      } 
      catch (Exception ex) 
      { 
       System.IO.File.AppendAllText(@"C:\ErrorLog.txt", ex.Message + " - StartTimer - " + DateTime.Now.ToString("hh:mm:ss.f") + "\n"); 
      } 
     } 


     [ComVisible(true)] 
     //Stop the timer 
     public void StopTimer() 
     {    
      //Stop the Thread 
      _timerThread.Abort(); 
      //Change the status 
      _started = false; 
     } 

     private void TimeTicking() 
     { 
      string SentTime; 

      //As long as the timer is running 
      while (_started) 
      { 
       try 
       { 
        //Pause the timer for the right number of milliseconds 

        Thread.Sleep(_timeInMilliseconds); 

        SentTime = DateTime.Now.ToString("hh:mm:ss.ffff"); 

        //########### The CODE Errors Here when Excel is busy! ########### 
        //Raise an event for Excel to grab with the time that the thread finished the sleep at. 
        OnTimeTick(SentTime); 

        //_lastTimeReceived is used so that if the link between Excel and the Thread is broken the thread stops after sometimes 
        //if no confirmation was received from Excel. 

        //If no last time was received just yet, we setup the last time received to the sent time 
        if (_lastTimeReceived.Equals("")) 
        { 
         _lastTimeReceived = SentTime; 
        } 
        //If the last time received is older than 10 x TimeInMilliseconds (in Seconds) we stop the timer. 
        else if (Convert.ToDateTime(_lastTimeReceived).AddSeconds(_timeInMilliseconds * 10/1000) < Convert.ToDateTime(SentTime)) 
        { 
         OnTimeTick("Timer timed out. No Confirmation for more than " + _timeInMilliseconds * 10/1000 + " second(s)."); 

         //Stop the timer because the thread has not received a last time recently 
         _started = false; 

        } 
       } 
       catch (Exception ex) 
       { 
        System.IO.File.AppendAllText(@"C:\ErrorLog.txt", ex.Message + " - TimeTicking - " + DateTime.Now.ToString("hh:mm:ss.f") + "\n"); 
       } 

      } 
     } 

     protected virtual void OnTimeTick(string Time) 
     { 
      try 
      { 
       if (Time != null) 
       { 
        //Raise the event 
        TimeTickEvt(this, Time);      
       } 
      } 
      catch (Exception ex) 
      { 
       System.IO.File.AppendAllText(@"C:\ErrorLog.txt", ex.Message + " - OnTimeTick - " + DateTime.Now.ToString("hh:mm:ss.f") + "\n"); 
      } 
     }   
    } 
} 

답변

0

내가 C#을 코딩와 함께 당신을 도울 수는 없지만 1 초보다 더 정밀하게 Excel 및 타이머를 사용하는 것을 선호한다면, 이것은 수행 할 수 있습니다 내 COM 개체에 대한 코드 VBA는 :

Public Declare Function timeBeginPeriod Lib "winmm.dll" (ByVal uPeriod As Long) As Long

Public Declare Function timeGetTime Lib "winmm.dll"() As Long

그런 다음 원하는 경우, 밀리 초 정도의 시간을 반환하기 위해 서브 루틴에서 timeGetTime 기능을 사용할 수 있습니다. 이 때문에의

제공 : 귀하의 답변에 대한

http://www.excelforum.com/excel-programming-vba-macros/738087-using-milliseconds-in-vba.html?p=2748931&viewfull=1#post2748931

+0

감사합니다. 첫 번째 생각으로, 링크에 설명 된 접근법의 문제점은 타이머의 루프가 절전 모드 인 동안 (클릭, 수정 등) 사용자가 시트에 손을 대지 않는다는 것입니다. DoEvents). 내 용도로 큰 문제입니다. –

+0

당신은 userform을 추가하고, 그것의'.Visible = False'를 설정 한 다음,'.vowModeless'를 실행 중에 그것을 표시 할 수 있습니다. 이렇게하면 사용자가 루프 중에 워크 시트와 상호 작용할 수 있습니다. –

+0

문제는 Excel/VBA에서 C# 다중 스레드 개체를 사용하는 방법을 익히기위한 첫 번째 단계로이 작업을 수행했다는 것입니다. 다음 단계는 (C# 측면에서) 좀 더 복잡하고 Excel/VBA와의 상호 작용을 원활하고 효율적으로 만들 수 있는지 확인하고 싶었습니다. –