6
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

WinUI3でWindowSize(Width/Height)を設定する拡張メソッド

Last updated at Posted at 2025-11-03

Extension method to set Height/Width for a Window in WinUI3
Extension method to set Window Size for a Window in WinUI3

C# Advent Calendar 2025 2日目。

いいねが1年間付かない場合、この記事は削除されます。

多分誰でも真っ先に思いつくであろうネタ。
WinUI3の最初のネタはこれに決めていた。

WinUI3はWPFと違ってHeight,Widthプロパティがないです。
image.png

直接プロパティを設定できないとか確かになんか嫌!ってのあるし。

そこで呼び出しをWPF並みに簡単にしよう!ってなると、やっぱり拡張メソッドだよねっていう。

出来ればWPFと同じようにプロパティに設定する感じにしたいんだけど、技術的にはちょっと無理が出てしまうのでもうコレで手打ちかなみたいな。

↦ WPFと同じプロパティスタイルに出来ました。

以下の記事を参考にしている

Window Sizeの指定方法。

実装

なんかめんどいので珍しく自分で書いてみた。
Dpi変換だけAI出力を参考に

WindowExtensions.cs

WindowExtensions.cs

using Microsoft.UI;
using Microsoft.UI.Windowing;
using Microsoft.UI.Xaml;
using System;
using System.Runtime.InteropServices;
using Windows.Graphics;

namespace SampleApp1
{
    internal static class AppInstanceExtention
    {
        // Win32 API: 指定されたウィンドウのDPIを取得
        [DllImport("User32.dll")]
        private static extern int GetDpiForWindow(nint hwnd);

        private const double DefaultPixelsPerInch = 96.0;

          public static void SetAppWindowSize(this Window window, int height, int width)
        {

            IntPtr hWnd = WinRT.Interop.WindowNative.GetWindowHandle(window);
            WindowId myWndId = Microsoft.UI.Win32Interop.GetWindowIdFromWindow(hWnd);
            AppWindow appWindow = AppWindow.GetFromWindowId(myWndId);

+            // DPIスケールを計算(144 DPIなら1.5倍)
+            double dpiScale = GetDpiForWindow(hWnd) / DefaultPixelsPerInch;


+            // DIP → 実ピクセル変換
+            int widthInPixels = (int)(width * dpiScale);
+            int heightInPixels = (int)(height * dpiScale);


            if (appWindow != null)
            {
                appWindow.Resize(new SizeInt32(widthInPixels, heightInPixels));
            }


        }
    }
}


呼び出し

 public MainWindow()
 {
     InitializeComponent();

     this.SetAppWindowSize(300, 600);
 }

WinUI3はWindowsOSに依存しない設計なので、Window HandleなどのWin32API固有機能に依存した設計になっている。
 →つまりLinuxとか他のOSでは使えない。
だからWindow Handleも必要。
現状ではこれが限界らしい。

Resize()ResizeClient()

AI実装はResizeClient()を提示しており、自分でを参考に書いたのとほぼ変わらなかったが、WPFのWindowSizeプロパティはClientSizeを変更しているので後者が正しい

実際の画面サイズ

  • ReSizeの場合
    300x600 縦x横
    左がWPFでXAMLで指定したもの 
    右がWinUI3で拡張メソッド。わずかにずれるが然程気にならないだろう。

image.png

  • ClientSize()の場合
    あんまり変わっていない。
    image.png

なぜSizeが変わらないのか?(AI推定)

  • 理由①: WinUI 3のAppWindowが既定で “無枠ウィンドウ” 相当になっている

WinUI 3 (特にDesktopアプリテンプレート) の Window は、古いWin32スタイルの枠やシステムタイトルバーを使っていない場合が多い。
つまり「非クライアント領域(NC領域)」がほぼ存在しない。
→ 枠線もタイトルバーも描画をWinUI側で再現しているため、Resize() も ResizeClient() も結果的に同じクライアントサイズを指す。

  • 理由②: AppWindowPresenterKind.Overlapped の内部実装が OS 依存

WinUI 3 は DWM(Desktop Window Manager)の「ウィンドウ装飾」を利用しています。
しかし DWM 側では DPI スケールごとに装飾の境界サイズを調整しており、
ResizeClient() に渡したサイズを結局そのまま Resize() に転送しているケースがある。
(つまり内部的に同じ呼び出しになっている)

  • 理由③: 同一DPIモニター上では非クライアント領域がゼロ補正扱い

