web-dev-qa-db-ja.com

WPFサイズを物理ピクセルに変換するにはどうすればよいですか?

WPF(解像度に依存しない)の幅と高さを物理的な画面ピクセルに変換する最良の方法は何ですか?

WinFormsフォームで(ElementHostを介して)WPFコンテンツを表示し、サイジングロジックを実行しようとしています。 OSがデフォルトの96 dpiで実行されている場合、正常に動作します。ただし、OSが120 dpiまたは他の解像度に設定されている場合は機能しません。その場合、幅を96として報告するWPF要素は、WinFormsに関する限り実際には120ピクセルの幅になるためです。

System.Windows.SystemParametersで「インチあたりのピクセル」設定が見つかりませんでした。同等のWinForms(System.Windows.Forms.SystemInformation)を使用できると確信していますが、これを実行するより良い方法があります(WinForms APIを使用して手動で計算するのではなく、WPF APIを使用する方法をお読みください)? WPFの「ピクセル」を実際のスクリーンピクセルに変換する「最良の方法」は何ですか?

編集:私はまた、WPFコントロールが画面に表示される前にこれを行うことを探しています。 Visual.PointToScreenで正しい答えが得られるように見えますが、コントロールがまだペアレント化されておらず、「このVisualはPresentationSourceに接続されていません」という理由でInvalidOperationExceptionが発生するため、使用できません。

36
Joe White

既知のサイズをデバイスピクセルに変換する

視覚要素がすでにPresentationSourceにアタッチされている場合(たとえば、画面に表示されるウィンドウの一部である場合)、変換は次の方法で検出されます。

_var source = PresentationSource.FromVisual(element);
Matrix transformToDevice = source.CompositionTarget.TransformToDevice;
_

そうでない場合は、HwndSourceを使用して一時的なhWndを作成します。

_Matrix transformToDevice;
using(var source = new HwndSource(new HwndSourceParameters()))
  transformToDevice = source.TransformToDevice;
_

これはIntPtr.ZeroのhWndを使用して構築するよりも効率が悪いことに注意してください。ただし、HwndSourceによって作成されたhWndは、実際に新しく作成されたウィンドウと同じディスプレイデバイスに接続されるため、信頼性が高いと考えています。そうすれば、異なるディスプレイデバイスが異なるDPIを持っている場合、正しいDPI値を確実に取得できます。

変換が完了したら、WPFサイズからピクセルサイズに任意のサイズを変換できます。

_var pixelSize = (Size)transformToDevice.Transform((Vector)wpfSize);
_

ピクセルサイズを整数に変換する

ピクセルサイズを整数に変換する場合は、次のようにします。

_int pixelWidth = (int)pixelSize.Width;
int pixelHeight = (int)pixelSize.Height;
_

しかし、より堅牢なソリューションはElementHostで使用されるものです:

_int pixelWidth = (int)Math.Max(int.MinValue, Math.Min(int.MaxValue, pixelSize.Width));
int pixelHeight = (int)Math.Max(int.MinValue, Math.Min(int.MaxValue, pixelSize.Height));
_

IElementの目的のサイズを取得する

UIElementの目的のサイズを取得するには、それが測定されていることを確認する必要があります。状況によっては、次のいずれかの理由ですでに測定されています。

  1. あなたはすでにそれを測定しました
  2. 先祖の1つを測定したか、または
  3. これはPresentationSourceの一部であり(たとえば、表示されているウィンドウ内にあります)、DispatcherPriority.Renderの下で実行しているため、すでに自動的に測定が行われていることがわかります。

