2010-07-20 55 views
35

Cách tốt nhất để chuyển đổi chiều rộng và chiều rộng WPF (độ phân giải độc lập) thành pixel màn hình vật lý là gì?Làm cách nào để chuyển đổi kích thước WPF thành pixel vật lý?

Tôi đang hiển thị nội dung WPF trong Biểu mẫu WinForms (qua ElementHost) và cố gắng tìm ra một số logic định cỡ. Tôi đã có nó hoạt động tốt khi hệ điều hành đang chạy ở 96 dpi mặc định. Nhưng nó sẽ không hoạt động khi hệ điều hành được thiết lập đến 120 dpi hoặc một số độ phân giải khác, bởi vì sau đó một phần tử WPF báo cáo chiều rộng của nó là 96 sẽ thực sự là 120 pixel rộng như xa như WinForms là có liên quan.

Tôi không thể tìm thấy bất kỳ cài đặt "pixel trên inch" nào trên System.Windows.SystemParameters. Tôi chắc rằng tôi có thể sử dụng WinForms tương đương (System.Windows.Forms.SystemInformation), nhưng là có một cách tốt hơn để làm điều này (đọc: một cách sử dụng API WPF, hơn là sử dụng WinForms API và tự làm toán)? "Cách tốt nhất" để chuyển đổi WPF "pixel" thành pixel màn hình thực tế là gì?

EDIT: Tôi cũng đang tìm cách thực hiện việc này trước khi điều khiển WPF được hiển thị trên màn hình. Có vẻ như Visual.PointToScreen có thể được thực hiện để cung cấp cho tôi câu trả lời đúng, nhưng tôi không thể sử dụng nó, bởi vì kiểm soát không được parented và tôi nhận được InvalidOperationException "Visual này không được kết nối với một PresentationSource".

+0

Đây có phải là những gì bạn đang tìm kiếm không? http://books.google.com/books?id=xEDKq_QlZ18C&lpg=PA7&ots=lxNG6ADXPa&dq=WPF%20pixels%20to%20real%20screen%20pixels&pg=PA7#v=onepage&q=WPF%20pixels%20to%20real%20screen% 20pixels & f = false – Ragepotato

+2

@Ragepotato, đó chỉ là tổng quan về khái niệm - nó không cung cấp mã để chuyển đổi từ WPF thành pixel và không giải thích cách xử lý các điều kiện biên. –

Trả lời

57

chuyển một kích thước được biết đến với thiết bị pixel

Nếu yếu tố hình ảnh của bạn đã được gắn liền với một PresentationSource (ví dụ, nó là một phần của một cửa sổ có thể nhìn thấy trên màn hình), biến đổi được tìm thấy theo cách này :

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

Nếu không, sử dụng HwndSource để tạo ra một hWnd tạm thời:

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

không e rằng điều này kém hiệu quả hơn việc xây dựng bằng cách sử dụng hWnd của IntPtr.Zero nhưng tôi cho rằng nó đáng tin cậy hơn vì hWnd được tạo bởi HwndSource sẽ được gắn vào cùng một thiết bị hiển thị như một Cửa sổ mới được tạo ra thực tế. Bằng cách đó, nếu các thiết bị hiển thị khác nhau có các DPI khác nhau, bạn chắc chắn sẽ nhận được giá trị DPI phù hợp.

Một khi bạn đã biến đổi, bạn có thể chuyển đổi bất kỳ kích thước từ một kích thước WPF đến một kích thước điểm ảnh:

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

Chuyển đổi kích thước pixel để nguyên

Nếu bạn muốn chuyển đổi pixel kích thước thành số nguyên, bạn có thể chỉ cần thực hiện:

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

nhưng giải pháp mạnh mẽ hơn là giải pháp được sử dụng bởi 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)); 

Bắt kích thước mong muốn của một UIElement

Để có được kích thước mong muốn của một UIElement bạn cần phải chắc chắn rằng nó được đo. Trong một số trường hợp nó sẽ tự động được đo, hoặc vì:

  1. Bạn đo nó đã
  2. Bạn đo một trong những tổ tiên của nó, hoặc
  3. Nó là một phần của một PresentationSource (ví dụ như nó là trong một cửa sổ có thể nhìn thấy) và bạn đang thực hiện dưới đây DispatcherPriority.Render để bạn biết đo lường đã xảy ra tự động.

