2016-10-05 6 views
-2

Я работаю над клиент-серверным приложением для обмена сообщениями на практике. В моем клиентском модуле есть класс Client, класс ClientController, класс ClientGUI и файл fxml, класс Message, CSS-файл и основной класс. У меня есть метод в моем классе ClientGUI и классе ClientController, отображающий (сообщение String), который добавляет объект Message в VBox, который имеет id #messages.Методы вызова на VBox throws NullPointerException

Вот моя проблема: внутри моего класса ClientController есть метод display(), который вызывает метод display() ClientGUI (я знаю, что кажется излишним, но это не проблема). Когда метод display() вызывается в моем классе ClientGUI, например внутри метода setOnMouseClick(), он работает нормально. Однако, когда этот метод вызывается из моего класса ClientController, я получаю исключение NullPointerException, указывающее на мою переменную сообщений типа VBox внутри метода display() моего класса ClientGUI. Я пробовал заменить messages.getChildren().add(new Message(message)); на VBox test = messages;

Последний работает отлично, в то время как бывший выбрасывает исключение. Я также попытался вызвать другие методы в моей переменной сообщений, все из которых вызывают одну и ту же ошибку. Почему это происходит?

(Это все еще в процессе разработки, так что могут быть дополнительные несвязанные вопросы)

Клиент:

package client; 

import java.io.*; 
import java.net.Socket; 

/** 
* Gabe Castelli 
* 9/26/2016 
* Description: Represents client and its socket 
*/ 

public class Client implements Serializable { 
    private static Client instance; 
    private Socket clientSocket; 
    private BufferedReader in; 
    private PrintWriter out; 
    private ObjectOutputStream objectOutputStream; 
    private boolean connected = false; 
    private static final long serialVersionUID = 0L; 

    private Client() {} 

    public void connect(String targetAddress, String username_, int port_) { 
     try { 
      clientSocket = new Socket(targetAddress, port_); 
      clientSocket.setReuseAddress(true); // Allows same port to be used successively without cool-down 
      in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream())); 
      out = new PrintWriter(clientSocket.getOutputStream(), true); 
      objectOutputStream = new ObjectOutputStream(clientSocket.getOutputStream()); 
      objectOutputStream.writeObject(username_); 
      ClientController.getInstance().display("Attention: Successfully connected to server"); 
      connected = true; 
     } catch (IOException e) { 
      ClientController.getInstance().display("Attention: Unable to connect to server"); 
     } 
    } 

    public void disconnect() { 
     try { 
      if (in != null) { 
       in.close(); 
      } 
      if (out != null) { 
       out.close(); 
      } 
      if (objectOutputStream != null) { 
       objectOutputStream.close(); 
      } 
      if (clientSocket != null) { 
       clientSocket.close(); 
      } 
     } catch (IOException e) { 
      e.printStackTrace(); 
     } 

     connected = false; 
    } 

    public boolean connected() { 
     return connected; 
    } 

    public static Client getInstance() { 
     if (instance == null) { 
      instance = new Client(); 
     } 
     return instance; 
    } 

    public void sendMessage(String message) { 
     out.println(message); 
    } 
} 

ClientController:

package client; 

import javafx.scene.layout.VBox; 

/** 
* Gabe Castelli 
* 9/28/2016 
* Description: 
*/ 

public class ClientController implements Controller { 
    private static ClientController instance; 

    private ClientController() {} 

    public static ClientController getInstance() { 
     if (instance == null) { 
      instance = new ClientController(); 
     } 
     return instance; 
    } 

    // Controls 

    public void display(String message) { 
     ClientGUI.getInstance().display(message); 
    } 

    public void displayAndSend(String message) { 
     display(message); 
     Client.getInstance().sendMessage(message); 
    } 

    public void connect(String address_, String username_, int port_) { 
     Client.getInstance().connect(address_, username_, port_); 
    } 

    public void disconnect() { 
     Client.getInstance().disconnect(); 
    } 
} 

ClientGUI:

package client; 

import javafx.application.Application; 
import javafx.fxml.FXMLLoader; 
import javafx.geometry.Pos; 
import javafx.scene.Parent; 
import javafx.scene.Scene; 
import javafx.scene.control.Button; 
import javafx.scene.control.Label; 
import javafx.scene.control.TextField; 
import javafx.scene.input.KeyCode; 
import javafx.scene.layout.VBox; 
import javafx.stage.Stage; 

/** 
* Gabe Castelli 
* 9/26/2016 
* Description: GUI for client-side using JavaFX 
*/ 

