ハクソク

世界を動かす技術を、日本語で。

直接Win32 API、奇妙な形のウィンドウ、そしてそれらがほとんど消えた理由

概要

  • 現代のWindowsアプリは没個性で同質化
  • 多くがElectronやTauriなどのWebラッパー利用
  • Win32 APIによる自由なUI設計の価値
  • カスタムウィンドウ実装例の紹介
  • GitHubリポジトリにサンプルコード公開

現代Windowsアプリの画一化と不満

  • ほとんどのWindowsデスクトップアプリがReact、Electron、TauriなどのWebラッパーで構築
  • その結果、動作が遅く、メモリ消費が多い傾向
  • NotepadCalculatorなどのシンプルなアプリですら数十MBのメモリ消費
  • かつてのWin32 C製アプリは1~2MB程度の軽量動作
  • Windows 11起動直後でさえ大量のメモリ消費現象
  • 最適化意識の低下Web開発者主導によるUI設計の単調化

Win32 APIによる自由なUI設計の魅力

  • Win32 APIによる開発は完全なコントロールが可能
  • XP時代には非標準ウィンドウや独自デザインが一般的
  • Media Playerデスクトップマスコットなど多様なウィンドウ形状
  • 矩形ウィンドウ以外の形状も実現可能
  • アイデンティティ重視のUI設計文化

Win32カスタムウィンドウ実装の基礎

  • Win32プログラムイベントメッセージ駆動型
    • 例:
      while (GetMessage(&msg, NULL, 0, 0) > 0) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
      }
      
  • WM_CREATE:ウィンドウ生成
    WM_PAINT:再描画要求
    WM_SIZE:サイズ変更
    WM_DESTROY:終了処理
  • ウィンドウの形状制御は**HRGN(Regionオブジェクト)**で実現
  • 例:SetWindowRgnを使い楕円形ウィンドウを作成
    • region = CreateEllipticRgn(0, 0, rc.right, rc.bottom);
    • SetWindowRgn(hwnd, region, TRUE);
  • タイトルバー廃止時はドラッグ処理なども自前実装が必要
    • 例:WM_LBUTTONDOWNWM_NCLBUTTONDOWN/HTCAPTIONを送信

ビットマップやレイヤードウィンドウによる応用

  • drivenbyimage/main.c:ビットマップ画像の形状に合わせてウィンドウ形状を決定
    • Magenta(RGB(255,0,255))を透明色として扱う
    • GetDIBitsでピクセル読み取り、透明でない部分をウィンドウ領域
  • Animated/:レイヤードウィンドウによるアニメーションマスコット実装
    • WS_EX_LAYEREDウィンドウで32bitアルファ画像を表示
    • UpdateLayeredWindowで毎フレーム画像更新
    • GDI+利用でスプライトアニメーションを実現
    • ピクセル単位の透過ソフトなエッジが可能

カスタムウィンドウの課題と文化の変化

  • Win32 APIでのカスタムウィンドウは全責任が開発者側
    • ドラッグ、リサイズ、クローズ、ヒットテスト、DPI対応など全て自作
    • プロトタイプは簡単だが製品レベルの仕上げは困難
  • 奇抜なウィンドウアドウェアやツールバーのイメージ悪化で衰退
  • 信頼性重視シンプルなUI志向への文化転換
  • それでもWin32 APIは今も自由なUI表現を可能にする環境

まとめとGitHubリポジトリ案内

  • Win32 API自由なウィンドウ設計を今もサポート
  • 矩形ウィンドウは「選択肢」であり「制約」ではない
  • GitHubリポジトリサンプルコードを公開中
  • 興味があればリポジトリ参照推奨

Hackerたちの意見

