Swanman's Horizon

性欲をもてあましつつなんらかの話をするよ。

Alt+Enterによるフルスクリーン化を無効にする。

これで悩む人が地味に多そうなので備忘録的に。

最近のDirectXは自動でAlt+Enterを処理してくれる

ちゃんと調べてないので10からか11からか分からないですが、最近のDirectXはAlt+Enterを自動で処理してフルスクリーン化してくれる機能があるそうです。というかあります。
が、ぶっちゃけフルスクリーン化したくない場面は多く、特にビジネス向けの多そうなFireMonkeyでは(bsNoneを指定した疑似フルスクリーン化ならともかく)排他モードに切り替わるフルスクリーン化は鬱陶しいことこの上ないです。
その上この鬱陶しい動作はDirectXのデフォルト動作なため、(たぶん)Vista以降でFireMonkeyフォームを作ると漏れなくAlt+Enter機能が装備されます。なんというありがた迷惑な機能/(^o^)\

ということで無効化

MSも鬼じゃないので無効化手段はちゃんと用意されています。IDXGIFactoryインターフェースのMakeWindowAssociationメソッドです。
こいつにDXGI_MWA_NO_ALT_ENTERフラグを渡すことで無効化できるらしいです。
…が、びっくりするほど資料がなく、ググっても「なんかこれ動かないんだけど…」的なサイトが結構あって、僕もそれなりにハマりました。

うまくやるためには

重要なのはこの3点です。

  1. IDXGIFactory.MakeWindowAssociationはIDXGIFactory.CreateSwapChainの後に呼ぶ
  2. DXGI_MWA_NO_ALT_ENTERだけでなくDXGI_MWA_NO_WINDOW_CHANGESも渡す
  3. 全フォームで対策する

1はDirectXの仕様的な話ですが、この順番でコールしないと反映されません。
2は物凄い罠で、DXGI_MWA_NO_WINDOW_CHANGESを一緒に指定しないといけないという話ではなく、DXGI_MWA_NO_WINDOW_CHANGESだけを指定すれば無効化されます。ゲイツェ…。
ただあからさまに動作としてはおかしいので、将来的に直ったことも考えてDXGI_MWA_NO_WINDOW_CHANGESだけでなくDXGI_MWA_NO_ALT_ENTERも指定しておいた方が良さそうです。
3は1つでも未対策のフォームがあった場合、そいつにフォーカスがあたっていようとなかろうとAlt+Enterが発動します。しかもフルスクリーン化には失敗し、描画だけ変な感じになります。

コード的なあれ

uses
  Winapi.DXGI, FMX.Context.DX10, FMX.Platform.Win;

procedure DisableAltEnter(AForm: TForm);
begin
  if TCanvasManager.DefaultCanvas.ClassName <> 'TCanvasD2D' then Exit;
  if TCustomDX10Context.DXGIFactory = nil then Exit;
  AForm.Canvas.BeginScene;
  TCustomDX10Context.DXGIFactory.MakeWindowAssociation(FmxHandleToHWND(AForm.Handle), DXGI_MWA_NO_ALT_ENTER or DXGI_MWA_NO_WINDOW_CHANGES);
  AForm.Canvas.EndScene;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  DisableAltEnter(Self);
end;

コード的なあれの説明

BeginSceneを呼ぶ理由は内部でCreateResources→IDXGIFactory.CreateSwapChainと呼んでもらうためなので別に他の手段でもいいんですが、これが一番手っ取り早いです。
これをフォームのOnCreateで呼んでやればAlt+Enterが無効化されるはずです。
(動的に生成したりする場合も考えるとOnPaintでやった方が確実かも。)

今日のまとめ

FireMonkeyにAlt+Enterの有効無効を切り替える機能入れて下さい><
あとMSはくたばれ。