gpt4 book ai didi

c# - 多 DPI 系统上的 VSTO 自定义任务 Pane 显示两次内容

转载 作者:可可西里 更新时间:2023-11-01 08:27:20 26 4
gpt4 key购买 nike

我正在使用 VSTO 构建一个办公插件。在具有多个不同 DPI 设置的显示器的系统上,我的自定义任务 Pane 的内容在具有较高 DPI 设置的显示器上绘制两次:

enter image description here

只有较小的版本才真正响应用户输入。较大的版本似乎只是一个放大的图像。

我试过各种 DPI 相关设置,例如:

  • AutoScaleMode 在我的用户控件上。我尝试了所有选项,没有任何改变。
  • 使用 SetProcessDpiAwareness 将进程设置为 DPI 感知(或不感知)。我尝试了所有选项,没有任何改变。
  • 使用 app.manifest 并将 dpiAware 设置为 truefalse。没有变化。

新的 Web 插件没有这个问题。此外,内部任务 Pane 没有此问题。

这是一个已知问题吗?我该如何解决这个问题?

最佳答案

这似乎是 Office 产品处理 WM_DPICHANGED 的方式中的错误。信息。该应用程序应该枚举其所有子窗口并重新缩放它们以响应消息,但它无法正确处理加载项 Pane 。

解决此错误的方法是禁用 DPI 缩放。你说你试过调用 SetProcessDpiAwareness,但一旦为应用程序设置了 DPI 感知,该函数就会失败,而你正在使用的应用程序显然已经设置了它,因为它适用于父窗口。然后你应该做的是调用 SetThreadDpiAwarenessContext ,如 this C# wrapper .不幸的是,我自己没有 Win10 multimon 设置来对此进行测试,但这应该在应用程序运行时起作用。尝试 this add-in ,它有一个按钮来设置线程 DPI 感知上下文,看看它是否适合你。


应用程序 Hook 方法

由于 SetThreadDpiAwarenessContext 在您的系统上可能不可用,处理该问题的一种方法是让主窗口忽略 WM_DPICHANGED 消息。这可以通过安装应用程序 Hook 来更改消息或通过子类化窗口来完成。应用程序 Hook 是一种稍微容易一些且陷阱较少的方法。基本上这个想法是拦截主应用程序的 GetMessagechange WM_DPICHANGED to WM_NULL ,这将使应用程序丢弃该消息。缺点是这种方法仅适用于发布的消息,但 WM_DPICHANGED 应该是其中之一。

因此,要安装应用程序 Hook ,您的加载项代码应如下所示:

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 消息,您可能必须确保在应用程序退出之前通过删除 Hook 进行清理。


子类化方法

如果您要阻止的消息没有发布到窗口,而是发送了,应用程序 Hook 方法将不起作用,主窗口必须是subclassed instead。 .这次我们将代码放在用户控件中,因为在调用 SetWindowLong 之前需要完全初始化主窗口。

因此,为了对 Power Point 窗口进行子类化,我们的用户控件(在插件中)看起来像这样(请注意,我为此使用了 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;
}
}

关于c# - 多 DPI 系统上的 VSTO 自定义任务 Pane 显示两次内容,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50100564/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com