swing, javaで画面遷移

こんにちは、某社エンジニアです。

勉強のためにswingでタスク管理のウェブアプリケーションを作りました。
そのときの画面遷移が思いの外面倒だったので備忘録。
関連する過去記事はこちら↓
https://qiita.com/Ohtak/items/1be20a4b06b5833c3d61

#1. システムの構造
・親としてMainFrame、子としてMainPanel、TaskRegisterPanel、TaskEditPanel
・MainFrameだけでパネル作成、画面遷移、リロードもろもろ管理
・子からはMainFrameのそれぞれのメソッドを呼び出すだけ

#2. 画面
<img width="1193" alt="スクリーンショット 2018-01-02 11.04.22.png" src="https://qiita-image-store.s3.amazonaws.com/0/178311/43649473-a8aa-50b8-964c-236ed81766aa.png">

<img width="821" alt="スクリーンショット 2018-01-02 11.04.43.png" src="https://qiita-image-store.s3.amazonaws.com/0/178311/90242ce4-4636-f209-a46b-cf1e55257d6a.png">

<img width="871" alt="スクリーンショット 2018-01-02 11.05.11.png" src="https://qiita-image-store.s3.amazonaws.com/0/178311/a26c11c5-29d8-0561-0d1c-7ca2e421e1fa.png">

上からMainPanel、TaskRegisterPanel、TaskEditPanelです。

#3. コード
いよいよコードです。
まずは親となるmainFrameから

```java:MainFrame.java

public class MainFrame extends JFrame{
	public String[] PanelNames = {"main","register","edit"};//この名前でパネルの指定をする
	
	//各種パネルを作成
	MainPanel mainPanel = new MainPanel(this,PanelNames[0]);
    TaskRegisterPanel taskRegisterPanel = new TaskRegisterPanel(this,PanelNames[1]);
    TaskEditPanel taskEditPanel = new TaskEditPanel(this,PanelNames[2]);
    
    public MainFrame(){
        this.add(mainPanel);
        mainPanel.setVisible(true);
        
        this.add(taskRegisterPanel);
        taskRegisterPanel.setVisible(false);
        
        this.add(taskEditPanel);
        taskEditPanel.setVisible(false);
        
        this.setBounds(100, 100, 1200, 800);
    }
    
    public static void main(String[] args) {
        MainFrame mainFrame = new MainFrame();
        mainFrame.setDefaultCloseOperation(EXIT_ON_CLOSE);
        mainFrame.setVisible(true);
    }
    
    //ページの再描画用
    //mainPanelはこれをしないと変更が表示に反映されない
    //一応他の二つのパネル分も…
    public void reloadPage(String reloadPanelName){
    	if(reloadPanelName == PanelNames[0]){
    		this.remove(this.mainPanel);
    		MainPanel mainPanel = new MainPanel(this,PanelNames[0]);
    		this.add(mainPanel);
    	}
    	else if(reloadPanelName == PanelNames[1]){
    		this.remove(this.taskRegisterPanel);
    		TaskRegisterPanel taskRegisterPanel = new TaskRegisterPanel(this,PanelNames[1]);
    		this.add(taskRegisterPanel);
    	}
    	else if(reloadPanelName == PanelNames[2]){
    		this.remove(this.taskEditPanel);
    		TaskEditPanel taskEditPanel = new TaskEditPanel(this,PanelNames[2]);
    		this.add(taskEditPanel);
    	}
    }
    
    //パネル遷移メソッド
    //あらかじめ全パネルを作成して表示、非表示を切り替える方式
    
    //メインパネルを表示
    public void showMainPanel(JPanel nowPanel){
    	nowPanel.setVisible(false);
        mainPanel.setVisible(true);
    }
    
    //タスク登録パネルを表示
    public void showRegisterPanel(JPanel nowPanel){
    	nowPanel.setVisible(false);
        taskRegisterPanel.setVisible(true);
    }
    
    //タスク編集パネルを表示
    public void showEditPanel(JPanel nowPanel, TaskDto task){
    	nowPanel.setVisible(false);
        //初期文字列を設定
        taskEditPanel.setEditString(task);
        taskEditPanel.setVisible(true);
    }
}

```

