java - 如何在 SWT 中绘制 Composite 的子元素?

标签 java drawing swt

我知道要在 Composite 上绘图,您可以添加一个绘图监听器,但这会导致在子项下绘图。如果我想在 children 的上方画画怎么办?

下面画了一条线,但是 subc 画在了它上面。

Composite c = new Composite(shell, 0);
c.setBackground(shell.getDisplay().getSystemColor(SWT.COLOR_BLUE));
c.setBounds(100, 100, 800, 600);

c.addPaintListener(new PaintListener() {
    public void paintControl(PaintEvent e) {
        e.gc.drawLine(0, 0, 500, 1000);
    }
});

final Composite subc = new Composite(c, 0);
subc.setLayout(null);
subc.setBounds(10, 10, 600, 400);

最佳答案

解决起来并不难:-)

您不仅需要将 SWT.Paint 监听器添加到 Composite,还需要将其添加到所有包含的子项(递归)。诀窍是为每个控件适本地映射坐标...

为了说明,我附上了我在许多项目中使用的一些代码。是的,我确实知道缺少类(class),但您或许可以从中得到启发。

/*******************************************************************************
 * Copyright (c) 2007, 2011 The RCP Company and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     The RCP Company - initial API and implementation
 *******************************************************************************/
package com.rcpcompany.uibindings.utils;

import org.eclipse.jface.fieldassist.ControlDecoration;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Event;

import com.rcpcompany.uibindings.IDisposable;
import com.rcpcompany.uibindings.internal.utils.PaintDecorationManager;

/**
 * Support for arbitrary decorations for {@link Control controls}.
 * <p>
 * <p>
 * Differs from {@link ControlDecoration} in a number of ways:
 * <ul>
 * <li>Support for cells in tables</li>
 * <li>Vastly more efficient when there are many decorations</li>
 * </ul>
 * 
 * @author Tonny Madsen, The RCP Company
 */
public interface IPaintDecoration {
    /**
     * Factory for {@link IPaintDecoration}.
     */
    final class Factory {
        private Factory() {
        }

        /**
         * Adds a new decoration.
         * <p>
         * The decoration is only added if the control of the decoration is non-<code>null</code>.
         * <p>
         * If the decoration supports the {@link IDisposable} interface, it will be notified when
         * the decoration is no longer in use - e.g. when the decoration is removed with
         * {@link #removeDecoration(IPaintDecoration)} or if the control is disposed.
         * 
         * @param decoration the new decoration
         */
        public static void addDecoration(IPaintDecoration decoration) {
            PaintDecorationManager.addDecoration(decoration);
        }

        public static IPaintDecoration paintRectangle(final Control c, Rectangle rect, final Color color) {
            final Rectangle r;
            if (rect == null) {
                final Point s = c.getSize();
                r = new Rectangle(0, 0, s.x, s.y);
            } else {
                r = rect;
            }
            final IPaintDecoration pd = new IPaintDecoration() {

                @Override
                public void paint(Event event, Rectangle area) {
                    // LogUtils.debug(this, event.widget + ": clip=" + event.gc.getClipping() +
                    // " area=" + area);
                    final Color oldForeground = event.gc.getForeground();
                    event.gc.setForeground(color);
                    event.gc.drawRectangle(area.x, area.y, area.width - 1, area.height - 1);
                    event.gc.setForeground(oldForeground);
                }

                @Override
                public Control getControl() {
                    return c;
                }

                @Override
                public Rectangle getArea() {
                    return r;
                }
            };
            addDecoration(pd);

            return pd;
        }

        /**
         * Removes an existing decoration.
         * 
         * @param decoration the decoration to remove
         */
        public static void removeDecoration(IPaintDecoration decoration) {
            PaintDecorationManager.removeDecoration(decoration);
        }
    };

    /**
     * The control of this decoration.
     * <p>
     * The control of a specific decoration may not change during the lifetime of the decoration.
     * 
     * @return the control
     */
    Control getControl();

