43 Stimmen

Wie kann ich eine WPF-Größe in physische Pixel umwandeln?

Was ist der beste Weg, um eine WPF (auflösungsunabhängige) Breite und Höhe in physische Bildschirm-Pixel zu konvertieren?

Ich zeige WPF-Inhalt in einem WinForms-Formular (über ElementHost) und versuchen, einige Sizing-Logik auszuarbeiten. Ich habe es funktioniert gut, wenn das Betriebssystem auf die Standard 96 dpi ausgeführt wird. Aber es wird nicht funktionieren, wenn das Betriebssystem auf 120 dpi oder eine andere Auflösung festgelegt ist, weil dann ein WPF-Element, das seine Breite als 96 berichtet wird tatsächlich 120 Pixel breit, soweit WinForms betroffen ist.

Ich konnte keine Einstellungen für "Pixel pro Zoll" in System.Windows.SystemParameters finden. Ich bin sicher, ich könnte die WinForms-Äquivalent (System.Windows.Forms.SystemInformation) verwenden, aber gibt es einen besseren Weg, dies zu tun (lesen: ein Weg mit WPF-APIs, anstatt mit WinForms-APIs und manuell tun die Mathematik)? Was ist der "beste Weg" zu konvertieren WPF "Pixel" auf realen Bildschirm Pixel?

EDIT: Ich bin auch suchen, dies zu tun, bevor das WPF-Steuerelement auf dem Bildschirm angezeigt wird. Es sieht aus wie Visual.PointToScreen gemacht werden könnte, um mir die richtige Antwort zu geben, aber ich kann es nicht verwenden, weil das Steuerelement noch nicht geparented ist und ich InvalidOperationException "Dieses Visual ist nicht mit einer PresentationSource verbunden" erhalten.

75voto

Ray Burns Punkte 60870

Umwandlung einer bekannten Größe in Gerätepixel

Wenn Ihr visuelles Element bereits mit einer PresentationSource verbunden ist (z. B. Teil eines Fensters, das auf dem Bildschirm sichtbar ist), wird die Transformation auf diese Weise gefunden:

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

Falls nicht, verwenden Sie HwndSource, um ein temporäres hWnd zu erstellen:

Matrix transformToDevice;
using(var source = new HwndSource(new HwndSourceParameters()))
  transformToDevice = source.CompositionTarget.TransformToDevice;

Beachten Sie, dass dies weniger effizient ist als die Konstruktion mit einem hWnd von IntPtr.Zero, aber ich halte es für zuverlässiger, weil das hWnd, das von HwndSource erstellt wird, an dasselbe Anzeigegerät angehängt wird wie ein tatsächlich neu erstelltes Fenster. Wenn verschiedene Anzeigegeräte unterschiedliche DPI-Werte haben, können Sie auf diese Weise sicher sein, dass Sie den richtigen DPI-Wert erhalten.

Sobald Sie die Transformation haben, können Sie jede Größe von einer WPF-Größe in eine Pixelgröße umwandeln:

var pixelSize = (Size)transformToDevice.Transform((Vector)wpfSize);

Umwandlung der Pixelgröße in Ganzzahlen

Wenn Sie die Pixelgröße in ganze Zahlen umwandeln möchten, können Sie dies einfach tun:

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

aber eine robustere Lösung wäre die von ElementHost verwendete:

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));

Ermitteln der gewünschten Größe eines UIElements

Um die gewünschte Größe eines UIElements zu erhalten, müssen Sie sicherstellen, dass es gemessen wird. In einigen Fällen wird es bereits gemessen, entweder weil:

  1. Sie haben es bereits gemessen
  2. Sie haben einen seiner Vorfahren gemessen, oder
  3. Sie ist Teil einer PresentationSource (z. B. in einem sichtbaren Fenster) und wird unter DispatcherPriority.Render ausgeführt, so dass Sie wissen, dass die Messung bereits automatisch erfolgt ist.

Wenn Ihr visuelles Element noch nicht vermessen wurde, sollten Sie Measure für das Steuerelement oder einen seiner Vorfahren aufrufen und die verfügbare Größe (oder new Size(double.PositivieInfinity, double.PositiveInfinity) wenn Sie die Größe an den Inhalt anpassen wollen:

element.Measure(availableSize);

Sobald die Messung abgeschlossen ist, muss die Matrix nur noch zur Transformation von DesiredSize verwendet werden:

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

Alles zusammenfügen

Hier ist eine einfache Methode, die zeigt, wie man die Pixelgröße eines Elements ermitteln kann:

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);
}

Beachten Sie, dass ich in diesem Code Measure nur aufrufe, wenn keine DesiredSize vorhanden ist. Dies ist eine bequeme Methode, um alles zu tun, hat aber mehrere Mängel:

  1. Es kann sein, dass das übergeordnete Element eine kleinere availableSize übergeben hätte
  2. Es ist ineffizient, wenn die tatsächliche DesiredSize gleich Null ist (sie wird wiederholt neu gemessen)
  3. Es können Fehler maskiert werden, die dazu führen, dass die Anwendung aufgrund eines unerwarteten Timings fehlschlägt (z. B. weil der Code bei oder über DispatchPriority.Render aufgerufen wird).

Aus diesen Gründen wäre ich geneigt, den Measure-Aufruf in GetElementPixelSize wegzulassen und dies einfach dem Client zu überlassen.

12voto

Lu55 Punkte 19425

Einfaches Verhältnis zwischen Screen.WorkingArea y SystemParameters.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 // ——
}

Dies funktioniert auch mit mehreren Monitoren.

9voto

Joe White Punkte 90737

Ich habe einen Weg gefunden, es zu tun, aber er gefällt mir nicht besonders:

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);
    // ...
}

Ich mag es nicht, weil (a) es einen Verweis auf System.Drawing erfordert, anstatt die WPF-APIs zu verwenden, und (b) ich die Berechnung selbst durchführen muss, was bedeutet, dass ich die Implementierungsdetails von WPF dupliziere. In .NET 3.5 muss ich das Ergebnis der Berechnung abschneiden, um mit dem übereinzustimmen, was ElementHost mit AutoSize=true tut, aber ich weiß nicht, ob dies in zukünftigen Versionen von .NET noch korrekt sein wird.

Das scheint zu funktionieren, also poste ich es, falls es anderen hilft. Aber wenn jemand eine bessere Antwort hat, bitte posten Sie es.

1voto

Micael Bergeron Punkte 1144

Ich habe gerade einen kurzen Blick in die ObjectBrowser und habe etwas sehr Interessantes gefunden, das Sie sich vielleicht ansehen sollten.

System.Windows.Form.AutoScaleMode hat es eine Eigenschaft namens DPI. Hier ist die Dokumentation, es könnte das sein, wonach Sie suchen:

p System.Windows.Forms.AutoScaleMode Dpi [ ] Mitglied von System.Windows.Forms.AutoScaleMode

Zusammenfassung: Die Kontrollen skalieren relativ zur der Bildschirmauflösung. Übliche Auflösungen sind 96 und 120 DPI.

Wenden Sie das auf Ihr Formular an, dann sollte es klappen.

{Genuss}

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X