/// <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); }
//public void ShowAnimate(bool show) //{ // //Don't add AWnd function, because: // //Rarely used. // //Api.AnimateWindow() works only with windows of current thread. // //Only programmers would need it, and they can call the API directly. //} /// <summary> /// Registers new window class in this process. /// </summary> /// <param name="className">Class name.</param> /// <param name="wndProc"> /// Delegate of a window procedure. See <msdn>Window Procedures</msdn>. /// /// Use null when you need a different delegate (method or target object) for each window instance; create windows with <see cref="CreateWindow(Native.WNDPROC, string, string, WS, WS2, int, int, int, int, AWnd, LPARAM, IntPtr, LPARAM)"/> or <see cref="CreateMessageOnlyWindow(Native.WNDPROC, string)"/>. /// If not null, it must be a static method; create windows with any other function, including API <msdn>CreateWindowEx</msdn>. /// </param> /// <param name="ex"> /// Can be used to specify more fields of <msdn>WNDCLASSEX</msdn> that is passed to API <msdn>RegisterClassEx</msdn>. /// Defaults: hCursor = arrow; hbrBackground = COLOR_BTNFACE+1; style = CS_GLOBALCLASS; others = 0/null/default. /// This function also adds CS_GLOBALCLASS style. /// </param> /// <exception cref="ArgumentException"><i>wndProc</i> is an instance method. Must be static method or null. If need instance method, use null here and pass <i>wndProc</i> to <see cref="CreateWindow"/>.</exception> /// <exception cref="InvalidOperationException">The class already registered with this function and different <i>wndProc</i> (another method or another target object).</exception> /// <exception cref="Win32Exception">Failed, for example if the class already exists and was registered not with this function.</exception> /// <remarks> /// Calls API <msdn>RegisterClassEx</msdn>. /// The window class is registered until this process ends. Don't need to unregister. /// If called next time for the same window class, does nothing if <i>wndProc</i> is equal to the previous (or both null). Then ignores <i>ex</i>. Throws exception if different. /// Thread-safe. /// Protects the <i>wndProc</i> delegate from GC. /// </remarks> public static unsafe void RegisterWindowClass(string className, Native.WNDPROC wndProc = null, WndClassEx ex = null) { if (wndProc?.Target != null) { throw new ArgumentException("wndProc must be static method or null. Use non-static wndProc with CreateWindow."); } lock (s_classes) { if (s_classes.TryGetValue(className, out var wpPrev)) { if (wpPrev != wndProc) { throw new InvalidOperationException("Window class already registered"); //another method or another target object } return; } var x = new Api.WNDCLASSEX(ex); fixed(char *pCN = className) { x.lpszClassName = pCN; if (wndProc != null) { x.lpfnWndProc = Marshal.GetFunctionPointerForDelegate(wndProc); } else { #if CW_CBT if (s_defWindowProc == default) { s_defWindowProc = Api.GetProcAddress("user32.dll", "DefWindowProcW"); } x.lpfnWndProc = s_defWindowProc; #else if (s_cwProcFP == default) { s_cwProcFP = Marshal.GetFunctionPointerForDelegate(s_cwProc); } x.lpfnWndProc = s_cwProcFP; #endif } x.style |= Api.CS_GLOBALCLASS; if (0 == Api.RegisterClassEx(x)) { throw new Win32Exception(); } //note: we don't return atom because: 1. Rarely used. 2. If assigned to an unused field, compiler may remove the function call. s_classes.Add(className, wndProc); } } }
/// <summary> /// Subclasses clipOwner. /// </summary> /// <param name="paste">true if used for paste, false if for copy.</param> /// <param name="data">If used for paste, can be string containing Unicode text or int/string dictionary containing clipboard format/data.</param> /// <param name="clipOwner">Our clipboard owner window.</param> /// <param name="wFocus">The target control or window.</param> public _ClipboardListener(bool paste, object data, AWnd clipOwner, AWnd wFocus) { _paste = paste; _data = data; _wndProc = _WndProc; _wFocus = wFocus; clipOwner.SetWindowLong(Native.GWL.WNDPROC, Marshal.GetFunctionPointerForDelegate(_wndProc)); //rejected: use SetClipboardViewer to block clipboard managers/viewers/etc. This was used in QM2. // Nowadays most such programs don't use SetClipboardViewer. Probably they use AddClipboardFormatListener+WM_CLIPBOARDUPDATE. // known apps that have clipboard viewer installed with SetClipboardViewer: // OpenOffice, LibreOffice: tested Writer, Calc. // VLC: after first Paste. //_wPrevClipViewer = Api.SetClipboardViewer(clipOwner); //AOutput.Write(_wPrevClipViewer); //TRY: Hook posted messages (in C++ dll) and block WM_CLIPBOARDUPDATE. Then don't need _DisableClipboardHistory. }
/// <summary> /// Creates native/unmanaged <msdn>message-only window</msdn> of a class registered with <see cref="RegisterWindowClass"/> with null <i>wndProc</i>, and sets its window procedure. /// </summary> /// <param name="className">Window class name.</param> /// <param name="wndProc"></param> /// <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> /// Styles: WS_POPUP, WS_EX_NOACTIVATE. /// Protects the <i>wndProc</i> delegate from GC. /// Later call <see cref="DestroyWindow"/> or <see cref="Close"/>. /// </remarks> public static AWnd CreateMessageOnlyWindow(Native.WNDPROC wndProc, string className) { return(CreateWindow(wndProc, className, null, WS.POPUP, WS2.NOACTIVATE, parent: Native.HWND.MESSAGE)); //note: WS_EX_NOACTIVATE is important. }