    /**
     * Returns the area of this decoration in relation to the control.
     * 
     * @return the relative location
     */
    Rectangle getArea();

    /**
     * Paints the decoration.
     * 
     * @param area TODO
     */
    void paint(Event event, Rectangle area);
}

/*******************************************************************************
 * Copyright (c) 2007, 2011 The RCP Company and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     The RCP Company - initial API and implementation
 *******************************************************************************/
package com.rcpcompany.uibindings.internal.utils;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import org.eclipse.jface.util.Util;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Shell;

import com.rcpcompany.uibindings.IDisposable;
import com.rcpcompany.uibindings.internal.Activator;
import com.rcpcompany.uibindings.utils.IPaintDecoration;
import com.rcpcompany.utils.logging.LogUtils;

/**
 * Simple manager that can draw arbitrary "stuff".
 * <p>
 * A manager exists for each {@link Shell} of the application and is automatically disposed with the
 * shell.
 * <p>
 * Each decoration of the manager is handled internally via an {@link DecorationData} object.
 * 
 * @author Tonny Madsen, The RCP Company
 */
public final class PaintDecorationManager implements IDisposable, Listener {

    /**
     * The shell of this manager.
     */
    private final Shell myShell;

    /**
     * Cached platform flag for dealing with platform-specific issue:
     * https://bugs.eclipse.org/bugs/show_bug.cgi?id=219326 : Shell with custom region and
     * SWT.NO_TRIM still has border
     */
    private static boolean MAC = Util.isMac();

    /**
     * Constructs and returns a new manager.
     * 
     * @param shell the shell of the manager
     */
    private PaintDecorationManager(Shell shell) {
        myShell = shell;
        theManagers.put(getShell(), this);
        hookControl(getShell());
    }

    @Override
    public void dispose() {
        /*
         * Unhook all controls. This is automatically remove all decorations.
         */
        for (final Control c : myHookedControls.toArray(new Control[myHookedControls.size()])) {
            unhookControl(c);
        }
        theManagers.remove(getShell());
    }

    public static void addDecoration(IPaintDecoration decoration) {
        final PaintDecorationManager mng = getManager(decoration);
        if (mng != null) {
            mng.addADecoration(decoration);
        }
    }

    public static void removeDecoration(IPaintDecoration decoration) {
        final PaintDecorationManager mng = getManager(decoration);
        if (mng != null) {
            mng.removeADecoration(decoration);
        }
    }

    /**
     * Mapping of all decorations of this manager to internal data for the same decoration.
     */
    private final Map<IPaintDecoration, DecorationData> myDecorations = new HashMap<IPaintDecoration, DecorationData>();

    public void addADecoration(IPaintDecoration decoration) {
        DecorationData dd = myDecorations.get(decoration);
        if (dd == null) {
            dd = new DecorationData(decoration);
        }
        dd.update();
    }

    public void removeADecoration(IPaintDecoration decoration) {
        if (Activator.getDefault().TRACE_CONTROL_DECORATIONS) {
            LogUtils.debug(this, "control: " + decoration.getControl() + "@" + decoration.getControl().hashCode() + "/"
                    + decoration.getArea());
        }
        final DecorationData dd = myDecorations.get(decoration);
        if (dd == null) return;
        dd.dispose();
    }

    /**
     * Map with all defined managers indexed by the shell.
     */
    private static Map<Shell, PaintDecorationManager> theManagers = new HashMap<Shell, PaintDecorationManager>();

    /**
     * Returns the shell of the manager.
     * 
     * @return the shell
     */
    private Shell getShell() {
        return myShell;
    }