public class ClientGUI extends Application { 
    private Scene scene; 
    private TextField targetAddress; 
    private TextField fieldUsername; 
    private TextField fieldPort; 
    private VBox messages; 
    private TextField input; 
    private Button btnConnect; 
    private String username = "Anonymous"; 
    private int port = 2000; 

    private static ClientGUI instance; 

    public static ClientGUI getInstance() { 
     if (instance == null) { 
      instance = new ClientGUI(); 
     } 
     return instance; 
    } 

    @Override 
    public void start(Stage stage) throws Exception { 
     Parent root = FXMLLoader.load(getClass().getResource("ClientGUI.fxml")); 
     scene = new Scene(root, 480, 360); 
     stage.setTitle("MyMessenger"); 
     stage.setScene(scene); 
     stage.setMinWidth(660); 
     stage.setMinHeight(495); 
     stage.setMaxWidth(990); 
     stage.show(); 

     targetAddress = (TextField) scene.lookup("#targetAddress"); 
     fieldUsername = (TextField) scene.lookup("#fieldUsername"); 
     fieldPort = (TextField) scene.lookup("#fieldPort"); 
     messages = (VBox) scene.lookup("#messages"); 
     input = (TextField) scene.lookup("#input"); 
     btnConnect = (Button) scene.lookup("#btnConnect"); 

     input.setOnKeyPressed(event -> { 
      if (event.getCode() == KeyCode.ENTER) { 
       if (Client.getInstance().connected()) { 
        ClientController.getInstance().displayAndSend(input.getText()); 
        input.setText(""); 
       } else { 
        display("Attention: Not connected to server"); 
        input.setText(""); 
       } 
      } 
     }); 

     btnConnect.setOnMouseClicked(event -> { 
      if (!Client.getInstance().connected()) { 
       if (!targetAddress.getText().equals("")) { 
        if (!fieldUsername.getText().equals("")) { 
         username = fieldUsername.getText(); 
        } 
        if (!fieldPort.getText().equals("")) { 
         port = Integer.valueOf(fieldPort.getText()); 
        } 

        ClientController.getInstance().connect(targetAddress.getText(), username, port); 
        btnConnect.setText("Disconnect"); 
       } else { 
        display("Attention: Address required"); 
       } 
      } else { 
       ClientController.getInstance().disconnect(); 
       display("Attention: Disconnecting..."); 
       btnConnect.setText("Connect"); 
      } 
     }); 
    } 

    public void display(String message) { 
     messages.getChildren().add(new Message(message)); 
    } 
} 

Стек след:

Exception in thread "JavaFX Application Thread" java.lang.NullPointerException 
    at client.ClientGUI.display(ClientGUI.java:96) 
    at client.ClientController.display(ClientController.java:26) 
    at client.Client.connect(Client.java:34) 
    at client.ClientController.connect(ClientController.java:35) 
    at client.ClientGUI.lambda$start$1(ClientGUI.java:81) 
    at com.sun.javafx.event.CompositeEventHandler.dispatchBubblingEvent(CompositeEventHandler.java:86) 
    at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:238) 
    at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:191) 
    at com.sun.javafx.event.CompositeEventDispatcher.dispatchBubblingEvent(CompositeEventDispatcher.java:59) 
    at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:58) 
    at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114) 
    at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56) 
    at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114) 
    at com.sun.javafx.event.EventUtil.fireEventImpl(EventUtil.java:74) 
    at com.sun.javafx.event.EventUtil.fireEvent(EventUtil.java:54) 
    at javafx.event.Event.fireEvent(Event.java:198) 
    at javafx.scene.Scene$ClickGenerator.postProcess(Scene.java:3470) 
    at javafx.scene.Scene$ClickGenerator.access$8100(Scene.java:3398) 
    at javafx.scene.Scene$MouseHandler.process(Scene.java:3766) 
    at javafx.scene.Scene$MouseHandler.access$1500(Scene.java:3485) 
    at javafx.scene.Scene.impl_processMouseEvent(Scene.java:1762) 
    at javafx.scene.Scene$ScenePeerListener.mouseEvent(Scene.java:2494) 
    at com.sun.javafx.tk.quantum.GlassViewEventHandler$MouseEventNotification.run(GlassViewEventHandler.java:352) 
    at com.sun.javafx.tk.quantum.GlassViewEventHandler$MouseEventNotification.run(GlassViewEventHandler.java:275) 
    at java.security.AccessController.doPrivileged(Native Method) 
    at com.sun.javafx.tk.quantum.GlassViewEventHandler.lambda$handleMouseEvent$355(GlassViewEventHandler.java:388) 
    at com.sun.javafx.tk.quantum.QuantumToolkit.runWithoutRenderLock(QuantumToolkit.java:389) 
    at com.sun.javafx.tk.quantum.GlassViewEventHandler.handleMouseEvent(GlassViewEventHandler.java:387) 
    at com.sun.glass.ui.View.handleMouseEvent(View.java:555) 
    at com.sun.glass.ui.View.notifyMouse(View.java:937) 
