Skip to content

Commit 85ef6eb

Browse files
committed
Switch to a new internal function/event registration system
1 parent b0d0ead commit 85ef6eb

7 files changed

Lines changed: 462 additions & 103 deletions

File tree

examples/WpfControllerApp/MainWindow.xaml.cs

Lines changed: 45 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using WebUI;
77
using Windows.Win32;
88
using Windows.Win32.UI.Input.KeyboardAndMouse;
9+
using kvp=System.Collections.Generic.KeyValuePair<string,string>;
910

1011
namespace WpfControllerApp
1112
{
@@ -38,32 +39,67 @@ public WinDispatcher(Dispatcher dispatcher)
3839

3940
public void InvokeWithVoid(Action action) => dispatcher.Invoke(action);
4041
}
41-
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
42+
private async void MainWindow_Loaded(object sender, RoutedEventArgs e)
4243
{
4344
WebUI.Window.UseSpecificDispatcher = new WinDispatcher(Dispatcher);
4445

4546
window = new(new WebUI.WindowProperties { Width = 640, Height = 480, X = 800, Y = 50 });
4647
var evts = window.RegisterDefaultEventHandler();
47-
evts.OnDisconnect += (_) =>
48+
49+
//evts.OnDisconnect += (_) =>
50+
//{
51+
// window = null;
52+
// Dispatcher.InvokeAsync(() => Close());
53+
//};
54+
55+
window.InitOurBridge();
56+
//window.Show("<html><head><script src='webui.js'></script></head><body>Hi Bob</body></html>");
57+
//await Task.Delay(2000);
58+
//btnDevTools_Click(null, null);
59+
// await Task.Delay(2000);
60+
61+
evts.OnConnect += (window) =>
4862
{
49-
window = null;
50-
Dispatcher.InvokeAsync(() => Close());
63+
//this.window = window;
64+
NewConnect();
5165
};
66+
67+
68+
5269
window.Show("index.html");
5370

54-
window.RegisterOnClick(JSClickedButton, "TestButton");
55-
window.RegisterBoundFunction(() => MyTest_function);
71+
//window.RegisterBoundFunction(() => MyTest_function);
5672
}
73+
private async void NewConnect()
74+
{
75+
window.SetDebug();
76+
window.AddEventListener(JSClickedButton, CommonEventTypes.click, "TestButton");
77+
window.RegisterBoundFunction("MyTest_function", RawMyTestCalled);
78+
window.AddEventListener(JSDblClickAny,CommonEventTypes.dblclick,"window",null,new kvp[]{new("button","button"), new("X Pos","clientX"), new("Y Pos","clientY") });
79+
80+
}
81+
82+
private void JSDblClickAny(DomEvent evt)
83+
{
84+
LogItem($"DoubleClick event: " + Newtonsoft.Json.JsonConvert.SerializeObject(evt, Newtonsoft.Json.Formatting.Indented).Replace("\n","\t\n"));
85+
}
86+
87+
private Task<string> RawMyTestCalled(string serialized)
88+
{
89+
var args = Newtonsoft.Json.JsonConvert.DeserializeObject<string[]>(serialized);
90+
return MyTest_function(args[0], args[1], double.Parse(args[2]));
91+
}
92+
5793
private async Task<string> MyTest_function(String arg1, String arg2, double arg3)
5894
{
5995
var str = $"I am called: {arg1}({arg1.GetType()}) and {arg2}({arg2.GetType()}) {arg3}({arg3.GetType()})";
6096
LogItem(str);
6197
await Task.Delay(500);
6298
return "Nice Call " + arg1;
6399
}
64-
private void JSClickedButton(String elem)
100+
private void JSClickedButton(DomEvent evt)
65101
{
66-
LogItem($"Got a click from JS for: {elem}");
102+
LogItem($"Got a click from JS for: {evt.originalTargetId}");
67103
}
68104