Nếu yếu tố hình ảnh của bạn chưa được đo nào, bạn nên gọi Đo trên điều khiển hoặc một trong những tổ tiên của nó như là thích hợp, đi qua trong kích thước có sẵn (hoặc new Size(double.PositivieInfinity, double.PositiveInfinity) nếu bạn muốn kích thước nội dung:

element.Measure(availableSize); 

Khi đo được thực hiện, tất cả những gì cần thiết là sử dụng ma trận để chuyển đổi DesiredSize:

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

Đưa nó tất cả cùng nhau

Đây là một phương pháp đơn giản cho thấy làm thế nào để có được kích thước pixel của một phần tử:

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

Lưu ý rằng trong mã này tôi gọi Đo chỉ nếu không có DesiredSize có mặt. Điều này cung cấp một phương pháp thuận tiện để làm tất cả mọi thứ nhưng có một số thiếu sót:

  1. Nó có thể là cha mẹ của phần tử sẽ trôi qua trong một nhỏ availableSize
  2. Nó không hiệu quả nếu DesiredSize thực tế là zero (nó được remeasured nhiều lần)
  3. Nó có thể che giấu lỗi trong một cách mà làm cho các ứng dụng để thất bại do thời gian bất ngờ (ví dụ. các mã được gọi tại hoặc cao hơn DispatchPriority.Render)

Vì những lý do này, tôi sẽ nghiêng để bỏ qua cuộc gọi Đo lường trong Nhận ElementPixelSize và chỉ để cho khách hàng làm điều đó.

+1

Câu trả lời này là quá mức cần thiết, vì trong câu hỏi tôi đã nói tôi đã có chiều rộng và chiều cao. (cười) –

+1

Và nó trả về 'double's cho Chiều rộng và Chiều cao. Tôi muốn kích thước thực tế mà ElementHost sẽ kích thước, có nghĩa là 'int', được làm tròn theo cùng cách mà ElementHost thực hiện. –

+0

Tôi đã thêm một phần giải thích cách chuyển đổi sang các số nguyên theo cùng cách như ElementHost. –

8

Tôi tìm thấy một cách để làm điều đó, nhưng tôi không thích nó nhiều:

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

Tôi không thích nó vì (a) nó đòi hỏi một tham chiếu đến System.Drawing, thay vì sử dụng API WPF; và (b) Tôi phải tự mình làm toán, điều đó có nghĩa là tôi đang nhân đôi chi tiết thực hiện của WPF. Trong .NET 3.5, tôi phải cắt bớt kết quả của phép tính để khớp với những gì mà ElementHost thực hiện với AutoSize = true, nhưng tôi không biết liệu điều này vẫn chính xác trong các phiên bản sau của .NET.

Điều này dường như hoạt động, vì vậy tôi đăng nó trong trường hợp nó giúp người khác. Nhưng nếu có ai có câu trả lời tốt hơn, xin vui lòng, gửi đi.

+0

Không có phương pháp WPF gốc để có được màn hình DPI, và điều này là sạch hơn so với một số phương pháp Win32 tôi đã thấy: (Tuy nhiên, bạn có thể thiết lập một chuyển đổi quy mô cấp cao nhất (để đi từ WPF "pixel" trở lại máy chủ/màn hình pixel) một khi bạn làm việc ra tỷ lệ chuyển đổi chính xác.Nếu điều khiển WPF được lưu trữ trong một môi trường WinForms dưới sự kiểm soát của bạn, bạn có thể có thể vượt qua trong các thông tin từ máy chủ - vẫn icky –

+0

Làm việc cho tôi trong Win 7 & 8.1. Sử dụng giá trị 'cửa sổ' riêng tư của 'NotifyIcon' là' hwnd được chuyển đến 'FromHwnd'. Giá trị riêng là rủi ro mong manh cho những thay đổi MS trong tương lai, nhưng nó nhanh và có vẻ hoạt động tốt. Ví dụ về CodeProject] (http://www.codeproject.com/Articles/37912/Embedding-NET-Controls-to-NotifyIcon-Balloon-Toolt), nhưng nếu không có [câu trả lời SO này] (http://stackoverflow.com)/a/8033317/135114) bao gồm mã phản chiếu để lấy 'NativeWindow.Handle'. – Daryn

+0

Vấn đề lớn với điều này là, nó không hoạt động với nhiều màn hình nếu màn hình sử dụng chế độ chia tỷ lệ riêng biệt. Sigh - nếu chỉ .NET (hoặc Windows cho rằng vấn đề) đã có một cách dễ dàng để có được chế độ Scale cho mỗi màn hình. –

1

Chỉ cần tìm kiếm nhanh chóng trong ObjectBrowser và tìm thấy điều gì đó khá thú vị, bạn có thể muốn kiểm tra.

System.Windows.Form.AutoScaleMode, nó có thuộc tính được gọi là DPI. Dưới đây là các tài liệu, nó có thể là những gì bạn đang tìm kiếm:

const công System.Windows.Forms.AutoScaleMode Dpi = 2 viên System.Windows.Forms.AutoScaleMode

Tóm tắt: Điều khiển tỷ lệ tương đối với độ phân giải màn hình. Các độ phân giải phổ biến là là 96 và 120 DPI.

Áp dụng điều đó cho biểu mẫu của bạn, bạn nên thực hiện thủ thuật.

{} hưởng

7

tỷ lệ đơn giản giữa 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 // —— 
} 

này hoạt động tốt với nhiều màn hình là tốt.

+0

Làm việc hoàn hảo cho tôi. Giải pháp đơn giản! – JoeMjr2

+0

Cảm ơn, nhưng hãy đổi tên enum thành một cái gì đó như "enum Axis {Width, Height}", điều này sẽ dễ hiểu hơn nhiều. – Alex

Các vấn đề liên quan