Dynamic JTree 和 SwingUtilities.invokeLater() 不执行任何操作

发布于 2025-01-04 15:38:59 字数 1788 浏览 1 评论 0原文

为了创建我的动态 JTree,我正在阅读 这个网站在章节“4.2 OutlineNode.java”

现在我已经实现了它,并认识到在 GUI 线程中加载数据需要很长时间并且也很难看。因此,我添加了一个线程来扩展子级,然后将 TreeNode 元素添加到树中。

private void getChildNodes() {
    areChildrenDefined = true;
    Thread t = new Thread(new Runnable()
    {
        @Override
        public void run() {
        System.out.println("Expand");
            final List<DECTTreeNode> listNodes = new ArrayList<DECTTreeNode>();
            if (castNode().canExpand())
            {
                for(DECTNode crt : castNode().getChildren())
                {   
                    DECTTreeNode treeNode = new DECTTreeNode(crt);
                    listNodes.add(treeNode);
                }

                try {
                    SwingUtilities.invokeAndWait(new Runnable()
                    {
                        @Override
                        public void run() {
                            System.out.println(listNodes.size());
                            for (DECTTreeNode crt : listNodes)
                            {
                                add(crt); // <==== Adds the node to the JTree
                            }
                        }

                    });
                    //}).run();
                } catch (Exception e) {
                    e.printStackTrace();
                }

            }
        }

    });
    t.start();
}

没有线程,它可以毫无问题地工作。如果我添加线程并将 add-调用放入 SwingUtilities.invokeAndWait(...) 中,子级似乎已展开,但它们在树。

我已经在树上尝试了 revalidate()repaint() - 没有任何效果。

知道如何使这些元素可见吗?

先感谢您。

To create my dynamic JTree, I was reading the tutorial about Dynamic JTrees on this website in chapter "4.2 OutlineNode.java"

Now I have implemented it and recognize that loading the data in the GUI thread takes long and is ugly as well. Therefore I added a thread which expands the children and then adds the TreeNode element to the tree.

private void getChildNodes() {
    areChildrenDefined = true;
    Thread t = new Thread(new Runnable()
    {
        @Override
        public void run() {
        System.out.println("Expand");
            final List<DECTTreeNode> listNodes = new ArrayList<DECTTreeNode>();
            if (castNode().canExpand())
            {
                for(DECTNode crt : castNode().getChildren())
                {   
                    DECTTreeNode treeNode = new DECTTreeNode(crt);
                    listNodes.add(treeNode);
                }

                try {
                    SwingUtilities.invokeAndWait(new Runnable()
                    {
                        @Override
                        public void run() {
                            System.out.println(listNodes.size());
                            for (DECTTreeNode crt : listNodes)
                            {
                                add(crt); // <==== Adds the node to the JTree
                            }
                        }

                    });
                    //}).run();
                } catch (Exception e) {
                    e.printStackTrace();
                }

            }
        }

    });
    t.start();
}

Without the thread it works without problems. If I add the thread and put the add-calls into a SwingUtilities.invokeAndWait(...), the children seem to be expanded, but they're not visible in the tree.

I've already tried revalidate() and repaint() on the tree - without any effect.

Any idea how to make these elements visible?

Thank you in advance.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

心凉 2025-01-11 15:38:59

检查您的 add() 方法是否触发正确的 TreeModelEvent

Check that your add() method fires the correct TreeModelEvent

零時差 2025-01-11 15:38:59

要扩展 Walter +1 的建议,请从 JButton addButton 调用 Runnable#Thread

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.tree.*;

public class DynamicTreeDemo extends JPanel implements ActionListener {

    private static final long serialVersionUID = 1L;
    private int newNodeSuffix = 1;
    private static String ADD_COMMAND = "add";
    private static String REMOVE_COMMAND = "remove";
    private static String CLEAR_COMMAND = "clear";
    private DynamicTree treePanel;