+3

Почему вы делаете (или пытаетесь сделать) эти классы одиночными? Нет никакого способа, который будет работать, поскольку они создаются базовой структурой (т. Е. «ClientGUI» создается как часть процесса запуска для JavaFX, а «ClientController» создается экземпляром «FXMLLoader».). –

+0

Действительно, и 'ClientGUI.getInstance()' возвращает ** другой ** экземпляр класса 'ClientGUI', чем экземпляр JavaFX; один, в котором переменная-член 'messages' является' null'. – Jesper

+0

@James_D Я понимаю, что сейчас я впервые пытался использовать синглтоны, а также пытался понять MVC. Я знал, что собираюсь получить downvotes, потому что мне 16 и неопытным, но это то, как я могу научиться – Gabe

ответ

0

Исключение нулевого указателя происходит потому, что экземпляр ClientGUI вы получаете от ClientGUI.getInstance() не экземпляр, который был создан как часть процесса запуска JavaFX, и на котором был вызван метод start(). Поскольку messages был инициализирован в start(), экземпляр, который вы получаете от ClientGUI.getInstance(), не имеет инициализированного messages. Поэтому, когда вы вызываете ClientGUI.getInstance().display(message), он вызывает messages.getChildren()...: messages имеет значение null в этом экземпляре ClientGUI, и вы получаете исключение нулевого указателя.

Основная проблема заключается в общей структуре. Вы действительно не можете сделать эти синглеты, особенно подклассы Application, поскольку они создаются в рамках фреймворка. ClientGUI создается при запуске JavaFX, и с использованием FXMLLoader, ClientController создается через FXMLLoader.load() (так что ClientController.getInstance(), вероятно, не дает вам экземпляр, на который вы надеетесь, это либо).

Это не совсем понятно и, конечно же, не имеет никакого смысла, почему вы пытаетесь сделать эти классы одиночными. Я предполагаю, что вы пытаетесь обойти структуру, которая у вас есть, что совершенно нестандартно. Методы, которые вы определили, в значительной степени относятся к классам, которые не должны нести ответственность за функциональность, реализованную этими методами.

В частности: подкласс класса Application отвечает за жизненный цикл приложения. Он должен иметь метод start, который просто загружает начальный вид (файл FMXL), помещает его в окно и отображает его. Он также может запускать другие службы, если у вас есть/нужны они.Он может по желанию переопределить init() и stop(), если вам нужно.

Класс контроллера - это тот, который отвечает за обновление вида. Это единственное место, где вы должны ссылаться на элементы пользовательского интерфейса, определенные в файле FXML, и единственное место, которое вы должны изменить.

Класс модели (Client) не должен знать ни о каком виде или контроллере. Он должен представлять только состояние или данные, которые представлены. Это ответственность контроллера (на самом деле ведущий, в некоторых других архитектурах это может сделать вид), чтобы обновить представление в зависимости от данных, представленных моделью.

Так что вы, вероятно, захотите реструктурировать приложение по следующим строкам. Есть вариации на это в зависимости от других вещей, которые вы, возможно, потребуется, но это должно по крайней мере, вы на работоспособной трассе:

ClientGUI:

public class ClientGUI extends Application { 

    @Override 
    public void start(Stage stage) throws Exception { 
     Parent root = FXMLLoader.load(getClass().getResource("ClientGUI.fxml")); 
     scene = new Scene(root, 480, 360); 
     stage.setTitle("MyMessenger"); 
     stage.setScene(scene); 
     stage.setMinWidth(660); 
     stage.setMinHeight(495); 
     stage.setMaxWidth(990); 
     stage.show(); 
    } 
} 

ClientController:

public class ClientController implements Controller { 

    @FXML 
    private TextField targetAddress; 
    @FXML 
    private TextField fieldUsername; 
    @FXML 
    private TextField fieldPort; 
    @FXML 
    private VBox messages; 
    @FXML 
    private TextField input; 
    @FXML 
    private Button btnConnect; 

    private String username = "Anonymous"; 
    private int port = 2000; 

    private Client client ; 