    /**
     * Returns the manager for the specified decoration.
     * <p>
     * Creates a new manager if none exists
     * 
     * @param decoration the decoration
     * @return the manager for the shell of the decoration
     */
    private static PaintDecorationManager getManager(IPaintDecoration decoration) {
        final Control c = decoration.getControl();
        if (c == null) return null;
        final Shell shell = c.getShell();
        PaintDecorationManager mng = theManagers.get(shell);
        if (mng == null) {
            mng = new PaintDecorationManager(shell);
        }

        return mng;
    }

    /**
     * The hooked controls of this manager.
     * <p>
     * A control is hooked when first referred in a decoration or a parent...
     * <p>
     * It is not unhooked until the control or this manager is disposed.
     */
    private final Set<Control> myHookedControls = new HashSet<Control>();

    /**
     * Hooks the specified control into this manager.
     * <p>
     * Also hooks all parent controls.
     * 
     * @param control the control
     */
    public void hookControl(Control control) {
        if (myHookedControls.contains(control)) return;

        myHookedControls.add(control);
        control.addListener(SWT.Dispose, this);
        control.addListener(SWT.Paint, this);

        if (control != getShell()) {
            hookControl(control.getParent());
        }
    }

    /**
     * Unhooks a specific control from the manager.
     * 
     * @param control the control
     */
    public void unhookControl(Control control) {
        if (!myHookedControls.contains(control)) return;
        myHookedControls.remove(control);
        if (!control.isDisposed()) {
            control.removeListener(SWT.Dispose, this);
            control.removeListener(SWT.Paint, this);
        }
        for (final DecorationData dd : myDecorations.values()
                .toArray(new DecorationData[myDecorations.values().size()])) {
            if (dd.getControl() == control) {
                dd.dispose();
            }
        }
    }

    @Override
    public void handleEvent(Event event) {
        // LogUtils.debug(this, ToStringUtils.toString(event));
        switch (event.type) {
        case SWT.Dispose:
            handleDispose(event);
            break;
        case SWT.Paint:
            handlePaint(event);
            break;
        default:
            break;
        }
    }

    /**
     * Handles the dispose event.
     * 
     * @param event the event
     */
    private void handleDispose(Event event) {
        if (event.widget == getShell()) {
            dispose();
            return;
        }
        unhookControl((Control) event.widget);
    }

    /**
     * Handles the paint event.
     * 
     * @param event the event
     */
    private void handlePaint(Event event) {
        final Control c = (Control) event.widget;
        final Display display = c.getDisplay();
        final Rectangle area = display.map(c, null, event.x, event.y, event.width, event.height);
        for (final DecorationData dd : myDecorations.values()) {
            if (dd.intersects(area)) {
                dd.paint(event);
            }
        }
    }

    /**
     * Manager internal decoration data for one decoration.
     */
    protected class DecorationData implements IDisposable {

        private final IPaintDecoration myDecoration;

        /**
         * The previous area painted by this decoration relative to the display.
         */
        private Rectangle myPreviousArea = null;

        /**
         * Set to true when the decoration is disposed
         */
        private boolean isDisposed = false;

        /**
         * Constructs and returns a new decoration data object
         * 
         * @param decoration he base decoration
         */
        protected DecorationData(IPaintDecoration decoration) {
            myDecoration = decoration;
            myDecorations.put(getDecoration(), this);
            if (Activator.getDefault().TRACE_CONTROL_DECORATIONS) {
                LogUtils.debug(this, "control: " + this);
            }

            getManager().hookControl(getDecoration().getControl());
        }

        /**
         * Returns the control of the decoration
         * 
         * @return the control
         */
        public Control getControl() {
            return getDecoration().getControl();
        }

        @Override
        public void dispose() {
            isDisposed = true;
            update();
            myDecorations.remove(getDecoration());
            if (Activator.getDefault().TRACE_CONTROL_DECORATIONS) {
                LogUtils.debug(this, "control: " + this);
            }
        }

        /**
         * Returns the manager of this decoration
         * 
         * @return the manager
         */
        public PaintDecorationManager getManager() {
            return PaintDecorationManager.this;
        }

