// Copyright (c) Microsoft Corporation // The Microsoft Corporation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Drawing; using System.Runtime.InteropServices; using System.Windows.Forms; using PreviewHandlerCommon.ComInterop; namespace Common { /// /// Form based implementation of . /// public abstract class FormHandlerControl : Form, IPreviewHandlerControl { /// /// Needed to make the form a child window. /// private static int gwlStyle = -16; private static int wsChild = 0x40000000; /// /// Holds the parent window handle. /// private IntPtr parentHwnd; /// /// Initializes a new instance of the class. /// public FormHandlerControl() { // Gets the handle of the control to create the control on the VI thread. Invoking the Control.Handle get accessor forces the creation of the underlying window for the control. // This is important, because the thread that instantiates the preview handler component and calls its constructor is a single-threaded apartment (STA) thread, but the thread that calls into the interface members later on is a multithreaded apartment (MTA) thread. Windows Forms controls are meant to run on STA threads. // More details: https://docs.microsoft.com/en-us/archive/msdn-magazine/2007/january/windows-vista-and-office-writing-your-own-preview-handlers. var forceCreation = this.Handle; this.FormBorderStyle = FormBorderStyle.None; this.Visible = false; } /// public IntPtr GetWindowHandle() { return this.Handle; } /// public void QueryFocus(out IntPtr result) { var getResult = IntPtr.Zero; this.InvokeOnControlThread(() => { getResult = NativeMethods.GetFocus(); }); result = getResult; } /// public void SetBackgroundColor(Color argbColor) { this.InvokeOnControlThread(() => { this.BackColor = argbColor; }); } /// public void SetFocus() { this.InvokeOnControlThread(() => { this.Focus(); }); } /// public void SetFont(Font font) { this.InvokeOnControlThread(() => { this.Font = font; }); } /// public void SetRect(Rectangle windowBounds) { this.UpdateWindowBounds(windowBounds); } /// public void SetTextColor(Color color) { this.InvokeOnControlThread(() => { this.ForeColor = color; }); } /// public void SetWindow(IntPtr hwnd, Rectangle rect) { this.parentHwnd = hwnd; this.UpdateWindowBounds(rect); } /// public virtual void Unload() { this.InvokeOnControlThread(() => { this.Visible = false; foreach (Control c in this.Controls) { c.Dispose(); } this.Controls.Clear(); }); // Call garbage collection at the time of unloading of Preview. // Which is preventing prevhost.exe to exit at the time of closing File explorer. // Preview Handlers run in a separate process from PowerToys. This will not affect the performance of other modules. // Mitigate the following Github issue: https://github.com/microsoft/PowerToys/issues/1468 GC.Collect(); } /// public virtual void DoPreview(T dataSource) { this.Visible = true; } /// /// Executes the specified delegate on the thread that owns the control's underlying window handle. /// /// Delegate to run. public void InvokeOnControlThread(MethodInvoker func) { this.Invoke(func); } /// /// Update the Form Control window with the passed rectangle. /// /// An instance of rectangle. private void UpdateWindowBounds(Rectangle windowBounds) { this.InvokeOnControlThread(() => { // We must set the WS_CHILD style to change the form to a control within the Explorer preview pane int windowStyle = NativeMethods.GetWindowLong(Handle, gwlStyle); if ((windowStyle & wsChild) == 0) { _ = NativeMethods.SetWindowLong(Handle, gwlStyle, windowStyle | wsChild); } NativeMethods.SetParent(Handle, parentHwnd); Bounds = windowBounds; }); } } }