-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #21 from cwollenhaupt/develop
- Loading branch information
Showing
11 changed files
with
4,725 additions
and
21 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; } | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
Oops, something went wrong.