如何在Swing同步要求重点是什么? [英] How to request focus synchronously in Swing?

查看:308
本文介绍了如何在Swing同步要求重点是什么?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

当我称之为 component.requestFocusInWindow(),摇摆排队,异步 FOCUS_GAINED FOCUS_LOST 事件,而不是同步转移焦点。作为一种变通方法,看来 DefaultKeyboardFocusManager 正在尝试的模拟的推迟键盘事件的调度,直到焦点事件已经完成调度同步切换焦点。但现在看来,这是不正常。

问:有什么办法可以在Swing同步变化的重点是什么?为 DefaultKeyboardFocusManager 真的试图模拟同步对焦,并认真对待马车?是否有正确执行此焦点管理器?

动机:我有一个的JTextField 时,它的全时自动对焦转移。在使用 java.awt.Robot中的编写集成测试,我需要它的行为是确定性的,而不是与时间相关的。

相关疑问没有得到太多回应:如何抓住重点的现在的?

下面是一个演示。预期行为:当您按住一个键,每个的JTextField 将包含一个字符。实际行为:重点不改变速度不够快,使他们获得多个字符

更新:请注意此行为不是特定于progammatically变化的焦点。在第三个例子,我拿出了自定义焦点调用和而不是简单地设置文档的更新延迟500毫秒。然后我输入1Tab2Tab3Tab4Tab5Tab6Tab7Tab8Tab9Tab0.正如你所看到的数字是正确的,但排队的标签presses会被丢弃。



 进口java.awt中的*。
