40

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

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

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

編集:WPFコントロールが画面に表示される前に、これを行うことも検討しています。Visual.PointToScreen を作成して正しい答えを得ることができるように見えますが、コントロールがまだ親になっておらず、InvalidOperationException "This Visual is not connected to a PresentationSource" が発生するため、使用できません。

4

4 に答える 4

71

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

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

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

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

Matrix transformToDevice;
using(var source = new HwndSource(new HwndSourceParameters()))
  transformToDevice = source.CompositionTarget.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));

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

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

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

ビジュアル要素がまだ測定されていない場合は、必要に応じてコントロールまたはその先祖の 1 つで 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 呼び出しを省略し、クライアントに任せたいと思います。

于 2010-08-10T15:08:34.870 に答える
10

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 // ——
}

これは、複数のモニターでも問題なく機能します。

于 2013-09-05T12:50:51.063 に答える
9

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

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の将来のバージョンでも正確であるかどうかはわかりません。

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

于 2010-07-20T01:45:37.877 に答える
1

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

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

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

概要: ディスプレイの解像度に合わせてスケールを制御します。一般的な解像度は 96 および 120 DPI です。

それをフォームに適用すると、うまくいくはずです。

{楽しい}

于 2010-08-13T15:21:18.350 に答える