/// <summary> /// Creates native/unmanaged window of a class registered with <see cref="RegisterWindowClass"/> with null <i>wndProc</i>, and sets its window procedure. /// </summary> /// <exception cref="ArgumentException">The class is not registered with <see cref="RegisterWindowClass"/>, or registered with non-null <i>wndProc</i>.</exception> /// <exception cref="AuException">Failed to create window. Unlikely.</exception> /// <remarks> /// Calls API <msdn>CreateWindowEx</msdn>. /// Protects the <i>wndProc</i> delegate from GC. /// Later call <see cref="DestroyWindow"/> or <see cref="Close"/>. /// </remarks> public static AWnd CreateWindow(Native.WNDPROC wndProc, string className, string name = null, WS style = 0, WS2 exStyle = 0, int x = 0, int y = 0, int width = 0, int height = 0, AWnd parent = default, LPARAM controlId = default, IntPtr hInstance = default, LPARAM param = default) { var a = t_windows ??= new List <(AWnd w, Native.WNDPROC p)>(); for (int i = a.Count; --i >= 0;) { if (!a[i].w.IsAlive) { a.RemoveAt(i); } } lock (s_classes) { if (!s_classes.TryGetValue(className, out var wp) || wp != null) { throw new ArgumentException("Window class must be registered with RegisterWindowClass with null wndProc"); } } AWnd w; //need to cubclass the new window. But not after CreateWindowEx, because wndProc must receive all messages. #if CW_CBT //slightly slower and dirtier. Invented before Core, to support multiple appdomains. using (AHookWin.ThreadCbt(c => { //let CBT hook subclass before any messages if (c.code == HookData.CbtEvent.CREATEWND) { //note: unhook as soon as possible. Else possible exception etc. // If eg hook proc uses 'lock' and that 'lock' must wait, // our hook proc is called again and again while waiting, until 'lock' throws exception. // In STA thread 'lock' dispatches messages, but I don't know why hook proc is called multiple times for same event. c.hook.Unhook(); var ww = (AWnd)c.wParam; Debug.Assert(ww.ClassNameIs(className)); ww.SetWindowLong(Native.GWL.WNDPROC, Marshal.GetFunctionPointerForDelegate(wndProc)); } else { Debug.Assert(false); } return(false); })) { w = Api.CreateWindowEx(exStyle, className, name, style, x, y, width, height, parent, controlId, hInstance, param); } #else t_cwProc = wndProc; //let _DefWndProc subclass on first message try { w = Api.CreateWindowEx(exStyle, className, name, style, x, y, width, height, parent, controlId, hInstance, param); } finally { t_cwProc = null; } //if CreateWindowEx failed and _CWProc not called #endif if (w.Is0) { throw new AuException(0); } a.Add((w, wndProc)); return(w); }
protected override unsafe void WndProc(ref Message m) { //AWnd.More.PrintMsg(m, Api.WM_SETCURSOR, Api.WM_NCHITTEST, Api.WM_NCMOUSEMOVE); switch (m.Msg) { case Api.WM_MOUSEACTIVATE: switch (AMath.HiShort(m.LParam)) { case Api.WM_MBUTTONDOWN: Hide(); //never mind: we probably don't receive this message if our thread is inactive m.Result = (IntPtr)Api.MA_NOACTIVATEANDEAT; break; default: m.Result = (IntPtr)Api.MA_NOACTIVATE; break; } return; case Api.WM_NCLBUTTONDOWN: var wa = AWnd.ThisThread.Active; if (wa != default && wa.Handle != m.HWnd) { var h = m.HWnd; using (AHookWin.ThreadCbt(d => d.code == HookData.CbtEvent.ACTIVATE && d.ActivationInfo(out _, out _).Handle == h)) base.WndProc(ref m); return; } break; } //Somehow OS ignores WS_EX_NOACTIVATE if the active window is of this thread. Workaround: on WM_MOUSEACTIVATE return MA_NOACTIVATE. //Also then activates when clicked in non-client area, eg when moving or resizing. Workaround: on WM_NCLBUTTONDOWN suppress activation with a CBT hook. //When moving or resizing, WM_NCLBUTTONDOWN returns when moving/resizing ends. On resizing would activate on mouse button up. base.WndProc(ref m); switch (m.Msg) { case Api.WM_SHOWWINDOW: if (m.WParam == default) { ZHiddenOrDestroyed?.Invoke(false); } break; case Api.WM_DESTROY: ZHiddenOrDestroyed?.Invoke(true); break; } }
static void _Hook() { s_hook = AHookWin.ThreadCbt(m => { //AOutput.Write(m.code, m.wParam, m.lParam); //switch(m.code) { //case HookData.CbtEvent.ACTIVATE: //case HookData.CbtEvent.SETFOCUS: // AOutput.Write((AWnd)m.wParam); // AOutput.Write(AWnd.Active); // AOutput.Write(AWnd.ThisThread.Active); // AOutput.Write(AWnd.Focused); // AOutput.Write(AWnd.ThisThread.Focused); // break; //} if (m.code == HookData.CbtEvent.ACTIVATE) { var w = (AWnd)m.wParam; if (!w.HasExStyle(WS2.NOACTIVATE)) { //AOutput.Write(w); //AOutput.Write(w.ExStyle); //Api.SetForegroundWindow(w); //does not work ATimer.After(1, _ => { if (s_hook == null) { return; } //AOutput.Write(AWnd.Active); //AOutput.Write(AWnd.ThisThread.Active); bool isActive = w == AWnd.Active, activate = !isActive && w == AWnd.ThisThread.Active; if (isActive || activate) { s_hook.Dispose(); s_hook = null; } if (activate) { Api.SetForegroundWindow(w); //w.ActivateLL(); //no, it's against Windows rules, and works differently with meta outputPath //Before starting task, editor calls AllowSetForegroundWindow. But if clicked etc a window after that: // SetForegroundWindow fails always or randomly; // Activate[LL] fails if that window is of higher UAC IL, unless the foreground lock timeout is 0. } }); } } return(false); }); }
protected override unsafe void WndProc(ref Message m) { //if(_tb.Name=="ddd") AWnd.More.PrintMsg(m, new PrintMsgOptions(Api.WM_GETTEXT, Api.WM_GETTEXTLENGTH, Api.WM_NCHITTEST, Api.WM_SETCURSOR, Api.WM_NCMOUSEMOVE, Api.WM_MOUSEMOVE)); //AWnd.More.PrintMsg(m, new PrintMsgOptions(Api.WM_GETTEXT, Api.WM_GETTEXTLENGTH)); switch (m.Msg) { case Api.WM_CREATE: _WmCreate(); break; case Api.WM_DESTROY: _WmDestroy(); break; case Api.WM_NCPAINT: if (_WmNcpaint((AWnd)m.HWnd)) { return; //draws border if need } break; case Api.WM_NCHITTEST: if (_WmNchittest(ref m)) { return; //returns a hittest code to move or resize if need } break; case Api.WM_NCLBUTTONDOWN: //workaround for: Windows tries to activate this window when moving or sizing it, unless this process is not allowed to activate windows. // Usually this window would not become the foreground window, but it receives wm_activateapp, wm_activate, wm_setfocus, and is moved to the top of Z order. // tested: LockSetForegroundWindow does not work. // This code better would be under WM_SYSCOMMAND, but then works only when sizing. When moving, activates before WM_SYSCOMMAND. int ht = (int)m.WParam; if (ht == Api.HTCAPTION || (ht >= Api.HTSIZEFIRST && ht <= Api.HTSIZELAST)) { using (AHookWin.ThreadCbt(d => d.code == HookData.CbtEvent.ACTIVATE)) base.WndProc(ref m); return; } break; case Api.WM_ENTERSIZEMOVE: _tb._InMoveSize(true); break; case Api.WM_EXITSIZEMOVE: _tb._InMoveSize(false); break; case Api.WM_LBUTTONDOWN: case Api.WM_RBUTTONDOWN: case Api.WM_MBUTTONDOWN: var tb1 = _tb._SatPlanetOrThis; if (tb1.IsOwned && !tb1.MiscFlags.Has(TBFlags.DontActivateOwner)) { tb1.OwnerWindow.ActivateLL(); //never mind: sometimes flickers. Here tb1._Zorder() does not help. The OBJECT_REORDER hook zorders when need. This feature is rarely used. } break; case Api.WM_MOUSEMOVE: case Api.WM_NCMOUSEMOVE: _tb._SatMouse(); break; case Api.WM_WINDOWPOSCHANGING: _tb._OnWindowPosChanging(ref *(Api.WINDOWPOS *)m.LParam); break; } base.WndProc(ref m); switch (m.Msg) { case Api.WM_WINDOWPOSCHANGED: _tb._OnWindowPosChanged(in * (Api.WINDOWPOS *)m.LParam); break; case Api.WM_DISPLAYCHANGE: _tb._OnDisplayChanged(); break; case Api.WM_PAINT: _paintedOnce = true; //APerf.NW(); break; case Api.WM_RBUTTONUP: case Api.WM_NCRBUTTONUP: _ContextMenu(this); break; } }