java.awt.event中导入*。
进口java.text.DateFormat中;
进口java.text.SimpleDateFormat的;
进口的java.util.ArrayList;
进口java.util.Date;
导入java.util.logging的*。进口的javax.swing *。
进口javax.swing.GroupLayout.Group;
进口javax.swing.event.DocumentEvent;
进口javax.swing.event.DocumentListener;公共类AwtEventListenerDemo {
    公共静态最后的记录器记录= Logger.getLogger(AwtEventListenerDemo.class.getName());
    私人静态字符串keyEventToString(KeyEvent的的keyEvent){
        INT ID = keyEvent.getID();
        串eventName的=
            ID == KeyEvent.KEY_ preSSED? KEY_ pressed:
            ID == KeyEvent.KEY_TYPED? KEY_TYPED:
            ID == KeyEvent.KEY_RELEASED? KEY_RELEASED:未知+ ID;
        串什么= ID = = KeyEvent.KEY_TYPED? + keyEvent.getKeyChar():#+ keyEvent.getKey code();
        字符串componentString = keyEvent.getComponent()的getName()。
        如果(componentString == NULL)componentString = keyEvent.getComponent()的toString()。
        返回的String.format(%12秒在%s%4S,eventName的,什么,componentString);
    }
    私人静态字符串focusEventToString(FocusEvent FocusEvent)方法{
        INT ID = focusEvent.getID();
        串eventName的= ID = = FocusEvent.FOCUS_GAINED? FOCUS_GAINED:
            ID == FocusEvent.FOCUS_LOST? FOCUS_LOST:
                空值;
        如果(eventName的== NULL)返回focusEvent.toString();
        字符串componentString = focusEvent.getComponent()的getName()。
        如果(componentString == NULL)componentString = focusEvent.getComponent()的toString()。
        返回的String.format(%s上的%12S,eventName的,componentString);
    }
    私人最终AWTEventListener所loggingListener =新AWTEventListener所(){
        @覆盖公共无效eventDispatched(AWTEvent中的事件){
            如果(事件的instanceof的KeyEvent){
                的KeyEvent的keyEvent =(KeyEvent的)事件;
                INT ID = keyEvent.getID();
                如果(ID == KeyEvent.KEY_ preSSED&放大器;&放大器; keyEvent.getComponent()的instanceof的JTextField&放大器;&放大器;((JTextField中)keyEvent.getComponent())getDocument()的getLength()== 1){
                    的EventQueue的EventQueue = Toolkit.getDefaultToolkit()getSystemEventQueue()。
                    ArrayList的<&AWTEvent中GT; INQUEUE =新的ArrayList<&AWTEvent中GT;();
                    INT [] = interestingIds新INT [] {KeyEvent.KEY_ preSSED,KeyEvent.KEY_TYPED,KeyEvent.KEY_RELEASED,FocusEvent.FOCUS_GAINED,FocusEvent.FOCUS_LOST};
                    对于(INT I:interestingIds){
                        偷看的AWTEvent = eventQueue.peekEvent(I)
                        如果(偷看!= NULL)
                            inQueue.add(PEEK);
                    }
                    ArrayList的<串GT; inQueueString =新的ArrayList<串GT;();
                    对于(AWTEvent中偷看:INQUEUE){
                        如果(PEEK的instanceof的KeyEvent){
                            inQueueString.add(keyEventToString((KeyEvent)方法PEEK));
                        }否则如果(PEEK的instanceof FocusEvent)方法{
                            inQueueString.add(focusEventToString((FocusEvent)方法PEEK));
                        }
                    }                    logger.info(的String.format(仍在队列(排名不分先后):%S,inQueueString));
                }
                logger.info(keyEventToString(的keyEvent));
            }其他{
                logger.info(event.toString());
            }
        }
    };
    私人的JFrame JFrame的;    公共无效的init(){
        长面膜= AWTEvent.KEY_EVENT_MASK; // AWTEvent.MOUSE_EVENT_MASK |
        。Toolkit.getDefaultToolkit()addAWTEventListener(loggingListener,掩模);
        如果(JFrame的== NULL)的JFrame =新的JFrame(AwtEventListenerDemo.class.getSimpleName());
        SwingUtilities.invokeLater(Runnable的新(){
            @覆盖公共无效的run(){
                initUI();
            }
        });
    }
    公共无效cleanupForRestart(){
        。Toolkit.getDefaultToolkit()removeAWTEventListener(loggingListener);
    }
    公共无效的destroy(){
        cleanupForRestart();
        jframe.setVisible(假);
        的JFrame = NULL;
    }    公共无效initUI(){
        的GroupLayout GroupLayout的=新的GroupLayout(jframe.getContentPane());
        jframe.getContentPane()的removeAll()。
        。jframe.getContentPane()的setLayout(GroupLayout的);        JButton的一个JButton =的新的JButton(新AbstractAction(重新启动){
            私有静态最后的serialVersionUID长1L =;
            @覆盖公共无效的actionPerformed(ActionEvent的五){
                cleanupForRestart();
                在里面();
            }
        });
        groupLayout.setAutoCreateGaps(真);
        groupLayout.setAutoCreateContainerGaps(真);
        集团verticalGroup = groupLayout.createSequentialGroup()
                .addComponent(JButton的);
        集团horizo​​ntalGroup = groupLayout.createParallelGroup()
                .addComponent(JButton的);
        groupLayout.setVerticalGroup(verticalGroup);
        groupLayout.setHorizo​​ntalGroup(horizo​​ntalGroup);
        对(INT I = 0; I&小于10;我++){
            最终的JTextField JTextField的=新的JTextField();
            jtextfield.setName(的String.format(%的JTextField D,i)段);
            verticalGroup.addComponent(JTextField的);
            horizo​​ntalGroup.addComponent(JTextField的);
            布尔useDocumentListener = TRUE;
            最终布尔useFocusRootAncestor = FALSE;
            如果(useDocumentListener){
                听众的DocumentListener =新的DocumentListener(){
                    @覆盖公共无效中的removeUpdate(的DocumentEvent E){}
                    @覆盖公共无效的insertUpdate(的DocumentEvent E){                        //模拟一个缓慢的事件侦听器。当监听器
                        //慢,问题变得更糟。
                        尝试{
                            视频下载(50);
                        }赶上(InterruptedException的E1){
                            logger.warning(e1.toString());
                        }
                        如果(e.getDocument()的getLength()方式> 0){
                            //这两个重点转移方法的出现
                            //等价的。
                            如果(useFocusRootAncestor){
                                容器focusRoot = jtextfield.getFocusCycleRootAncestor();
                                FocusTraversalPolicy政策= focusRoot.getFocusTraversalPolicy();
                                组件nextComponent = policy.getComponentAfter(focusRoot,JTextField中);
                                nextComponent.requestFocusInWindow();
                            }其他{
                                。KeyboardFocusManager.getCurrentKeyboardFocusManager()focusNextComponent(JTextField中);
                            }
                        }
                    }
                    @覆盖公共无效的changedUpdate(的DocumentEvent E){}
                };
                。jtextfield.getDocument()addDocumentListener(监听);
            }
        }        如果(!jframe.isVisible()){
            jframe.pack();
            jframe.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
            jframe.setVisible(真);
        }
    }
    公共静态无效的主要(字符串[] argv的){
        //使用单行控制台日志处理程序。
        LogManager.getLogManager()重置()。
        格式化格式化=新格式化(){
            私人SimpleDateFormat的日期格式=新的SimpleDateFormat(KK:MM:SS.SSS);
            @覆盖
            公共字符串格式(的LogRecord将LogRecord){
                日期日期=新的日期(logRecord.getMillis());
                DateFormat.getTimeInstance()格式(日期)。
                返回的String.format(%s%s%S%S%N,
                        dateFormat.format(日期),
                        logRecord.getLoggerName(),
                        logRecord.getLevel(),
                        logRecord.getMessage());
            }
        };
        ConsoleHandler consoleHandler =新ConsoleHandler();
        consoleHandler.setFormatter(格式);
        consoleHandler.setLevel(Level.FINEST);
        logger.addHandler(consoleHandler);
        记录仪focusLogger = Logger.getLogger(java.awt.focus.DefaultKeyboardFocusManager);
        focusLogger.setLevel(Level.FINEST);
        focusLogger.addHandler(consoleHandler);
        最后AwtEventListenerDemo演示=新AwtEventListenerDemo();
        demo.init();
    }
}