    public DynamicTreeDemo() {
        super(new BorderLayout()); // Create the components.
        treePanel = new DynamicTree();
        populateTree(treePanel);
        JButton addButton = new JButton("Add");
        addButton.setActionCommand(ADD_COMMAND);
        addButton.addActionListener(this);
        JButton removeButton = new JButton("Remove");
        removeButton.setActionCommand(REMOVE_COMMAND);
        removeButton.addActionListener(this);
        JButton clearButton = new JButton("Clear");
        clearButton.setActionCommand(CLEAR_COMMAND);
        clearButton.addActionListener(this);  // Lay everything out.
        treePanel.setPreferredSize(new Dimension(300, 150));
        add(treePanel, BorderLayout.CENTER);
        JPanel panel = new JPanel(new GridLayout(0, 3));
        panel.add(addButton);
        panel.add(removeButton);
        panel.add(clearButton);
        add(panel, BorderLayout.SOUTH);
    }

    public void populateTree(DynamicTree treePanel) {
        String p1Name = "Parent 1";
        String p2Name = "Parent 2";
        String c1Name = "Child 1";
        String c2Name = "Child 2";
        DefaultMutableTreeNode p1, p2;
        p1 = treePanel.addObject(null, p1Name);
        p2 = treePanel.addObject(null, p2Name);
        treePanel.addObject(p1, c1Name);
        treePanel.addObject(p1, c2Name);
        treePanel.addObject(p2, c1Name);
        treePanel.addObject(p2, c2Name);
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        String command = e.getActionCommand();
        if (ADD_COMMAND.equals(command)) { // Add button clicked
            treePanel.addObject("New Node " + newNodeSuffix++);
        } else if (REMOVE_COMMAND.equals(command)) { // Remove button clicked
            treePanel.removeCurrentNode();
        } else if (CLEAR_COMMAND.equals(command)) { // Clear button clicked.
            treePanel.clear();
        }
    }


    private static void createAndShowGUI() { // Create and set up the window.
        JFrame frame = new JFrame("DynamicTreeDemo");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // Create and set up the content pane.
        DynamicTreeDemo newContentPane = new DynamicTreeDemo();
        newContentPane.setOpaque(true); // content panes must be opaque
        frame.setContentPane(newContentPane);  // Display the window.
        frame.pack();
        frame.setVisible(true);
    }

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

            @Override
            public void run() {
                createAndShowGUI();
            }
        });
    }
}


class DynamicTree extends JPanel {
    private static final long serialVersionUID = 1L;

    private DefaultMutableTreeNode rootNode;
    private DefaultTreeModel treeModel;
    private JTree tree;
    private Toolkit toolkit = Toolkit.getDefaultToolkit();

    public DynamicTree() {
        super(new GridLayout(1, 0));
        rootNode = new DefaultMutableTreeNode("Root Node");
        treeModel = new DefaultTreeModel(rootNode);
        treeModel.addTreeModelListener(new MyTreeModelListener());
        tree = new JTree(treeModel);
        tree.setEditable(true);
        tree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
        tree.setShowsRootHandles(true);
        JScrollPane scrollPane = new JScrollPane(tree);
        add(scrollPane);
    }

    public void clear() {
        rootNode.removeAllChildren();
        treeModel.reload();
    }

    public void removeCurrentNode() {
        TreePath currentSelection = tree.getSelectionPath();
        if (currentSelection != null) {
            DefaultMutableTreeNode currentNode = (DefaultMutableTreeNode) (currentSelection.getLastPathComponent());
            MutableTreeNode parent = (MutableTreeNode) (currentNode.getParent());
            if (parent != null) {
                treeModel.removeNodeFromParent(currentNode);
                return;
            }
        } // Either there was no selection, or the root was selected.
        toolkit.beep();
    }


    public DefaultMutableTreeNode addObject(Object child) {
        DefaultMutableTreeNode parentNode = null;
        TreePath parentPath = tree.getSelectionPath();
        if (parentPath == null) {
            parentNode = rootNode;
        } else {
            parentNode = (DefaultMutableTreeNode) (parentPath.getLastPathComponent());
        }
        return addObject(parentNode, child, true);
    }

