Skip to content

Create and Communicate Window in Child Thread

Ward edited this page Apr 13, 2020 · 3 revisions

Code

Here is the demonstration of window creation in child thread. After a window is created, it can communicate with the window in main thread by SendMessage/PostMessage.

import threadpool, winim/lean, sets
import wNim/[wApp, wFrame, wButton]

const
  wEvent_RegisterChildFrame = wEvent_App + 1
  wEvent_UnregisterChildFrame = wEvent_App + 2
  wEvent_Ping = wEvent_App + 3
  wEvent_Pong = wEvent_App + 4

var
  app = App()
  frame = Frame(title="main", size=(400, 300))
  button1 = Button(frame, label="Create")
  button2 = Button(frame, label="Ping")
  childFrames: HashSet[HWND]

frame.wEvent_Size do ():
  frame.autolayout """
    H: |[button1..2]|
    V: |[button1][button2(button1)]|
  """

proc createChildThread(hMain: HWND) {.thread.} =
  {.gcsafe.}:
    let threadId = GetCurrentThreadId()
    echo threadId, " thread started"

    var app = App()
    var frame = Frame(title="child", size=(400, 300))
    SendMessage(hMain, wEvent_RegisterChildFrame, WPARAM frame.handle, 0)

    frame.wEvent_Ping do ():
      echo threadId, " wEvent_Ping"
      PostMessage(hMain, wEvent_Pong, WPARAM threadId, 0)

    frame.wEvent_Destroy do ():
      SendMessage(hMain, wEvent_UnregisterChildFrame, WPARAM frame.handle, 0)

    frame.show()
    app.mainLoop()
    echo threadId, " thread closed"

button1.wEvent_Button do ():
  spawn createChildThread(frame.handle)

button2.wEvent_Button do ():
  for hwnd in childFrames:
    PostMessage(hwnd, wEvent_Ping, 0, 0)

frame.wEvent_RegisterChildFrame do (event: wEvent):
  childFrames.incl HWND event.wParam

frame.wEvent_UnregisterChildFrame do (event: wEvent):
  childFrames.excl HWND event.wParam

frame.wEvent_Pong do (event: wEvent):
  echo event.wParam, " wEvent_Pong"

frame.center()
frame.show()
app.mainLoop()

Another way is use HWND_BROADCAST. It is easier but a system-wide message must be registed.

import threadpool, winim/lean
import wNim/[wApp, wFrame, wButton]

var
  wEvent_Ping = RegisterWindowMessage("wEvent_Ping")
  wEvent_Pong = RegisterWindowMessage("wEvent_Pong")

var
  app = App()
  frame = Frame(title="main", size=(400, 300))
  button1 = Button(frame, label="Create")
  button2 = Button(frame, label="Ping")

frame.wEvent_Size do ():
  frame.autolayout """
    H: |[button1..2]|
    V: |[button1][button2(button1)]|
  """

proc createChildThread() {.thread.} =
  {.gcsafe.}:
    let threadId = GetCurrentThreadId()
    echo threadId, " thread started"

    var app = App()
    var frame = Frame(title="child", size=(400, 300))

    frame.wEvent_Ping do ():
      echo threadId, " wEvent_Ping"
      PostMessage(HWND_BROADCAST, wEvent_Pong, WPARAM threadId, 0)

    frame.show()
    app.mainLoop()
    echo threadId, " thread closed"

button1.wEvent_Button do ():
  spawn createChildThread()

button2.wEvent_Button do ():
  PostMessage(HWND_BROADCAST, wEvent_Ping, 0, 0)

frame.wEvent_Pong do (event: wEvent):
  echo event.wParam, " wEvent_Pong"

frame.center()
frame.show()
app.mainLoop()

A Win32's event object can be used as a signal to communicate with child thread without a window.

import threadpool, os, winim/lean
import wNim/[wApp, wFrame, wButton]

const wEvent_Pong = wEvent_App + 1

var
  app = App()
  frame = Frame(title="main", size=(400, 300))
  button = Button(frame, label="Ping")
  hEventPing = CreateEvent(nil, false, false, nil)

proc signal(hEvent: HANDLE) {.inline.} =
  SetEvent(hEvent)

proc isSignaled(hEvent: HANDLE): bool =
  if WaitForSingleObject(hEvent, 0) == WAIT_OBJECT_0:
    ResetEvent(hEvent)
    result = true

proc createChildThread(hEventPing: HANDLE, hMain: HWND) {.thread.} =
  while true:
    if hEventPing.isSignaled():
      echo "ping"
      PostMessage(hMain, wEvent_Pong, 0, 0)

    os.sleep(1)

button.wEvent_Button do ():
  hEventPing.signal()

frame.wEvent_Pong do ():
  echo "pong"

spawn createChildThread(hEventPing, frame.handle)
frame.center()
frame.show()
app.mainLoop()

To check the event object in main thread, we need a timer or a hook proc of mainloop.

import threadpool, os, winim/lean
import wNim/[wApp, wFrame, wButton]

var
  hEventPing = CreateEvent(nil, false, false, nil)
  hEventPong = CreateEvent(nil, false, false, nil)

proc signal(hEvent: HANDLE) {.inline.} =
  SetEvent(hEvent)

proc isSignaled(hEvent: HANDLE): bool =
  if WaitForSingleObject(hEvent, 0) == WAIT_OBJECT_0:
    ResetEvent(hEvent)
    result = true

var
  app = App()
  frame = Frame(title="main", size=(400, 300))
  button = Button(frame, label="Ping")

proc createChildThread(hEventPing, hEventPong: HANDLE) {.thread.} =
  while true:
    if hEventPing.isSignaled():
      hEventPong.signal()
      echo "ping"

    os.sleep(1)

button.wEvent_Button do ():
  hEventPing.signal()

app.addMessageLoopHook() do (msg: var wMsg, modalHwnd: HWND) -> int:
  if hEventPong.isSignaled():
    echo "pong"

spawn createChildThread(hEventPing, hEventPong)
frame.center()
frame.show()
app.mainLoop()
Clone this wiki locally