كيف يمكنك تنفيذ التكبير/التصغير الحساس للموضع داخل JScrollPane؟
-
02-07-2019 - |
سؤال
أحاول تنفيذ تكبير حساس للموضع داخل ملف JScrollPane
.ال JScrollPane
يحتوي على مكون مخصص paint
من شأنها أن ترسم نفسها داخل أي مساحة مخصصة لها - لذا فإن التكبير/التصغير سهل مثل استخدام MouseWheelListener
الذي يغير حجم المكون الداخلي كما هو مطلوب.
لكنني أريد أيضًا تكبير (أو تصغير) نقطة ما لإبقاء هذه النقطة مركزية قدر الإمكان ضمن العرض المكبر (أو - الخارج) الناتج (وهذا ما أشير إليه بالتكبير "الحساس للموضع")، مشابه كيفية عمل التكبير في خرائط جوجل.أنا متأكد من أن هذا قد تم القيام به عدة مرات من قبل - هل يعرف أحد الطريقة "الصحيحة" للقيام بذلك ضمن Java Swing؟.هل سيكون من الأفضل اللعب معه Graphic2D
التحولات بدلا من استخدام JScrollPanes
?
نموذج التعليمات البرمجية التالي:
package test;
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import javax.swing.*;
public class FPanel extends javax.swing.JPanel {
private Dimension preferredSize = new Dimension(400, 400);
private Rectangle2D[] rects = new Rectangle2D[50];
public static void main(String[] args) {
JFrame jf = new JFrame("test");
jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jf.setSize(400, 400);
jf.add(new JScrollPane(new FPanel()));
jf.setVisible(true);
}
public FPanel() {
// generate rectangles with pseudo-random coords
for (int i=0; i<rects.length; i++) {
rects[i] = new Rectangle2D.Double(
Math.random()*.8, Math.random()*.8,
Math.random()*.2, Math.random()*.2);
}
// mouse listener to detect scrollwheel events
addMouseWheelListener(new MouseWheelListener() {
public void mouseWheelMoved(MouseWheelEvent e) {
updatePreferredSize(e.getWheelRotation(), e.getPoint());
}
});
}
private void updatePreferredSize(int n, Point p) {
double d = (double) n * 1.08;
d = (n > 0) ? 1 / d : -d;
int w = (int) (getWidth() * d);
int h = (int) (getHeight() * d);
preferredSize.setSize(w, h);
getParent().doLayout();
// Question: how do I keep 'p' centered in the resulting view?
}
public Dimension getPreferredSize() {
return preferredSize;
}
private Rectangle2D r = new Rectangle2D.Float();
public void paint(Graphics g) {
super.paint(g);
g.setColor(Color.red);
int w = getWidth();
int h = getHeight();
for (Rectangle2D rect : rects) {
r.setRect(rect.getX() * w, rect.getY() * h,
rect.getWidth() * w, rect.getHeight() * h);
((Graphics2D)g).draw(r);
}
}
}
المحلول
تم اختبار هذا، يبدو أنه يعمل ...
private void updatePreferredSize(int n, Point p) {
double d = (double) n * 1.08;
d = (n > 0) ? 1 / d : -d;
int w = (int) (getWidth() * d);
int h = (int) (getHeight() * d);
preferredSize.setSize(w, h);
int offX = (int)(p.x * d) - p.x;
int offY = (int)(p.y * d) - p.y;
setLocation(getLocation().x-offX,getLocation().y-offY);
getParent().doLayout();
}
تحديث
وهنا توضيح:النقطة p
هو موقع الفأرة بالنسبة إلى FPanel
.نظرًا لأنك تقوم بقياس حجم اللوحة، فإن موقعها p
(بالنسبة لحجم اللوحة) سيتم قياسه بنفس العامل.من خلال طرح الموقع الحالي من الموقع الذي تم قياسه، يمكنك الحصول على مقدار "تحولات" النقطة عند تغيير حجم اللوحة.ثم يتعلق الأمر ببساطة بتغيير موقع اللوحة في جزء التمرير بنفس المقدار في الاتجاه المعاكس لوضعه p
مرة أخرى تحت مؤشر الماوس.
نصائح أخرى
إليك إعادة هيكلة بسيطة لحل @Kevin K:
private void updatePreferredSize(int wheelRotation, Point stablePoint) {
double scaleFactor = findScaleFactor(wheelRotation);
scaleBy(scaleFactor);
Point offset = findOffset(stablePoint, scaleFactor);
offsetBy(offset);
getParent().doLayout();
}
private double findScaleFactor(int wheelRotation) {
double d = wheelRotation * 1.08;
return (d > 0) ? 1 / d : -d;
}
private void scaleBy(double scaleFactor) {
int w = (int) (getWidth() * scaleFactor);
int h = (int) (getHeight() * scaleFactor);
preferredSize.setSize(w, h);
}
private Point findOffset(Point stablePoint, double scaleFactor) {
int x = (int) (stablePoint.x * scaleFactor) - stablePoint.x;
int y = (int) (stablePoint.y * scaleFactor) - stablePoint.y;
return new Point(x, y);
}
private void offsetBy(Point offset) {
Point location = getLocation();
setLocation(location.x - offset.x, location.y - offset.y);
}
يتعين على MouseWheelListener أيضًا تحديد موقع المؤشر وتحريكه إلى منتصف JScrollPane وضبط xmin/ymin وxmax/ymax للمحتوى المراد عرضه.
أعتقد أن smt مثل هذا يجب أن يعمل ...
private void updatePreferredSize(int n, Point p) {
double d = (double) n * 1.08;
d = (n > 0) ? 1 / d : -d;
int w = (int) (getWidth() * d);
int h = (int) (getHeight() * d);
preferredSize.setSize(w, h);
// Question: how do I keep 'p' centered in the resulting view?
int parentWdt = this.getParent( ).getWidth( ) ;
int parentHgt = this.getParent( ).getHeight( ) ;
int newLeft = p.getLocation( ).x - ( p.x - ( parentWdt / 2 ) ) ;
int newTop = p.getLocation( ).y - ( p.y - ( parentHgt / 2 ) ) ;
this.setLocation( newLeft, newTop ) ;
getParent().doLayout();
}
يحرر:تغيرت بضعة أشياء.