    public DefaultMutableTreeNode addObject(DefaultMutableTreeNode parent, Object child) {
        return addObject(parent, child, false);
    }

    public DefaultMutableTreeNode addObject(DefaultMutableTreeNode parent, Object child, boolean shouldBeVisible) {
        DefaultMutableTreeNode childNode = new DefaultMutableTreeNode(child);
        if (parent == null) {
            parent = rootNode;
        }
        // It is key to invoke this on the TreeModel, and NOT DefaultMutableTreeNode
        treeModel.insertNodeInto(childNode, parent, parent.getChildCount());
        // Make sure the user can see the lovely new node.
        if (shouldBeVisible) {
            tree.scrollPathToVisible(new TreePath(childNode.getPath()));
        }
        return childNode;
    }



    class MyTreeModelListener implements TreeModelListener {

        @Override
        public void treeNodesChanged(TreeModelEvent e) {
            DefaultMutableTreeNode node = (DefaultMutableTreeNode) (e.getTreePath().getLastPathComponent());            /*
             * If the event lists children, then the changed node is the child of the
             * node we've already gotten. Otherwise, the changed node and the
             * specified node are the same.
             */ int index = e.getChildIndices()[0];
            node = (DefaultMutableTreeNode) (node.getChildAt(index));
            System.out.println("The user has finished editing the node.");
            System.out.println("New value NodesChanged: " + node.getUserObject());
        }

        @Override
        public void treeNodesInserted(TreeModelEvent e) {
            DefaultMutableTreeNode node= (DefaultMutableTreeNode) (e.getTreePath().getLastPathComponent());            /*
             * If the event lists children, then the changed node is the child of the
             * node we've already gotten. Otherwise, the changed node and the
             * specified node are the same.
             */ int index = e.getChildIndices()[0];
            node = (DefaultMutableTreeNode) (node.getChildAt(index));
            System.out.println("New value NodesInserted : " + node.getUserObject());
        }

        @Override
        public void treeNodesRemoved(TreeModelEvent e) {
            DefaultMutableTreeNode node = (DefaultMutableTreeNode) (e.getTreePath().getLastPathComponent());            /*
             * If the event lists children, then the changed node is the child of the
             * node we've already gotten. Otherwise, the changed node and the
             * specified node are the same.
             */ int index = e.getChildIndices()[0];
            node = (DefaultMutableTreeNode) (node.getChildAt(index));
            System.out.println("New value NodesRemoved : " + node.getUserObject());
        }

        @Override
        public void treeStructureChanged(TreeModelEvent e) {
            DefaultMutableTreeNode node = (DefaultMutableTreeNode) (e.getTreePath().getLastPathComponent());            /*
             * If the event lists children, then the changed node is the child of the
             * node we've already gotten. Otherwise, the changed node and the
             * specified node are the same.
             */ int index = e.getChildIndices()[0];
            node = (DefaultMutableTreeNode) (node.getChildAt(index));
            System.out.println("New value StructureChanged : " + node.getUserObject());
        }
    }
}

to expand suggestion by Walter +1, invoke Runnable#Thread from JButton addButton

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.tree.*;

public class DynamicTreeDemo extends JPanel implements ActionListener {

    private static final long serialVersionUID = 1L;
    private int newNodeSuffix = 1;
    private static String ADD_COMMAND = "add";
    private static String REMOVE_COMMAND = "remove";
    private static String CLEAR_COMMAND = "clear";
    private DynamicTree treePanel;

    public DynamicTreeDemo() {
        super(new BorderLayout()); // Create the components.
        treePanel = new DynamicTree();
        populateTree(treePanel);
        JButton addButton = new JButton("Add");
        addButton.setActionCommand(ADD_COMMAND);
        addButton.addActionListener(this);
        JButton removeButton = new JButton("Remove");
        removeButton.setActionCommand(REMOVE_COMMAND);
        removeButton.addActionListener(this);
        JButton clearButton = new JButton("Clear");
        clearButton.setActionCommand(CLEAR_COMMAND);
        clearButton.addActionListener(this);  // Lay everything out.
        treePanel.setPreferredSize(new Dimension(300, 150));
        add(treePanel, BorderLayout.CENTER);
        JPanel panel = new JPanel(new GridLayout(0, 3));
        panel.add(addButton);
        panel.add(removeButton);
        panel.add(clearButton);
        add(panel, BorderLayout.SOUTH);
    }

