java SWT入门:自定义背景透明且可鼠标拖动改变尺寸和位置的Composite

版权声明:本文为博主原创文章,转载请注明源地址。 https://blog.csdn.net/10km/article/details/53426108

下面的代码实现了一个透明可移动可改变尺寸的Composite窗体,如下图

鼠标点击窗口获取焦点,在获取焦点时会显示9个锚点用于改变窗口的位置和尺寸。 可以通过鼠标拖动锚点来改变窗口的位置或尺寸,也可以通过上下左右键来移动窗口

ActiveRectangle.java

package net.gdface.ui;


import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.wb.swt.SWTResourceManager;
import org.eclipse.swt.events.MouseMoveListener;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseAdapter;
import org.eclipse.swt.events.FocusAdapter;
import org.eclipse.swt.events.FocusEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Cursor;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.KeyAdapter;
import org.eclipse.swt.events.KeyEvent;

/**
 * 自定义透明窗口,
 * 窗口位置和尺寸可以通过鼠标和上下左右键修改
 * @author gudong
 */
public class ActiveRectangle extends Composite {
    /**
     * 锚点对象
     * @author guyadong
     */
    private class Anchor {
        /**
         * 描述锚点位置尺寸的矩形对象
         */
        final Rectangle rect=new Rectangle(0, 0, anchorSize, anchorSize);
        /**
         * 锚点的光标对象
         */
        final Cursor cusor;
        /**
         * 锚点位置计算的掩码
         */
        final Rectangle mask;
        Anchor(Cursor cusor, Rectangle mask) {
            this.cusor = cusor;
            this.mask=mask;
        }
    }

    /**
     * 焦点矩形边框颜色
     */
    private static Color focusRectColor=SWTResourceManager.getColor(SWT.COLOR_GREEN);
    /**
     * 非焦点矩形边框颜色
     */
    private static Color nofocusRectColor=SWTResourceManager.getColor(SWT.COLOR_RED);
    /**
     * 矩形边框线宽
     */
    private static int lineWidth=1; 
    /**
     * 锚点矩形尺寸
     */
    private static int anchorSize=8;
    /**
     * 上下左右键移动窗口的步长
     */
    private static int arrowStep=1;
    /**
     * 是否焦点
     */
    boolean focus=true;
    /**
     * 当前鼠标位置所在的锚点索引 {@link #anchors} 
     */
    private int anchorIndex=-1;
    // 光标定义
    private final Cursor CURSOR_SIZENESW=new Cursor(Display.getDefault(),SWT.CURSOR_SIZENESW);
    private final Cursor CURSOR_SIZENS=new Cursor(Display.getDefault(),SWT.CURSOR_SIZENS);
    private final Cursor CURSOR_SIZENWSE=new Cursor(Display.getDefault(),SWT.CURSOR_SIZENWSE);
    private final Cursor CURSOR_SIZEWE=new Cursor(Display.getDefault(),SWT.CURSOR_SIZEWE);
    private final Cursor CURSOR_SIZEALL=new Cursor(Display.getDefault(),SWT.CURSOR_SIZEALL);    
    //lt+-------+-------+rt
    //  |       t       |
    //  |               |
    //  |       c       |
    //l +       +       +r
    //  |               |
    //  |               |
    //  |       b       |
    //lb+-------+-------+rb
    enum AnchorType{
        LT,T,RT,L,C,R,LB,B,RB
    }
    /**
     * 9个锚点位置({@link Anchor})对象数组(按从左到右从上到下顺序排序)
     */
    private final Anchor[] anchors=new Anchor[]{
            new Anchor(CURSOR_SIZENWSE,new Rectangle(1,1,-1,-1)),
            new Anchor(CURSOR_SIZENS,new Rectangle(0,1,0,-1)),
            new Anchor(CURSOR_SIZENESW,new Rectangle(0,1,1,-1)),
            new Anchor(CURSOR_SIZEWE,new Rectangle(1,0,-1,0)),
            new Anchor(CURSOR_SIZEALL,new Rectangle(1,1,0,0)),
            new Anchor(CURSOR_SIZEWE,new Rectangle(0,0,1,0)),
            new Anchor(CURSOR_SIZENESW,new Rectangle(1,0,-1,1)),
            new Anchor(CURSOR_SIZENS,new Rectangle(0,0,0,1)),
            new Anchor(CURSOR_SIZENWSE,new Rectangle(0,0,1,1))};;   
    /**
     * 矩形修改标记,为true时,处于鼠标拖动修改窗口位置和尺寸的状态,
     * 鼠标位于9个锚点区域({@link Anchor})之一,且鼠标键按下(mouseDown)
     */
    private boolean onMouseModfied;
    /**
     * 矩形修改状态下({@link #onMouseModfied}为true),记录上次鼠标位置
     */
    private Point lastPos;
    /**
     * 真实的窗口位置和尺寸
     */
    protected Rectangle originalBounds;
    private boolean originalBoundsNeedupdate=false;