解决方案

是,焦点是pretty异步的,那么应包INT的invokeLater,没办法

修改

和@camickr <不错的解决方法/ p>

 进口的javax.swing *。
进口java.awt中的*。
java.awt.event中导入*。
//http://www.$c$cranch.com/t/342205/GUI/java/Tab-order-swing-components
公共类测试{    私有静态最后的serialVersionUID长1L =;
    私有组件[] focusList;
    私人INT focusNumber = 0;
    私人的JFrame框架;    公开测试(){
        JTextField的TF1 =新JTextField中(5);
        JTextField的TF2 =新的JTextField,(5);
        JTextField的TF3 =新的JTextField,(5);
        JButton的B1 =的新的JButton(B1);
        JButton的B2 =的新的JButton(B2);
        tf2.setEnabled(假);
        focusList =新组件[] {TF1,B1,TF2,B2,TF3};
        的JPanel面板=新JPanel(新的GridLayout(5,1));
        panel.add(TF1);
        panel.add(B1);
        panel.add(TF2);
        panel.add(B2);
        panel.add(TF3);
        帧=新的JFrame();
        frame.setFocusTraversalPolicy(新MyFocusTraversalPolicy());
        frame.add(面板);
        frame.pack();
        frame.setLocation(150,100);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setVisible(真);
        KeyboardFocusManager.getCurrentKeyboardFocusManager()。addKeyEventDispatcher(新的KeyEventDispatcher(){            公共布尔dispatchKeyEvent(KeyEvent的KE){
                如果(ke.getID()== KeyEvent.KEY_ preSSED){
                    如果(ke.getKey code()== KeyEvent.VK_TAB){
                        组件comp = KeyboardFocusManager.getCurrentKeyboardFocusManager()getFocusOwner()。
                        如果(comp.isEnabled()==假){
                            如果(ke.isShiftDown()){
                                。KeyboardFocusManager.getCurrentKeyboardFocusManager()重点previousComponent();
                            }其他{
                                。KeyboardFocusManager.getCurrentKeyboardFocusManager()focusNextComponent()聚焦;
                            }
                        }
                    }
                }
                返回false;
            }
        });
    }    私有类MyFocusTraversalPolicy扩展FocusTraversalPolicy {        公共组件getComponentAfter(集装箱focusCycleRoot,组件在aComponent){
            focusNumber =(focusNumber + 1)%focusList.length;
            返回focusList [focusNumber]
        }        公共组件getComponentBefore(集装箱focusCycleRoot,组件在aComponent){
            focusNumber =(focusList.length + focusNumber - 1)%focusList.length;
            返回focusList [focusNumber]
        }        公共组件getDefaultComponent(集装箱focusCycleRoot){
            返回focusList [0];
        }        公共组件getLastComponent(集装箱focusCycleRoot){
            返回focusList [focusList.length - 1];
        }        公共组件getFirstComponent(集装箱focusCycleRoot){
            返回focusList [0];
        }
    }    公共静态无效的主要(字串[] args){
        SwingUtilities.invokeLater(Runnable的新(){            @覆盖
            公共无效的run(){
                测试测试=新测试();
            }
        });
    }
}