    public void populateTree(DynamicTree treePanel) {
        String p1Name = "Parent 1";
        String p2Name = "Parent 2";
        String c1Name = "Child 1";
        String c2Name = "Child 2";
        DefaultMutableTreeNode p1, p2;
        p1 = treePanel.addObject(null, p1Name);
        p2 = treePanel.addObject(null, p2Name);
        treePanel.addObject(p1, c1Name);
        treePanel.addObject(p1, c2Name);
        treePanel.addObject(p2, c1Name);
        treePanel.addObject(p2, c2Name);
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        String command = e.getActionCommand();
        if (ADD_COMMAND.equals(command)) { // Add button clicked
            treePanel.addObject("New Node " + newNodeSuffix++);
        } else if (REMOVE_COMMAND.equals(command)) { // Remove button clicked
            treePanel.removeCurrentNode();
        } else if (CLEAR_COMMAND.equals(command)) { // Clear button clicked.
            treePanel.clear();
        }
    }


    private static void createAndShowGUI() { // Create and set up the window.
        JFrame frame = new JFrame("DynamicTreeDemo");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // Create and set up the content pane.
        DynamicTreeDemo newContentPane = new DynamicTreeDemo();
        newContentPane.setOpaque(true); // content panes must be opaque
        frame.setContentPane(newContentPane);  // Display the window.
        frame.pack();
        frame.setVisible(true);
    }

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

            @Override
            public void run() {
                createAndShowGUI();
            }
        });
    }
}


class DynamicTree extends JPanel {
    private static final long serialVersionUID = 1L;

    private DefaultMutableTreeNode rootNode;
    private DefaultTreeModel treeModel;
    private JTree tree;
    private Toolkit toolkit = Toolkit.getDefaultToolkit();

    public DynamicTree() {
        super(new GridLayout(1, 0));
        rootNode = new DefaultMutableTreeNode("Root Node");
        treeModel = new DefaultTreeModel(rootNode);
        treeModel.addTreeModelListener(new MyTreeModelListener());
        tree = new JTree(treeModel);
        tree.setEditable(true);
        tree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
        tree.setShowsRootHandles(true);
        JScrollPane scrollPane = new JScrollPane(tree);
        add(scrollPane);
    }

    public void clear() {
        rootNode.removeAllChildren();
        treeModel.reload();
    }

    public void removeCurrentNode() {
        TreePath currentSelection = tree.getSelectionPath();
        if (currentSelection != null) {
            DefaultMutableTreeNode currentNode = (DefaultMutableTreeNode) (currentSelection.getLastPathComponent());
            MutableTreeNode parent = (MutableTreeNode) (currentNode.getParent());
            if (parent != null) {
                treeModel.removeNodeFromParent(currentNode);
                return;
            }
        } // Either there was no selection, or the root was selected.
        toolkit.beep();
    }


    public DefaultMutableTreeNode addObject(Object child) {
        DefaultMutableTreeNode parentNode = null;
        TreePath parentPath = tree.getSelectionPath();
        if (parentPath == null) {
            parentNode = rootNode;
        } else {
            parentNode = (DefaultMutableTreeNode) (parentPath.getLastPathComponent());
        }
        return addObject(parentNode, child, true);
    }

    public DefaultMutableTreeNode addObject(DefaultMutableTreeNode parent, Object child) {
        return addObject(parent, child, false);
    }

    public DefaultMutableTreeNode addObject(DefaultMutableTreeNode parent, Object child, boolean shouldBeVisible) {
        DefaultMutableTreeNode childNode = new DefaultMutableTreeNode(child);
        if (parent == null) {
            parent = rootNode;
        }
        // It is key to invoke this on the TreeModel, and NOT DefaultMutableTreeNode
        treeModel.insertNodeInto(childNode, parent, parent.getChildCount());
        // Make sure the user can see the lovely new node.
        if (shouldBeVisible) {
            tree.scrollPathToVisible(new TreePath(childNode.getPath()));
        }
        return childNode;
    }