69105
private void LogItem(object val, [CallerArgumentExpression(nameof(val))] string expression = "unknown")
@@ -74,7 +110,7 @@ private void LogItem(object val, [CallerArgumentExpression(nameof(val))] string
74110
double or decimal or float or char or bool or int or string => val.ToString(),
75111
_ => "Serialized as: " + Newtonsoft.Json.JsonConvert.SerializeObject(val, Newtonsoft.Json.Formatting.None)
76112
};
77-
txtLog.Text += $"{expression} => {valStr} ({val.GetType()})\n";
113+
txtLog.Text += $"{expression} => {valStr.Replace("\r","")} ({val.GetType()})\n";
78114
}
79115
private async void btnTest1_Click(object sender, RoutedEventArgs e)
80116
{

examples/WpfControllerApp/index.html

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
44
<head>
55
<meta charset="utf-8" />
6-
<script src="webui.js"></script>
6+
<script src="webui.js"></script>
7+
<script src="webui_net.js"></script>
78
<title>JS Interop</title>
89
<script>
910

@@ -25,7 +26,7 @@
2526
const getComplexObj = () => complexObj;
2627

2728
async function btn2Clicked() {
28-
let res = await webui.call("MyTest_function", "an", "arg", 12345.78);
29+
let res = await MyTest_function("1st JS Arg Passed to C#", "arg", 12345.78);
2930
alert("You returned: " + res);
3031
}
3132

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
using System;
2+
using Newtonsoft.Json;
3+
4+
namespace WebUI
5+
{
6+
public class MilisecondEpochConverter : RandomNumberTimeConverter<long, DateTime>
7+
{
8+
private static readonly DateTime _epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
9+
10+
public MilisecondEpochConverter() : base(long.Parse) { }
11+
12+
public static DateTime ParseLong(long val) => _epoch.AddMilliseconds(val);
13+
14+
protected override DateTime ParseValue(long val) => ParseLong(val);
15+
16+
protected override string WriteValue(DateTime when)
17+
{
18+
return ((long)(when - _epoch).TotalMilliseconds).ToString();
19+
}
20+
public override bool CanConvert(Type objectType)
21+
{
22+
if (objectType == typeof(DateTime) || objectType == typeof(DateTime?))
23+
{
24+
return true;
25+
}
26+
27+
if (objectType == typeof(DateTimeOffset) || objectType == typeof(DateTimeOffset?))
28+
{
29+
return true;
30+
}
31+
32+
return false;
33+
}
34+
}
35+
public abstract class RandomNumberTimeConverter<T, SOURCE_TYPE> : JsonConverter
36+
{
37+
protected RandomNumberTimeConverter(Func<String, T> ParseNotNullStringHandler)
38+
{
39+
this.ParseNotNullStringHandler = ParseNotNullStringHandler;
40+
}
41+
Func<String, T> ParseNotNullStringHandler;
42+
43+
protected abstract SOURCE_TYPE ParseValue(T val);
44+
protected abstract String WriteValue(SOURCE_TYPE when);
45+
46+
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
47+
{
48+
writer.WriteRawValue(WriteValue((SOURCE_TYPE)value));
49+
50+
51+
}
52+
53+
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
54+
{
55+
if (reader.Value == null) { return null; }
56+
T val;
57+
if (reader.TokenType == JsonToken.String)
58+
val = ParseNotNullStringHandler((string)reader.Value);
59+
else
60+
val = (T)Convert.ChangeType(reader.Value, typeof(T));
61+
return ParseValue(val);
62+
}
63+
}
64+
}

src/WebUI.NET/WebUI.NET.csproj

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,4 +64,10 @@
6464
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
6565
</ItemGroup>
6666

67+
<ItemGroup>
68+
<None Update="webui_net.js">
69+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
70+
</None>
71+
</ItemGroup>
72+
6773
</Project>

src/WebUI.NET/WebUINetUI.cs

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
using System;
2+
using System.Collections.Concurrent;
3+
using System.Collections.Generic;
4+
using System.Diagnostics;
5+
using System.IO;
6+
using System.Linq;
7+
using System.Runtime.CompilerServices;
8+
using System.Text;
9+
using System.Threading;
10+
using System.Threading.Tasks;
11+
using Newtonsoft.Json;
12+
13+
namespace WebUI
14+
{
15+
16+
public class DomEvent
17+
{
18+
19+
public string currentTargetId { get; set; }
20+
public string originalTargetId { get; set; }
21+
[JsonConverter(typeof(MilisecondEpochConverter))]
22+
public DateTime timestamp { get; set; }
23+
public string type { get; set; }
24+
public Dictionary<string, string> additionalProps; //map of additional property values requested
25+
26+
}
27+
public enum CommonEventTypes { load, unload, error, resize, scroll, focus, blur, click, dblclick, mousedown, mouseup, mouseover, mouseout, mousemove, input, keydown, keypress, keyup, submit, change, DOMNodeInserted, DOMNodeRemoved, DOMSubtreeModified, DOMNodeInsertedIntoDocument, DOMNodeRemovedFromDocument, DOMContentLoaded, hashchange, beforeunload }
28+
public static class WebUINetUI
29+
{
30+
31+
public class EventOpts
32+
{
33+
public bool capture { get; set; }
34+
public bool once { get; set; }
35+
public string abortKey { get; set; }
36+
37+
}
38+
private const int startId = 5432;
39+
private static volatile int curFuncId = startId;
40+
// CommonEventTypes
41+
public static void AddEventListener(this Window window, Action<DomEvent> OnFired, CommonEventTypes eventType, String domId, EventOpts additionalOpts = null, params KeyValuePair<string, string>[] additionalEventCaptureProps) => window.AddEventListener(OnFired, eventType.ToString(), domId, additionalOpts, additionalEventCaptureProps);
42+
43+
public static void AddEventListener(this Window window, Action<DomEvent> OnFired, String eventType, String domId, EventOpts additionalOpts = null, params KeyValuePair<string, string>[] additionalEventCaptureProps)
44+
{
45+
46+
//addCSEventListener(type, elementID, csFuncID, additionalProps, options, abortKey)
47+
var addlDict = additionalEventCaptureProps?.Length > 0 ?
48+
Newtonsoft.Json.JsonConvert.SerializeObject( additionalEventCaptureProps.ToDictionary(x => x.Key, x => x.Value)) : null;
49+
var abortKey = additionalOpts?.abortKey;
50+
if (abortKey != null)
51+
additionalOpts.abortKey = null;
52+
var curId = window._RegisterBoundFunction(OnCalled: (str) => OnFired(Newtonsoft.Json.JsonConvert.DeserializeObject<DomEvent[]>(str)[0]), registerFunc: false);
53+
//window.InvokeJavascriptMethod($"{ourJSClass}.addCSEventListener", eventType, domId, curId, addlDict, additionalOpts, abortKey);
54+
window.ScriptEvaluateMethod<string>($"{ourJSClass}.addCSEventListener", eventType, domId, curId, addlDict, additionalOpts, abortKey);//not awiating but at least will be in background task exception
55+
}
56+
private static async void JavascriptFuncCallback(this Window window, int csFuncId, int jsCallId, string jsonOfArgs)
57+
{
58+
if (!csFuncIdToFired.TryGetValue(csFuncId, out var info))
59+
throw new Exception($"The JS func callback handler was passed an invalid function id {csFuncId}"); ;
60+
if (info.normFunc != null)
61+
{
62+
Window.UseSpecificDispatcher.InvokeWithVoid(() => info.normFunc(jsonOfArgs));
63+
return;
64+
}
65+
var res = await Window.UseSpecificDispatcher.InvokeWithReturnType(() => info.taskFunc(jsonOfArgs));
66+
67+
await window.ScriptEvaluateMethod<string>($"{ourJSClass}.setCSFunctionResult", jsCallId, res);//we don't use invoke so we can get the error
68+
//window.InvokeJavascriptMethod("setCSFunctionResult", jsCallId, res);
69+
70+
}
71+
private const string ourJSClass = $"window.WebUINet";
72+
public static void SetDebug(this Window window, bool debugging = true)
73+
{
74+
var jsDebug = debugging ? "true" : "false";
75+
window.InvokeJavaScript($@"{ourJSClass}.DEBUG_MODE={jsDebug};
76+
window.webui.setLogging({jsDebug});
77+
");
78+
}
79+
public static void RegisterBoundFunction(this Window window, String RegisteredName, Func<string, Task<string>> OnCalled) => window._RegisterBoundFunction(RegisteredName, OnCalledTask: OnCalled);
80+
public static void RegisterBoundFunction(this Window window, String RegisteredName, Action<string> OnCalled) => window._RegisterBoundFunction(RegisteredName, OnCalled);
81+
82+
private static int _RegisterBoundFunction(this Window window, String RegisteredName = null, Action<string> OnCalled = null, Func<string, Task<string>> OnCalledTask = null, bool registerFunc = true)
83+
{
84+
var curId = Interlocked.Increment(ref curFuncId);
85+
csFuncIdToFired[curId] = new RegisteredFunc { taskFunc = OnCalledTask, normFunc = OnCalled };
86+
if (registerFunc)
87+
{
88+
//we don't use invoke so we can get the error
89+
window.ScriptEvaluateMethod<string>($"{ourJSClass}.addCSFunction", curId, RegisteredName, OnCalledTask != null); //not awaiting but exception will at least show in logs just no ST
90+
//window.InvokeJavascriptMethod("WebUINet.addCSFunction", curId, RegisteredName, OnCalledTask != null);
91+
}
92+
if (curId == startId + 1)
93+
{
94+
window.RegisterEventHandler(RawFuncCallback, "webuiNet_Callback");
95+
}
96+
return curId;
97+
}
98+
public static void InitOurBridge(this Window window)
99+
{
100+
OurJSBytes = Encoding.UTF8.GetBytes(File.ReadAllText("webui_net.js"));
101+
window.SetFileHandler((path) => path == "/webui_net.js" ? OurJSBytes : null);
102+
}
103+
private static byte[] OurJSBytes;
104+
private static object RawFuncCallback(WebUI.Events.Event evt, string element, ulong handlerId)
105+
{
106+
evt.Window.JavascriptFuncCallback((int)evt.GetNumber(0), (int)evt.GetNumber(1), evt.GetString(2));
107+
return null;
108+
}
109+
110+
private class RegisteredFunc
111+
{
112+
public Func<string, Task<string>> taskFunc;
113+
public Action<string> normFunc;
114+
}
115+
private static ConcurrentDictionary<int, RegisteredFunc> csFuncIdToFired = new();
116+
}
117+
}

0 commit comments

Comments
 (0)