如何使用Java“正确地”检测显示器的DPI?

13

我有一个绘制规则的应用程序:

public class Rule extends JComponent
{
  public static final long serialVersionUID=26362862L;
//  public static final int INCH=Toolkit.getDefaultToolkit().getScreenResolution();
  public static final int INCH=(int)(Toolkit.getDefaultToolkit().getScreenResolution()*1.15);  // Auto adjust this 1.15 ?
  public static final int HORIZONTAL=0;
  public static final int VERTICAL=1;
  public static final int SIZE=35;
  public int orientation;
  public boolean isMetric;
  private int increment;
  private int units;
//  private Color rule_color=new Color(0,135,235);
  private Color rule_color=new Color(120,170,230);
  static JFrame frame=new JFrame("Rule");
  static Dimension screenSize=Toolkit.getDefaultToolkit().getScreenSize();  // 1600 x 1200  ,  1024 x 768
  static JPanel rulerPanel=new JPanel(new BorderLayout());

  public Rule(int o,boolean m)
  {
    orientation=o;
    isMetric=m;
    setIncrementAndUnits();
  }

  public void setIsMetric(boolean isMetric)
  {
    this.isMetric=isMetric;
    setIncrementAndUnits();
    repaint();
  }

  private void setIncrementAndUnits()
  {
    if (isMetric)
    {
      units=(int)((double)INCH/(double)2.54); // dots per centimeter
      increment=units;
    }
    else
    {
      units=INCH;
      increment=units/2;
    }
  }

  public boolean isMetric() { return this.isMetric; }

  public int getIncrement() { return increment; }

  public void setPreferredHeight(int ph) { setPreferredSize(new Dimension(SIZE,ph)); }

  public void setPreferredWidth(int pw) { setPreferredSize(new Dimension(pw,SIZE)); }

  public void setColor(Color color) { rule_color=color; }

  public void paintComponent(Graphics g)
  {
    Rectangle drawHere=g.getClipBounds();

    // Fill clipping area with blue-gray.
    g.setColor(rule_color);
    g.fillRect(drawHere.x,drawHere.y,drawHere.width,drawHere.height);

    // Do the ruler labels in a small font that's black.
    g.setFont(new Font("SansSerif",Font.PLAIN,10));
    g.setColor(Color.black);

    // Some vars we need.
    int end=0;
    int start=0;
    int tickLength=0;
    String text=null;

    // Use clipping bounds to calculate first tick and last tick location.
    if (orientation==HORIZONTAL)
    {
      start=(drawHere.x/increment)*increment;
      end=(((drawHere.x+drawHere.width)/increment)+1)*increment;
    }
    else
    {
      start=(drawHere.y/increment)*increment;
      end=(((drawHere.y+drawHere.height)/increment)+1)*increment;
    }

    // Make a special case of 0 to display the number within the rule and draw a units label.
    if (start==0)
    {
      text=Integer.toString(0)+(isMetric?" cm":" in");
      tickLength=10;
      if (orientation==HORIZONTAL)
      {
        g.drawLine(0,SIZE-1,0,SIZE-tickLength-1);
        g.drawString(text,2,21);
      }
      else
      {
        g.drawLine(SIZE-1,0,SIZE-tickLength-1,0);
        g.drawString(text,9,10);
      }
      text=null;
      start=increment;
    }

    // ticks and labels
    for (int i=start;i<end;i+=increment)
    {
      if (i%units==0)
      {
        tickLength=10;
        text=Integer.toString(i/units);
      }
      else
      {
        tickLength=5;
        text=null;
      }

      if (tickLength!=0)
      {
        if (orientation==HORIZONTAL)
        {
          g.drawLine(i,SIZE-1,i,SIZE-tickLength-1);
          if (text!=null) g.drawString(text,i-3,21);
        }
        else
        {
          g.drawLine(SIZE-1,i,SIZE-tickLength-1,i);
          if (text!=null) g.drawString(text,9,i+3);
        }
      }
    }
  }