    class MyTreeModelListener implements TreeModelListener {

        @Override
        public void treeNodesChanged(TreeModelEvent e) {
            DefaultMutableTreeNode node = (DefaultMutableTreeNode) (e.getTreePath().getLastPathComponent());            /*
             * If the event lists children, then the changed node is the child of the
             * node we've already gotten. Otherwise, the changed node and the
             * specified node are the same.
             */ int index = e.getChildIndices()[0];
            node = (DefaultMutableTreeNode) (node.getChildAt(index));
            System.out.println("The user has finished editing the node.");
            System.out.println("New value NodesChanged: " + node.getUserObject());
        }

        @Override
        public void treeNodesInserted(TreeModelEvent e) {
            DefaultMutableTreeNode node= (DefaultMutableTreeNode) (e.getTreePath().getLastPathComponent());            /*
             * If the event lists children, then the changed node is the child of the
             * node we've already gotten. Otherwise, the changed node and the
             * specified node are the same.
             */ int index = e.getChildIndices()[0];
            node = (DefaultMutableTreeNode) (node.getChildAt(index));
            System.out.println("New value NodesInserted : " + node.getUserObject());
        }

        @Override
        public void treeNodesRemoved(TreeModelEvent e) {
            DefaultMutableTreeNode node = (DefaultMutableTreeNode) (e.getTreePath().getLastPathComponent());            /*
             * If the event lists children, then the changed node is the child of the
             * node we've already gotten. Otherwise, the changed node and the
             * specified node are the same.
             */ int index = e.getChildIndices()[0];
            node = (DefaultMutableTreeNode) (node.getChildAt(index));
            System.out.println("New value NodesRemoved : " + node.getUserObject());
        }

        @Override
        public void treeStructureChanged(TreeModelEvent e) {
            DefaultMutableTreeNode node = (DefaultMutableTreeNode) (e.getTreePath().getLastPathComponent());            /*
             * If the event lists children, then the changed node is the child of the
             * node we've already gotten. Otherwise, the changed node and the
             * specified node are the same.
             */ int index = e.getChildIndices()[0];
            node = (DefaultMutableTreeNode) (node.getChildAt(index));
            System.out.println("New value StructureChanged : " + node.getUserObject());
        }
    }
}
虚拟世界 2025-01-11 15:38:59
@Override
public void treeWillExpand(TreeExpansionEvent e) throws ExpandVetoException {
    CustomTreeNode t = (CustomTreeNode) e.getPath().getLastPathComponent();
    t.addChildLoadedListener(new ChildLoadedListener() {
        @Override
        public void childLoaded(TreeNode parent) {
            ((CustomTreeNode) parent).setExpanded(true);
            expandPath(new TreePath(((CustomTreeNode) parent).getPath()));
        }
    });
    if (!t.isExpanded()) {
        factory.loadChildren(t);
        throw new ExpandVetoException(null);
    }
}

public void treeWillCollapse(TreeExpansionEvent e) throws ExpandVetoException {
    CustomTreeNode t = (CustomTreeNode) e.getPath().getLastPathComponent();
    t.setExpanded(false);
}

评判我与否。这对我有用。 CustomTreeNode 是从 defaultMutableTreeNode 扩展而来的,并添加了一个自己编写的 ChildLoadedListener,当工厂加载子级时调用该监听器。 isExpanded 布尔值是为了避免无限循环。
工厂创建一个 SwingWorker 来加载子项并执行它。之后调用 ChilLoadedListener 并再次展开树。

希望这会有所帮助或至少帮助您思考您的问题;-)

编辑:

@Override
public void loadChildren(CustomTreeNode tn) {
    ctn = tn;
    LoadChildrenWorker worker = new LoadChildrenWorker();
    worker.execute();
}


private class LoadChildrenWorker extends SwingWorker<String, Object> {