    public void initialize() { 
     client = new Client(); 
    } 

    @FXML 
    private void connect() { 

     if (!client.connected()) { 
      if (!targetAddress.getText().equals("")) { 
       if (!fieldUsername.getText().equals("")) { 
        username = fieldUsername.getText(); 
       } 
       if (!fieldPort.getText().equals("")) { 
        port = Integer.valueOf(fieldPort.getText()); 
       } 

       try { 
        client.connect(targetAddress.getText(), username, port); 
        btnConnect.setText("Disconnect"); 
        display("Attention: Successfully connected to server"); 
       } catch (IOException exc) { 
        display("Attention: Unable to connect to server"); 
       } 
      } else { 
       display("Attention: Address required"); 
      } 
     } else { 
      client.disconnect(); 
      display("Attention: Disconnecting..."); 
      btnConnect.setText("Connect"); 
     } 

    } 

    @FXML 
    private void message() { 

     if (client.connected()) { 
      displayAndSend(input.getText()); 
      input.setText(""); 
     } else { 
      display("Attention: Not connected to server"); 
      input.setText(""); 
     } 

    } 

    public void display(String message) { 
     messages.getChildren().add(new Message(message)); 
    } 

    public void displayAndSend(String message) { 
     display(message); 
     client.sendMessage(message); 
    } 

} 

, а затем

// Note: why on earth is this Serializable? What state are you intending to serialize??? 
public class Client implements Serializable { 
    private Socket clientSocket; 
    private BufferedReader in; 
    private PrintWriter out; 
    private ObjectOutputStream objectOutputStream; 
    private boolean connected = false; 
    private static final long serialVersionUID = 0L; 


    public void connect(String targetAddress, String username_, int port_) throws IOException { 

     clientSocket = new Socket(targetAddress, port_); 
     clientSocket.setReuseAddress(true); // Allows same port to be used successively without cool-down 
     in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream())); 
     out = new PrintWriter(clientSocket.getOutputStream(), true); 
     objectOutputStream = new ObjectOutputStream(clientSocket.getOutputStream()); 
     objectOutputStream.writeObject(username_); 
     connected = true; 

    } 

    public void disconnect() { 
     try { 
      if (in != null) { 
       in.close(); 
      } 
      if (out != null) { 
       out.close(); 
      } 
      if (objectOutputStream != null) { 
       objectOutputStream.close(); 
      } 
      if (clientSocket != null) { 
       clientSocket.close(); 
      } 
     } catch (IOException e) { 
      e.printStackTrace(); 
     } 

     connected = false; 
    } 

    public boolean connected() { 
     return connected; 
    } 



    public void sendMessage(String message) { 
     out.println(message); 
    } 
} 

Для того, чтобы контроллер работал, необходим корневой элемент для определения fx:controller="client.ClientController" атрибутов и элементов управления в ClientGUI.fxml определить fx:id атрибуты, соответствующие имена полей в классе контроллера, и методы-обработчики событий, которые будут определены, например:

<!-- ... --> 

<TextField fx:id="targetAddress" /> 
<TextField fx:id="fieldUserName" /> 
<TextField fx:id="fieldPort" /> 

<VBox fx:id="messages" /> 

<TextField fx:id="input" onAction="#message" /> 

<Button text="Connect" fx:id="btnConnect" onAction="#connect" /> 

<!-- ... --> 

(Обратите внимание, что текстовое поле сгореть ActionEvent при входе нажата, поэтому нет необходимости возиться с нажатыми клавишами и т. д.)

Возможно, вам придется немного изменить это, чтобы остальная часть функциональности приложения работала, но пока вы придерживаетесь основных принципов: только контроллер должен изменить пользовательский интерфейс; класс Client не должен знать информацию о презентации и т. д., тогда вы будете на правильном пути. Если вы сделаете что-нибудьstatic, и, конечно, если вы попытаетесь сделать какой-либо из этих синглонов, вы делаете что-то неправильно.

+0

Большое спасибо, это полезно – Gabe

+0

Кстати, в чем преимущество переопределения метода init()? Вызывается ли это когда вызывается 'Application.launch()'? – Gabe

+0

Это: в отличие от 'start()' это * not *, вызываемый в приложении приложения FX; однако 'start()' гарантированно не вызываться до завершения 'init()'. На мой взгляд, мало смысла вкладывать код в 'init()' по сравнению с началом метода 'start()', но вам может понадобиться семантический способ отделить истинную «инициализацию» от фактического «запуска GUI ». –

 Смежные вопросы

  • Нет связанных вопросов^_^