How do the effects of Mouse Events differ between standard Windows Desktop application usage and Windows Tablet usage?

我们创建了一个应用程序,可以在某些文本字段获得焦点时创建弹出虚拟键盘 . 然后可以单击此虚拟键盘以键入文本字段,而不会导致文本字段失去焦点 .

Example Code Screenshot

这是 works correctly when using a mouse 在标准台式PC上 . 键盘上运行 mobile device (如Windows平板电脑)和使用触摸命令时,键盘 behaves differently, losing focus.

我们一直认为移动触摸命令很大程度上模拟了鼠标点击事件,尽管这似乎并非如此 . Why do these two input methods work differently? 如何让我们的键盘在两种平台上都能完全相同?

一个小测试程序如下:

import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ControlEvent;
import org.eclipse.swt.events.ControlListener;
import org.eclipse.swt.events.FocusAdapter;
import org.eclipse.swt.events.FocusEvent;
import org.eclipse.swt.events.MouseAdapter;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;

public class KeyboardEventTest {
    private Kbd kbd = null;
    protected Shell shell;
    private Label lblNoEvent;
    private Text txtNoEvent;
    private Label lblYesEvent;
    private Text txtYesEvent;

    private ControlListener _parentControlListener = new ControlListener() {
        @Override
        public void controlResized(ControlEvent e) {
        }

        @Override
        public void controlMoved(ControlEvent e) {
            if(kbd != null)
                kbd.positionRelativeToControl();
        }
    };

    public static void main(String[] args) {
        try {
            KeyboardEventTest window = new KeyboardEventTest();
            window.open();
        }
        catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void open() {
        Display display = Display.getDefault();
        createContents();
        shell.open();
        shell.layout();
        while(!shell.isDisposed()) {
            if(!display.readAndDispatch()) {
                display.sleep();
            }
        }
    }

    protected void createContents() {
        shell = new Shell();
        shell.setSize(450, 300);
        shell.setText("Keyboard Event Test");
        GridLayout gl_shell = new GridLayout();
        gl_shell.numColumns = 2;
        shell.setLayout(gl_shell);

        lblNoEvent = new Label(shell, SWT.NONE);
        lblNoEvent.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1));
        lblNoEvent.setText("No Event");

        txtNoEvent = new Text(shell, SWT.BORDER);
        txtNoEvent.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1));

        lblYesEvent = new Label(shell, SWT.NONE);
        lblYesEvent.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1));
        lblYesEvent.setText("Event");

        txtYesEvent = new Text(shell, SWT.BORDER);
        txtYesEvent.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1));
        txtYesEvent.addFocusListener(new FocusAdapter() {
            @Override
            public void focusGained(FocusEvent e) {
                showKeyboard();
            }
            @Override
            public void focusLost(FocusEvent e) {
                closeKeyboard();
            }
        });
    }

    protected void closeKeyboard() {
        if(kbd != null) {
            if(!kbd.isDisposed())
                kbd.close();

            kbd = null;
        }
    }

    protected void showKeyboard() {
        Control ctrlFocus = shell.getDisplay().getFocusControl();

        if(!(ctrlFocus instanceof Text))
            return;

        Text txt = (Text) ctrlFocus;
        if(kbd == null && txt.isEnabled() && txt.getEditable()) {
            kbd = new Kbd(shell, txt);

            kbd.positionRelativeToControl();
            kbd.setVisible(true);

            Composite parent = txt.getParent();
            do {
                parent.addControlListener(_parentControlListener);
            } while((parent = parent.getParent()) != null);
        }
    }

    private class Kbd extends Shell {
        private static final int XMARGIN = 5;
        private static final int YMARGIN = 5;
        private Display _display;
        private Text _txt;
        private Point _ptKey;
        private Color _clrInfoFG;
        private Color _clrInfoBG;

        public Kbd(Shell parent, Text associatedCtrl) {
            super(parent, SWT.ON_TOP | SWT.NO_TRIM | SWT.TOOL | SWT.NO_FOCUS | SWT.NO_BACKGROUND);

            _txt = associatedCtrl;
            _display = getDisplay();
            _clrInfoFG = _display.getSystemColor(SWT.COLOR_INFO_FOREGROUND);
            _clrInfoBG = _display.getSystemColor(SWT.COLOR_INFO_BACKGROUND);

            addPaintListener(new PaintListener() {
                @Override
                public void paintControl(PaintEvent e) {
                    onPaint(e);
                }
            });

            addMouseListener(new MouseAdapter() {
                @Override
                public void mouseDown(MouseEvent e) {
                    if(_txt.isEnabled() && _txt.getEditable()) {
                        char c = getItemMouseIsOn(e);
                        if(c != '\0') {
                            sendKeyEvent(c, SWT.KeyDown);
                            sendKeyEvent(c, SWT.KeyUp);
                        }
                    }
                }
            });

            GC gc = new GC(_display);
            _ptKey = new Point(0,0);

            for(char c = 'A' ; c <= 'Z' ; c++) {
                Point ptChar = gc.textExtent(Character.toString(c));
                _ptKey.x = Math.max(_ptKey.x, ptChar.x); 
                _ptKey.y = Math.max(_ptKey.y, ptChar.y); 
            }
            gc.dispose();
            setSize(26 * (_ptKey.x + 2 * XMARGIN), _ptKey.y + 2 * YMARGIN);
        }

        public void positionRelativeToControl() {
            Rectangle rc = _txt.getBounds();

            Point ptTxt = new Point(rc.x, rc.y + rc.height);
            Point ptNewPosition = _display.map(_txt.getParent(), null, ptTxt);

            Point ptCurrentShellPosition = getLocation();

            if(!ptNewPosition.equals(ptCurrentShellPosition))
                setLocation(ptNewPosition);
        }

        private void sendKeyEvent(char c, int eventType) {
            Event event = new Event();
            event.type = eventType;
            event.character = c;
            _display.post(event);
        }

        private char getItemMouseIsOn(MouseEvent e) {
            Rectangle rc = getClientArea();
            if ((rc.x <= e.x) && (e.x <= rc.x + rc.width)) {
                int key = e.x / (2 * XMARGIN + _ptKey.x);
                if(0 <= key && key <= 25) {
                    char c = (char) ('A' + key);
                    return c;
                }
            }
            return '\0';
        }

        protected void onPaint(PaintEvent e) {
            Rectangle rc = getClientArea();
            Image img = new Image(_display, rc.width, rc.height);  
            GC gc = new GC(img);
            Color clrFG = gc.getForeground();
            Color clrBG = gc.getBackground();

            gc.setBackground(_clrInfoBG);
            gc.fillRectangle(rc);
            gc.setForeground(_clrInfoFG);

            int x = rc.x + XMARGIN;
            int y = rc.y + YMARGIN;
            for(char c = 'A' ; c <= 'Z' ; c++) {
                gc.drawText(Character.toString(c), x, y);
                x += _ptKey.x + XMARGIN;
                gc.drawLine(x, rc.y, x, rc.y + rc.height);
                x += XMARGIN;
            }
            gc.setForeground(clrFG);
            gc.setBackground(clrBG);
            e.gc.drawImage(img, 0, 0);
            gc.dispose();
            img.dispose();
        }

        @Override
        protected void checkSubclass() {
        }
    }
}