我想在WPF窗口中创建一个dpi感知子窗口。子窗口将用于DirectX渲染。
我用如下窗口创建了一个最小示例:
<Window ...
Loaded="OnLoaded"
MouseMove="MainWindow_OnMouseMove">
<DockPanel>
<Menu DockPanel.Dock="Top">
<MenuItem Header="TestItem"/>
</Menu>
<Border Background="Blue" x:Name="BorderHost"/>
</DockPanel>
</Window>
边框将用于托管我的子窗口。边框的HwndHost
创建了directX交换链,如下所示:
public class ChildWindow : HwndHost
{
private readonly Border parent;
private IntPtr hWnd = IntPtr.Zero;
public SwapChain SwapChain { get; private set; }
public ChildWindow(Border parent)
{
this.parent = parent;
parent.SizeChanged += ParentOnSizeChanged;
}
private void ParentOnSizeChanged(object sender, SizeChangedEventArgs e)
{
// problem: width and height is not correctly scaled
SwapChain?.Resize((int)(parent.ActualWidth), (int)(parent.ActualHeight));
}
protected override HandleRef BuildWindowCore(HandleRef hwndParent)
{
// create subwindow
hWnd = CreateWindowEx(
0, // dwstyle
"static", // class name
"", // window name
WS_CHILD | WS_VISIBLE, // style
0, // x
0, // y
(int)parent.ActualWidth, // renderWidth
(int)parent.ActualHeight, // renderHeight
hwndParent.Handle, // parent handle
IntPtr.Zero, // menu
IntPtr.Zero, // hInstance
0 // param
);
// directx swap chain
// problem: width and height is not correctly scaled
SwapChain = new SwapChain(hWnd, (int)parent.ActualWidth, (int)parent.ActualHeight);
return new HandleRef(this, hWnd);
}
protected override void DestroyWindowCore(HandleRef hwnd)
{
DestroyWindow(hWnd);
}
[DllImport("user32.dll", EntryPoint = "DestroyWindow", CharSet = CharSet.Unicode)]
internal static extern bool DestroyWindow(IntPtr hwnd);
[DllImport("user32.dll", EntryPoint = "CreateWindowEx", CharSet = CharSet.Unicode)]
internal static extern IntPtr CreateWindowEx(
int dwExStyle,
string lpszClassName,
string lpszWindowName,
int style,
int x, int y,
int width, int height,
IntPtr hwndParent,
IntPtr hMenu,
IntPtr hInst,
[MarshalAs(UnmanagedType.AsAny)] object pvParam
);
internal const int
WS_CHILD = 0x40000000,
WS_VISIBLE = 0x10000000;
}
主窗口会初始化子窗口,并在鼠标移动后将其背景清除为黑色或白色:
public partial class MainWindow : Window
{
private ChildWindow child;
private float childColor = 1.0f;
public MainWindow()
{
InitializeComponent();
}
private void OnLoaded(object sender, RoutedEventArgs e)
{
child = new ChildWindow(BorderHost);
BorderHost.Child = child;
}
private void MainWindow_OnMouseMove(object sender, MouseEventArgs e)
{
if (child == null) return;
if (child.SwapChain == null) return;
// switch between black and white background
childColor = 1.0f - childColor;
// clear child background
child.SwapChain.BeginFrame();
Device.Get().ClearRenderTargetView(child.SwapChain.Rtv, new RawColor4(childColor, childColor, childColor, childColor));
child.SwapChain.EndFrame();
}
}
完整的源代码可以在https://github.com/kopaka1822/DpiAwareChildwindow上找到。
注意:
为了向我的应用程序添加dpi意识,我添加了以下清单:
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings>
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">True/PM</dpiAware>
<gdiScaling xmlns="http://schemas.microsoft.com/SMI/2017/WindowsSettings">true</gdiScaling>
</windowsSettings>
</application>
我将gdiScaling
设置为true,因为我希望窗口内的所有WPF组件都能像往常一样缩放。
但是,在使用DPI 2.0的显示器上,子窗口的位置和大小是错误的:
如何正确缩放和放置子窗口?
编辑:这是我不使用清单中的gdiScaling缩放比例并将窗口从2.0 dpi移至1.0 dpi屏幕时得到的结果:该窗口在2.0 dpi屏幕上看起来不错,但是我的窗口标题栏现在在1.0 dpi屏幕上尺寸过大。
根据document,我将gdiScaling
修改为以下格式,并且对我有用。
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings>
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/pm</dpiAware>
<!--<gdiScaling xmlns="http://schemas.microsoft.com/SMI/2017/WindowsSettings">true</gdiScaling>-->
</windowsSettings>
</application>
<application>
<windowsSettings xmlns="https://schemas.microsoft.com/SMI/2017/WindowsSettings">
<gdiScaling>true</gdiScaling>
</windowsSettings>
</application>