When I call component.requestFocusInWindow(), Swing enqueues asynchronous FOCUS_GAINED and FOCUS_LOST events rather than synchronously transferring focus. As a workaround, it appears that DefaultKeyboardFocusManager is trying to simulate synchronously switching focus by delaying the dispatch of keyboard events until focus events have finished dispatching. But it appears that this isn’t working properly.

Question: Is there any way to change focus synchronously in Swing? Is DefaultKeyboardFocusManager really trying to simulate synchronous focus, and is it seriously buggy? Is there a focus manager that does this correctly?

Motivation: I have a JTextField that automatically transfers focus when it’s full. In writing integration tests using java.awt.Robot, I need its behavior to be deterministic and not timing-dependent.

Related question that didn’t get much response: How to grab focus now?

Here’s a demo. Expected behavior: when you hold down a key, each JTextField will contain a single character. Actual behavior: focus does not change fast enough so they get multiple characters.

Update: Note that this behavior is not specific to progammatically changing focus. In the third example, I took out the custom focus calls and instead simply set the document update delay to 500ms. Then I typed 1Tab2Tab3Tab4Tab5Tab6Tab7Tab8Tab9Tab0. As you can see, the digits are queued properly but the tab presses get dropped.

import java.awt.*;
import java.awt.event.*;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.logging.*;

import javax.swing.*;
import javax.swing.GroupLayout.Group;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;

public class AwtEventListenerDemo {
    public static final Logger logger = Logger.getLogger(AwtEventListenerDemo.class.getName());
    private static String keyEventToString(KeyEvent keyEvent) {
        int id = keyEvent.getID();
        String eventName =
            id == KeyEvent.KEY_PRESSED ? "key_pressed" :
            id == KeyEvent.KEY_TYPED ? "key_typed" :
            id == KeyEvent.KEY_RELEASED? "key_released" : "unknown " + id;
        String what = id == KeyEvent.KEY_TYPED ? "" + keyEvent.getKeyChar() : "#" + keyEvent.getKeyCode();
        String componentString = keyEvent.getComponent().getName();
        if (componentString == null) componentString = keyEvent.getComponent().toString();
        return String.format("%12s %4s on %s", eventName, what, componentString);
    }
    private static String focusEventToString(FocusEvent focusEvent) {
        int id = focusEvent.getID();
        String eventName = id == FocusEvent.FOCUS_GAINED ? "focus_gained" :
            id == FocusEvent.FOCUS_LOST ? "focus_lost" :
                null;
        if (eventName == null) return focusEvent.toString();
        String componentString = focusEvent.getComponent().getName();
        if (componentString == null) componentString = focusEvent.getComponent().toString();
        return String.format("%12s on %s", eventName, componentString);
    }
    private final AWTEventListener loggingListener = new AWTEventListener() {
        @Override public void eventDispatched(AWTEvent event) {
            if (event instanceof KeyEvent) {
                KeyEvent keyEvent = (KeyEvent) event;
                int id = keyEvent.getID();
                if (id == KeyEvent.KEY_PRESSED && keyEvent.getComponent() instanceof JTextField && ((JTextField)keyEvent.getComponent()).getDocument().getLength() == 1) {
                    EventQueue eventQueue = Toolkit.getDefaultToolkit().getSystemEventQueue();
                    ArrayList<AWTEvent> inQueue = new ArrayList<AWTEvent>();
                    int[] interestingIds = new int[] {KeyEvent.KEY_PRESSED, KeyEvent.KEY_TYPED, KeyEvent.KEY_RELEASED, FocusEvent.FOCUS_GAINED, FocusEvent.FOCUS_LOST};
                    for (int i: interestingIds) {
                        AWTEvent peek = eventQueue.peekEvent(i);
                        if (peek != null)
                            inQueue.add(peek);
                    }
                    ArrayList<String> inQueueString = new ArrayList<String>();
                    for (AWTEvent peek: inQueue) {
                        if (peek instanceof KeyEvent) {
                            inQueueString.add(keyEventToString((KeyEvent) peek));
                        } else if (peek instanceof FocusEvent) {
                            inQueueString.add(focusEventToString((FocusEvent) peek));
                        }
                    }

                    logger.info(String.format("Still in the queue (in no particular order): %s", inQueueString));
                }
                logger.info(keyEventToString(keyEvent));
            } else {
                logger.info(event.toString());
            }
        }
    };
    private JFrame jframe;