> ポイントは通常、使いやすさじゃなくて、アイデンティティだったんだよね。これを読んで「これ、LLMが書いたんじゃない?」って思ったのは悪いことなのかな。
まったく同感だわ。でも、著者が何か言いたかったのも分かるし、最終的にはうまく伝わってる。最初は愚痴みたいな感じだけど、面白い例に繋がって、過去にやったことを思い出させてくれる。
この記事のすべての行はLLMによって書かれたんだ…
うん、変な形のウィンドウは絶対に復活すべきじゃないよね。できるからって、やるべきとは限らない。 > 今のところ、すべてのWindowsデスクトップアプリは同じに見えるし、同じものだからね。クソみたいなReactやElectron、electronbun、Tauriのブラウザラッパーで作られて、本物のデスクトップアプリを真似てるだけ。デスクトップアプリはOSのGUIフレームワークを使うべきだから、見た目は同じであるべきだよ。ReactやElectronとは関係ない。 この議論はちょっと理解できないな。ウェブビューに基づいているから、アプリケーションはウェブサイトのように異なる見た目になるはずなのに、似ているのは良いことだと思う。 > ポイントは通常、使いやすさじゃなくて、アイデンティティだったんだよね。そうそう。使いやすさも大事だし。独自のウィジェットやカラースキーム、非標準の形を実装しているアプリは、使いやすさやアクセシビリティに全く気を使ってないことが多い。ほとんどの場合、標準的な配慮が欠けていて、UXガイドラインも無視してるし。皮肉なことに、今の「アイデンティティ」が最も強いアプリは、HWメーカーの制御パネルやデバイスドライバーにバンドルされたアクセサリーで、平均的なユーザーが遭遇する最もクソなブloatwareだったりする。
どうだろう。見た目はかっこいいよね。誰かが復活させたいなら、それはアリだと思う。私は全然歓迎するよ。
アクセシビリティもすごく重要だよね。ここでも人々の権利を守るための法律や規制があるし。現代のクロスプラットフォームGUIフレームワーク(どんなに重くても)は、視覚に障害のある人たちのためにスクリーンリーダーやHiDPIをサポートするのに問題ないし。
> ReactやElectronとは関係ない話だね。それだけじゃなくて、Electronは逆の問題を引き起こすと思う。全てのアプリが見た目も動作もバラバラで、プラットフォームのガイドラインに従ってないし、場違いに見えるんだよね。
その通り。みんながUIデザインで「自分のスタイルを出そう」としてた時代は、あんまり快適でも使いやすくもなかったよね。例えば、これらのデザインを見てみてよ。http://hallofshame.gp.co.at/mshame.htm
同意。Vistaの頃には、特にMicrosoftがWindows Presentation Foundationフレームワークを大々的に宣伝してXAMLを使ってUIデザインしてた時期に、かなりの数の非標準デザインを見た記憶がある。こういうセットアップの問題は、サイズを変更したり、特定の場所に配置したり、大きいモニターや小さいモニターに移動したりする必要が出てきた瞬間、スケールがひどくなって、「千の傷による死」のような問題を引き起こすことだね。
それに、その発言は間違ってるよね。Windowsアプリは同じに見えないし、それは悪いことだ。ああ…タイトルバーがないせいで、どのアプリを見ているのかもわからない。これ、Edgeで開いてるPDFなのか、Acrobatなのか?誰にもわからないよ。ウィンドウは同じに見えるし、その先は…本当にひどい混乱だよ。メニューバーがないアプリがあって、代わりにハンバーガーボタンや「ギア」ボタンが散らばってる。さらに、「ファイルを保存」といった一般的な機能も、そういうメニューの中で「もっと見る」ってラベルの後ろに隠されてる。Windowsのひどい後退のもう一つの例は、多くのアプリでファイルダイアログが廃止されて、代わりに粗雑に描かれたラベルのない超広いテキストボックスと普通のテキストのページに置き換えられていることだ。ファイル構造が表示されないから、どこにファイルを保存するのか全くわからない…本当にクソみたいなUIの見本だね。情けない。
> うん、変な形の窓は復活させるべきじゃないよね。できるからって、やるべきとは限らない。俺の意見は君とは真逆だよ。コンピューターをかっこよく戻そうぜ!昔はエイリアンの宇宙船みたいだったのに、今は全部書類みたいになっちゃった。もう一度かっこよくできるはず!楽しさを取り戻そう。人生の楽しい部分は選択肢なんだから。
スチームには「デスクトップゲーム」の波が来てるね。ほとんどがアイドルジャンルかたまごっちみたいなやつ。そういうのには、こういうエキゾチックな形の窓が楽しくて、いい差別化になると思うよ。例: https://store.steampowered.com/app/2666510/Rustys_Retirement... たくさんのゲームが入ったバンドル: https://store.steampowered.com/bundle/48558/BottomOfYourScre...
盲目的に平凡な均一性を擁護するんじゃなくて、使いやすさについてもっと具体的なことを言えるといいね。例えば、音楽プレイヤーアプリが物理的な昔の丸いCDプレイヤーみたいに見えるのに、長方形じゃないと、使いやすさにどう影響するの? > 自分のウィジェットやカラースキーム、非標準の形を実装しているアプリは、通常、使いやすさやアクセシビリティに全く注意を払ってないよ。OSもこれに近い。常に変わるOSのカラースキームやウィジェットデザインのどの時代を選んでも、基本中の基本、つまり読みやすさに問題がたくさんあるのがわかる。だから、なぜ「ガイドライン」を書いたからって、みんなが一律に悪くなる必要があるの?確かに、変化が良いとは限らないけど、デフォルトを使うのも良いとは限らないよね。
アドビはネイティブに移行するのを拒んでいる有名な例だね。
> ポイントは通常、使いやすさじゃなくて、アイデンティティだったんだよね。しかも、使いやすさすら得られてない!あの無味乾燥なreact-角は、OSやお互い、そしてしばしば自分自身とも微妙に不一致なんだよね。6ヶ月後には、UXを改善することなく、責任者を雇い続けるためだけに、すべてがまた動き回るだろうし。アクセシビリティはどこかで忘れ去られて、隅っこで泣いてる。
MacのCD焼きアプリ「Disco」を思い出すな。ディスクが焼かれているときに実際に煙が出てたんだよね。普通のウィンドウだけど、上から半透明の煙がもくもく出てる感じ。確か、スティーブ・ジョブズがステージで見せて、すごく気に入ってたよね。
その観察は問題の核心を突いてると思う。 > だから、変な形のウィンドウはプロトタイプは簡単だけど、磨き上げるのは高くつくんだよね。 > でも、Win32 APIプログラミングには問題がある。実際、カスタムウィンドウはすべて自分でやらなきゃいけなくて、すべてのWindowsメッセージを制御する必要があるから、脆弱なんだ。ソフトウェアは小さなチーム(しばしば1人)によって作られていた。リリースされたら、数年間は安定していることが期待されていた。優先順位は、大きなチームが迅速に構築し、反復できるソフトウェアにシフトした。人気のあるソフトウェアが毎週、毎日更新されるのは珍しくない。これがスキューモーフィックデザインが進化のレースに負けた理由でもあると思う。すべてはその場で調整され、整列され、最適化される必要があるから。インターフェースを再編成したいなら、多くのアセットを再作成する可能性がある。フラットデザインは互換性があり、モジュール式だし。これらのトレンドは、エージェンティックなソフトウェアエンジニアリングによってますます強くなるだろうね。インセンティブはスケールとスピードを報酬するから。これが、今では大きな装飾の施された手彫りの木製家具があまり見られなくなった理由でもある。今はすべてフラットパックのパーティクルボードになっちゃった。
> Win32 APIプログラミングには問題があるんだ。実際、カスタムウィンドウを作るってことは、自分で全てをコントロールしなきゃいけないってことだし、それは脆弱なんだよね。でも、実際にはそうじゃないよ。デフォルトのウィンドウプロシージャに委譲して、必要な部分だけカスタマイズすればいいんだ。確かに、ウィンドウが三角形になったら、リサイズの仕方を考えなきゃいけないけど、全てをゼロから再実装する必要はないよ。新しいデザインに合わないデフォルトだけを変更すればいいんだから。
> スキューモーフィックデザイン そのアイデア自体は悪くないかもしれないけど、実際に見た実装は視覚的にごちゃごちゃしてて…正直、醜かったよね。あれから離れられてよかったと思う。もっと良くできたかもしれないけど、それにはたくさんの努力が必要だよね。
> おそらく、これがスキューモーフィックデザインが進化のレースに負けた理由でもあるんだろうね。すべてをその場で調整したり、整列させたり、最適化したりしなきゃいけないから。インターフェースを再編成したいなら、多くのアセットを再作成する必要があるってこと。フラットデザインは互換性があってモジュール化されてる。別の言い方をすれば、コスト削減だね。ソフトウェアを安く作るために、優れた使いやすさを諦めたんだ。車のボタンやノブを大きなiPadに置き換えるのもその一例。
何年もWindowsアプリを作ってきたけど、問題はWin32インターフェースが実際には90%のコントロールしか得られないことなんだ。一番大変だったのは、プロダクトマネージャーがアプリのカラーテーマを新機能として必須だと言った時だね。その時、特定の条件下でスクロールバーが通常のメッセージループを完全にバイパスすることがあるってことが分かるんだ。特に印象に残っているのは、システムが提供するMessageBoxの内部が変更できる形で公開されていなかったから、ゼロから再作成したことだね。
わー、ゼロから何かを再現するのは痛いかもね。QTでうまくいくかな?
昔のことを思い出すと、Win32フックを使ってMessageBoxを変更できたよね。HCBT_CREATEWNDを使うとMessageBoxのHWNDが取得できて、それをサブクラス化(Win32の意味で)して自分のWndProcを挿入できるんだ。そうすれば、もう自分のダイアログになるってわけ。
これを深く掘り下げて、ネイティブのWin32 API関数を迂回する覚悟があれば、いろいろ改造できるよ。User32.dllに実装されているいくつかのものは、他のUser32.dll関数に適切なAPIコールを返さないから、Win32U.dllを迂回する必要があるんだ。
Win32には賛成だけど、あの変な形やカスタムスキンは、今の「ビジュアルアイデンティティ=ブランディング」という考え方の先駆けで、デスクトップコンピューティングの体験を何年もダメにしてきた原因の一つなんだ。だから、ReactやElectron、半端なウィジェットライブラリが増えてしまったんだよね。あれは特定のコントロールに見えないけど、みんなぼやけたテキストレンダリングや不安定なアクセシビリティ、情報密度の低さ、独自のバグを抱えてる。ブレンダーやアーダー、あるいはトレーディングプラットフォームやゲームを作るんじゃない限り、個別のGUIは優先順位の最後にすべきだと思うよ。
違うね。Electronアプリは見た目が同じで、実際には「個性的」にするための努力をあまりしてない。とにかくアプリを早くリリースしたいからElectronを選んでるんだ。一方で、Win32時代の「スキン」、例えばビデオプレーヤーやWinampで使われていたものはすごく個性的だった。スタイルがはっきりしてるんだよね。好みじゃないかもしれないけど、少なくともユニークなテイストを作ろうとしてた。Electronアプリにはテイストがないよ。フラットデザインとできるだけ少ないUIをテイストと見なすなら別だけど。現代のOSはサーバーや企業向けで、個人的なものじゃない。Linuxは当時ハッカーやシスアド向けだったし、今はサーバー向けだね。Windowsチームが自分たちの製品を台無しにするためにすごい努力をしてるから、Linuxがデスクトップに戻ってきてる。Win 3.1からWin XPの時代が本当の「個人的な」時代だよ。
普通のビジネスアプリに関しては、そうだね、同意する。でも、iPadで使ってるアプリの中には、実質的にこの「カスタムスキン」UIをフルスクリーンラッパーの要素に移行させて、すごく見た目も動作も素晴らしいものもあるよ。あと、超個性的なUIを持ってるスマートデバイスや、現代の車、画面のあるオーディオ関連のものなんかもたくさんある。だけど、そう、もし一連のフォームを見せるだけなら、プラットフォームのネイティブコントロールを使って、完璧に動かすべきだね。
1990年代後半のWindowsアプリケーションを思い出すなぁ。特に、フラットベッドスキャナーを操作するためのアプリみたいな、ちょっと変わったやつは、独自のテーマを持とうと頑張ってたよね。でも、2005年頃には業界が興味を失った感じがする。そこからしばらくしてSilverlightプログラマーの仕事を始めて、WPFを学ぶことになったんだけど、WPFにはテーマを設定するための機能があって、結構考えられてたんだよね(例えば、ボタンを斜めにするのも簡単だったり)。でも、あんまり使われてなかった気がする。業界が進化しちゃったんだろうね。最近、Office '98を使うことになったんだけど、Clippyとかでデスクトップを占拠しようとして、Windows 11でもそれをやろうとしてる。Office '98のボーダーレスウィンドウは今見るとちょっと変だけど、ちゃんと動いてるよ。
HiDPIも理由の一つだと思う。これらは「96 DPI」の時代のもので、どの画面でもピクセルが同じに見えたんだ。ピクセルアートを2倍(または3倍)で描いて、読み込み時に縮小することはできるけど、そんなに簡単じゃないよね。それに、現代のアプリのRAM使用量の一因は、各ウィンドウのためにフルバックストアが必要なことなんだ。「本当の」Win32時代、例えばWindows 95やXP、Win7のクラシックモードでは、フロントバッファに直接描画してたから、ピクセルごとの追加RAM/VRAM使用はなかったんだ。もちろん、ちらついて見た目は悪かったけど、速くて安かった。
ウィンドウの透明度は、すべての主要なプラットフォームで利用可能で、それをサポートするクロスプラットフォームのライブラリもあるよ。例えばSDL3: https://wiki.libsdl.org/SDL3/SDL_WINDOW_TRANSPARENT(描画用、ウィンドウの表面ピクセルが透明なところで下のウィンドウを見えるようにする)、https://wiki.libsdl.org/SDL3/SDL_SetWindowShape(入力用、表面ピクセルが完全に透明なところでポインターイベントを下のウィンドウに通す)。俺は画像(プレビュー)ビューアでこれを(悪用して)使ってる。画像をパン&ズームしてもウィンドウの境界が制限せずに、常に画像にフィットさせるためにね。その間に下のウィンドウが見えるようにしてる(透明なフルスクリーンオーバーレイ): https://github.com/shatsky/lightning-image-viewer