ほぼコメントの通りです。
どのパネルを表示させるかの指定は

```
public String[] PanelNames = {"main","register","edit"};
```
でしています。
なるべくpublic変数を入れないようにしたんですが、クラスの外からのパネルの指定だけがうまくいかず
これだけ残ってしまいました…

次に他のパネルのMainFrameへのアクセス部分

```java:MainPanel.java
public class MainPanel extends JPanel{
	
    MainFrame mainFrame;
    
    //panel作成
    MainPanel(MainFrame mf,String  name){
    	mainFrame = mf;
    	this.setName(name);

    	[中略]

    	//ボタン配置
    	JPanel buttonPanel = new JPanel();
    	JButton createButton = new JButton("新規作成");
    	createButton.setPreferredSize(new Dimension(100,50));
    	createButton.addActionListener(new ActionListener(){
    		public void actionPerformed(ActionEvent e){
    			panelChangeToRegister(mainFrame.PanelNames[1]);
    		}
    	});
    	buttonPanel.add(createButton);

    	JButton editButton = new JButton("タスクを編集");
    	editButton.setPreferredSize(new Dimension(100,50));
    	editButton.addActionListener(new ActionListener(){
    		public void actionPerformed(ActionEvent e){
    			//ボタンを押したとき選択中の要素をdtoにセット
    			TaskDto task = setTaskDtoFromSelectedString();
    			if(!isNotSelectedError){
    				panelChangeToEdit(mainFrame.PanelNames[2], task);
    			}
    		}
    	});
    	buttonPanel.add(editButton);
    }

    public void panelChangeToRegister(String toPanelName){
    	mainFrame.showRegisterPanel((JPanel)this);
    }
    
    public void panelChangeToEdit(String toPanelName, TaskDto task){
    	mainFrame.showEditPanel((JPanel)this, task);
    }
}

```
MainPanelだけ貼りましたがほかの二つもだいたいこんな感じです。
ちなみに他の二つのパネルからmainPanelへの遷移時にmainFrame.reloadPageを呼んで表示をリセットさせてます。
編集画面に遷移する時に初期文字列を表示させたかったのでpanelChangeToEditだけ引数あります。

Dtoも使っていますがこれは他の記事で…

#4. 所感
FrameとPanelの親子構造が想像以上に面倒でした…
new,addのタイミングを意識しないとどの変数が表示されているか混乱します。
下記が失敗例です。

```java:MainFrame_失敗例.java
    public void reloadPage(String panelName, JPanel jp){
    	
    	MainFrame mainFrame = new MainFrame();//frameをnewしちゃっています
    	mainFrame.setDefaultCloseOperation(EXIT_ON_CLOSE);
    	mainFrame.setVisible(true);

    	if(reloadPanelName == PanelNames[0]){
    		mainFrame.remove(jp);//はじめに作ったフレームではなく新しいものからremoveしようとしてます。
    		MainPanel mainPanel = new MainPanel(this,PanelNames[0]);
    		mainFrame.add(mainPanel);//そして追加してます。
    	}
    	else if(reloadPanelName == PanelNames[1]){
    		mainFrame.remove(jp);
    		TaskRegisterPanel taskRegisterPanel = new TaskRegisterPanel(this,PanelNames[1]);
    		mainFrame.add(taskRegisterPanel);
    	}
    	else if(reloadPanelName == PanelNames[2]){
    		mainFrame.remove(jp);
    		TaskEditPanel taskEditPanel = new TaskEditPanel(this,PanelNames[2]);
    		mainFrame.add(taskEditPanel);
    	}
    }
```
やりたかったのは、
１、frameにaddされているpanelを一旦削除
２、新しいものをnew,add
でしたが、この例だと１の段階で新しいframeを作ってそこから削除してしまっているので
当然もとのframeからは削除されていません。
結果的にふたつの同じパネルが重なって表示されます。
変数名もちょくちょく意味わからんですね…
[３.コード]のとこに貼ったやつでは直ってますが

newのタイミングはswingだけでなく他のjavaアプリケーションにもいえることなので気をつけたいです。