    public void init() {
        long mask = AWTEvent.KEY_EVENT_MASK;  //  AWTEvent.MOUSE_EVENT_MASK | 
        Toolkit.getDefaultToolkit().addAWTEventListener(loggingListener, mask);
        if (jframe == null) jframe = new JFrame(AwtEventListenerDemo.class.getSimpleName());
        SwingUtilities.invokeLater(new Runnable() {
            @Override public void run() {
                initUI();
            }
        });
    }
    public void cleanupForRestart() {
        Toolkit.getDefaultToolkit().removeAWTEventListener(loggingListener);
    }
    public void destroy() {
        cleanupForRestart();
        jframe.setVisible(false);
        jframe = null;
    }

    public void initUI() {
        GroupLayout groupLayout = new GroupLayout(jframe.getContentPane());
        jframe.getContentPane().removeAll();
        jframe.getContentPane().setLayout(groupLayout);

        JButton jbutton = new JButton(new AbstractAction("Restart") {
            private static final long serialVersionUID = 1L;
            @Override public void actionPerformed(ActionEvent e) {
                cleanupForRestart();
                init();
            }
        });
        groupLayout.setAutoCreateGaps(true);
        groupLayout.setAutoCreateContainerGaps(true);
        Group verticalGroup = groupLayout.createSequentialGroup()
                .addComponent(jbutton);
        Group horizontalGroup = groupLayout.createParallelGroup()
                .addComponent(jbutton);
        groupLayout.setVerticalGroup(verticalGroup);
        groupLayout.setHorizontalGroup(horizontalGroup);
        for (int i = 0; i < 10; i++) {
            final JTextField jtextfield = new JTextField();
            jtextfield.setName(String.format("JTextField %d", i));
            verticalGroup.addComponent(jtextfield);
            horizontalGroup.addComponent(jtextfield);
            boolean useDocumentListener = true;
            final boolean useFocusRootAncestor = false;
            if (useDocumentListener) {
                DocumentListener listener = new DocumentListener() {
                    @Override public void removeUpdate(DocumentEvent e) { }
                    @Override public void insertUpdate(DocumentEvent e) {

                        // Simulate a slow event listener. When the listener is
                        // slow, the problems get worse.
                        try {
                            Thread.sleep(50);
                        } catch (InterruptedException e1) {
                            logger.warning(e1.toString());
                        }
                        if (e.getDocument().getLength() > 0) {
                            // These two methods of transferring focus appear
                            // equivalent.
                            if (useFocusRootAncestor) {
                                Container focusRoot = jtextfield.getFocusCycleRootAncestor();
                                FocusTraversalPolicy policy = focusRoot.getFocusTraversalPolicy();
                                Component nextComponent = policy.getComponentAfter(focusRoot, jtextfield);
                                nextComponent.requestFocusInWindow();
                            } else {
                                KeyboardFocusManager.getCurrentKeyboardFocusManager().focusNextComponent(jtextfield);
                            }
                        }
                    }
                    @Override public void changedUpdate(DocumentEvent e) { }
                };
                jtextfield.getDocument().addDocumentListener(listener);
            }
        }

        if (!jframe.isVisible()) {
            jframe.pack();
            jframe.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
            jframe.setVisible(true);
        }
    }
    public static void main(String[] argv) {
        // Use a single-line console log handler.
        LogManager.getLogManager().reset();
        Formatter formatter = new Formatter() {
            private SimpleDateFormat dateFormat = new SimpleDateFormat("kk:mm:ss.SSS");
            @Override
            public String format(LogRecord logRecord) {
                Date date = new Date(logRecord.getMillis());
                DateFormat.getTimeInstance().format(date);
                return String.format("%s %s %s %s%n",
                        dateFormat.format(date),
                        logRecord.getLoggerName(),
                        logRecord.getLevel(),
                        logRecord.getMessage());
            }
        };
        ConsoleHandler consoleHandler = new ConsoleHandler();
        consoleHandler.setFormatter(formatter);
        consoleHandler.setLevel(Level.FINEST);
        logger.addHandler(consoleHandler);
        Logger focusLogger = Logger.getLogger("java.awt.focus.DefaultKeyboardFocusManager");
        focusLogger.setLevel(Level.FINEST);
        focusLogger.addHandler(consoleHandler);


        final AwtEventListenerDemo demo = new AwtEventListenerDemo();
        demo.init();
    }
}