WinUI チームの実装方針で、AdjustWindowRectExForDpi 相当の処理が、
ウィンドウが「既に作成済み」かつ「OverlappedPresenter上」であればスキップされる。
その結果、DPI補正なしの「生ピクセル」が両者で一致。

おまけ WPFと同じプロパティスタイルにするには

結局AIが出してきた。
悪くはないんだけど、後述する理由で非推奨となります。

そもそも拡張メソッドなんだからそのまま呼べますね(笑)

個別にheight/Widthを設定する拡張メソッド

SetHeight()、 GetHeight()をそれぞれ定義している。こうすることで正確なスケールのWindowSizeを取得できる。
ついでにユーザー操作後のサイズなんかも反映する必要があるため、こういう実装に。

WindowExtensions.cs
using Microsoft.UI.Windowing;
using Microsoft.UI.Xaml;
using System;
using System.Runtime.InteropServices;
using Windows.Graphics;

namespace SampleApp1
{
    public static class WindowExtensions
    {
        private const double DefaultPixelsPerInch = 96.0;

        [DllImport("User32.dll")]
        private static extern int GetDpiForWindow(nint hwnd);

+        private static AppWindow? GetAppWindow(Window window)
        {
            var hwnd = WinRT.Interop.WindowNative.GetWindowHandle(window);
            return AppWindow.GetFromWindowId(Microsoft.UI.Win32Interop.GetWindowIdFromWindow(hwnd));
        }

+        private static double GetDpiScale(Window window)
        {
            nint hwnd = WinRT.Interop.WindowNative.GetWindowHandle(window);
            return GetDpiForWindow(hwnd) / DefaultPixelsPerInch;
        }

+        public static void SetWidth(this Window window, double widthInDips)
        {
            var appWindow = GetAppWindow(window);
            if (appWindow == null) return;

            var dpi = GetDpiScale(window);
            int newWidth = (int)(widthInDips * dpi);

            appWindow.ResizeClient(new SizeInt32(newWidth, appWindow.ClientSize.Height));
        }

+       public static void SetHeight(this Window window, double heightInDips)
        {
            var appWindow = GetAppWindow(window);
            if (appWindow == null) return;

            var dpi = GetDpiScale(window);
            int newHeight = (int)(heightInDips * dpi);

            appWindow.ResizeClient(new SizeInt32(appWindow.ClientSize.Width, newHeight));
        }

+        public static double GetWidth(this Window window)
        {
            var appWindow = GetAppWindow(window);
            if (appWindow == null) return 0;
            var dpi = GetDpiScale(window);
            return appWindow.ClientSize.Width / dpi;
        }

+        public static double GetHeight(this Window window)
        {
            var appWindow = GetAppWindow(window);
            if (appWindow == null) return 0;
            var dpi = GetDpiScale(window);
            return appWindow.ClientSize.Height / dpi;
        }
    }
}

を書いたうえで、

カスタムWindowを継承してラッパーを作ります
但し、XAMLを使わないWindowという制限が付く。

不要です。そのままCodeBehindに直書きでOK。

public class MainWindow : Window
{
    public double Width
    {
        get => this.GetWidth();
        set => this.SetWidth(value);
    }

    public double Height
    {
        get => this.GetHeight();
        set => this.SetHeight(value);
    }

  Public MainWindow()
    {
       initializeComponent();
       this.Height = 300;
       this.Width = 600
    } 


に出来ます。

無理やりMainWindowを生成することは可能ですが、そうするとホットリロードによるXAML編集が使えません
というかWinformと同様にUIをCode定義しないといけなくなる。しかも自分で。
ちょっとそれは嫌だなと。

とりあえずこんな感じで使えるのではないでしょうか。
AIがラッパーを使う傾向にあるようで、軽く騙された。

あとがき

実装はAIでサクッと。
珍しく自分でコピペして組みました。

Codeそのものはある程度暗記したり、アルゴリズムを頭に叩き込んだりする。頻繁に忘れるが。

既読で記事化したものがあれば自分で実装するようにしている。

それにしても拡張メソッドとか誰でも思いつきそうだけど意外とないのかもしれない。海外記事だとまあ議論はされてる。
不便なので当たり前か。

もっと重くて(AIを駆使しても)実装がかなり大変なものを記事化する予定なんだけど、その前に軽いネタをやりたかったという動機です。

6
4
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?