視覚要素がまだ測定されていない場合、コントロールまたはその祖先のいずれかで必要に応じてMeasureを呼び出し、利用可能なサイズ(またはコンテンツにサイズを設定する場合はnew Size(double.PositivieInfinity, double.PositiveInfinity)を渡す必要があります:

_element.Measure(availableSize);
_

測定が完了したら、必要なのはマトリックスを使用してDesiredSizeを変換することだけです。

_var pixelSize = (Size)transformToDevice.Transform((Vector)element.DesiredSize);
_

すべてをまとめて

要素のピクセルサイズを取得する方法を示す簡単な方法を次に示します。

_public Size GetElementPixelSize(UIElement element)
{
  Matrix transformToDevice;
  var source = PresentationSource.FromVisual(element);
  if(source!=null)
    transformToDevice = source.CompositionTarget.TransformToDevice;
  else
    using(var source = new HwndSource(new HwndSourceParameters()))
      transformToDevice = source.CompositionTarget.TransformToDevice;

  if(element.DesiredSize == new Size())
    element.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));

  return (Size)transformToDevice.Transform((Vector)element.DesiredSize);
}
_

このコードでは、DesiredSizeが存在しない場合にのみMeasureを呼び出すことに注意してください。これはすべてを行う便利な方法を提供しますが、いくつかの欠点があります。

  1. 要素の親がより小さいavailableSizeを渡した可能性があります
  2. 実際のDesiredSizeがゼロの場合は非効率的です(繰り返し再測定されます)
  3. 予期しないタイミング(たとえば、DispatchPriority.Render以上で呼び出されるコード)が原因でアプリケーションが失敗するような方法でバグをマスクする場合があります

これらの理由により、GetElementPixelSizeのMeasure呼び出しを省略して、クライアントに実行させたいと思います。

62
Ray Burns

Screen.WorkingAreaSystemParameters.WorkArea の間の単純な比率:

private double PointsToPixels (double wpfPoints, LengthDirection direction)
{
    if (direction == LengthDirection.Horizontal)
    {
        return wpfPoints * Screen.PrimaryScreen.WorkingArea.Width / SystemParameters.WorkArea.Width;
    }
    else
    {
        return wpfPoints * Screen.PrimaryScreen.WorkingArea.Height / SystemParameters.WorkArea.Height;
    }
}

private double PixelsToPoints(int pixels, LengthDirection direction)
{
    if (direction == LengthDirection.Horizontal)
    {
        return pixels * SystemParameters.WorkArea.Width / Screen.PrimaryScreen.WorkingArea.Width;
    }
    else
    {
        return pixels * SystemParameters.WorkArea.Height / Screen.PrimaryScreen.WorkingArea.Height;
    }
}

public enum LengthDirection
{
    Vertical, // |
    Horizontal // ——
}

これは、複数のモニターでも正常に機能します。

8
Lu55

私はそれを行う方法を見つけましたが、あまり好きではありません:

using (var graphics = Graphics.FromHwnd(IntPtr.Zero))
{
    var pixelWidth = (int) (element.DesiredSize.Width * graphics.DpiX / 96.0);
    var pixelHeight = (int) (element.DesiredSize.Height * graphics.DpiY / 96.0);
    // ...
}

(a)WPF APIを使用するのではなく、System.Drawingへの参照を必要とするため、私は好きではありません。 (b)自分で計算する必要があるため、WPFの実装の詳細を複製しています。 .NET 3.5では、ElementHostがAutoSize = trueで行うことと一致するように計算結果を切り捨てる必要がありますが、これが.NETの将来のバージョンでまだ正確かどうかはわかりません。

これは機能しているように見えるので、他の人に役立つ場合に備えて投稿しています。しかし、誰かがより良い答えを持っているなら、投稿してください。

8
Joe White

ObjectBrowserで簡単な検索を行ったところ、非常に興味深いものが見つかったので、チェックしてみてください。

System.Windows.Form.AutoScaleMode、DPIというプロパティがあります。ここにドキュメントがあります、それはあなたが探しているものかもしれません:

public const System.Windows.Forms.AutoScaleMode Dpi = 2 System.Windows.Forms.AutoScaleModeのメンバー

要約:ディスプレイの解像度に対するコントロールのスケール。一般的な解像度は96および120 DPIです。

それをフォームに適用すると、トリックが実行されるはずです。

{楽しい}

1
Micael Bergeron