解决方案

yes, Focus is pretty asynchronous, then should be wrapped int invokeLater, no way

EDIT

and nice workaround by @camickr

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
//http://www.coderanch.com/t/342205/GUI/java/Tab-order-swing-components
public class Testing {

    private static final long serialVersionUID = 1L;
    private Component[] focusList;
    private int focusNumber = 0;
    private JFrame frame;

    public Testing() {
        JTextField tf1 = new JTextField(5);
        JTextField tf2 = new JTextField(5);
        JTextField tf3 = new JTextField(5);
        JButton b1 = new JButton("B1");
        JButton b2 = new JButton("B2");
        tf2.setEnabled(false);
        focusList = new Component[]{tf1, b1, tf2, b2, tf3};
        JPanel panel = new JPanel(new GridLayout(5, 1));
        panel.add(tf1);
        panel.add(b1);
        panel.add(tf2);
        panel.add(b2);
        panel.add(tf3);
        frame = new JFrame();
        frame.setFocusTraversalPolicy(new MyFocusTraversalPolicy());
        frame.add(panel);
        frame.pack();
        frame.setLocation(150, 100);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setVisible(true);
        KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventDispatcher(new KeyEventDispatcher() {

            public boolean dispatchKeyEvent(KeyEvent ke) {
                if (ke.getID() == KeyEvent.KEY_PRESSED) {
                    if (ke.getKeyCode() == KeyEvent.VK_TAB) {
                        Component comp = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();
                        if (comp.isEnabled() == false) {
                            if (ke.isShiftDown()) {
                                KeyboardFocusManager.getCurrentKeyboardFocusManager().focusPreviousComponent();
                            } else {
                                KeyboardFocusManager.getCurrentKeyboardFocusManager().focusNextComponent();
                            }
                        }
                    }
                }
                return false;
            }
        });
    }

    private class MyFocusTraversalPolicy extends FocusTraversalPolicy {

        public Component getComponentAfter(Container focusCycleRoot, Component aComponent) {
            focusNumber = (focusNumber + 1) % focusList.length;
            return focusList[focusNumber];
        }

        public Component getComponentBefore(Container focusCycleRoot, Component aComponent) {
            focusNumber = (focusList.length + focusNumber - 1) % focusList.length;
            return focusList[focusNumber];
        }

        public Component getDefaultComponent(Container focusCycleRoot) {
            return focusList[0];
        }

        public Component getLastComponent(Container focusCycleRoot) {
            return focusList[focusList.length - 1];
        }

        public Component getFirstComponent(Container focusCycleRoot) {
            return focusList[0];
        }
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {

            @Override
            public void run() {
                Testing testing = new Testing();
            }
        });
    }
}

这篇关于如何在Swing同步要求重点是什么?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