            @Override
    protected String doInBackground() throws Exception {
                    //load source here and return a string when finished.
                    //In my case its a string repesentation of a directory
    }

    @Override
    protected void done() {
            //with get(), you get the string from doBackground()
            for (String str : parseFromOutput(get())) {
                    if (str.endsWith("/")) {
                        ctn.add(new CustomTreeNode("Directory");
                    } else {
                        ctn.add(new CustomTreeNode("Leaf");
                    }
            }
            //call listeners
            ctn.fireChildrenLoaded();
    }
@Override
public void treeWillExpand(TreeExpansionEvent e) throws ExpandVetoException {
    CustomTreeNode t = (CustomTreeNode) e.getPath().getLastPathComponent();
    t.addChildLoadedListener(new ChildLoadedListener() {
        @Override
        public void childLoaded(TreeNode parent) {
            ((CustomTreeNode) parent).setExpanded(true);
            expandPath(new TreePath(((CustomTreeNode) parent).getPath()));
        }
    });
    if (!t.isExpanded()) {
        factory.loadChildren(t);
        throw new ExpandVetoException(null);
    }
}

public void treeWillCollapse(TreeExpansionEvent e) throws ExpandVetoException {
    CustomTreeNode t = (CustomTreeNode) e.getPath().getLastPathComponent();
    t.setExpanded(false);
}

Jugde me or not. This works for me. CustomTreeNode is extended from defaultMutableTreeNode and has been added a selfwritten ChildLoadedListener which is called when the children have been loaded by the factory. The isExpanded boolean is to avoid a endless loop.
the factory creates a SwingWorker that loads the children and executes it. after that the ChilLoadedListener is called and the tree is expanded again.

Hope this will help or at least help you think about your problem ;-)

EDIT :

@Override
public void loadChildren(CustomTreeNode tn) {
    ctn = tn;
    LoadChildrenWorker worker = new LoadChildrenWorker();
    worker.execute();
}


private class LoadChildrenWorker extends SwingWorker<String, Object> {

            @Override
    protected String doInBackground() throws Exception {
                    //load source here and return a string when finished.
                    //In my case its a string repesentation of a directory
    }

    @Override
    protected void done() {
            //with get(), you get the string from doBackground()
            for (String str : parseFromOutput(get())) {
                    if (str.endsWith("/")) {
                        ctn.add(new CustomTreeNode("Directory");
                    } else {
                        ctn.add(new CustomTreeNode("Leaf");
                    }
            }
            //call listeners
            ctn.fireChildrenLoaded();
    }
撩起发的微风 2025-01-11 15:38:59

我现在正在做的项目中也遇到了同样的问题。

我使用 TreeWillExpandListener 来确定何时必须加载我的树。 (延迟加载)
当树展开时,我捕获了该节点并在线程中加载了它的子节点,因为我必须从服务器输出中解析节点。

您面临的问题是树在您的孩子加载之前扩展。所以你必须抛出一个ExpandVetoException或类似的东西并等待你的孩子被加载。然后扩展你的树。在这种情况下,一切都会显示正确。

希望能解决你的问题。

展开->停止展开->加载children->添加子项->现在展开树 ->查看您的节点

编辑:

如果您使用 swing,则最好使用 SwingWorker。对我来说效果更好。

I've had the same problems in the project I am working on right now.

I use an TreeWillExpandListenerto determine when my tree has to be loaded. (LazyLoading)
When the tree expanded I catched the node an loaded it's children inside a thread, because i had to parse the nodes from a server output.

The problem you are facing is that the tree expands before your children are loaded. so you have to throw a ExpandVetoException or something like that and wait till your children are loaded. then expand your tree. in this case everything will show right.

Hope that will hope your problem.

Exapand-> Stop expand-> loadchildren-> addChildren -> now expand tree -> see your nodes

EDIT :

If you work with swing, you better use SwingWorker. Works better for me.

春花秋月 2025-01-11 15:38:59

尝试将 invokeAndWait 替换为 invokeLater

Try replacing invokeAndWait with invokeLater.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文