Question:
There is a JTree
based on the implemented TreeModel
, which is built on the basis of a selection from the MySQL database. There are buttons by pressing which add / delete data to the MySQL table. How do I keep the data in JTree
updated automatically? The revalidate()
and repaint()
methods do not work. Only updateUI()
works, but for some reason it is not recommended to use it (by the way, the second question is why it is not recommended?).
The code:
package catalogs;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JInternalFrame;
import javax.swing.JOptionPane;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTree;
import javax.swing.ListSelectionModel;
import javax.swing.event.TableModelListener;
import javax.swing.event.TreeModelListener;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.table.TableModel;
import javax.swing.tree.TreeModel;
import javax.swing.tree.TreePath;
import main.Global;
/*------Класс окна справочника товаров.------*/
public class ProductsCatalog extends JInternalFrame{
private static final long serialVersionUID = 1L;
//Объявления компонент окна.
private JTree groupTree;
private GroupTreeModel groupTreeModel;
private JScrollPane groupTreeScrlPane;
private JButton btnAddGroup;
private JButton btnDelGroup;
private ButtonListener btnListener;
private TreeListener groupTreeListener;
private JTable prodTable;
private ProdTableModel prodTableModel;
private JScrollPane prodTableScrlPane;
//Конструктор.
public ProductsCatalog(){
Global.DB_NAME = "mydb"; //УДАЛИТЬ СТРОКУ
//Инициализация компонент окна.
//Древо групп товаров.
groupTreeModel = new GroupTreeModel();
groupTree = new JTree(groupTreeModel);
groupTree.setSelectionRow(0);
groupTreeListener = new TreeListener();
groupTree.addTreeSelectionListener(groupTreeListener);
groupTreeScrlPane = new JScrollPane(groupTree);
groupTreeScrlPane.setBounds(10, 10, 150, 200);
//Кнопки добавления и удаления групп товаров.
btnListener = new ButtonListener();
btnAddGroup = new JButton();
ImageIcon btnAddGroupIcon = new ImageIcon("images/btnAddGroup.png");
btnAddGroup.setIcon(btnAddGroupIcon);
btnAddGroup.addActionListener(btnListener);
btnAddGroup.setBounds(10, 220, 30, 30);
btnDelGroup = new JButton();
ImageIcon btnDelGroupIcon = new ImageIcon("images/btnDelGroup.png");
btnDelGroup.setIcon(btnDelGroupIcon);
btnDelGroup.addActionListener(btnListener);
btnDelGroup.setBounds(50, 220, 30, 30);
//Таблица товаров.
prodTableModel = new ProdTableModel();
prodTable = new JTable(prodTableModel);
prodTableModel.buildTable(groupTree.getLastSelectedPathComponent());
prodTable.setAutoCreateRowSorter(true);
prodTable.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS);
prodTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
prodTable.getColumnModel().getColumn(0).setPreferredWidth(25);
prodTable.getColumnModel().getColumn(1).setPreferredWidth(200);
prodTable.getColumnModel().getColumn(2).setPreferredWidth(25);
prodTable.getColumnModel().getColumn(3).setPreferredWidth(50);
prodTableScrlPane = new JScrollPane(prodTable);
prodTableScrlPane.setBounds(170, 10, 500, 200);
//Параметры окна.
this.setTitle("Каталог товаров");
this.setClosable(true);
this.setMaximizable(true);
this.setIconifiable(true);
this.setResizable(true);
this.getContentPane().setLayout(null);
this.setSize(700, 350);
this.setVisible(true);
this.add(groupTreeScrlPane);
this.add(btnAddGroup);
this.add(btnDelGroup);
this.add(prodTableScrlPane);
}
/*------Модель дерева.------*/
private class GroupTreeModel implements TreeModel{
@Override
public Object getChild(Object parent, int index) {
Object[][] buf = Global.sqlQueryResult("SELECT * FROM `prod_group` WHERE `name`='"+ parent +"'");
Object parentCode = buf[0][0];
Object[][] res = Global.sqlQueryResult("SELECT * FROM `prod_group` WHERE `parent`='"+ parentCode +"'");
return res[index][1];
}
@Override
public int getChildCount(Object parent) {
Object[][] buf = Global.sqlQueryResult("SELECT * FROM `prod_group` WHERE `name`='"+ parent +"'");
Object parentCode = buf[0][0];
Object[][] res = Global.sqlQueryResult("SELECT * FROM `prod_group` WHERE `parent`='"+ parentCode +"'");
return res.length;
}
@Override
public int getIndexOfChild(Object parent, Object child) {
Object[][] buf = Global.sqlQueryResult("SELECT * FROM `prod_group` WHERE `name`='"+ parent +"'");
Object parentCode = buf[0][0];
Object[][] res = Global.sqlQueryResult("SELECT * FROM `prod_group` WHERE `parent`='"+ parentCode +"'");
int index = 0;
for(int i =0; i<res.length; i++){
if(res[i][1].equals(child)) index = i;
}
return index;
}
@Override
public Object getRoot() {
Object[][] res = Global.sqlQueryResult("SELECT * FROM `prod_group` WHERE `parent`='0'");
return res[0][1];
}
@Override
public boolean isLeaf(Object arg0) {
return false;
}
@Override
public void addTreeModelListener(TreeModelListener arg0) {}
@Override
public void removeTreeModelListener(TreeModelListener arg0) {}
@Override
public void valueForPathChanged(TreePath arg0, Object arg1) {}
}
/*------Слушатель изменения выбранной строки дерева групп товаров.------*/
public class TreeListener implements TreeSelectionListener{
@Override
public void valueChanged(TreeSelectionEvent selection) {
prodTableModel.buildTable(groupTree.getLastSelectedPathComponent());
prodTable.revalidate();
prodTable.repaint();
}
}
/*------Слушатель нажатия кнопок добавления/удаления групп/товаров.------*/
public class ButtonListener implements ActionListener{
@Override
public void actionPerformed(ActionEvent e) {
//Если нажата кнопка удаления группы товаров.
if(e.getSource().equals(btnDelGroup)){
//Проверяем, пуста ли папка, которую удаляем.
boolean isEmpty = false;
Object[][] buf = Global.sqlQueryResult("SELECT `id` FROM `prod_group` WHERE `name`='"+ groupTree.getLastSelectedPathComponent() +"'");
Object[][] buf2 = Global.sqlQueryResult("SELECT * FROM `product` WHERE `group`='"+ buf[0][0] +"'");
Object[][] buf3 = Global.sqlQueryResult("SELECT * FROM `prod_group` WHERE `parent`='"+ buf[0][0] +"'");
if(buf2.length==0 && buf3.length==0) isEmpty = true;
//Если выделена группа, она пуста и мы подтверждаем удаление, тогда удаляем.
if(!groupTree.isSelectionEmpty()){
if(isEmpty){
if(JOptionPane.showConfirmDialog(null, "Вы уверены?", "Подтверждение удаления", JOptionPane.YES_NO_OPTION)==0){
Global.sqlQueryVoid("DELETE FROM `prod_group` WHERE `name`='"+ groupTree.getLastSelectedPathComponent() +"'");
//groupTree.updateUI();
groupTree.setVisible(false);
groupTree.revalidate();
groupTree.repaint();
groupTree.setVisible(true);
}
}else JOptionPane.showMessageDialog(null, "Нельзя удалить эту папку, т.к. она не пуста!", "Ошибка", JOptionPane.ERROR_MESSAGE);
}else JOptionPane.showMessageDialog(null, "Вы не выбрали ни одну папку!", "Ошибка", JOptionPane.ERROR_MESSAGE);
}
}
}
/*------Модель таблицы.------*/
public class ProdTableModel implements TableModel{
//Двумерный массив данных.
private Object[][] selection = null;
//Метод построения двумерного массива данных, на основе которого отрисовывается таблица товаров.
public void buildTable(Object group){
Object[][] buf = Global.sqlQueryResult("SELECT `id` FROM `prod_group` WHERE `name`='"+ group +"'");
selection = Global.sqlQueryResult("SELECT `id`, `name`, `unit`, `comments` FROM `product` WHERE `group`='"+ buf[0][0] +"'");
}
@Override
public Class<?> getColumnClass(int colInd) {
return String.class;
}
@Override
public int getColumnCount() {
return 4;
}
@Override
public String getColumnName(int colInd) {
String colName ="";
switch(colInd){
case 0: colName = "Код"; break;
case 1: colName = "Наименование"; break;
case 2: colName = "Ед. изм."; break;
case 3: colName = "Комментарий"; break;
}
return colName;
}
@Override
public int getRowCount() {
return selection.length;
}
@Override
public Object getValueAt(int rowInd, int colInd) {
return selection[rowInd][colInd];
}
@Override
public void addTableModelListener(TableModelListener arg0) {}
@Override
public boolean isCellEditable(int arg0, int arg1) {return false;}
@Override
public void removeTableModelListener(TableModelListener arg0) {}
@Override
public void setValueAt(Object arg0, int arg1, int arg2) {}
}
}
Answer:
You can transfer data operations to the tree model ( GroupTreeModel
), which will notify the view ( JTree
) of changes. To do this, you need to add listeners to the GroupTreeModel
:
Set<TreeModelListener> listeners = new CopyOnWriteArraySet<>();
@Override
public void addTreeModelListener(TreeModelListener arg0) {
listeners.add( arg0 );
}
@Override
public void removeTreeModelListener(TreeModelListener arg0) {
listeners.remove( arg0 );
}
and the removal method:
public void removeGroupAtPath( TreePath selectionPath ) {
Object groupToRemove = selectionPath.getLastPathComponent();
TreePath pathToParent = selectionPath.getParentPath();
if ( pathToParent != null ) { // one does not simply remove jtree root node
Object parent = pathToParent.getLastPathComponent();
int childIndex = getIndexOfChild( parent, groupToRemove );
Global.sqlQueryVoid( "DELETE FROM `prod_group` WHERE `name`='" + groupToRemove + "'" );
// для события удаления нужен путь к родительскому узлу
// индекс удаляемого узла в списке узлов родителя
// и удаляемый объект
fireNodeRemoved( pathToParent, new int[] { childIndex }, new Object[] { groupToRemove } );
}
}
public void fireNodeRemoved( TreePath parentPath, int[] removedIndexes, Object[] removedChildren ) {
TreeModelEvent e = new TreeModelEvent( this, parentPath, removedIndexes, removedChildren );
for ( TreeModelListener l : listeners ) {
l.treeNodesRemoved( e );
}
}
The tree will register itself in the list of listeners of the model and, when deleted, will be redrawn as needed.
Then, simply in the click handler, you can call the model method:
groupTreeModel.removeGroupAtPath( groupTree.getSelectionPath() );
No forced redrawing is required.