Ответ 1
Кажется, это ошибка в продуктах Office, так как они обрабатывают обработку сообщения WM_DPICHANGED
. Предполагается, что приложение должно перечислить все дочерние окна и перемасштабировать их в ответ на сообщение, но оно каким-то образом не сможет правильно обработать панели надстройки.
Что вы можете сделать, чтобы обойти ошибку, отключить масштабирование DPI. Вы говорите, что пытались вызвать SetProcessDpiAwareness
, но эта функция документирована с ошибкой после того, как была установлена осведомленность о DPI для приложения, и приложение, которое вы используете, явно установило его, потому что оно работает для родительского окна. То, что вы должны сделать, это вызвать SetThreadDpiAwarenessContext
, как в этой SetThreadDpiAwarenessContext
С#. К сожалению, у меня нет настройки для нескольких мониторов Win10, чтобы проверить это самостоятельно, но это должно работать как приложение работает. Попробуйте эту надстройку, у нее есть кнопка для установки контекста осведомленности потока DPI и посмотрите, работает ли это для вас.
Подход приложения
Поскольку SetThreadDpiAwarenessContext
может быть недоступен в вашей системе, один из способов решения этой проблемы - заставить главное окно игнорировать сообщение WM_DPICHANGED
. Это можно сделать либо путем установки крюка приложения для изменения сообщения, либо путем подкласса окна. Крючок приложения - это немного более легкий подход с меньшим количеством ошибок. В основном идея состоит в том, чтобы перехватить основное приложение GetMessage
и изменить WM_DPICHANGED
на WM_NULL
, что заставит приложение отказаться от сообщения. Недостатком является то, что этот подход работает только для опубликованных сообщений, но WM_DPICHANGED
должен быть одним из них.
Поэтому, чтобы установить приложение, ваш код надстройки будет выглядеть примерно так:
public partial class ThisAddIn
{
public enum HookType : int
{
WH_JOURNALRECORD = 0,
WH_JOURNALPLAYBACK = 1,
WH_KEYBOARD = 2,
WH_GETMESSAGE = 3,
WH_CALLWNDPROC = 4,
WH_CBT = 5,
WH_SYSMSGFILTER = 6,
WH_MOUSE = 7,
WH_HARDWARE = 8,
WH_DEBUG = 9,
WH_SHELL = 10,
WH_FOREGROUNDIDLE = 11,
WH_CALLWNDPROCRET = 12,
WH_KEYBOARD_LL = 13,
WH_MOUSE_LL = 14
}
delegate IntPtr HookProc(int code, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll", SetLastError = true)]
static extern IntPtr SetWindowsHookEx(HookType hookType, HookProc lpfn, IntPtr hMod, uint dwThreadId);
[DllImport("user32.dll")]
static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam);
[StructLayout(LayoutKind.Sequential)]
public struct POINT
{
public int X;
public int Y;
}
public struct MSG
{
public IntPtr hwnd;
public uint message;
public IntPtr wParam;
public IntPtr lParam;
public uint time;
public POINT pt;
}
HookProc cbGetMessage = null;
private UserControl1 myUserControl1;
private Microsoft.Office.Tools.CustomTaskPane myCustomTaskPane;
private void ThisAddIn_Startup(object sender, System.EventArgs e)
{
this.cbGetMessage = new HookProc(this.MyGetMessageCb);
SetWindowsHookEx(HookType.WH_GETMESSAGE, this.cbGetMessage, IntPtr.Zero, (uint)AppDomain.GetCurrentThreadId());
myUserControl1 = new UserControl1();
myCustomTaskPane = this.CustomTaskPanes.Add(myUserControl1, "My Task Pane");
myCustomTaskPane.Visible = true;
}
private IntPtr MyGetMessageCb(int code, IntPtr wParam, IntPtr lParam)
{
unsafe
{
MSG* msg = (MSG*)lParam;
if (msg->message == 0x02E0)
msg->message = 0;
}
return CallNextHookEx(IntPtr.Zero, code, wParam, lParam);
}
private void ThisAddIn_Shutdown(object sender, System.EventArgs e)
{
}
#region VSTO generated code
private void InternalStartup()
{
this.Startup += new System.EventHandler(ThisAddIn_Startup);
this.Shutdown += new System.EventHandler(ThisAddIn_Shutdown);
}
#endregion
}
Обратите внимание, что это в основном непроверенный код, и если он работает в блокировке сообщения WM_DPICHANGED
вам, вероятно, придется почистить его, удалив крючок перед выходом приложения.
Подклассический подход
Если сообщение, которое вы хотите заблокировать, не отправляется в окно, а отправляется вместо этого, метод привязки приложения не будет работать, и основное окно нужно будет подклассифицировать. На этот раз мы SetWindowLong
наш код в пользовательский элемент управления, потому что основные окна должны быть полностью инициализированы перед SetWindowLong
.
Поэтому для подкласса окна Power Point наш пользовательский элемент управления (который находится внутри addin) будет выглядеть примерно так (обратите внимание, что я использую OnPaint для этого, но вы можете использовать что угодно, если он гарантирует, что окно инициализируется во время вызывая SetWindowLong
):
public partial class UserControl1 : UserControl
{
const int GWLP_WNDPROC = -4;
[DllImport("user32", SetLastError = true)]
extern static IntPtr CallWindowProc(IntPtr lpPrevWndFunc, IntPtr hWnd, uint uMsg, IntPtr wParam, IntPtr lParam);
[DllImport("user32", SetLastError = true)]
extern static IntPtr SetWindowLongPtr(IntPtr hWnd, int nIndex, IntPtr lpNewLong);
[DllImport("user32", SetLastError = true)]
extern static IntPtr SetWindowLong(IntPtr hWnd, int nIndex, IntPtr lpNewLong);
delegate IntPtr WindowProc(IntPtr hwnd, uint uMsg, IntPtr wParam, IntPtr lParam);
private IntPtr origProc = IntPtr.Zero;
private WindowProc wpDelegate = null;
public UserControl1()
{
InitializeComponent();
this.Paint += UserControl1_Paint;
}
void UserControl1_Paint(object sender, PaintEventArgs e)
{
if (origProc == IntPtr.Zero)
{
//Subclassing
this.wpDelegate = new WindowProc(MyWndProc);
Process process = Process.GetCurrentProcess();
IntPtr wpDelegatePtr = Marshal.GetFunctionPointerForDelegate(wpDelegate);
if (IntPtr.Size == 8)
{
origProc = SetWindowLongPtr(process.MainWindowHandle, GWLP_WNDPROC, wpDelegatePtr);
}
else
{
origProc = SetWindowLong(process.MainWindowHandle, GWLP_WNDPROC, wpDelegatePtr);
}
}
}
//Subclassing
private IntPtr MyWndProc(IntPtr hwnd, uint uMsg, IntPtr wParam, IntPtr lParam)
{
if (uMsg == 0x02E0) //WM_DPICHANGED
return IntPtr.Zero;
IntPtr retVal = CallWindowProc(origProc, hwnd, uMsg, wParam, lParam);
return retVal;
}
}