        /**
         * Returns the decoration itself
         * 
         * @return the decoration
         */
        public IPaintDecoration getDecoration() {
            return myDecoration;
        }

        /**
         * Updates this decoration
         */
        public void update() {
            if (Activator.getDefault().TRACE_CONTROL_DECORATIONS) {
                LogUtils.debug(this, "control: " + this);
            }
            /*
             * Calculate new area
             */
            final Rectangle newArea = getDecorationRectangle(getShell());
            /*
             * Compare with last area and image
             */
            if ((newArea == null ? myPreviousArea == null : newArea.equals(myPreviousArea))) {
                if (Activator.getDefault().TRACE_CONTROL_DECORATIONS_VERBOSE) {
                    LogUtils.debug(this, "-- return");
                }
                return;
            }
            Rectangle r = null;
            if (myPreviousArea != null) {
                r = myPreviousArea;
                if (newArea != null) {
                    r.add(newArea);
                }
            } else {
                r = newArea;
            }
            myPreviousArea = newArea;
            if (r != null) {
                // LogUtils.debug(this, "redraw: " + r);
                getShell().redraw(r.x, r.y, r.width, r.height, true);
                if (Activator.getDefault().TRACE_CONTROL_DECORATIONS_VERBOSE) {
                    LogUtils.debug(this, "redraw " + r);
                }
            }
        }

        /**
         * Calculates the area taken by the image translated to a specified target control
         * 
         * @param c the target control or null for the Display
         * 
         * @return the {@link Rectangle} for the image or <code>null</code> if no image is specified
         */
        private Rectangle getDecorationRectangle(Control c) {
            final Control control = getDecoration().getControl();
            final Rectangle b = getDecoration().getArea();
            final Rectangle bounds = new Rectangle(b.x, b.y, b.width + 1, b.height + 1);

            return getShell().getDisplay().map(control, c, bounds);
        }

        /**
         * Paints this decoration.
         * 
         * @param event the {@link SWT#Paint} event
         */
        public void paint(Event event) {
            if (Activator.getDefault().TRACE_CONTROL_DECORATIONS_VERBOSE) {
                LogUtils.debug(this, "paint: " + event.widget);
            }
            // if (!shouldShowDecoration()) {
            // return;
            // }
            final Rectangle rect = getDecorationRectangle((Control) event.widget);
            if (Activator.getDefault().TRACE_CONTROL_DECORATIONS_VERBOSE) {
                LogUtils.debug(this, "paint: " + event.widget + "/" + event.widget.hashCode() + ": rect=" + rect);
            }

            getDecoration().paint(event, rect);
        }

        /**
         * Returns whether this decoration intersects with specified rectangle.
         * 
         * @param eventArea the area to check
         * @return <code>true</code> if the decoration is visible and the area intersects
         */
        public boolean intersects(Rectangle eventArea) {
            if (isDisposed) return false;
            if (!getControl().isVisible()) return false;
            final Rectangle area = getDecorationRectangle(null);
            if (area == null) return false;
            if (!area.intersects(eventArea)) return false;

            return true;
        }

        @Override
        public String toString() {
            return getControl() + "@" + getControl().hashCode() + " " + getDecoration().getArea() + " area "
                    + getDecorationRectangle(null);
        }
    }
}

关于java - 如何在 SWT 中绘制 Composite 的子元素?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/6849216/

相关文章:

java - 如何在swt中使子复合填充父

java - 如何增加数组索引

java - 为 Windows 和 Mac 制作 Java 应用程序

java - 带有 ListView 异步任务的多下载器

java - Swing 油漆问题

JPanel 上的 Java Path2D.Double 都画有 'tail'

java - 如何更改 swt 表中的单元格间距

java - 批量更新缓存问题? hibernate/集成测试/内存数据库/

c - GTK+3 多线程

java - eclipse "Widget is disposed"错误