Skip to content

Commit

Permalink
Merge pull request #21 from cwollenhaupt/develop
Browse files Browse the repository at this point in the history
  • Loading branch information
cwollenhaupt authored Mar 7, 2023
2 parents 0c35125 + 523c372 commit 613fcce
Show file tree
Hide file tree
Showing 11 changed files with 4,725 additions and 21 deletions.
99 changes: 99 additions & 0 deletions C#/fpCefSharp/fpEventSubscriber.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;

namespace fpCefSharp
{
/// <summary>
/// FoxPro interop access to .NET events. Handles all events of a source object for subsequent retrieval by a FoxPro client.
/// </summary>
/// <remarks>For a FoxPro program to be notified of events, it should use `wwDotNetBridge.InvokeMethodAsync` to call
/// <see cref="WaitForEvent"/>. When <see cref="WaitForEvent"/> asynchronously completes, the FoxPro program should
/// handle the event it returns and then call <see cref="WaitForEvent"/> again to wait for the next event.
/// The FoxPro class `EventSubscription`, which is returned by `SubscribeToEvents`, encapsulates this async wait loop.
///
/// NOTE: This code is based on wwDotNetBridge from Rick Strahl and Edward Brey. It's the same code as in pull request
/// #26 for wwDotNetBridge, the class name aside. This code can be removed after the pull request is merged and
/// the FoxPro code has been updated to use the built-in mechanism of wwDotNetBridge.
/// </remarks>
public class fpEventSubscriber : IDisposable
{
private readonly object _source;
private readonly List<DelegateInfo> _eventHandlers = new List<DelegateInfo>();
private readonly ConcurrentQueue<fpRaisedEvent> _raisedEvents = new ConcurrentQueue<fpRaisedEvent>();
private TaskCompletionSource<fpRaisedEvent> _completion = new TaskCompletionSource<fpRaisedEvent>();

public fpEventSubscriber(object source, String prefix = "", dynamic vfp = null)
{
// Indicates that initially the client is not waiting.
_completion.SetResult(null);

// For each event, adds a handler that calls QueueInteropEvent.
_source = source;
foreach (var ev in source.GetType().GetEvents())
{
// handler is a PRIVATE variable defined in EventSubscription.Setup().
Boolean hasMethod = vfp?.Eval($"PEMSTATUS(m.handler, '{prefix}{ev.Name}', 5)");
if (!hasMethod)
continue;

var eventParams = ev.EventHandlerType.GetMethod("Invoke").GetParameters().Select(p => Expression.Parameter(p.ParameterType)).ToArray();
var eventHandlerLambda = Expression.Lambda(ev.EventHandlerType,
Expression.Call(
instance: Expression.Constant(this),
method: typeof(fpEventSubscriber).GetMethod(nameof(QueueInteropEvent), BindingFlags.NonPublic | BindingFlags.Instance),
arg0: Expression.Constant(ev.Name),
arg1: Expression.NewArrayInit(typeof(object), eventParams.Select(p => Expression.Convert(p, typeof(object))))),
eventParams);
var eventHandler = eventHandlerLambda.Compile();
ev.AddEventHandler(source, eventHandler);
_eventHandlers.Add(new DelegateInfo(eventHandler, ev));
}
}

class DelegateInfo
{
public DelegateInfo(Delegate handler, EventInfo eventInfo)
{
Delegate = handler;
EventInfo = eventInfo;
}

public Delegate Delegate { get; }
public EventInfo EventInfo { get; }
}

public void Dispose()
{
foreach (var item in _eventHandlers)
item.EventInfo.RemoveEventHandler(_source, item.Delegate);
_completion.TrySetCanceled();
}

private void QueueInteropEvent(string name, object[] parameters)
{
var interopEvent = new fpRaisedEvent { Name = name, Params = parameters };
if (!_completion.TrySetResult(interopEvent))
_raisedEvents.Enqueue(interopEvent);
}

/// <summary>
/// Waits until an event is raised, or returns immediately if a queued event is available.
/// </summary>
/// <returns>The next event, or null if this subscriber has been disposed.</returns>
public fpRaisedEvent WaitForEvent()
{
if (_raisedEvents.TryDequeue(out var interopEvent)) return interopEvent;
_completion = new TaskCompletionSource<fpRaisedEvent>();
var task = _completion.Task;

task.Wait();

return task.IsCanceled ? null : task.Result;
}
}
}
8 changes: 8 additions & 0 deletions C#/fpCefSharp/fpRaisedEvent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace fpCefSharp
{
public class fpRaisedEvent
{
public string Name { get; internal set; }
public object[] Params { get; internal set; }
}
}
41 changes: 41 additions & 0 deletions Demos/Demo_Events.PRG
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
*========================================================================================
* Respond to events in the browser
*========================================================================================

LOCAL loConfig, loEvents
loConfig = CREATEOBJECT("MyHandler")
loEvents = CreateObject("MyEvents")

Local loForm
DO FORM ShowHtml WITH "https://myApp/index.html", m.loConfig Name loForm
loEvents.AddProperty ("oCefSharpBrowser", m.loForm.oCefSharpBrowser)
loForm.oCefSharpBrowser.HandleEvents (m.loEvents)

DEFINE CLASS MyHandler as Custom

index_html = ""

PROCEDURE Init
TEXT TO This.index_html NoShow
<html>
<script>
function changeTitle () {
window.top.document.title = new Date().toLocaleString();
}
</script>
<button onclick="changeTitle();">Change title</button>
</html>
ENDTEXT

Procedure TitleChangedEvent (m.tcTitle)
MessageBox("TitleChangedEvent: " + m.tcTitle)

ENDDEFINE


Define Class MyEvents as Custom

Procedure OnTitleChanged (toSender, toEventArgs)
This.oCefSharpBrowser.HandleTitleChangedEvent (m.toSender, m.toEventArgs)

EndDefine
Loading

0 comments on commit 613fcce

Please sign in to comment.