    /**
     * 父窗口x轴缩放比例
     */
    private float zoomY;
    /**
     * 父窗口y轴缩放比例
     */
    private float zoomX;
    /**
     * Create the composite.
     * @param parent
     * @param isFocus 是否为焦点对象
     * @param originalBounds 对象原始位置尺寸
     */
    public ActiveRectangle(Composite parent, boolean isFocus, Rectangle originalBounds) {
        // 透明背景样式
        super(parent, SWT.TRANSPARENT);
        this.originalBounds = originalBounds;
        this.focus=isFocus;     
        addPaintListener(new PaintListener() {
            public void paintControl(PaintEvent e) {
                // 绘制透明矩形窗口
                e.gc.setLineStyle(SWT.LINE_SOLID);
                e.gc.setLineWidth(lineWidth);
                e.gc.setForeground(focus?focusRectColor:nofocusRectColor);
                Point size = getSize();             
                e.gc.drawRectangle(new Rectangle(0,0,--size.x,--size.y));

                if(focus){
                    Color bc = e.gc.getBackground();                    
                    e.gc.setBackground(focusRectColor);

                    e.gc.fillRectangle(anchors[AnchorType.LT.ordinal()].rect.x, anchors[AnchorType.LT.ordinal()].rect.y, anchorSize, anchorSize);
                    e.gc.fillRectangle(anchors[AnchorType.T.ordinal()].rect.x, anchors[AnchorType.T.ordinal()].rect.y, anchorSize, anchorSize);
                    e.gc.fillRectangle(anchors[AnchorType.RT.ordinal()].rect.x, anchors[AnchorType.RT.ordinal()].rect.y, anchorSize, anchorSize);                   
                    e.gc.fillRectangle(anchors[AnchorType.L.ordinal()].rect.x, anchors[AnchorType.L.ordinal()].rect.y, anchorSize, anchorSize);
                    // 画中心十字
                    e.gc.drawLine(anchors[AnchorType.C.ordinal()].rect.x+(anchorSize>>1), anchors[AnchorType.C.ordinal()].rect.y, anchors[AnchorType.C.ordinal()].rect.x+(anchorSize>>1), anchors[AnchorType.C.ordinal()].rect.y+anchorSize);
                    e.gc.drawLine(anchors[AnchorType.C.ordinal()].rect.x, anchors[AnchorType.C.ordinal()].rect.y+(anchorSize>>1), anchors[AnchorType.C.ordinal()].rect.x+anchorSize, anchors[AnchorType.C.ordinal()].rect.y+(anchorSize>>1));
                    e.gc.fillRectangle(anchors[AnchorType.R.ordinal()].rect.x, anchors[AnchorType.R.ordinal()].rect.y, anchorSize, anchorSize);
                    e.gc.fillRectangle(anchors[AnchorType.LB.ordinal()].rect.x, anchors[AnchorType.LB.ordinal()].rect.y, anchorSize, anchorSize);
                    e.gc.fillRectangle(anchors[AnchorType.B.ordinal()].rect.x, anchors[AnchorType.B.ordinal()].rect.y, anchorSize, anchorSize);
                    e.gc.fillRectangle(anchors[AnchorType.RB.ordinal()].rect.x, anchors[AnchorType.RB.ordinal()].rect.y, anchorSize, anchorSize);                   
                    // 恢复背景色
                    e.gc.setBackground(bc);                 
                }
            }
        });
        // 当获取/失去焦点时重绘窗口
        addFocusListener(new FocusAdapter() {
            @Override
            public void focusGained(FocusEvent e) {
                focus=true;
                // 将对象设置到顶层,否则无法响应所有的mouseMove事件
                moveAbove(null);
                ((Control)(e.widget)).redraw();
            }
            @Override
            public void focusLost(FocusEvent e) {
                focus=false;
                ((Control)(e.widget)).redraw();
            }
        });
        // 实现上下左右键移动窗口
        addKeyListener(new KeyAdapter() {
            @Override
            public void keyPressed(KeyEvent e) {
                switch(e.keyCode){
                case SWT.ARROW_LEFT:
                    modify(-arrowStep,0,anchors[AnchorType.C.ordinal()].mask);
                    break;
                case SWT.ARROW_RIGHT:
                    modify(arrowStep,0,anchors[AnchorType.C.ordinal()].mask);
                    break;
                case SWT.ARROW_UP:
                    modify(0,-arrowStep,anchors[AnchorType.C.ordinal()].mask);
                    break;
                case SWT.ARROW_DOWN:
                    modify(0,arrowStep,anchors[AnchorType.C.ordinal()].mask);
                    break;
                default:
                }
            }
        });
        // 加入mouseMove事件处理,实现鼠标拖动锚点改变窗口位置和尺寸
        addMouseMoveListener(new MouseMoveListener() {
            private final Cursor defCursor=getCursor();         
            public void mouseMove(MouseEvent e) {
                if(focus){
                    if(onMouseModfied){
                        // 计算鼠标移动的距离
                        Point pos=((Control)(e.widget)).toDisplay(e.x, e.y);
                        modify(pos.x-lastPos.x,pos.y-lastPos.y,anchors[anchorIndex].mask);
                        // 记录当前鼠标位置,以供下次mouseMove消息时计算鼠标移动距离
                        lastPos=pos;
                    }else   if((anchorIndex=anchored(e.x, e.y))>=0&&anchors[anchorIndex].cusor!=getCursor()){
                        // 当鼠标位置某个锚点时,更新鼠标形状
                        setCursor(anchors[anchorIndex].cusor);
                    }else   if(defCursor!=getCursor())
                        // 鼠标不在锚点时,恢复默认鼠标形状
                        setCursor(defCursor);
                }else 
                    anchorIndex=-1;
            }
        });
        // 配合MouseMoveListener实现鼠标改变窗口位置和尺寸
        addMouseListener(new MouseAdapter() {           
            @Override
            public void mouseDown(MouseEvent e) {
                if(anchorIndex>=0){
                    // 记录当前鼠标位置,以供mouseMove消息时计算鼠标移动距离
                    lastPos=((Control)(e.widget)).toDisplay(e.x, e.y);
                    onMouseModfied=true;
                }else if(!isFocusControl()){
                    // 当前对象非焦点对象时,设置当前对象为焦点对象
                    setFocus();
                }
            }
            @Override
            public void mouseUp(MouseEvent e) {
                onMouseModfied=false;
            }
        });

    }

