#0. はじめに
Freeradicalの中の人、yamarahです。
いつまでもWinFormsではつらいので、WPFを使おうという話しです。
WPF自体については解説しません。Formの代わりにWPFのWindowを表示するには、どうすれば良いのかを説明します。
#1. ProjectにWPFを追加するための準備
Visual Studio → ソリューション エクスプローラー
で右クリック → 追加
→ 新しい項目
で'WPF'が無い場合があります。
上の画像にはWPF
がありますが、もし見当たらない場合は、ProjectでUseWPF
をtrue
にします。
これで、新しい項目の追加
にWPF
が表示されるはずです。
また、実際にWPFのWindowを追加した後に、
Windows デスクトップ アプリケーションを作成するには、Microsoft.NET.Sdk.WindowsDesktop が必要です。現在の SDK では、'UseWpf' と 'UseWindowsForms' はサポートされていません。
と警告が出る場合があります。この場合は、警告に従って、Projectの1行目を
<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">
に変更しましょう。
これらの変更を施したProjectの冒頭部分は、このようになると思います。
<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">
<PropertyGroup>
<OutputType>Library</OutputType>
<TargetFramework>net472</TargetFramework>
<UseWPF>true</UseWPF>
<RootNamespace>InvAddIn</RootNamespace>
<LangVersion>8.0</LangVersion>
<Nullable>enable</Nullable>
</PropertyGroup>
以前の記事(C#8.0、そして.NET Core3.0)に従って、nullable
も有効にしています。
ちなみに、WindowsFormsも使う場合は、
<UseWindowsForms>true</UseWindowsForms>
を加えます。
#2. 実際に表示する
表示するだけなら、単純に
var wpfWindow = new Window1();
wpfWindow.ShowDialog(); // modalの場合
// wpfWindow.Show(); // modelessの場合
とすれば、表示されます。しかし、これだけだと、WinFormsについて書いた以前の記事(Modeless FormのZ order)と同じ問題が発生して、WindowがInventorの後ろに隠れうる状態です。
WPFでは、WinFormsの場合と違って、modalでもInventorの後ろに回ります。
解決策は、modelessなWinFormsと同じように親子関係の設定をする必要があり、WPFでは次のようにします。
var wpfWindow = new Window1();
// Window Handleを操作するためのHelper
var helper = new System.Windows.Interop.WindowInteropHelper(wpfWindow);
// 親を設定する
helper.Owner = new System.IntPtr(InventorApplication.MainFrameHWND);
// 表示する
wpfWindow.ShowDialog(); // modalの場合
// wpfWindow.Show(); // modelessの場合
#3. Dockable Windowに表示したい
基本的には、WinFormsの場合と同じ手順でWPF Windowを表示できます。
(helperからHandleを取る前に、EnsureHandle()を呼ぶのと、VisibilityをVisbleにするのを忘れないように!!)
しかしながら、残念なことにキー入力ができません。Inventor Forumの
Dockable Window with WPF controls, don't receive keyboard input
によると、Inventor 2018から発病しているbugっぽいです。現時点(Inventor 2020.2)では、直接WPF Windowを貼っても、WinFormsで包んでも、キー入力ができません。
↓
2020.01.09 追記
Messageをhookして、WM_GETDLGCODE
に応答することで、キー入力できるようになることが分かりました。
以下は、そのサンプルコードです。
public void Activate(ApplicationAddInSite addInSiteObject, bool firstTime)
{
InventorApplication = addInSiteObject.Application;
// WPF Windowの初期化
var wpfWindow = new WpfWindow();
wpfWindow.WindowStyle = System.Windows.WindowStyle.None;
wpfWindow.ResizeMode = System.Windows.ResizeMode.NoResize;
wpfWindow.Visibility = System.Windows.Visibility.Visible;
// WPF WindowのHandleを得る
var helper = new WindowInteropHelper(wpfWindow);
helper.EnsureHandle();
var handle = helper.Handle;
// Dockable Windowの作成
var dockableWindow = InventorApplication.UserInterfaceManager.DockableWindows.Add(System.Guid.NewGuid().ToString(), "Test", "Test");
dockableWindow.AddChild(handle);
// Key hookをセット
HwndSource.FromHwnd(handle).AddHook(WndProc);
}
private const UInt32 DLGC_WANTARROWS = 0x0001;
private const UInt32 DLGC_WANTTAB = 0x0002;
private const UInt32 DLGC_WANTALLKEYS = 0x0004;
private const UInt32 DLGC_HASSETSEL = 0x0008;
private const UInt32 DLGC_WANTCHARS = 0x0080;
private const UInt32 WM_GETDLGCODE = 0x0087;
private static IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
if (msg == WM_GETDLGCODE)
{
handled = true;
return new IntPtr(DLGC_WANTCHARS | DLGC_WANTARROWS | DLGC_HASSETSEL | DLGC_WANTTAB | DLGC_WANTALLKEYS);
}
return IntPtr.Zero;
}
#99. 親の記事に戻る
Autodesk Inventor API Hacking (概略)