  // Create the GUI and show it. For thread safety, this method should be invoked from the event-dispatching thread.
  static void createAndShowGUI()
  {
    rulerPanel.setPreferredSize(new Dimension(570,78));

    Rule cmView=new Rule(Rule.HORIZONTAL,true);
    int H=35;
    cmView.setPreferredHeight(H);
    cmView.setColor(new Color(128,200,235));
    JScrollPane cmScrollPane=new JScrollPane();
    cmScrollPane.setViewportBorder(BorderFactory.createLineBorder(Color.black));
    cmScrollPane.setColumnHeaderView(cmView);

    rulerPanel.add("North",cmScrollPane);

    Rule inchView=new Rule(Rule.HORIZONTAL,true);
    inchView.setPreferredHeight(H);
    inchView.setColor(new Color(168,200,235)); //238,238,238
    inchView.setIsMetric(false);
    JScrollPane inchScrollPane=new JScrollPane();
    inchScrollPane.setViewportBorder(BorderFactory.createLineBorder(Color.black));
    inchScrollPane.setColumnHeaderView(inchView);

    rulerPanel.add("South",inchScrollPane);
    frame.getContentPane().add(rulerPanel);
    frame.addWindowListener(new WindowAdapter()
    {
      public void windowActivated(WindowEvent e) { }
      public void windowClosed(WindowEvent e) { }
      public void windowClosing(WindowEvent e) { System.exit(0); }
      public void windowDeactivated(WindowEvent e) { }
      public void windowDeiconified(WindowEvent e) { rulerPanel.repaint(); }
      public void windowGainedFocus(WindowEvent e) { rulerPanel.repaint(); }
      public void windowIconified(WindowEvent e) { }
      public void windowLostFocus(WindowEvent e) { }
      public void windowOpening(WindowEvent e) { rulerPanel.repaint(); }
      public void windowOpened(WindowEvent e) { }
      public void windowResized(WindowEvent e) { rulerPanel.repaint(); }
      public void windowStateChanged(WindowEvent e) { rulerPanel.repaint(); }
    });
    frame.pack();
    frame.setBounds((screenSize.width-rulerPanel.getWidth())/2,(screenSize.height-rulerPanel.getHeight())/2-19,rulerPanel.getWidth()+20,rulerPanel.getHeight()+38);
    frame.setVisible(true);
  }

  public static void main(String[] args)
  {
    // Schedule a job for the event-dispatching thread : creating and showing this application's GUI.
    SwingUtilities.invokeLater(new Runnable() { public void run() { createAndShowGUI(); } });
  }
}

这个方法在我旧的17英寸和20英寸显示器上运行良好,但是现在我注意到在我的新27英寸液晶显示器上它不准确了,所以我必须更改第四行来获取更准确的值,Toolkit.getDefaultToolkit().getScreenResolution()不应该获取准确的DPI吗? 为什么它不正确,为了使我的应用程序能够在其他具有不同显示大小和DPI的计算机上工作,如何自动调整手动输入的1.15?

PS:不仅我的Java应用程序的DPI不准确,而且当我查看Windows 7上的其他几个应用程序(例如paint.exe或paint.net)时,它们的英寸和厘米也不正确。 您可以在您的计算机上尝试它们。


操作系统甚至显示驱动程序报告的DPI信息通常不准确。对于真正的一般情况,你可能没有什么好运气。 - Carl Norum
我不是很确定,但是尝试使用这个获取屏幕大小:GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDisplayMode();,而不是 Toolkit.getDefaultToolkit().getScreenSize();。最后一个语句将给出显示模式,使用它来获取高度和宽度。 - Favonius
@Favonius:谢谢,但我得到这些信息后,如何获得我需要的准确DPI呢?数字看起来像这样:Default_device 宽度:2560 高度:1440 刷新率:59 位深度:32, 基本上是我从getScreenSize()获得的相同信息。 - Frank
从这个链接(http://weblogs.java.net/blog/kirillcool/archive/2007/03/java_on_the_des.html)可以看出,目前在Vista中高DPI支持的实现存在一些问题,尤其是在144+范围内并不完美。关于Win7没有提到任何信息。 - Favonius
1个回答

8

驱动问题。

真正的DPI只有驱动程序知道,它会向操作系统报告,然后再向Java和其他应用程序报告。既然不仅Java的比例错误,那就必须是驱动程序的问题。

这是一个古老的问题。许多图形应用程序都有一个全局设置“屏幕DPI”,用户可以根据其实际监视器进行调整。

此外,由于您正在使用Java编写,要记住某些操作系统无法告诉Java真正的DPI(因为它们自己也不知道)。因此,需要DPI配置设置更加清晰明了。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接