    @Override
    protected void checkSubclass() {
        // Disable the check that prevents subclassing of SWT components
    }
    /**
     * 改变窗口位置和尺寸
     * @param x x轴移动距离
     * @param y y轴移动距离
     * @param mask 锚点位置计算的掩码
     */
    private void modify(int x,int y,Rectangle mask){
        // 计算出新的窗口位置和尺寸
        Rectangle bound = getBounds();
        bound.x+=x*mask.x;
        bound.y+=y*mask.y;
        bound.width+=x*mask.width;
        bound.height+=y*mask.height;
        // 设置新的窗口位置
        this.originalBoundsNeedupdate=true;
        setBounds(bound);
        this.originalBoundsNeedupdate=false;
    }
    @Override
    public void dispose() {
        // 释放光标资源
        CURSOR_SIZENESW.dispose();
        CURSOR_SIZENS.dispose();
        CURSOR_SIZENWSE.dispose();
        CURSOR_SIZEWE.dispose();
        CURSOR_SIZEALL.dispose();
        super.dispose();
    }

    /**
     * 根据窗口尺寸计算各个{@link Anchor}的位置
     */
    private void setAnchors(){
        Point size = getSize();
        setAnchorPos(anchors[AnchorType.LT.ordinal()].rect,0, 0);
        setAnchorPos(anchors[AnchorType.T.ordinal()].rect,(size.x-anchorSize)>>1, 0);
        setAnchorPos(anchors[AnchorType.RT.ordinal()].rect,size.x-anchorSize, 0);
        setAnchorPos(anchors[AnchorType.L.ordinal()].rect,0, (size.y-anchorSize)>>1);
        setAnchorPos(anchors[AnchorType.C.ordinal()].rect,(size.x-anchorSize)>>1, (size.y-anchorSize)>>1);
        setAnchorPos(anchors[AnchorType.R.ordinal()].rect,size.x-anchorSize, (size.y-anchorSize)>>1);
        setAnchorPos(anchors[AnchorType.LB.ordinal()].rect,0, size.y-anchorSize);
        setAnchorPos(anchors[AnchorType.B.ordinal()].rect,(size.x-anchorSize)>>1, size.y-anchorSize);
        setAnchorPos(anchors[AnchorType.RB.ordinal()].rect,size.x-anchorSize, size.y-anchorSize);
    }

