// 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.Windows.Forms;
using Common.ComInterlop;
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://learn.microsoft.com/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;
getResult = NativeMethods.GetFocus();
result = getResult;
}
///
public void SetBackgroundColor(Color argbColor)
{
this.BackColor = argbColor;
}
///
public void SetFocus()
{
this.Focus();
}
///
public void SetFont(Font font)
{
this.Font = font;
}
///
public void SetRect(Rectangle windowBounds)
{
this.UpdateWindowBounds(parentHwnd, windowBounds);
}
///
public void SetTextColor(Color color)
{
this.ForeColor = color;
}
///
public void SetWindow(IntPtr hwnd, Rectangle rect)
{
this.parentHwnd = hwnd;
this.UpdateWindowBounds(hwnd, rect);
}
///
public virtual void Unload()
{
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;
}
///
/// Update the Form Control window with the passed rectangle.
///
public void UpdateWindowBounds(IntPtr hwnd, Rectangle newBounds)
{
if (this.Disposing || this.IsDisposed)
{
// For unclear reasons, this can be called when handling an error and the form has already been disposed.
return;
}
// 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, hwnd);
if (newBounds.IsEmpty)
{
RECT s = default(RECT);
NativeMethods.GetClientRect(hwnd, ref s);
newBounds = new Rectangle(s.Left, s.Top, s.Right - s.Left, s.Bottom - s.Top);
}
if (Bounds.Right != newBounds.Right || Bounds.Bottom != newBounds.Bottom || Bounds.Left != newBounds.Left || Bounds.Top != newBounds.Top)
{
Bounds = newBounds;
}
}
}
}