Skip to content

Commit

Permalink
Added three new demos:
Browse files Browse the repository at this point in the history
demo_basic.PRG
Demonstrates the simplest usage that displays an external web page

demo_evaluateScript.PRG
Uses the EvaluateScript function to read properties from the DOM and return them into Visual FoxPro

Demo_Events.PRG
Implements the TitleChanged event to respond to title changes due to navigation or setting the window.title property.

Added a new example form that provides a button to run code defined in the config object.
Implemented the reload method in the sample form.

Added the .NET event subscription mechanism from my customized version of wwDotNetBridge. CefSharp will now work with other versions of wwDotNetBridge as well.

Some demos missed include files. These have been added.

New versions of wwDotNetBridge return objects differently than before. Added code to handle the interface change for both, old versions and the current version of wwDotNetBridge.
  • Loading branch information
cwollenhaupt committed Mar 7, 2023
1 parent 85a0d0d commit 523c372
Show file tree
Hide file tree
Showing 11 changed files with 4,548 additions and 15 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 523c372

Please sign in to comment.