    private void setAnchorPos(Rectangle anchorRect,int x,int y){
        anchorRect.x=x;
        anchorRect.y=y;
    }

    /**
     * 计算指定的坐标(x,y)是否位于某个{@link Anchor}区域
     * @param x
     * @param y
     * @return 返回 {@link #anchors}的索引值,不在任何一个{@link Anchor}则返回-1
     */
    private int anchored(int x,int y){
        for(int i=0;i<anchors.length;++i){if(anchors[i].rect.contains(x, y))return i;}
        return -1;
    }

    private void setOriginalBounds(Rectangle rect){
        // 如果originalBounds没有初始化,则用第一次调用setBounds的参数来初始化
        if(null==originalBounds)
            originalBounds=rect;
        else if(originalBoundsNeedupdate)// 非zoom状态下更新将当前窗口的尺寸换算后更新originalBounds
            originalBounds=unZoom(zoomX,zoomY);
    }
    @Override
    public void setBounds(int x, int y, int width, int height) {
        super.setBounds(x, y, width, height);
        // 重写setBounds方法,修改窗口位置和尺寸时同步重新计算所有锚点位置
        setAnchors();
        setOriginalBounds(new Rectangle(x, y, width, height));
    }

    @Override
    public void setBounds(Rectangle rect) {
        super.setBounds(rect);
        // 重写setBounds方法,修改窗口位置和尺寸时同步重新计算所有锚点位置
        setAnchors();
        setOriginalBounds(rect);
    }

    /**
     * 根据zoom缩放比例缩放矩形显示
     * @param zoomX x轴缩放比例
     * @param zoomY y轴缩放比例
     */
    protected void zoom(float zoomX, float zoomY) {
        if(null!=originalBounds){
            this.zoomX=zoomX;
            this.zoomY=zoomY;
            setBounds(new Rectangle((int)(originalBounds.x*zoomX),(int)(originalBounds.y*zoomY),(int)(originalBounds.width*zoomX),(int)(originalBounds.height*zoomY)));
        }
    }

    /**
     * x,y轴等比例缩放
     * @param rect
     * @param zoom x,y轴缩放比例
     * @see #zoom(float, float)
     */
    void zoom(Rectangle rect, float zoom) {     
        zoom(zoom,zoom);
    }

    /**
     * 根据zoom缩放比例返回缩放前的矩形对象({@link Rectangle})
     * @param zoomX x轴缩放比例
     * @param zoomY y轴缩放比例
     * @return
     */
    protected Rectangle unZoom(float zoomX, float zoomY) {  
        Rectangle bounds = getBounds();
        return new Rectangle((int)(bounds.x/zoomX),(int)(bounds.y/zoomY),(int)(bounds.width/zoomX),(int)(bounds.height/zoomY));     
    }

    /**
     * x,y轴等比例缩放
     * @param zoom x,y轴缩放比例
     * @return
     * @see #unZoom(float, float)
     */
    protected Rectangle unZoom(float zoom) {    
        return unZoom(zoom,zoom);       
    }

}

测试代码 TestActiveRectangle.java

package testwb;

import org.eclipse.swt.widgets.Dialog;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;

import net.gdface.ui.ActiveRectangle;

import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Group;
import org.eclipse.swt.widgets.Control;

public class TestActiveRectangle extends Dialog {

    protected Object result;
    protected Shell shell;

    /**
     * Create the dialog.
     * @param parent
     * @param style
     */
    public TestActiveRectangle(Shell parent, int style) {
        super(parent, style);
        setText("SWT Dialog");
    }

    /**
     * Open the dialog.
     * @return the result
     */
    public Object open() {
        createContents();
        shell.open();
        shell.layout();
        Display display = getParent().getDisplay();
        while (!shell.isDisposed()) {
            if (!display.readAndDispatch()) {
                display.sleep();
            }
        }
        return result;
    }

    /**
     * Create contents of the dialog.
     */
    private void createContents() {
        shell = new Shell(getParent(), getStyle());
        shell.setSize(463, 366);
        shell.setText(getText());

        Group group = new Group(shell, SWT.NONE);
        group.setBounds(10, 10, 423, 302);
        Composite composite2 = new ActiveRectangle(group, false, null);
        composite2.setBounds(81, 102, 51, 90);

        Composite composite1 = new ActiveRectangle(group, true, null);
        composite1.setBounds(24, 141, 147, 90);
        group.setTabList(new Control[]{composite2, composite1});
        shell.setTabList(new Control[]{group});

    }
}

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

扫码关注云+社区

领取腾讯云代金券