web-dev-qa-db-fra.com

Comment convertir une taille WPF en pixels physiques?

Quelle est la meilleure façon de convertir une largeur et une hauteur WPF (indépendantes de la résolution) en pixels d'écran physiques?

J'affiche le contenu WPF dans un formulaire WinForms (via ElementHost) et j'essaie de trouver une logique de dimensionnement. Je le fais fonctionner correctement lorsque le système d'exploitation fonctionne à 96 dpi par défaut. Mais cela ne fonctionnera pas lorsque le système d'exploitation est défini sur 120 dpi ou une autre résolution, car un élément WPF qui indique sa largeur à 96 aura en fait 120 pixels de large en ce qui concerne WinForms.

Je n'ai trouvé aucun paramètre "pixels par pouce" sur System.Windows.SystemParameters. Je suis sûr que je pourrais utiliser l'équivalent WinForms (System.Windows.Forms.SystemInformation), mais y a-t-il une meilleure façon de le faire (lire: une façon d'utiliser les API WPF, plutôt que d'utiliser les API WinForms et de faire manuellement les calculs)? Quelle est la "meilleure façon" de convertir les "pixels" WPF en pixels réels?

EDIT: Je cherche également à le faire avant que le contrôle WPF ne soit affiché à l'écran. Il semble que Visual.PointToScreen pourrait être fait pour me donner la bonne réponse, mais je ne peux pas l'utiliser, car le contrôle n'est pas encore parenté et j'obtiens InvalidOperationException "Ce visuel n'est pas connecté à une source de présentation".

37
Joe White

Transformer une taille connue en pixels de l'appareil

Si votre élément visuel est déjà attaché à un PresentationSource (par exemple, il fait partie d'une fenêtre visible à l'écran), la transformation se trouve de cette façon:

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

Sinon, utilisez HwndSource pour créer un hWnd temporaire:

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

Notez que cela est moins efficace que la construction à l'aide d'un hWnd de IntPtr.Zero mais je le considère plus fiable car le hWnd créé par HwndSource sera attaché au même périphérique d'affichage qu'une fenêtre nouvellement créée. De cette façon, si différents périphériques d'affichage ont des DPI différents, vous êtes sûr d'obtenir la bonne valeur DPI.

Une fois que vous avez la transformation, vous pouvez convertir n'importe quelle taille d'une taille WPF en taille de pixel:

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

Conversion de la taille des pixels en entiers

Si vous souhaitez convertir la taille des pixels en entiers, vous pouvez simplement faire:

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

mais une solution plus robuste serait celle utilisée par 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));

Obtenir la taille souhaitée d'un UIElement

Pour obtenir la taille souhaitée d'un UIElement, vous devez vous assurer qu'il est mesuré. Dans certaines circonstances, il sera déjà mesuré, soit parce que:

  1. Vous l'avez déjà mesuré
  2. Vous avez mesuré l'un de ses ancêtres, ou
  3. Il fait partie d'une source de présentation (par exemple, il se trouve dans une fenêtre visible) et vous exécutez sous DispatcherPriority.Render afin que vous sachiez que la mesure s'est déjà produite automatiquement.

Si votre élément visuel n'a pas encore été mesuré, vous devez appeler Measure sur le contrôle ou l'un de ses ancêtres selon le cas, en transmettant la taille disponible (ou new Size(double.PositivieInfinity, double.PositiveInfinity) si vous souhaitez dimensionner au contenu:

element.Measure(availableSize);

Une fois la mesure effectuée, il suffit d'utiliser la matrice pour transformer la DesiredSize:

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

Tout mettre ensemble

Voici une méthode simple qui montre comment obtenir la taille en pixels d'un élément:

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

Notez que dans ce code, j'appelle Measure uniquement si aucun DesiredSize n'est présent. Cela fournit une méthode pratique pour tout faire, mais présente plusieurs lacunes:

  1. Il se peut que le parent de l'élément soit passé dans une taille disponible plus petite
  2. Il est inefficace si la taille réelle souhaitée est nulle (elle est réévaluée à plusieurs reprises)
  3. Il peut masquer les bogues d'une manière qui entraîne l'échec de l'application en raison d'un timing inattendu (par exemple, le code appelé à ou au-dessus de DispatchPriority.Render).

Pour ces raisons, je serais enclin à omettre l'appel Measure dans GetElementPixelSize et à laisser le client le faire.

65
Ray Burns

Proportion simple entre Screen.WorkingArea et 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 // ——
}

Cela fonctionne également très bien avec plusieurs moniteurs.

8
Lu55

J'ai trouvé un moyen de le faire, mais je ne l'aime pas beaucoup:

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

Je ne l'aime pas car (a) il nécessite une référence à System.Drawing, plutôt que d'utiliser des API WPF; et (b) je dois faire le calcul moi-même, ce qui signifie que je duplique les détails d'implémentation de WPF. Dans .NET 3.5, je dois tronquer le résultat du calcul pour faire correspondre ce que ElementHost fait avec AutoSize = true, mais je ne sais pas si cela sera toujours exact dans les futures versions de .NET.

Cela semble fonctionner, donc je le poste au cas où cela aiderait les autres. Mais si quelqu'un a une meilleure réponse, veuillez poster.

8
Joe White

Je viens de faire une recherche rapide dans le ObjectBrowser et j'ai trouvé quelque chose de très intéressant, vous voudrez peut-être le vérifier.

System.Windows.Form.AutoScaleMode, il a une propriété appelée DPI. Voici la documentation, c'est peut-être ce que vous cherchez:

const public System.Windows.Forms.AutoScaleMode Dpi = 2 Membre de System.Windows.Forms.AutoScaleMode

Résumé: contrôle l'échelle par rapport à la résolution d'affichage. Les résolutions courantes sont 96 et 120 DPI.

Appliquez cela à votre formulaire, cela devrait faire l'affaire.

{prendre plaisir}

1
Micael Bergeron